Chapter 14: Consuming Web Services Via JSON Messages If 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.
587
c14.indd 587
8/20/07 6:14:02 PM
Chapter 14: Consuming Web Services Via JSON Messages 7.
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 perform tasks that must be performed after the type of HTTP handler is specified.
9. 10.
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. 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.
588
c14.indd 588
8/20/07 6:14:02 PM
Chapter 14: Consuming Web Services Via JSON Messages To see this in action, follow these steps:
1. 2. 3. 4. 5. 6. 7.
Create an AJAX-enabled Web site in Visual Studio. Add a Global.asax file to the root directory of this Web site. Add the code shown in Listing 14-30 to the Global.asax file. Add a breakpoint to each method in the Global.asax file. Add a Web form (.aspx file) to this Web site. Add the code previously shown in Listing 14-17 to the .aspx file created in step 5. 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.
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:
589
c14.indd 589
8/20/07 6:14:03 PM
Chapter 14: Consuming Web Services Via JSON Messages ❑
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 implementation of the CreateHandler static method, which is shown again in Listing 14-31. As the highlighted 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
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
590
c14.indd 590
8/20/07 6:14:03 PM
Chapter 14: Consuming Web Services Via JSON Messages listing 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 { using System; using System.Net; using System.Web.Services; using System.Collections; using System.Xml.Serialization; using Microsoft.Web.Preview.Services; using System.Web.Script.Services; using System.Collections.Generic; [ScriptService()] [WebService(Name = “ />[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public partial class MyMath : BridgeHandler { public MyMath() { this.VirtualPath = “/AJAXFuturesEnabledWebSite2/NewFolder1/Math.asbx”; this.BridgeXml = @”<?xml version=””1.0”” encoding=””utf-8”” ?> <method name=””Divide””>
<input> </input> </method> </bridge>”; }
(continued)
591
c14.indd 591
8/20/07 6:14:04 PM
Chapter 14: Consuming Web Services Via JSON Messages Listing 14-32 (continued) [WebMethodAttribute()] [ScriptMethodAttribute(UseHttpGet = false, ResponseFormat = ResponseFormat.Json)] public virtual object Divide(System.Collections.IDictionary args) { BridgeRequest brequest = new BridgeRequest(“Divide”, args); return this.Invoke(brequest); } public override object CallServiceClassMethod(string method, Dictionary<string, object> args, ICredentials credentials,
string url) { if (“Divide”.Equals(method)) { Math proxy = new Math(); object obj; if (args.TryGetValue(“x”, out obj)) { } else 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); } throw new ArgumentException(“CallServiceClassMethod: Unknown method”); } [WebMethodAttribute()] [ScriptMethodAttribute(UseHttpGet = false, ResponseFormat = ResponseFormat.Json)] public virtual object @__invokeBridge(string method, IDictionary args) { BridgeRequest brequest = new BridgeRequest(method, args); return this.Invoke(brequest); } } }
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.
592
c14.indd 592
8/20/07 6:14:04 PM
Chapter 14: Consuming Web Services Via JSON Messages As 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.
Build Provider
File Type
PageBuildProvider
.aspx
UserControlBuildProvider
.ascx
WsdlBuildProvider
.wsdl
XsdBuildProvider
.xsd
MasterPageBuildProvider
.master
WebServiceBuildProvider
.asmx
BridgeBuildProvider
.asbx
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 provider 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: <compilation debug=”true”> <buildProviders> type=”Microsoft.Web.Preview.Services.BridgeBuildProvider” /> </buildProviders> </compilation>
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
593
c14.indd 593
8/20/07 6:14:04 PM
Chapter 14: Consuming Web Services Via JSON Messages
❑
RestHandler
❑
HandlerWrapper
❑
ScriptModule
As mentioned earlier, these replicas are fully functional. Follow these steps to see the replicas in action:
1. 2. 3. 4.
Create an AJAX-enabled Web site in Visual Studio. Add an App_Code directory in this Web site. 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. 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 ScriptHandlerFactory and ScriptModule with the replica ScriptHandlerFactory and ScriptModule):
594
c14.indd 594
8/20/07 6:14:04 PM
Chapter 14: Consuming Web Services Via JSON Messages <httpHandlers>
Now if you run the PageMethods.aspx, Math.aspx, and Math2.aspx pages, you should be able to see
the same results you saw when you ran these pages with the standard ASP.NET ScriptHandlerFactory and ScriptModule. Feel free to play with the code to get a better understanding of the processing infrastructure of the ASP.NET AJAX REST method call request.
Summar 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.
595
c14.indd 595
8/20/07 6:14:05 PM
c14.indd 596
8/20/07 6:14:05 PM
Proxy 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 button’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(); webServiceProxy.set_timeout(0); request = webServiceProxy._invoke(servicePath, methodName, useGet, params, onSuccess, onFailure, userContext); }
Now here is a question for you: How would you code this add method if the Math class (see Listing 14-15) were a local class in your client-side code, such as the local class shown in Listing 15-2?
c15.indd 597
8/20/07 9:08:00 PM
Chapter 15: Proxy Classes Listing 15-2: A Local Class with the Same Name and Methods as the Remote
Web Service Type.registerNamespace(“MyNamespace”); MyNamespace.Math = function () { } MyNamespace.Math.prototype = { Add : function (x, y) { return x + y; } } MyNamespace.Math.registerClass(“MyNamespace.Math”);
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:
Chapter 15: Proxy Classes This 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 service 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.
599
c15.indd 599
8/20/07 9:08:01 PM
Chapter 15: Proxy Classes This 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 Type.registerNamespace(‘MyNamespace’); MyNamespace.Math = function() { MyNamespace.Math.initializeBase(this); this._timeout = 0; this._userContext = null; this._succeeded = null; this._failed = null; } 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;
This code listing defines a namespace with the same name as the namespace of the remote Math Web service class: Type.registerNamespace(‘MyNamespace’);
The local Math proxy class derives from the WebServiceProxy class: MyNamespace.Math.registerClass(‘MyNamespace.Math’, Sys.Net.WebServiceProxy);
All 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();
602
c15.indd 602
8/20/07 9:08:02 PM
Chapter 15: Proxy Classes The 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); }
Finally, the code invokes the set_path static method on this local Math proxy class to set the service path for the static Math proxy instance: MyNamespace.Math.set_path(“/AJAXFuturesEnabledWebSite2/Math.asmx”);
Listing 15-5 presents a page that uses the Math proxy class. As you’ll see later in this chapter, the ASP.NET AJAX framework automatically generates the code for the proxy class such as the Math proxy class shown in this listing. For now, assume that you generated this code yourself and treat it like any other client code. As such, the content of Listing 15-4 is stored in a JavaScript file named 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); }
Thanks to the Math proxy class, you get to directly invoke the Add method and directly pass the x and y values into this method. Note that there is no sign of the WebServiceProxy and its weird-looking
_invoke method. The Math proxy class enables you to program against the remote Math Web service object as if you were programming against a local Math object. In other words, the Math proxy class gives your client code the illusion that it is making a local method call.
Listing 15-5: A Page that Uses the Local Static Math Proxy Instance <%@ Page Language=”C#” %> “ /><html xmlns=” /><head runat=”server”> <title>Untitled Page</title> <script type=”text/javascript” language=”javascript”> var request; function onSuccess(result, userContext, methodName) {
(continued)
603
c15.indd 603
8/20/07 9:08:02 PM
Chapter 15: Proxy Classes Listing 15-5 (continued) userContext.innerHTML = “} function onFailure(result, userContext, methodName) { var builder = new Sys.StringBuilder();
Proxy 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() { PageMethods.initializeBase(this); this._timeout = 0;
Chapter 15: Proxy Classes Comparing 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. <%@ Page Language=”C#” %> “ /><html xmlns=” /><head runat=”server”> <title>Untitled Page</title> <script type=”text/javascript” language=”javascript”> var request; function onSuccess(result, userContext, methodName) { userContext.innerHTML = “<b><u>” + result + “</b></u>”; } function onFailure(result, userContext, methodName) { //Same as Listing 15-5 } function add() { var userContext = $get(“result”); var xValue = $get(“firstNumber”).value; var yValue = $get(“secondNumber”).value; PageMethods.Add(xValue, yValue, onSuccess, onFailure, userContext); } </script> </head> <body> <form id=”form1” runat=”server”> <asp:ScriptManager runat=”server” ID=”ScriptManager1”> <Scripts> <asp:ScriptReference Path=”MathPageMethodsProxy.js” />
</Scripts> </asp:ScriptManager> <!-- Same as Listing 15-5 --> </form> </body> </html>
607
c15.indd 607
8/20/07 9:08:03 PM
Chapter 15: Proxy Classes
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 <%@ Page Language=”C#” %> “ /><html xmlns=” /><head runat=”server”> <title>Untitled Page</title> <script type=”text/javascript” language=”javascript”> var request; function onSuccess(result, userContext, methodName) { userContext.innerHTML = “<b><u>” + result + “</b></u>”; } function onFailure(result, userContext, methodName) { var builder = new Sys.StringBuilder(); builder.append(“timedOut: “); builder.append(result.get_timedOut()); builder.appendLine();
As 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 “ /><html xmlns=” /><head><title> Untitled Page </title> <script type=”text/javascript” language=”javascript”> var request; function onSuccess(result, userContext, methodName) { userContext.innerHTML = “<b><u>” + result + “</b></u>”; } function onFailure(result, userContext, methodName) { var builder = new Sys.StringBuilder(); builder.append(“timedOut: “); builder.append(result.get_timedOut()); builder.appendLine();