Beginning Ajax with ASP.NET- P15 pptx

15 273 0
Beginning Ajax with ASP.NET- P15 pptx

Đ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

Listing 8-3: (continued) 38. if(prototypeJs.Length > 0) 39. RegisterClientScriptBlock(page, Constant.AjaxID + “.prototype”, 40. “<script type=\”text/javascript\” src=\”” + prototypeJs + “\”></script>”); 41. 42. if(coreJs.Length > 0) 43. RegisterClientScriptBlock(page, Constant.AjaxID + “.core”, 44. “<script type=\”text/javascript\” src=\”” + coreJs + “\”></script>”); 45. 46. if(convertersJs.Length > 0) 47. RegisterClientScriptBlock(page, Constant.AjaxID + “.converters”, 48. “<script type=\”text/javascript\” src=\”” + convertersJs + “\”></script>”); 49. 50. 51. if(Settings.TokenEnabled) 52. { 53. RegisterClientScriptBlock(page, Constant.AjaxID + “.token”, 54. “<script type=\”text/javascript\”>AjaxPro.token = \”” + CurrentAjaxToken + “\”;</script>”); 55. } 56. } Being as flexible as possible, the library allows you to set these property values with additional Web.Config entries. The only thing you’ve done so far in the Web.Config is to wire up the Ajax Pro page handler. The library has defined a custom section that you can use in your Web.Config. This is processed by the AjaxPro.AjaxSettingsSectionHandler, found in the /Configuration/ AjaxSettingsSectionHandler.cs file . Try It Out Adding Custom Sections to Your Web.Config File Fortunately, ASP.NET has framework calls that make it really easy to add custom sections to your Web.Config file. Listing 8-4 shows a sample entry in your Web.Config to override the several default property and actions of the Ajax.NET Pro library. Listing 8-5 shows how the library reads those overridden values. Listing 8-4: Web.Config configSections Sample <configuration> 01. <configSections> 02. <sectionGroup name=”ajaxNet”> 03. <section name=”ajaxSettings” type=”AjaxPro.AjaxSettingsSectionHandler, AjaxPro” 04. requirePermission=”false” 05. restartOnExternalChanges=”true” /> 06. </sectionGroup> 07. </configSections> 08. 186 Chapter 8 11_78544X ch08.qxp 7/18/06 3:16 PM Page 186 09. <ajaxNet> 10. <ajaxSettings> 11. <urlNameSpaceMappings> 12. <add namespace=”Namespace.ClassName,AssemblyName” path=”MyPath” /> 13. </urlNameSpaceMappings> 14. <jsonConverters> 15. <! <remove type=”Namespace.Class1,Assembly”/> 16. <add type=”Namespace.Class2,Assembly”/> 17. > 18. </jsonConverters> 19. <debug enabled=”false” /> 20. <token enabled=”false” sitePassword=”password” /> 21. <! <scriptReplacements> 22. <file name=”core” path=”~/scripts/debugcore.js” /> 23. <file name=”prototype” path=”~/scripts/debugprototype.js” /> 24. <file name=”converter” path=”~/scripts/debugconverter.js” /> 25. </scriptReplacements> 26. > 27. </ajaxSettings> 28. </ajaxNet> </configuration> How It Works ❑ The first custom setting in Listing 8-4 is the urlNameSpaceMappings on line 11. This setting allows you to mask your JavaScript calls so that you do not expose the class name and assembly names in your application. You may or may not be able to use this, depending on the version of ASP.NET and Visual Studio that you are using. If you know the name of your final assembly, then you can add this masking. If you’re using dynamic compiling, as you did in Listing 8-1, then you can’t use this mask because you don’t know the assembly name beforehand. The benefit to masking your class and assembly names is that this removes the ability of someone to discover the internal knowledge of your application. ❑ Next is jsonConverters. This is the section that enables you to instruct Ajax.NET Pro to load your JavaScript JSON converters. In the commented section, on lines 15 and 16, you can see the format of loading these converters. Converters can be removed and added with the standard <remove /> and <add /> nodes. Debugging can also be turned on or off in this section. If debugging is turned on, and an excep- tion returned in the Response.Error object, then the exception will contain more information than just the exception text, such as Stack, TargetSize, and Source information. ❑ Ajax.NET Pro supports a token concept that tries to confirm that requests are coming from clients that you originally served content to. This is to help eliminate spoofing of your site. Because the calls back into the server are simple JavaScript calls, it’s possible to execute JavaScript from other pages against your site. If the token is enabled and the password is set, then a security token is placed in the head of the page that is rendered. <script type=”text/javascript”>AjaxPro.token = “f5274a7d77bc2a417b22efb3dfda9ba8”;</script> 187 Anatomy of Ajax.NET Pro Library 11_78544X ch08.qxp 7/18/06 3:16 PM Page 187 This token is a hash that is made up of several parameters about the user, including IP address, the site being visiting, the browser being used, and the password you supplied. This makes it much more difficult to try hacking routines from another machine against your site. ❑ Refresh your memory with Listing 8-1, specifically with the line number 1. Script replacements allow you to use scripts other than the default scripts that were installed with Ajax.NET Pro. Maybe you have a static file that you’ve added some debugging code to that you’d like to use while you’re debugging your application, but not once you deploy your application. Having these replacement script options in Web.Config is great because you can switch it without recompiling your application. How would you create an overridden core, prototype, or converter file? Don’t you still need all the base functionality that the original files offer? The answer is yes, you do, but that doesn’t mean that you can’t add you own debugging or other code into that script. Open your browser and point it to your web application, /AjaxPro/core.ashx. Do a view source, and you now have a copy of the core.ashx JavaScript file. Save this in your appli- cation as /scripts/debugcore.js. You might play with simply adding an alert() method in a few places, just to see it work. Now to try that debugcore.js in your application, add Listing 8-4 line 22 to your Web.Config. Set the path of the core to “~/scripts/debugcore.js”. Save and run your application, and view the source of the page. Notice that the listing for the Core.ashx shown in Listing 8-1 has changed to your /scripts/debugcore.js file. Listing 8-5 shows the code for Ajax/AjaxSettingsSectionHandler.cs that makes all of these config- uration settings possible. If you’re new to Web.Config section handlers, this code is a great read. It’s pretty simple to follow and might introduce you to a few new XML tricks. Listing 8-5: /Ajax/AjaxSettingsSectionHandler.cs 01. namespace AjaxPro 02. { 03. internal class AjaxSettingsSectionHandler : IConfigurationSectionHandler 04. { 05. #region IConfigurationSectionHandler Members 06. public object Create(object parent, object configContext, System.Xml.XmlNode section) 07. { 08. AjaxSettings settings = new AjaxSettings(); 09. 10. foreach(XmlNode n in section.ChildNodes) 11. { 12. if(n.Name == “coreScript”) 13. { 14. if(n.InnerText != null && n.InnerText.Length > 0) 15. { 16. settings.ScriptReplacements.Add(“core”, n.InnerText); 17. } 18. } The token should be enabled in all cases. It’s a very nice feature that makes hacking your site more difficult. 188 Chapter 8 11_78544X ch08.qxp 7/18/06 3:16 PM Page 188 19. else if(n.Name == “scriptReplacements”) 20. { 21. foreach(XmlNode file in n.SelectNodes(“file”)) 22. { 23. string name = “”; 24. string path = “”; 25. if(file.Attributes[“name”] != null) 26. { 27. name = file.Attributes[“name”].InnerText; 28. if(file.Attributes[“path”] != null) path = file.Attributes[“path”].InnerText; 29. if(settings.ScriptReplacements.ContainsKey(name)) 30. settings.ScriptReplacements[name] = path; 31. else 32. settings.ScriptReplacements.Add(name, path); 33. } 34. } 35. } 36. else if(n.Name == “urlNamespaceMappings”) 37. { 38. settings.UseAssemblyQualifiedName = n.SelectSingleNode(“@useAssemblyQualifiedName[.=’true’]”) != null; 39. XmlNode ns, url; 40. foreach(XmlNode e in n.SelectNodes(“add”)) 41. { 42. ns = e.SelectSingleNode(“@type”); 43. url = e.SelectSingleNode(“@path”); 44. if(ns == null || ns.InnerText == “” || url == null || url.InnerText == “”) 45. continue; 46. if(settings.UrlNamespaceMappings.Contains(url.InnerText)) 47. throw new Exception(“Duplicate namespace mapping ‘“ + url.InnerText + “‘.”); 48. settings.UrlNamespaceMappings.Add (url.InnerText, ns.InnerText); 49. } 50. } 51. else if(n.Name == “jsonConverters”) 52. { 53. XmlNodeList jsonConverters = n.SelectNodes(“add”); 54. foreach(XmlNode j in jsonConverters) 55. { 56. XmlNode t = j.SelectSingleNode(“@type”); 57. if(t == null) 58. continue; 59. Type type = Type.GetType(t.InnerText); 60. if(type == null) 61. { 62. // throw new ArgumentException(“Could not find type “ 63. // + t.InnerText + “.”); 64. continue; 65. } (continued) 189 Anatomy of Ajax.NET Pro Library 11_78544X ch08.qxp 7/18/06 3:16 PM Page 189 Listing 8-5: (continued) 66. if (!typeof(IJavaScriptConverter). IsAssignableFrom(type)) 67. { 68. // throw new ArgumentException(“Type “ + 69. // t.InnerText + “ does not inherit from 70. // JavaScriptObjectConverter.”); 71. continue; 72. } 73. 74. IJavaScriptConverter c = (IJavaScriptConverter)Activator.CreateInstance(type); 75. c.Initialize(); 76. settings.JavaScriptConverters.Add(c); 77. } 78. } 79. else if(n.Name == “encryption”) 80. { 81. string cryptType = n.SelectSingleNode(“@cryptType”) != null ? n.SelectSingleNode(“@cryptType”).InnerText : null; 82. string keyType = n.SelectSingleNode(“@keyType”) != null ? n.SelectSingleNode(“@keyType”).InnerText : null; 83. if(cryptType == null || keyType == null) 84. continue; 85. 86. AjaxEncryption enc = new AjaxEncryption(cryptType, keyType); 87. if(!enc.Init()) 88. throw new Exception(“Ajax.NET Professional encryption configuration failed.”); 89. settings.Encryption = enc; 90. } 91. else if(n.Name == “token”) 92. { 93. settings.TokenEnabled = n.SelectSingleNode(“@enabled”) != null && n.SelectSingleNode(“@enabled”) .InnerText == “true”; 94. settings.TokenSitePassword = n.SelectSingleNode(“@sitePassword”) != null ? n.SelectSingleNode(“@sitePassword”).InnerText : settings.TokenSitePassword; 95. } 96. else if (n.Name == “debug”) 97. { 98. if (n.SelectSingleNode(“@enabled”) != null && n.SelectSingleNode(“@enabled”).InnerText == “true”) 99. settings.DebugEnabled = true; 100. } 101. else if (n.Name == “oldStyle”) 102. { 103. if (n.SelectSingleNode(“objectExtendPrototype”) != null) 104. { 190 Chapter 8 11_78544X ch08.qxp 7/18/06 3:16 PM Page 190 105. if (!settings.OldStyle.Contains(“objectExtendPrototype”)) 106. settings.OldStyle.Add(“objectExtendPrototype”); 107. } 108. } 109. } 110. 111. return settings; 112. } 113. #endregion 114. } 115. } What Role Does the Ajax.AjaxMethod() Attribute Play? As far as the library goes, at this point, you’ve registered a type with the AjaxPro.RegisterType ForAjax() method, which in turn sent multiple <script> tags to the browser. These are seen in Listing 8-1. Now it’s time to examine the request that is generated because of the src of the script. In the example, the source of the registered type rendered as /BeginningAjax/ajaxpro/Chapter7_ OnLoading,App_Web_rjv9qbqt.ashx . Remember that Chapter7_OnLoading is the page class name, and App_Web_rjv9qbqt is the dynamic assembly that the page class is compiled into. The first code in the library to get this request is the AjaxHandlerFactory, which is assigned to catch all incoming requests for /AjaxPro/*.ashx. The AjaxHandlerFactory is in charge of looking at the incoming URL, creating a new AjaxPro.Request object, and then handing off that request to an AjaxProcessor, which is run. This is all pretty standard and happens on every request. Remember, one of the ideas of using a page handler is to handle dynamically generated requests. The library does this so that you can ask for the class and assembly that you want a JavaScript proxy for. Once you’ve sup- plied this information (class name and assembly), the library is smart enough to figure out what you’re looking for. How is it smart enough, you ask? Well, truthfully, it’s not without another piece of information that you’ve already supplied. That piece of information is the AjaxPro.AjaxMethod() attribute that you placed over the signature of the public method that you wanted to make available to the AjaxPro.NET library. So, we’ll fast forward a little bit and talk about what the library does. A request comes in, and the library is able to parse the class name and the assembly name from the URL. If you’re using the urlNameSpaceMappings, then the library is still able to figure out the class name and the assembly; it just has to look in this mappings’ HashTable first. Once the library has these two crucial pieces of infor- mation, it’s able to use a .NET Framework technology called reflection to inspect or, as it’s appropriately named, reflect upon the class that you’ve supplied. Reflection really is an aptly named portion of the .NET Framework. Something already exists, a compiled class in this example, and you wish to reflect upon that class. So, reflection has its own great support that is native in the .NET Framework, and it allows you to find everything you want to know about that class. The library at this point is interested in finding all the methods in the class that have been marked with the AjaxPro.AjaxMethod() attribute. Once the library finds a method that has been marked with that attribute, it knows that a JavaScript proxy is going to need to be built. This is, in fact, done for all the methods on the class that are marked 191 Anatomy of Ajax.NET Pro Library 11_78544X ch08.qxp 7/18/06 3:16 PM Page 191 with the AjaxPro.AjaxMethod() attribute. The proxy classes are all created sequentially and sent back to the browser as the source of your JavaScript call. You’ll look at the code in a minute, but it should all be coming into focus now, and to drill it in, consider the following quick review. 1. When you register a page class, the register utility is responsible for writing the <script> tag with a source that will call into the library and tell the library the class name and the assembly name to find that class in. 2. Once this call comes into the library, it uses reflection to load that class and find all the methods marked with the AjaxPro.AjaxMethod() attribute. 3. For each method it finds, a JavaScript proxy class is created, and this final result is sent back to the browser as the source of the <script> tag. Now all this JavaScript is loaded into the browser memory and is ready for you to make the JavaScript calls (as you did in Chapter 7) that use the proxy classes created to communicate back to the server. How Does the JavaScript Call Get to the Server and Back? So now you know how the framework creates the proxy classes. This is typically never seen by the devel- oper, or anyone, because the view source just shows a URL as the src attribute pointing into the library. Listing 8-6 shows the rendered code from the Chapter7_OnLoading JavaScript source page. This is what’s returned to the browser as a result of the <script> having a URL pointing to AjaxPro/Chapter7_ OnLoading,App_Web_rjv9qbqt.ashx . Listing 8-6: JavaScript Source from AjaxPro/Chapter7_OnLoading,App_Web_rjv9qbqt.ashx 01. addNamespace(“Chapter7_OnLoading”); 02. Chapter7_OnLoading_class = Class.create(); 03. Object.extend(Chapter7_OnLoading_class.prototype, Object.extend(new AjaxPro.AjaxClass(), { 04. WaitXSeconds: function(SecondsToWait) { 05. return this.invoke(“WaitXSeconds”, {“SecondsToWait”:SecondsToWait}, this.WaitXSeconds.getArguments().slice(1)); 06. }, 07. initialize: function() { 08. this.url = ‘/BeginningAjaxPro/ajaxpro/Chapter7_OnLoading,App_Web_ao7bzzpr.ashx’; 09. } 10. })); 11. Chapter7_OnLoading = new Chapter7_OnLoading_class(); Notice that the end of line 3 is a call to return a new AjaxPro.AjaxClass function. This function is call- ing into the source returned by the Core.ashx request. Remember that this is the code that is coming back from only one of the two requests you made. This is specific to the Chapter7_OnLoading request, but there is also an /AjaxPro/Core.ashx request. 192 Chapter 8 11_78544X ch08.qxp 7/18/06 3:16 PM Page 192 What Is an Ajax.NET Pro Converter? You now should have a great fundamental understanding of the Ajax.NET Pro library and how it works inside and out. You know how to register classes that create <script> tags with src values that will build and dynamically generate JavaScript proxy classes of your AjaxPro.AjaxMethod() methods. You also know how these methods get called in the proxy classes, and the return values processed with the sync request or async callback methods. There is only one thing that you have missed, and that’s deal- ing with parameter mismatching. The JavaScript language is not a part of the .NET Framework, and obviously it’s true that .NET is not part of the JavaScript language. One of the consequences of communicating between two different platforms is making sure that both platforms understand each other’s data types. The Ajax.NET Pro converters have the sole job of converting objects between .NET and JavaScript Object Notation (JSON). These converters make it possible to have access to objects and their properties in a similar fashion on both platforms, and lucky for us all, part of the Ajax.NET Pro library already defines a nice set of the converters for us. Once both platforms can understand each other’s data types, the types can be passed around, and interchanged more easily on both platforms. Think of the converters as language interpreters. They’re the man in the middle, communicating back and forth to both platforms. In the following example, you’ll look at the System.Data.DataTable converter that is built into the Ajax.NET Pro library. The example shows the JSON converter for a DataTable. You may recognize this format from the basic DataTable sample in the introduction to JSON in Chapter 5. 01: public override string Serialize(object o) 02: { 03: if(!(o is DataTable)) 04: throw new NotSupportedException(); 05: 06: StringBuilder sb = new StringBuilder(); 07: DataTable dt = (DataTable)o; 08: 09: DataColumnCollection cols = dt.Columns; 10: DataRowCollection rows = dt.Rows; 11: 12: bool b = true; 13: 14: sb.Append(“new “); 15: sb.Append(clientType); 16: sb.Append(“([“); 17: 18: foreach(DataColumn col in cols) 19: { 20: if(b){ b = false; } 21: else{ sb.Append(“,”); } 22: 23: sb.Append(‘[‘); 24: sb.Append(JavaScriptSerializer.Serialize(col.ColumnName)); 25: sb.Append(‘,’); 26: sb.Append(JavaScriptSerializer.Serialize(col.DataType.FullName)); 27: sb.Append(‘]’); 28: } 29: 193 Anatomy of Ajax.NET Pro Library 11_78544X ch08.qxp 7/18/06 3:16 PM Page 193 30: sb.Append(“],[“); 31: 32: b = true; 33: 34: foreach(DataRow row in rows) 35: { 36: if(b){ b = false; } 37: else{ sb.Append(“,”); } 38: 39: sb.Append(JavaScriptSerializer.Serialize(row)); 40: } 41: 42: sb.Append(“])”); 43: 44: return sb.ToString(); 45: } This converter allows you to work with a .NET DataTable in JavaScript. If you look back at the Try It Out “Building an HTML Table from a DataTable Object” section in Chapter 7, you’ll see how this DataTable is used in action on the JavaScript side. You have the ability to create custom converter classes for your objects as well. A custom converter class is needed only if you need to expose additional functionality in JavaScript. Most of the time, simply marking your custom class with a [Serializable()] attribute will be sufficient. Remember, if your class is marked with the [Serializable()] attribute, then Ajax.NET Pro will automatically create a JavaScript proxy class for that object and pass all the data back for you. Summary In this chapter, you took a deep dive into the Ajax.NET Pro library. You should now have a great under- standing of the library and the entire process taken to communicate with the library from the client browser using JavaScript to the server-side code behind. Specifically, in this chapter, you looked at: ❑ Ajax.NET Pro library C# source code ❑ Custom Setting through the Web.Config ❑ Ajax.NET Pro converters Now, in the next chapter, you will turn your attention to other Ajax libraries for .NET. 194 Chapter 8 11_78544X ch08.qxp 7/18/06 3:16 PM Page 194 9 Other Ajax Frameworks for .NET Ajax.NET is certainly the most popular of the open source Ajax frameworks for ASP.NET, but the following material will give you a working knowledge of additional frameworks that aid in Ajax development. This chapter is broken up into two parts. The first part provides a high-level review of three client- side Ajax frameworks: Sarissa, HTMLHttpRequest, and MochiKit. The second part of the chapter features more in-depth explanations and examples of three server-side frameworks: ComfortASP.NET, MagicAjax, and Anthem.NET. Throughout this chapter the terms framework and library will be used interchangeably to describe third-party assemblies referenced in your project to provide Ajax functionality. In this chapter, you will learn about: ❑ How client-side only Ajax frameworks operate ❑ The difference between returning data structures and using the changed-HTML architec- ture of server-side frameworks ❑ Integration methods of a library: custom controls and panels ❑ Different configuration options among the Ajax frameworks ❑ Implementing real-world scenarios using three different server-side Ajax frameworks Client-Side Frameworks Ajax development is about seamlessly integrating communication between the client and the server for the user. Although there may be times when you have full control over how data is pre- sented in an application, at times you may encounter a situation where you have no control over 12_78544X ch09.qxp 7/18/06 3:16 PM Page 195 [...]... XMLHttpRequest object, you manipulate it in exactly the same way you would a normal XMLHttpRequest 196 Other Ajax Frameworks for NET Testing for Features Using the Sarissa Framework There is provision within the framework to test for the existence of various features Sarissa provides a utility object called Sarissa with some properties that make it easy to determine the capabilities of the browser your application... XMLHttpRequest object directly Effectively, the creation of the XMLHttpRequest object has been masked for you with the code: var oDomDoc = Sarissa.getDomDocument(); 198 Other Ajax Frameworks for NET Attaching an event to check for the completion of the document load is exactly the same as if you were dealing with an XMLHttpRequest object directly, as shown in the following sample: oDomDoc.onreadystatechange... and checking for a particular feature can be often be done in many different ways, according to what browser is running your web applications These simple one-line checks can be performed within the page or be embedded with an include script to allow for seamless detection of a browsers features 197 Chapter 9 Using Sarissa Framework for Asynchronous XML Document Loading In addition to providing an easy...Chapter 9 what the server makes available to the client In these situations, a client-side-only Ajax framework is a great asset to your toolkit The following section will introduce you to three frameworks that abstract Ajax functionality on the client Sarissa URL: http://sarissa.sourceforge.net/doc/ Sarissa is an entirely client-side library written in JavaScript... Initiating the request is slightly different but very easy and intuitive: oDomDoc.load(“someXMLDocument.xml”); From the previous examples, it is easy to see how Sarissa simplifies the task of dealing with asynchronous or Ajax- like functionality but more important, Sarissa extends that simplicity to be a common factor across all supported browsers You will also notice a line in the preceding code that reads:... is not required (although this can be used) to load a remote document Instead, decorating tags in the document with a specific class style reference cause an asynchronous load of the referenced document to occur and place it in a predefined location, dynamically within the HTML document An example demonstrates this best: /* HTMLHttpRequest v1.0 beta2 (c) 2001-2005 Angus Turnbull, TwinHelix... 2001-2005 Angus Turnbull, TwinHelix Designs http://www.twinhelix.com Licensed under the CC-GNU LGPL, version 2.1 or later: http://creativecommons.org/licenses/LGPL/2.1/ This is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE */ My HTML Content Document... http://kupu.oscom.org/download/ecmaunit-0.3.html Currently this unit test framework is version 0.3 Overall, the Sarissa framework is a reasonably comprehensive programming interface Its adoption rate or usage within the industry is low; however, it is a relatively new framework and worth investigation HTMLHttpRequest URL: www.twinhelix.com/javascript/htmlhttprequest/ The HTMLHttpRequest library is a JavaScript... provide a unique way of asynchronously loading content and issuing requests It should be noted that as of this writing, this library is currently only in the beta 2 stage 199 Chapter 9 Like many other Ajax- style libraries and frameworks, this library provides a layer of abstraction around the XMLHttpRequest object and shields the developer from having to know any intricate details about coding against... properties that make it easy to determine the capabilities of the browser your application is running under This is a very useful feature because it ensures that you don’t have to be overly concerned with what browser your code is running under The code can simply test for the existence of a particular feature and make use of that feature if it exists or take corrective action Two primary examples . <configSections> 02. <sectionGroup name=”ajaxNet”> 03. <section name=”ajaxSettings” type=”AjaxPro.AjaxSettingsSectionHandler, AjaxPro” 04. requirePermission=”false” 05. restartOnExternalChanges=”true”. } 115. } What Role Does the Ajax. AjaxMethod() Attribute Play? As far as the library goes, at this point, you’ve registered a type with the AjaxPro.RegisterType ForAjax() method, which in turn. Refresh your memory with Listing 8-1, specifically with the line number 1. Script replacements allow you to use scripts other than the default scripts that were installed with Ajax. NET Pro. Maybe

Ngày đăng: 03/07/2014, 06:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan