ResolveRequestCache : The current HttpApplication object fires the ResolveRequestCache event to enable interested HTTP modules and application code to service the current request from th
Trang 1If both of the conditions are met, this indicates that the client has made the current request to invoke a server method that resides on an aspx file, and consequently, the Init method takes the following steps:
1 It invokes the CreateHandler static method on the RestHandler to create a RestHandler HTTP handler:
IHttpHandler restHandler = RestHandler.CreateHandler(application.Context);
As previously discussed, the RestHandler HTTP handler knows how to process REST method call requests
2 It calls the ProcessRequest method on this RestHandler HTTP handler to process the current REST method call request:
restHandler.ProcessRequest(application.Context);
As discussed earlier, this method invokes the server method
3 It calls the CompleteRequest method on the current HttpApplication object to complete and shortcut the request, and return the response to the client:
application.CompleteRequest();
}
As you can see, the current request does not go any further down the ASP.NET request processing pipeline To understand the significance of this shortcut, you need to take a look at a normal ASP.NET request processing pipeline where a normal ASP.NET request goes all the way down this pipeline, which consists of the following steps:
1 BeginRequest : The current HttpApplication object raises the BeginRequest event when it begins processing the current request An HTTP module can register an event handler for this event to perform tasks that must be performed at the beginning of the request For example, this
is a good place for an HTTP module to perform URL rewriting
2 AuthenticateRequest : The current HttpApplication object raises the AuthenticateRequest event to enable interested HTTP modules and application code to authenticate the current request
3 PostAuthenticateRequest : The current HttpApplication object fires the
PostAuthenticateRequest event after the request is authenticated An HTTP module can register an event handler for this event to perform tasks that must be performed after the current request is authenticated
4 AuthorizeRequest : The current HttpApplication object fires the AuthorizeRequest event
to enable interested HTTP modules and application code to authorize the current request
5 PostAuthorizeRequest : The current HttpApplication object fires the PostAuthorizeRequest event after the request is authorized An HTTP module can register an event handler for this event
to perform tasks that must be performed after the current request is authorized
6 ResolveRequestCache : The current HttpApplication object fires the ResolveRequestCache event to enable interested HTTP modules and application code to service the current request from the cache, bypassing the rest of the request processing pipeline to improve the performance
of the application
Trang 27 PostResolveRequestCache : If the response for the current request has not been cached
(because the current request is the first request to the specified resource for example), the current
HttpApplication object fires the PostResolveRequestCache event An HTTP module can
register an event handler for this event to perform tasks that must be performed after the search
in the cache fails
8 PostMapRequestHandler : The current HttpApplication object fires the
PostMapRequestHandler event after it has been detemined what type of HTTP handler must
handle the current request An HTTP module can register an event handler for this event to
per-form tasks that must be perper-formed after the type of HTTP handler is specified
9 AcquireRequestState : The current HttpApplication object fires the AcquireRequestState
event to enable interested HTTP modules and application code to acquire the request state from
the underlying data store
10 PostAcquireRequestState : The current HttpApplication object fires the
PostAcquireRequestState event after the request state is acquired to enable interested
HTTP modules and application code to perform tasks that must be performed after the request
state is acquired
11 PreRequestHandlerExecute : The current HttpApplication object fires the
PreReqeustHandlerExecute event before executing the HTTP handler responsible for
handling the current request An HTTP module can register an event handler for this event to
perform tasks that must be performed right before the ProcessRequest method of the HTTP
handler is invoked to execute the handler
12 PostRequestHandlerExecute : The current HttpApplication object fires the
PostRequestHandlerExecute event after the ProcessRequest method of the HTTP
handler returns, signifying that the HTTP handler responsible for handling the current request
has been executed
13 ReleaseRequestState : The current HttpApplication object fires the ReleaseRequestState
event to enable interested HTTP modules to release or store the request state into the underlying
data store
14 PostReleaseRequestState : The current HttpApplication object fires the
PostReleaseRequestState event right after the request state is stored into the underlying
data store to enable the interested HTTP modules and application code to run logic that must be
run after the request state is saved
15 UpdateRequestCache : The current HttpApplication object fires the UpdateRequestCache
event to enable interested HTTP modules to cache the current response in the ASP.NET cache
16 PostUpdateRequestCache : The current HttpApplication object fires the
PostUpdateRequestCache event after the current response is cached in the ASP.NET
Cache object
17 EndRequest : The current HttpApplication object fires the EndRequest event after the current
response is sent to the client to mark the end of processing the current request
As discussed earlier, the ScriptModule kicks in when the current HttpApplication fires its
PostAcquireRequestState event As you can see in Listing 14-29 , the ScriptModule ’s event handler
for this event invokes the CompleteRequest method on the current HttpApplication object to force
this object to bypass the rest of the events and directly raise the last event: EndRequest
Trang 3To see this in action, follow these steps:
1 Create an AJAX-enabled Web site in Visual Studio
2 Add a Global.asax file to the root directory of this Web site
3 Add the code shown in Listing 14-30 to the Global.asax file
4 Add a breakpoint to each method in the Global.asax file
5 Add a Web form ( aspx file) to this Web site
6 Add the code previously shown in Listing 14-17 to the .aspx file created in step 5
7 Press F5 to run the Web site in debug mode
The debugger stops at every breakpoint in the Global.asax file, in top-to-bottom order This signifies two things First, the first request goes through the entire ASP.NET request processing pipeline Second, the current HttpApplication raises its events in the order discussed earlier
8 When the Web page appears, enter two numbers in the specified text boxes and press F5 to run the Web site in debug mode again
The debugger jumps from the breakpoint in the Application_AcquireRequestState method directly to the breakpoint in the Application_EndRequest method This clearly shows that the current request goes through only the first 10 steps of the pipeline, skipping the last eight steps Listing 14-30: The Global.asax File
<%@ Application Language=”C#” %>
<script RunAt=”server”>
void Application_BeginRequest(object sender, EventArgs e) { } void Application_AuthenticateRequest(object sender, EventArgs e) { } void Application_PostAuthenticateRequest(object sender, EventArgs e){ } void Application_AuthorizeRequest(object sender, EventArgs e) { } void Application_PostAuthorizeRequest(object sender, EventArgs e) { } void Application_ResolveRequestCache(object sender, EventArgs e) { } void Application_PostResolveRequestCache(object sender, EventArgs e) { } void Application_PostMapRequestHandler(object sender, EventArgs e) { } void Application_AcquireRequestState(object sender, EventArgs e) { } void Application_PostAcquireRequestState(object sender, EventArgs e) { } void Application_PreRequestHandlerExecute(object sender, EventArgs e) { } void Application_PostRequestHandlerExecute(object sender, EventArgs e) { } void Application_ReleaseRequestState(object sender, EventArgs e) { } void Application_PostReleaseRequestState(object sender, EventArgs e) { } void Application_UpdateRequestCache(object sender, EventArgs e) { } void Application_PostUpdateRequestCache(object sender, EventArgs e) { } void Application_EndRequest(object sender, EventArgs e) { }
</script>
If you decide to allow your client-side code to asynchronously invoke a server-side method that belongs
to a Web page in your application, you must keep the following in mind:
Trang 4❑ None of the event handlers registered for PostAcquireRequestState ,
PreRequestHandlerExecute , ReleaseRequestState , PostReleaseRequestState ,
UpdateRequestCache , and PostUpdateRequestCache will be invoked
❑ The request state, such as Session data, will not be stored in the underlying data store
because the request skips the ReleaseRequestState step during which the request state is
stored in the data store This means that none of the changes made to the session data will
be stored in the data store, and therefore, they will be lost at the end of the current request
❑ The server response will not be cached in the ASP.NET Cache object because the current request
skips the UpdateRequestCache step
Due to these fundamental limitations, the ASP.NET AJAX framework requires the server-side method to
be static
As previously shown in Listing 14-29 , the ScriptModule hands the request over to the RestHandler
HTTP handler if the current request is a REST request In other words, after the ScriptModule kicks in,
the Page is no longer the HTTP handler responsible for processing the current request The ScriptModule
delegates this responsibility from Page to the RestHandler HTTP handler, and consequently, the
ProcessRequest method of the RestHandler HTTP handler (not Page ) is invoked This has significant
consequences The ProcessRequest method of the Page class starts what is known as the Page lifecycle
This means that the Page does not go through its lifecycle phases when the current request is a REST
method call request Therefore, you cannot access any of the server controls on the current page This is yet
another reason why the server-side method must be static
Web Services Bridges Demystified
As the implementation of the RestHandler class’s CreateHandler static method clearly shows, this
method assumes that the method being invoked is annotated with the WebMethodAttribute metadata
attribute In other words, the RestHandler HTTP handler assumes that the method being invoked is
always a Web method
How does the RestHandler HTTP handler process requests for an asbx file given the fact that this file
has nothing to do with Web services? To find the answer to this question, you need to revisit the
imple-mentation of the CreateHandler static method, which is shown again in Listing 14-31 As the
high-lighted portion of this listing shows, the CreateHandler method invokes the GetCompiledType static
method on the BuildManager class This method parses and compiles the file with the specified virtual
path into a dynamically generated class
Listing 14-31: The CreateHandler Static Method Revisited
internal static IHttpHandler CreateHandler(HttpContext context)
{
string servicePath = context.Request.FilePath;
Type serviceType = BuildManager.GetCompiledType(servicePath);
}
Now the question is: What type of class does the GetCompiledType method generate for an asbx file?
To find the answer to this question, run Listing 14-20 in debug mode The client code contained in this
Trang 5listing makes a REST request to the server to invoke the Divide method of the Math class This request is made for a file with extension asbx that describes the class and method
After running Listing 14-20 , go to the following directory on your machine (or, if you have installed NET framework in a different directory than the following standard directory, go to that directory):
%windir%\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files
In this directory, search for the directory with the same name as your application Then go down to a different directory, and search for a source file with a name that has the following format :
App_Web_math.asbx.23fc0e6b.kgwm5mhb.0.cs
Note that the name of this source file begins with App_Web_ , followed by the name of the asbx file (which is math.asbx in this case), followed by some randomly generated hash values to ensure the uniqueness of the file name
If you open this file in your favorite editor, you should see the code shown in Listing 14-32 (which has been cleaned up for presentation purposes)
Listing 14-32: The Dynamically Generated Code for the Web Service that Wraps
a Custom Class
namespace MyNamespace{
this.VirtualPath = “/AJAXFuturesEnabledWebSite2/NewFolder1/Math.asbx”;
this.BridgeXml = @”<?xml version=””1.0”” encoding=””utf-8”” ?>
<bridge namespace=””MyNamespace”” className=””MyMath””>
<proxy type=””CustomComponents.Math, App_Code””/>
Trang 6public override object CallServiceClassMethod(string method,
Dictionary<string, object> args,
throw new ArgumentException(“Argument not found: x”);
double arg0 = ((double)(BridgeHandler.ConvertToType(obj, typeof(double))));
if (args.TryGetValue(“y”, out obj)) { }
else
throw new ArgumentException(“Argument not found: y”);
double arg1 = ((double)(BridgeHandler.ConvertToType(obj, typeof(double))));
return proxy.Divide(arg0, arg1);
This code defines a class with the name specified in the className attribute on the bridge document
element The document element also belongs to a namespace with the name specified in the namespace
attribute on this element Note that this class exposes a string property named BridgeXml that contains
the contents of the asbx file
Trang 7As you can see in the code listing, this class is annotated with the WebServiceAttribute metadata attribute, which means that this class is a Web service Therefore, the call into the BuildManager class’s
GetCompiledType static method that was previously highlighted in Listing 14-31 creates a Web service under the hood with the name specified in the className attribute on the bridge document element
This exposes a Web method with the name specified in the name attribute on the <method> element of the asbx file The ASP.NET AJAX Web services bridge simply creates a Web service wrapper around your custom class and exposes its methods as Web methods
Considering the fact that the GetCompiledType static method of the BuildManager class takes only the virtual path of the file being compiled, and has no knowledge of the type of file it is dealing with, how does this method know what type of class to generate? The answer is it doesn’t Under the hood, the
GetCompiledType method delegates the responsibility of parsing the file and generating the code for the
class that represents the file to another component known as a build provider Each type of build provider is
specifically designed to parse and generate code for a file with specific extension The following table presents
a few examples of build providers and the file extension for which each build provider generates code
As shown in this table, the ASP.NET framework includes a build provider named BridgeBuildProvider , which is specifically designed to parse and generate code for files with extension asbx This build pro-vider is the one that generates the code for the Web service wrapper shown in Listing 14-32 If you check out the web.config file in your AJAX-enabled Web site, you’ll see the following code, which registers the
BridgeBuildProvider with the ASP.NET compilation infrastructure:
Using the Replicas
The previous sections provided you with the complete replica implementations of the following main components of the ASP.NET AJAX REST method call request processing infrastructure:
❑ ScriptHandlerFactory ❑ RestHandlerFactory
Build Provider File Type
Trang 8❑ RestHandler
❑ HandlerWrapper
❑ ScriptModule
As mentioned earlier, these replicas are fully functional Follow these steps to see the replicas in action:
1 Create an AJAX-enabled Web site in Visual Studio
2 Add an App_Code directory in this Web site
3 Add a new source file named ScriptHandlerFactory.cs to the App_Code directory, and then
add the code shown in Listing 14-22 to this source file
4 Add a new source file named RestHandlerFactory.cs to the App_Code directory, and then
add the code shown in Listing 14-24 to this source file Comment out the following two lines of
code from this source file to remove the reference to the RestClientProxyHandler (which
hasn’t been covered yet):
//if (IsClientProxyRequest(context.Request.PathInfo))
// return new RestClientProxyHandler();
5 Add a new source file named RestHandler.cs to the App_Code directory, and then add the
code shown in Listing 14-25 to this source file
6 Add a new source file named HandlerWrapper.cs to the App_Code directory, and then add the
code shown in Listing 14-26 to this source file
7 Add a new source file named ScriptModule.cs to the App_Code directory, and then add the
code shown in Listing 14-29 to this source file
8 Add a new Web form ( .aspx file) named PageMethods.aspx , and then add the code shown in
Listing 14-17 to this aspx file
9 Add a new Web form ( .aspx file) named Math.aspx to the root directory of this Web site, and
then add the code shown in Listing 14-18 to this aspx file
10 Add a new XML file named Math.asbx to the root directory of this Web site, and then add the
XML document shown in Listing 14-19
11 Add a new source file named Math.cs to the App_Code directory, and then add the code shown
in Listing 14-18 to this source file
12 Add a new Web form ( .aspx file) named Math2.aspx to the root directory of this Web site, and
then add the code shown in Listing 14-15 to this aspx file
13 Add a new Web service ( .asmx ) named Math.asmx to the root directory of this Web site, and
then add the code shown in Listing 14-16 to this asmx file
14 In the web.config file, comment out the italicized lines shown in the following code, and add
the boldface portion of the code (which is basically replacing the standard ASP.NET
Scrip-tHandlerFactory and ScriptModule with the replica ScriptHandlerFactory and
ScriptModule ):
Trang 9Summar y
This chapter provided you with in-depth coverage of the ASP.NET AJAX REST method call request’s processing infrastructure It introduced Web services bridges, which are covered in more detail in Chapter 19 , where you’ll learn how to develop a custom script server control that uses a bridge to enable the client code to interact with Amazon Web services
The next chapter builds on what you learned in this chapter to show you how this infrastructure manages to hide its complexity behind proxy classes
Trang 11Proxy 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 12Listing 15-2: A Local Class with the Same Name and Methods as the Remote
Wouldn’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 13This 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 14This enables you to program against the local object, instead of the WebServiceProxy object as you did
in the last chapter, and consequently, makes programming against remote objects more like programming
against 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 15var 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 16All 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 17The 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 18var 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 19Proxy 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 21Comparing 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 22
Proxy Classes Associated with Custom Classes
The 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 23var 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 24As 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 25var 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 26var 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 27MyNamespace.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 28As 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 29Listing 15-8: The Source of the Page Shown in Listing 15-6 with a ScriptMode Value
var builder = new Sys.StringBuilder();
var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
(continued)
Trang 30Listing 15-8 (continued)
var 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 32This script block sets the src to the value /AJAXFuturesEnabledWebSite2/Math.asmx/jsdebug
Note that this value consists of two parts: the first part is the URL of the Math Web service, and the
second 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 33var 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 34Listing 15-9 (continued)
<body>
<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 35is 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 36var userContext = $get(“result”);
var xValue = $get(“firstNumber”).value;
var yValue = $get(“secondNumber”).value;
MyNamespace.Math.Add(xValue, yValue, onSuccess, onFailure, userContext);
Trang 37ScriptManagerProxy 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 38(as far as the discussions in this section are concerned) replicas of the components of the framework that
are responsible for generating a client script that defines, instantiates, and initializes the proxy class
ScriptManager
Listing 15-12 presents the implementation of the replica ScriptManager server control
Listing 15-12: The Replica ScriptManager Server Control
Trang 39protected override void OnInit(EventArgs e) {
base.OnInit(e);
this.Page.PreRenderComplete += new EventHandler(Page_PreRenderComplete);
} void Page_PreRenderComplete(object sender, EventArgs e) {
if (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 40System.Web.Extensions.Design, Version=1.0.61025.0, Culture=neutral,
Listing 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