1. Trang chủ
  2. » Công Nghệ Thông Tin

ASP.NET AJAX Programmer’s Reference with ASP.NET 2.0 or ASP.NET 3.5 phần 5 pdf

156 348 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 156
Dung lượng 595,51 KB

Nội dung

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: 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 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 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: 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 AuthenticateRequest: The current HttpApplication object raises the AuthenticateRequest event to enable interested HTTP modules and application code to authenticate the current request 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 AuthorizeRequest: The current HttpApplication object fires the AuthorizeRequest event to enable interested HTTP modules and application code to authorize the current request 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 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 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 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 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: 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 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 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 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) { } 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 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 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 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 = “http://tempuri.org/”)] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public partial class MyMath : BridgeHandler { public MyMath() { this.VirtualPath = “/AJAXFuturesEnabledWebSite2/NewFolder1/Math.asbx”; this.BridgeXml = @” ”; } (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 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 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: 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: 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(); 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 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 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 Add a new Web form (.aspx file) named PageMethods.aspx, and then add the code shown in Listing 14-17 to this aspx file 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 > > 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 Chapter 17: Script and Extender Server Controls Keep in mind that an ASP.NET AJAX component is any ASP.NET AJAX class that directly or indirectly inherits from the ASP.NET AJAX Component base class Since all ASP.NET AJAX controls and behaviors inherit from this base class, they are all ASP.NET AJAX components Listing 17-6 presents the implementation of the AddComponentProperty method As you can see, this method takes the following two steps to ensure that the id property value passed into the method as its second argument is a valid JSON string (Keep in mind that this id property is the id property value of the component referenced by the property being initialized.) Recall that a valid JSON string is a collection of zero or more Unicode characters wrapped in double quotes and using backslash escapes: ❑ First, the AddComponentProperty method calls the QuoteString static method, passing in the component id Recall from Listing 17-7 that the QuoteString method ensures that the specified string, which is the component id in this case, is a collection of Unicode characters using backslash escapes ❑ Second, it wraps the component id in double quotes The AddComponentProperty method then stores the property name and its associated value, which is the id property value of the component that the property references, in an internal collection named References As you can see from Listing 17-6, the References collection is a SortedList of KeyValuePair objects for which each object represents a component property The Key and Value properties of each object in the References collection respectively contain the name of the associated property and its value, which is nothing but the id property value of the component that the property references AddElementProperty Use the AddElementProperty method to initialize those properties of an ASP.NET AJAX component that reference DOM elements on the current page These properties are known as element properties As you can see from Listing 17-6, the AddElementProperty method takes two arguments: a string that contains the name of the property whose value is being initialized, and a string that contains the id HTML attribute value of a DOM element on the current page As Listing 17-6 shows, the AddElementProperty method begins by evaluating the value of the property, which is a reference to the DOM element with the specified id HTML attribute value As such, this method generates the script that contains a call into the $get global JavaScript function The method takes the following steps to ensure that this script is a valid JSON string: ❑ Calls the QuoteString static method, passing in the element id Recall from Listing 17-7 that the QuoteString method ensures that the specified string, which is the element id in this case, is a collection of Unicode characters using backslash escapes ❑ Wraps the element id in double quotes Finally, the AddElementProperty method adds the name of the property and its value, which is the above script, to an internal collection named Properties Notice that the Properties collection is again a SortedList of KeyValuePair objects where each object represents an element property The Key and Value properties of each in the Properties collection respectively contain the name of the associated property and its value, which is nothing but the script that returns a reference to the DOM element with the specified element id 728 c17.indd 728 8/20/07 6:29:59 PM Chapter 17: Script and Extender Server Controls AddEvent The AddEvent method takes two parameters: a string that contains an event name, and a string that contains an event handler In other words, this method enables you to register an event handler for the specified event of an ASP.NET AJAX component As Listing 17-6 shows, this method adds the specified event name and its associated event handler to an internal dictionary named Events Notice that the Events collection is a SortedList of KeyValuePair objects The Key and Value properties of each object in the Events collection contain an event name and its associated event handler, respectively AddProperty The AddProperty public method takes the name of the property being initialized as its first argument and the value of the property as its second argument The value could be any NET object that the JavaScriptSerializer can serialize into a valid JSON string As you can see from Listing 17-6, this method first invokes the Serialize method on the JavaScriptSerializer, passing in the property value, which is a NET object The Serialize method serializes the specified NET object into its JSON representation and returns a string that contains this JSON representation The AddProperty method then adds the name of this property and its value, which is the string that contains the JSON representation of the original NET object, to the Properties collection As you can see from Listing 17-6, our replica ScriptComponentDescriptor exposes a property of type JavaScriptSerializer named Serializer that instantiates and returns a JavaScriptSerializer object AddScriptProperty The AddScriptProperty method enables you to initialize those properties of an ASP.NET AJAX component whose values are client scripts As you can see from Listing 17-6, this method takes the name of the property being initialized as its first argument and the script that constitutes the value of the property as its second argument This method stores the name of the property and its associated value in the Properties collection AppendScript Recall from Listing 17-6 that the GetScript method of the ScriptComponentDescriptor instantiates a StringBuilder and populates it with the script that invokes the $create global JavaScript function to create a new ASP.NET AJAX component As you saw, this method invokes the AppendScript method three times, as follows: ❑ The first time, it passes the Properties collection into the method to have the method to serialize this collection into its object literal representation and to append a string to the specified StringBuilder that contains this representation Recall that this representation contains one name/value pair for each item in the Properties collection ❑ The second time, it passes the Events collection into the method to have the method to serialize this collection into its object literal representation and to append a string to the specified StringBuilder that contains this representation Recall that this representation contains one name/value pair for each item in the Events collection 729 c17.indd 729 8/20/07 6:29:59 PM Chapter 17: Script and Extender Server Controls ❑ The third time, it passes the References collection into the method to have the method to serialize this collection into its object literal representation and to append a string to the specified StringBuilder that contains this representation Recall that this representation contains one name/value pair for each item in the References collection Next, I’ll walk you through the implementation of the AppendScript method, as shown in Listing 17-6 This method takes two arguments: a SortedList of KeyValuePair objects, and a StringBuilder The main responsibility of this method is to serialize the specified SortedList into its JSON representation, which is a JSON object This JSON object, like any other JSON object, starts with an open curly brace ({): builder.Append(“{“); This JSON object also contains a comma-separated list of name/value pairs, for which each name/value pair is the JSON serialization of a KeyValuePair object in the SortedList Here is how the AppendScript method serializes each KeyValuePair object in the SortedList: since the AddComponentProperty, AddElementProperty, AddEvent, AddProperty, and AddScriptProperty methods have already ensured that the value contained in the Value property of each KeyValuePair object in the References, Properties, and Events collections is a valid JSON representation, the AppendScript method must only serialize the value contained in the Key property of each KeyValuePair object To so, the method performs these tasks: ❑ Invokes the QuoteString static method on the HelperMethods class once for each KeyValuePair object in the SortedList, passing in the value of the Key property of the KeyValuePair object to ensure that this value is a collection of Unicode characters using backslash escapes: builder.Append(HelperMethods.QuoteString(pair.Key)); ❑ Wraps the return value of the QuoteString static method in double quotes: builder.Append(‘”’); builder.Append(HelperMethods.QuoteString(pair.Key)); builder.Append(‘”’); Finally, the AppendScript method appends a colon character followed by the value of the Value property of the KeyValuePair object as is: builder.Append(‘:’); builder.Append(pair.Value); ScriptControlDescriptor Listing 17-8 presents the implementation of the replica ScriptControlDescriptor As you can see, this class derives from the ScriptComponentDescriptor base class discussed in the previous sections Note that the constructor of the ScriptControlDescriptor class makes use of the internal constructor of the ScriptComponentDescriptor base class As discussed earlier, this internal constructor takes two parameters, the first containing the fully qualified name of the type of the ASP.NET AJAX control being 730 c17.indd 730 8/20/07 6:29:59 PM Chapter 17: Script and Extender Server Controls instantiated and initialized, and the second containing the id HTML attribute of the associated DOM element of this ASP.NET AJAX control Listing 17-8: The ScriptControlDescriptor namespace CustomComponents3 { using System; public class ScriptControlDescriptor : ScriptComponentDescriptor { public ScriptControlDescriptor(string type, string elementID) : base(type, elementID) { base.RegisterDispose = false; } public override string ClientID { get { return this.ElementID; } } public string ElementID { get { return base.ElementIDInternal; } } public override string ID { get { return base.ID; } set { throw new InvalidOperationException(“ID Not Settable”); } } } } ScriptBehaviorDescriptor Listing 17-9 presents the implementation of the replica ScriptBehaviorDescriptor As you can see, this class, just like the ScriptControlBehavior class, derives from the ScriptComponentDescriptor base class Note that the constructor of the ScriptBehaviorDescriptor class, just like the constructor of the ScriptControlBehavior class, makes use of the internal constructor of the ScriptComponentDescriptor base class In this case, the first parameter passed into this internal constructor contains the fully qualified name of the type of the ASP.NET AJAX behavior being instantiated and initialized, and the second parameter contains the id HTML attribute of the associated DOM element of this ASP.NET AJAX behavior 731 c17.indd 731 8/20/07 6:30:00 PM Chapter 17: Script and Extender Server Controls As discussed in Chapter 16, every ASP.NET AJAX behavior exposes a property named name that contains the name of the behavior As a result, the ScriptBehaviorDescriptor overrides the GetScript method of its base class — that is, the ScriptComponentDescriptor — to make a call to the AddProperty method to add the value of its _name field as the value of the name property of the behavior being instantiated and initialized: if (!string.IsNullOrEmpty(this._name)) base.AddProperty(“name”, this._name); return base.GetScript(); As you can see from Listing 17-9, the ScriptBehaviorDescriptor class exposes a read/write property named Name that enables you to get and to set the value of the _name field of the class Note that the getter of this property first checks whether the value of the _name field is set — that is, whether the setter method has been called to set this value If so, it simply returns the value of the _name field If not, it invokes another method named GetTypeName, passing in the fully qualified name of the type of the behavior being instantiated and initialized to generate and return an appropriate value for the Name property As Listing 17-9 shows, the GetTypeName method simply extracts the name of the type of the behavior being instantiated and initialized from its fully qualified name Recall that the fully qualified name of the type of an ASP.NET AJAX component such as a behavior contains both the name and the complete namespace containment hierarchy of the component Listing 17-9: The ScriptBehaviorDescriptor namespace CustomComponents3 { public class ScriptBehaviorDescriptor : ScriptComponentDescriptor { private string _name; public ScriptBehaviorDescriptor(string type, string elementID) : base(type, elementID) { base.RegisterDispose = false; } protected internal override string GetScript() { if (!string.IsNullOrEmpty(this._name)) base.AddProperty(“name”, this._name); return base.GetScript(); } private static string GetTypeName(string type) { int num = type.LastIndexOf(‘.’); if (num == -1) return type; 732 c17.indd 732 8/20/07 6:30:00 PM Chapter 17: Script and Extender Server Controls return type.Substring(num + 1); } public override string ClientID { get { if (string.IsNullOrEmpty(this.ID)) return (this.ElementID + “$” + this.Name); return this.ID; } } public string ElementID { get { return base.ElementIDInternal; } } public string Name { get { if (string.IsNullOrEmpty(this._name)) return GetTypeName(base.Type); return this._name; } set { this._name = value; } } } } ScriptReference Listing 17-10 presents the implementation of the replica ScriptReference class The main responsibility of a ScriptReference object is to specify and represent a reference to a JavaScript file The ASP.NET AJAX ScriptReference class provides you with two approaches to specify the location of the JavaScript file The first approach requires you to set the value of the Path property of the ScriptReference object to the URL of the JavaScript file The second approach, which only applies to JavaScript files embedded in an assembly, requires you to assign a string that contains the assembly information to the Assembly property of the ScriptReference object and to assign a string that specifies the name of the JavaScript file to the Name property of the ScriptReference object To keep our discussions focused, the replica ScriptReference class only supports the first approach However, you can easily extend the replica to add support for the second approach as well 733 c17.indd 733 8/20/07 6:30:00 PM Chapter 17: Script and Extender Server Controls Listing 17-10: The ScriptReference Class using System; using System.Web.UI; using System.ComponentModel; namespace CustomComponents3 { public class ScriptReference { private Control _containingControl; private bool _isStaticReference; private string _path; public ScriptReference() {} public ScriptReference(string path) : this() { this.Path = path; } [DefaultValue(“”), Category(“Behavior”)] public string Path { get { if (this._path != null) return this._path; return string.Empty; } set { this._path = value; } } internal bool IsStaticReference { get { return this._isStaticReference; } set { this._isStaticReference = value; } } internal Control ContainingControl { get { return this._containingControl; } set { this._containingControl = value; } } } } 734 c17.indd 734 8/20/07 6:30:01 PM Chapter 17: Script and Extender Server Controls ScriptReferenceCollection Listing 17-11 presents the implementation of the replica ScriptReferenceCollection class As the name suggests, this collection contains objects of type ScriptReference As you’ll see in the next section, the ScriptManager exposes a property of type ScriptReferenceCollection named Scripts Thanks to the NET 2.0 generics, implementing a new collection class such as ScriptReferenceCollection is as easy as declaring a class that derives from one of the standard NET generic collections Listing 17-11: The ScriptReferenceCollection Class using System.Collections.ObjectModel; namespace CustomComponents3 { public class ScriptReferenceCollection : Collection { } } ScriptManager Listing 17-12 contains the implementation of the replica ScriptManager class As you can see, this class drives from the Control base class This derivation turns the ScriptManager into a server control and consequently allows it to participate in the typical ASP.NET page/control life-cycle phases I’ll discuss the implementation of the methods and properties of the replica ScriptManager server control in the following sections Listing 17-12: The ScriptManager Class using using using using using using System; System.Web; System.Text; System.Web.UI; System.ComponentModel; System.Collections.Generic; namespace CustomComponents3 { [ParseChildren(true), DefaultProperty(“Scripts”), NonVisualControl, PersistChildren(false)] public class ScriptManager : Control { public event EventHandler ResolveScriptReference { add { base.Events.AddHandler(ResolveScriptReferenceEvent, value); } remove (continued) 735 c17.indd 735 8/20/07 6:30:01 PM Chapter 17: Script and Extender Server Controls Listing 17-12 (continued) { base.Events.RemoveHandler(ResolveScriptReferenceEvent, value); } } private ScriptReferenceCollection _scripts; [PersistenceMode(PersistenceMode.InnerProperty), Editor(“System.Web.UI.Design.CollectionEditorBase, System.Web.Extensions.Design, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”, typeof(System.Drawing.Design.UITypeEditor)), DefaultValue((string)null), MergableProperty(false), Category(“Behavior”)] public ScriptReferenceCollection Scripts { get { if (this._scripts == null) this._scripts = new ScriptReferenceCollection(); return this._scripts; } } protected override void OnInit(EventArgs e) { base.OnInit(e); Page.Items[typeof(ScriptManager)] = this; this.Page.PreRenderComplete += new EventHandler(Page_PreRenderComplete); } public static ScriptManager GetCurrent(Page page) { return (page.Items[typeof(ScriptManager)] as ScriptManager); } private static readonly object ResolveScriptReferenceEvent = new object(); protected virtual void OnResolveScriptReference(ScriptReferenceEventArgs e) { EventHandler handler = (EventHandler) base.Events[ResolveScriptReferenceEvent]; if (handler != null) handler(this, e); } void Page_PreRenderComplete(object sender, EventArgs e) { List list1 = new List(); this.CollectScripts(list1); ScriptReferenceEventArgs args; 736 c17.indd 736 8/20/07 6:30:01 PM Chapter 17: Script and Extender Server Controls foreach (ScriptReference reference3 in list1) { args = new ScriptReferenceEventArgs(reference3); this.OnResolveScriptReference(args); } foreach (ScriptReference reference4 in list1) { string url = reference4.Path; if (this.LoadScriptsBeforeUI) this.Page.ClientScript.RegisterClientScriptInclude(typeof(ScriptManager), url, url); else { string script = “\r\n”; this.Page.ClientScript.RegisterStartupScript(typeof(ScriptManager), url, script, false); } } } private void CollectScripts(List scripts) { if (this._scripts != null) { foreach (ScriptReference reference1 in this._scripts) { reference1.ContainingControl = this; reference1.IsStaticReference = true; scripts.Add(reference1); } } this.AddScriptReferencesForScriptControls(scripts); this.AddScriptReferencesForExtenderControls(scripts); } private void AddScriptReferencesForScriptControls( List scriptReferences) { if (this._scriptControls != null) { foreach (IScriptControl scriptControl in this._scriptControls.Keys) { IEnumerable enumerable1 = scriptControl.GetScriptReferences(); if (enumerable1 != null) { using (IEnumerator enumerator1 = enumerable1.GetEnumerator()) { while (enumerator1.MoveNext()) { (continued) 737 c17.indd 737 8/20/07 6:30:01 PM Chapter 17: Script and Extender Server Controls Listing 17-12 (continued) ScriptReference reference1 = enumerator1.Current; if (reference1 != null) { reference1.ContainingControl = (Control)scriptControl; reference1.IsStaticReference = false; scriptReferences.Add(reference1); } } } } } } } private void AddScriptReferencesForExtenderControls(List scriptReferences) { if (this._extenderControls != null) { foreach (IExtenderControl extenderControl in this._extenderControls.Keys) { IEnumerable enumerable1 = extenderControl.GetScriptReferences(); if (enumerable1 != null) { using (IEnumerator enumerator1 = enumerable1.GetEnumerator()) { while (enumerator1.MoveNext()) { ScriptReference reference1 = enumerator1.Current; if (reference1 != null) { reference1.IsStaticReference = false; reference1.ContainingControl = (Control)extenderControl; scriptReferences.Add(reference1); } } } } } } } public void RegisterScriptControl(TScriptControl scriptControl) where TScriptControl : Control, IScriptControl { int num; this.ScriptControls.TryGetValue(scriptControl, out num); num++; this.ScriptControls[scriptControl] = num; } 738 c17.indd 738 8/20/07 6:30:02 PM Chapter 17: Script and Extender Server Controls private Dictionary _scriptControls; private Dictionary ScriptControls { get { if (this._scriptControls == null) this._scriptControls = new Dictionary(); return this._scriptControls; } } public void RegisterExtenderControl(TExtenderControl extenderControl, Control targetControl) where TExtenderControl : Control, IExtenderControl { List list; if (!this.ExtenderControls.TryGetValue(extenderControl, out list)) { list = new List(); this.ExtenderControls[extenderControl] = list; } list.Add(targetControl); } private Dictionary _extenderControls; private Dictionary ExtenderControls { get { if (this._extenderControls == null) this._extenderControls = new Dictionary(); return this._extenderControls; } } private bool _loadScriptsBeforeUI; [Category(“Behavior”), DefaultValue(true)] public bool LoadScriptsBeforeUI { get { return this._loadScriptsBeforeUI; } set { this._loadScriptsBeforeUI = value; } } public void RegisterScriptDescriptors(IExtenderControl extenderControl) { List list; Control control = extenderControl as Control; if (!this.ExtenderControls.TryGetValue(extenderControl, out list)) throw new ArgumentException(“Extender Control Not Registered”); (continued) 739 c17.indd 739 8/20/07 6:30:02 PM Chapter 17: Script and Extender Server Controls Listing 17-12 (continued) foreach (Control control2 in list) { if (control2.Visible) { IEnumerable scriptDescriptors = extenderControl.GetScriptDescriptors(control2); if (scriptDescriptors != null) { StringBuilder builder = null; foreach (ScriptDescriptor descriptor in scriptDescriptors) { if (builder == null) { builder = new StringBuilder(); builder.AppendLine(“Sys.Application.add_init(function() {“); } builder.Append(“ “); builder.AppendLine(descriptor.GetScript()); descriptor.RegisterDisposeForDescriptor(this, control); } if (builder != null) { builder.AppendLine(“});”); string key = builder.ToString(); Page.ClientScript.RegisterStartupScript(typeof(ScriptManager), key, key, true); } } } } } public void RegisterScriptDescriptors(IScriptControl scriptControl) { int num; Control control = scriptControl as Control; if (!this.ScriptControls.TryGetValue(scriptControl, out num)) throw new ArgumentException(“Script Control Not Registered”); for (int i = 0; i < num; i++) { IEnumerable scriptDescriptors = scriptControl.GetScriptDescriptors(); if (scriptDescriptors != null) { StringBuilder builder = null; foreach (ScriptDescriptor descriptor in scriptDescriptors) { if (builder == null) { 740 c17.indd 740 8/20/07 6:30:02 PM Chapter 17: Script and Extender Server Controls builder = new StringBuilder(); builder.AppendLine(“Sys.Application.add_init(function() {“); } builder.Append(“ “); builder.AppendLine(descriptor.GetScript()); descriptor.RegisterDisposeForDescriptor(this, control); } if (builder != null) { builder.AppendLine(“});”); string key = builder.ToString(); Page.ClientScript.RegisterStartupScript(typeof(ScriptManager), key, key, true); } } } } } } Scripts As you can see from Listing 17-12, the ScriptManager server control exposes a collection property of the type ScriptReferenceCollection named Scripts that contains the ScriptReference objects that reference JavaScript files Note that this property is marked with the PersistenceMode(PersistenceMode.InnerProperty) metadata attribute to enable you to add ScriptReference objects to this collection in a purely declarative fashion, without writing a single line of imperative code LoadScriptsBeforeUI As Listing 17-12 shows, the ScriptManager server control exposes a Boolean property named LoadScriptsBeforeUI that specifies whether the script files referenced by the ScriptReference objects in the Scripts collection must be loaded before the HTML markup text The default is true The decision as to whether to load the scripts before or after UI depends on whether the scripts contain any references to the UI elements If they do, they must be loaded after UI to ensure that the UI elements that the scripts reference are already loaded You’ll see an example of this property later in this chapter ScriptControls The replica ScriptManager server control maintains the list of all script server controls on the current page in an internal collection named ScriptControls (see Listing 17-12) RegisterScriptControl The RegisterScriptControl method of the ScriptManager server control adds the specified script server control to the ScriptControls collection discussed in the previous section (see Listing 17-12) 741 c17.indd 741 8/20/07 6:30:03 PM Chapter 17: Script and Extender Server Controls ExtenderControls The replica ScriptManager server control also maintains the list of all extender server controls on the current page in an internal collection named ExtenderControls (see Listing 17-12) RegisterExtenderControl As you saw in the previous section, the ExtenderControls collection is a dictionary of items, each of which contains a list of server controls associated with a particular extender server control The RegisterExtenderControl method takes two parameters, the first referencing the extender server control being registered and the second the server control whose client-side functionality the specified extender server control extends Recall that this server control is known as the target server control of the extender server control The RegisterExtenderControl simply accesses the item associated with the specified extender server control and adds the specified target server control to the associated server control list of this item Recall also that each item in the ExtenderControls dictionary contains a list of server controls associated with a particular extender server control GetCurrent As Listing 17-12 shows, the GetCurrent static method of the ScriptManager server control returns a reference to the current ScriptManager server control Recall that the ScriptManager server control maintains this reference in the Items collection of the current Page object This ensures that the same ScriptManager server control is used throughout the current request OnInit As Listing 17-12 shows, the ScriptManager server control overrides the OnInit method that inherits from the Control base class This method performs three tasks First, it invokes the OnInit method of its base class to raise the Init event and consequently to invoke all the event handlers registered for the Init event of the current ScriptManager server control: base.OnInit(e); Next, it stores the reference to the current ScriptManager server control in the Items collection of the current Page object As discussed earlier, the GetCurrent static method returns this reference to its caller to ensure that the same instance of the ScriptManager server control is used during processing of the current request Page.Items[typeof(ScriptManager)] = this; Finally, the OnInit method registers a method named Page_PreRenderComplete as an event handler for the PreRenderComplete event of the current Page object: this.Page.PreRenderComplete += new EventHandler(Page_PreRenderComplete); 742 c17.indd 742 8/20/07 6:30:03 PM ... directory than the following standard directory, go to that directory): %windir%\Microsoft.NET\Framework\v2.0 .50 727\Temporary ASP.NET Files In this directory, search for the directory with the... value=”/wEPDwULLTEzMTg5MjA5NzVkZDZArSkraR3ukOEGxC944PmDWFHr” />

Ngày đăng: 12/08/2014, 08:23

TỪ KHÓA LIÊN QUAN