As you learned in the previous chapter, there are three types of remote method invocations: ❑ Invoking a Web method that is part of a Web service ❑ Invoking a page method that is par
Trang 1Proxy Classes The previous chapter provided you with in-depth coverage of the ASP.NET AJAX REST method call request processing infrastructure This chapter shows you how this infrastructure hides its complexity behind proxy classes to enable you to program against a remote object as you would against a local object
What’s a Proxy, Anyway?
Let’s revisit the add JavaScript function shown in Listing 14-14 of Chapter 14 , and shown again here in Listing 15-1 This JavaScript function was registered as the event handler for the Add but-ton’s click event of in Listing 14-14
Listing 15-1: The add Method
function add() {
var servicePath = “ http://localhost/AJAXEnabledFuturesWebSite2/Math.asmx”;
var methodName = “Add”;
var useGet = false;
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
var params = {x : xValue, y : yValue};
var userContext = $get(“result”);
var webServiceProxy = new Sys.Net.WebServiceProxy();
Trang 2Wouldn’t your implementation of the add method be something like the one shown in Listing 15-3 ?
Listing 15-3: Implementation of add Method if the Web Service Class Were a Local Class
function add()
{
var math = new MyNamespace.Math();
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
var z = math.Add(Number.parseInvariant(xValue),
Number.parseInvariant(yValue));
$get(“result”).innerText = z;
}
As you can see in this listing, if the Math object were a local object, you would directly invoke the
Add method on the object and directly pass the x and y values into the Add method itself
However, when the Math object becomes a Web service, it is a remote object, so you cannot directly
invoke the Add method on it, nor can you directly pass the x and y values into it When you’re calling
the Add method on the remote Math object:
❑ You have to worry about the service path where the remote Math object is located:
var servicePath = “http://localhost/AJAXEnabledFuturesWebSite2/Math.asmx”;
You don’t have to worry about the location of a local Math object, because it always resides in
the same address space as the rest of your program and, consequently, you have direct access
to the object
❑ You have to pass the name of the method as a string into the _invoke method of the
WebServiceProxy object:
var methodName = “Add”;
request = webServiceProxy._invoke(servicePath, methodName, useGet, params,
onSuccess, onFailure, userContext);
Trang 3This is obviously very different from a local method invocation where you directly invoke the method on the object instead of passing a string around
❑ You have to pass the names and values of the parameters of the method as a dictionary into the
_invoke method of the WebServiceProxy object
var params = {x : xValue, y : yValue};
request = webServiceProxy._invoke(servicePath, methodName, useGet, params,
onSuccess, onFailure, userContext);
This is obviously very different from a local method invocation where you directly pass these parameters into the method itself instead of passing a dictionary around
❑ You’re trying to call a method named Add , which takes two parameters of type double and returns a value of type double , on an object of type Math , but you have to call a method with a different name ( _invoke ), with completely different parameters ( servicePath , methodName , useGet ,
params , onSucess , onFailure , and userContext ), and with a completely different return type ( WebRequest ), on a completely different object (the WebServiceProxy object):
request = webServiceProxy._invoke(servicePath, methodName, useGet, params, onSuccess, onFailure, userContext);
This is obviously very different from a local method invocation where you directly invoke the
Add method on the Math object
As you can see, invoking the Add method on the remote Math object doesn’t look anything like invoking the Add method on a local Math object
Proxy Class
The ASP.NET AJAX framework provides you with a local object that has the following characteristics:
❑ It has the same name as the remote object For example, in the case of the Math Web service, the AJAX framework provides you with a local object named Math
❑ It exposes methods with the same names as the methods of the remote object For example, the local
Math object associated with the remote Math Web service object exposes a method named Add ❑ Its methods take parameters with the same names as the associated methods of the remote object For example, the Add method of the local Math object associated with the remote Math Web service object also takes two parameters with the same names as the parameters of the Add method of the remote Math Web service object: x and y
❑ Its methods take parameters of the same types as the associated methods of the remote object For example, the Add method of the local Math object associated with the remote Math Web ser-vice object also takes two parameters of the same types as the parameters of the Add method of the remote Math Web service object: double
❑ Its methods return values of the same types as the associated methods of the remote object For example, the Add method of the local Math object associated with the remote Math Web service object returns a value of the same type as the return value of the Add method of the remote Math Web service object: double
Trang 4against local objects Because this local object makes it feel like you’re directly interacting with the remote
object, it is known as a proxy object In other words, this local object acts as a proxy for the remote object
Let’s begin by discussing the implementation of the proxy class and object that the ASP.NET AJAX
framework automatically generates for you (The mechanism that actually generates this proxy class and
object is discussed later in the chapter.)
As you learned in the previous chapter, there are three types of remote method invocations:
❑ Invoking a Web method that is part of a Web service
❑ Invoking a page method that is part of an ASP.NET page
❑ Invoking a method that is part of a custom class
Therefore, there are three types of proxy classes:
❑ Proxy classes associated with Web services
❑ Proxy classes associated with page methods
❑ Proxy classes associated with custom classes
Proxy Classes Associated with Web Services
Listing 15-4 presents the implementation of the local Math proxy class associated with the remote Math
Web service class
Listing 15-4: The Local Math Proxy Class Associated with the Remote Math
Web Service Class
var servicePath = MyNamespace.Math.get_path();
var methodName = ‘Add’;
var useGet = false;
var params = {x : x, y : y};
var onSuccess = succeededCallback;
Trang 5var onFailure = failedCallback;
return this._invoke(servicePath, methodName, useGet, params, onSuccess,onFailure, userContext);
}}
MyNamespace.Math.registerClass(‘MyNamespace.Math’, Sys.Net.WebServiceProxy);
MyNamespace.Math._staticInstance = new MyNamespace.Math();
MyNamespace.Math.set_path = function(value){
MyNamespace.Math._staticInstance._path = value;
}MyNamespace.Math.get_path = function(){
return MyNamespace.Math._staticInstance._path;
}MyNamespace.Math.set_timeout = function(value){
MyNamespace.Math._staticInstance._timeout = value;
}MyNamespace.Math.get_timeout = function(){
return MyNamespace.Math._staticInstance._timeout;
}MyNamespace.Math.set_defaultUserContext = function(value){
MyNamespace.Math._staticInstance._userContext = value;
}MyNamespace.Math.get_defaultUserContext = function(){
return MyNamespace.Math._staticInstance._userContext;
}MyNamespace.Math.set_defaultSucceededCallback = function(value){
MyNamespace.Math._staticInstance._succeeded = value;
}MyNamespace.Math.get_defaultSucceededCallback = function(){
return MyNamespace.Math._staticInstance._succeeded;
}MyNamespace.Math.set_defaultFailedCallback = function(value){
MyNamespace.Math._staticInstance._failed = value;
}
(continued)
Trang 6All ASP.NET AJAX proxy classes directly or indirectly derive from the Sys.Net.WebServiceProxy class
As you can see from in the following excerpt from Listing 15-4 , this local Math proxy class exposes a
method with the same name as the remote Web service class — Add This method takes parameters with
the same names and types as the remote Web service class’s Add method parameters, and returns a value
of the same type as the remote Web service class’s Add method return value:
MyNamespace.Math.prototype =
{
Add : function(x, y, succeededCallback, failedCallback, userContext)
{
var servicePath = MyNamespace.Math.get_path();
var methodName = ‘Add’;
var useGet = false;
var params = {x : x, y : y};
var onSuccess = succeededCallback;
var onFailure = failedCallback;
return this._invoke(servicePath, methodName, useGet, params,
onSuccess,onFailure, userContext);
}
}
The Add method of this local Math proxy class encapsulates the code that you would otherwise have
to write to interact with the WebServiceProxy object as you did in the previous chapter Note that
Listing 15-4 instantiates an instance of this local Math proxy class and assigns the instance to a private
static field on this class named _staticInstance :
MyNamespace.Math._staticInstance = new MyNamespace.Math();
Trang 7The code then defines static getters and setters that delegate to the associated getters and setters of this static Math proxy instance Also note that the code exposes a static method named Add on this local Math proxy class, which delegates to the Add method of the static Math proxy instance:
MyNamespace.Math.Add = function(x, y, onSuccess, onFailed, userContext){
MyNamespace.Math._staticInstance.Add(x, y, onSuccess, onFailed, userContext);
MathWebServiceProxy.js , and Listing 15-5 adds a reference to this JavaScript file
Now let’s walk through the implementation of the add JavaScript function shown in the following excerpt from Listing 15-5 :
function add() {
var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
MyNamespace.Math.Add(xValue, yValue, onSuccess, onFailure, userContext);
Listing 15-5: A Page that Uses the Local Static Math Proxy Instance
Trang 8userContext.innerHTML = “<b<>u>” + result + “</u></b>”;
var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
MyNamespace.Math.Add(xValue, yValue, onSuccess, onFailure, userContext);
}
</script>
</head>
<body>
<form id=”form1” runat=”server”>
<asp:ScriptManager runat=”server” ID=”ScriptManager1”>
Trang 9Proxy Classes Associated with Page Methods
The following code presents a proxy class associated with a page method named Add , which belongs to the PageMethods.aspx page:
PageMethods = function(){
Add : function(x, y, succeededCallback, failedCallback, userContext) { return this._invoke(PageMethods.get_path(), ‘Add’, false, {x:x, y:y}, succeededCallback, failedCallback, userContext);
}}PageMethods.registerClass(‘PageMethods’, Sys.Net.WebServiceProxy);
(continued)
Trang 11Comparing this code with Listing 15-4 clearly shows that a proxy class associated with page methods has a fixed named — PageMethods — and does not belong to any namespace All methods annotated with WebMethod metadata attribute on a given page are associated with the same proxy class named
PageMethods on the client side As the boldface portion of the code shows, the set_path method is invoked on the PageMethods proxy object to specify the URL of the PageMethods.aspx page as the target URL for the proxy class
The following code presents a page that uses the PageMethods proxy class As you’ll see later in this chapter, the ASP.NET AJAX framework automatically generates the code for the PageMethods proxy class and adds this code to the current page For now, assume that you generated this code yourself and treat it like any other client script As such, this code is stored in a JavaScript file named
MathPageMethodsProxy.js , and the following code adds a reference to this file Note that the following code is the same as Listing 15-5 , except for the boldface portion where the PageMethods proxy is used to communicate with the underlying page method
//Same as Listing 15-5 }
function add() {
var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
} </script>
</head>
<body>
<form id=”form1” runat=”server”>
<asp:ScriptManager runat=”server” ID=”ScriptManager1”>
Trang 12The proxy classes associated with custom classes are very similar to the proxy classes associated with
Web services The main difference is that the target URL of the proxy class associated with a custom class
is set to the URL of the asbx file that describes the custom class
Automatic Proxy Class Generation
This illusion will work only if someone implements the Math proxy class for you Otherwise, if you
were to implement this class yourself, it would not be much of an illusion The main challenge with
implementing a proxy class is that one proxy class will not work with all types of remote classes
For example, you cannot use the Math proxy class to talk to the Products Web service class because the
Products Web service class is a completely different Web service class than the Math Web service class
For one thing, the Products Web service class exposes methods such as GetProducts as opposed to
Add This means that you have to use separate proxy classes to talk to different remote classes
This is where the ASP.NET AJAX server-side framework comes to the rescue This framework contains
the logic that automatically generates the code for the proxy class for each remote class that your client
code needs to interact with All you have to do is add a ServiceReference object to the Services
collection of the current ScriptManager server control to specify which remote class you need to talk to
You can do this either imperatively or declaratively
Declarative Approach
Listing 15-6 presents a page that that uses the declarative approach to add a ServiceReference object
to the Services collection of the current ScriptManager server control
Listing 15-6: A Page that Uses the Declarative Approach to Add a ScriptReference Object
Trang 13var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
MyNamespace.Math.Add(xValue, yValue, onSuccess, onFailure, userContext);
} </script>
</head>
<body>
<form id=”form1” runat=”server”>
<asp:ScriptManager runat=”server” ID=”ScriptManager1”>
Trang 14As you can see in the boldface portion of this code listing, the page adds an <asp:ServiceReference>
element to the <Services> child element of the <asp:ScriptManager> tag that represents the current
ScriptManager server control on the aspx page Note that this page sets the Path attribute on this
<asp:ServiceReference> tag to the service path of the Math Web service Also note that the page
sets the InlineScript attribute on this tag to true to tell the ASP.NET AJAX server-side framework to
add the definition of the Math proxy class to the markup sent to the requesting browser As a matter of
fact, if you run Listing 15-6 and view the source from your browser, you’ll see Listing 15-7
Listing 15-7: The Source for the Page Shown in Listing 15-6
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
Trang 15var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
MyNamespace.Math.Add(xValue, yValue, onSuccess, onFailure, userContext);
} </script>
</head>
<body>
<form name=”form1” method=”post” action=”WebServiceProxy.aspx” id=”form1”>
<div>
<input type=”hidden” name=” EVENTTARGET” id=” EVENTTARGET” value=”” />
<input type=”hidden” name=” EVENTARGUMENT” id=” EVENTARGUMENT” value=”” />
<input type=”hidden” name=” VIEWSTATE” id=” VIEWSTATE”
value=”/wEPDwULLTEzMTg5MjA5NzVkZDZArSkraR3ukOEGxC944PmDWFHr” />
</div>
<script type=”text/javascript”>
var theForm = document.forms[‘form1’];
<! if (!theForm) {
theForm = document.form1;
}function doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm. EVENTTARGET.value = eventTarget;
(continued)
Trang 16var e = Function._validateParams(arguments, [{name: ‘path’, type: String}]); if (e)
throw e; MyNamespace.Math._staticInstance._path = value; }
MyNamespace.Math.get_path = function() { return
MyNamespace.Math._staticInstance._ path; }
MyNamespace.Math.set_timeout = function(value) { var e =
Function._validateParams(arguments, [{name: ‘timeout’, type: Number}]); if (e)
throw e; if (value < 0) { throw Error.argumentOutOfRange(‘value’, value,
Trang 17MyNamespace.Math.set_defaultSucceededCallback = function(value) { var e = Function._validateParams(arguments, [{name: ‘defaultSucceededCallback’, type: Function}]); if (e) throw e; MyNamespace.Math._staticInstance._succeeded = value; }
MyNamespace.Math.get_defaultSucceededCallback = function() { return MyNamespace.Math._staticInstance._succeeded; } MyNamespace.Math.set_defaultFailedCallback = function(value) { var e = Function._validateParams(arguments, [{name: ‘defaultFailedCallback’, type:
Function}]); if (e) throw e; MyNamespace.Math._staticInstance._failed = value; } MyNamespace.Math.get_defaultFailedCallback = function() {
return MyNamespace.Math._staticInstance._failed; } MyNamespace.Math.set_path(“/AJAXFuturesEnabledWebSite2/Math.asmx”);
MyNamespace.Math.Add= function(x,y,onSuccess,onFailed,userContext) {MyNamespace.Math._staticInstance.Add(x,y,onSuccess,onFailed,userContext); } // >
</script>
<script type=”text/javascript”>
//<![CDATA[
Sys.WebForms.PageRequestManager._initialize(‘ScriptManager1’,document.getElementById(‘form1’));
Trang 18As you can see, the boldface portion of this listing is just the inline definition of the Math proxy class
This inline solution has the following drawbacks:
❑ Like any other inline solution, it does not allow the browser to cache the same script used in
dif-ferent pages to improve performance For example, if you have multiple pages in your
applica-tion that use the Math proxy class, every single page will include the boldface portion of Listing
15-7 However, if the script that defines the Math proxy class were in a separate file (as shown in
the following code snippet), the browser could download and cache this file once
❑ It increases the size of the page because the definition of the Math proxy class is directly added
to the page As you can see in Listing 15-7 , your pages could get quite large, and the bigger a
page is, the longer it takes to download However, if the boldface portion of Listing 15-7 were in
a separate file (as shown in the following code snippet), the browser would download this script
once and use it across all pages in your application that use the Math proxy class
<asp:ScriptManager runat=”server” ID=”ScriptManager1”>
If you set the InlineScript attribute on the <asp:ScriptReference> to false , run the same page,
and view the source from your browser, you’ll get Listing 15-8
Trang 19var builder = new Sys.StringBuilder();
var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
(continued)
Trang 20var yValue = $get(“secondNumber”).value;
MyNamespace.Math.Add(xValue, yValue, onSuccess, onFailure, userContext);
<input type=”hidden” name=” EVENTTARGET” id=” EVENTTARGET” value=”” />
<input type=”hidden” name=” EVENTARGUMENT” id=” EVENTARGUMENT” value=”” />
<input type=”hidden” name=” VIEWSTATE” id=” VIEWSTATE”
Trang 22second part is jsdebug The path trailer jsdebug tells the ASP.NET AJAX server-side framework that
the client code wants to download the debug version of the script If you enter this URL in the address
bar of your browser, you’ll get the JavaScript file that contains the definition of the Math proxy class
(the boldfacportion of Listing 15-7 )
The ScriptManager server control exposes an enumerator property of type ScriptMode and named
ScriptMode This property determines which version of the script to download The following code
presents the definition of the ScriptMode enumerator:
public enum ScriptMode
For example, if you set the ScriptMode attribute on the <asp:ServiceReference> child element
previously shown in Listing 15-6 to Release , run the listing page, and view the page source from your
browser, you’ll see that the source contains the following script block As the boldface portion shows, the
path trailer is now js instead of jsdebug , which means that this time around, the release version of
the script will be downloaded:
type=”text/javascript”> </script>
Imperative Approach
Listing 15-9 presents a page that uses the imperative approach to add a ServiceReference object to the
Services collection of the current ScriptManager server control
Listing 15-9: A Page that Uses the Imperative Approach to Add a ServiceReference
Trang 23var builder = new Sys.StringBuilder();
var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
MyNamespace.Math.Add(xValue, yValue, onSuccess, onFailure, userContext);
} </script>
</head>
(continued)
Trang 24<form id=”form1” runat=”server”>
<asp:ScriptManager runat=”server” ID=”ScriptManager1”/>
ServiceReference serviceRef = new ServiceReference();
Next, it sets the InlineScript and Path properties of this instance:
serviceRef.InlineScript = false;
serviceRef.Path = “/AJAXFuturesEnabledWebSite2/Math.asmx”;
Finally, it adds the instance to the Services collection of the current ScriptManager server control:
ScriptManager1.Services.Add(serviceRef);
Trang 25is the user control and the parent page is the page that hosts the user control
The problem in these parent/child page scenarios is that the parent and child are finally merged and form a single page, which means that you cannot include a separate instance of the ScriptManager server control on the parent and child pages As previously discussed, every page can contain only a single instance of the ScriptManager server control
If you put the ScriptManager server control on the parent page, the child page would not be able to add its ServiceReference objects to the Services collection of the ScriptManager server control If you put the ScriptManager server control on the child page, the parent page would not be able to add its ServiceReference objects to the Services collection of the ScriptManager server control
You would have the same problem with ScriptReferences If you put the ScriptManager server control on the parent page, the child page would not be able to add its ScriptReference objects to the
Scripts collection of the ScriptManager server control If you put the ScriptManager server control
on the child page, the parent page would not be able to add its ScriptReference objects to the
Scripts collection of the ScriptManager server control
To tackle these situations, the ASP.NET AJAX framework includes a new server control named
ScriptManagerProxy A child page whose parent page contains an instance of the ScriptManager server control, or a parent page whose child page contains an instance of the ScriptManager server control, can add its ServiceReference and ScriptReference objects to the Services and Scripts collections
of the ScriptManagerProxy server control and rest assured that the ASP.NET AJAX framework will automatically add these ServiceReference and ScriptReference objects to the ScriptManager server control Because the ServiceReference and ScriptReference objects added to the Services and
Scripts collections of the ScriptManagerProxy server control are added to the Services and Scripts collections of the current ScriptManager server control, the ScriptManagerProxy server control acts as a proxy for the current ScriptManager server control
Listing 15-10 contains a user control that employs the ScriptManagerProxy server control
Listing 15-10: A User Control that Employs the ScriptManagerProxy Server Control
<%@ Control Language=”C#” ClassName=”MathUserControl” %>
<script type=”text/javascript” language=”javascript”>
Trang 26function onFailure(result, userContext, methodName)
var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
MyNamespace.Math.Add(xValue, yValue, onSuccess, onFailure, userContext);
Trang 27ScriptManagerProxy server control
Listing 15-11: A Page that Hosts the User Control shown in Listing 15-10
<form id=”form1” runat=”server”>
<asp:ScriptManager runat=”server” ID=”ScriptManager1”/>
<custom:MyUserControl runat=”server” ID=”MyUserControl1” />
</form>
</body>
</html>
Under the Hood
Adding a ServiceReference object to the Services collection of the current ScriptManager server control is all it takes to instruct the ASP.NET AJAX server-side framework to automatically generate a client script that defines, instantiates, and initializes the proxy class To help you understand how the ASP.NET AJAX server-side framework manages to do this, this section implements fully functional
Trang 28ScriptManager
Listing 15-12 presents the implementation of the replica ScriptManager server control
Listing 15-12: The Replica ScriptManager Server Control
Trang 29if (this._services != null) {
foreach (ServiceReference reference in this._services) {
reference.Register(this);
} }
if (this.EnablePageMethods) {
ClientProxyGenerator generator2 = new ClientProxyGenerator();
string script = generator2.GetClientProxyScript(this.Page.Request.FilePath, true);
this.Page.ClientScript.RegisterClientScriptBlock(typeof(Page), script, script, true);
} } public bool EnablePageMethods {
get { return ViewState[“EnablePageMethods”] != null ? (bool)ViewState[“EnablePageMethods”] : false;
} set { ViewState[“EnablePageMethods”] = value;
} } }}
Services
The replica ScriptManager exposes a collection property of type ServiceReferenceCollection named Services , as defined in the following excerpt from Listing 15-12 Note that this property is annotated with the PersistenceMode(PersistenceMode.InnerProperty) metadata attribute, which enables page developers to declare the property as the child element of the tag that represents the
ScriptManager on the aspx page
private ServiceReferenceCollection _services;
[PersistenceMode(PersistenceMode.InnerProperty), Editor(“System.Web.UI.Design.CollectionEditorBase,
(continued)
Trang 30Listing 15-13 presents the implementation of the replica ServiceReferenceCollection class Thanks
to the NET 2.0 generics, implementing a new type-safe collection class is just a matter of deriving from a
generic collection class such as Collection<ServiceReference>
Listing 15-13: The ServiceReferenceCollection Class
The replica ScriptManager server control also exposes a Boolean property named EnablePageMethods ,
as shown in the following excerpt from Listing 15-12 Page developers can set this property to have the
control generate a client script that defines, instantiates, and initializes the PageMethods client class
public bool EnablePageMethods
Trang 31Page_RenderComplete as the event handler for the Page object’s PreRenderComplete event:
protected override void OnInit(EventArgs e) {
When the page finally enters its PreRenderComplete lifecycle phase, it automatically calls the
Page_PreRenderComplete method, which in turn performs the following tasks:
1 It iterates through the ServiceReference objects in the Services collections and invokes their
Register methods:
reference.Register(this);
2 It checks whether the EnablePageMethods property is set to true If so, it instantiates an instance of a class named ClientProxyGenerator :
ClientProxyGenerator generator2 = new ClientProxyGenerator();
3 It invokes the GetClientProxyScript method on this ClientProxyGenerator object to erate the client script that defines, instantiates, and initializes the PageMethods client class:
string script = generator2.GetClientProxyScript(this.Page.Request.FilePath, true);
4 It invokes the RegisterClientScriptBlock method on the ClientScript property of the containing page to register this script for rendering:
this.Page.ClientScript.RegisterClientScriptBlock(typeof(Page), script, script, true);
As a result, when the containing page enters its rendering phase, it will automatically render all tered scripts, including the script that defines, instantiates, and initializes the PageMethods client class:
ServiceReference
Listing 15-14 presents the implementation of the replica ServiceReference class