Writing lightweight Ajax component 02 — Analysis of Ajax Pro

Time:2020-11-25

preface

  LastThis paper introduces some ways to implement Ajax in webform platform, and implements a base class. In this article, let’s look at an open source component: ajaxpro. Although this is a relatively old component, but the implementation idea and source code is worth learning. Through the introduction of the last article, we know that the method to call the page object is realized by reflection. The key is the whole processing process, including reflection calling method, parameter mapping, etc. Ajax Pro not only helps us to implement this process in the background, but also encapsulates the method of request call in the foreground, such as the related methods of Ajax. The asynchronous request can be sent by using the method of Ajax pro. There is no need to encapsulate JS or use js library. Next, we will analyze this component.

1、 The use of ajaxpro

Let’s first look at how this component is used.

  1. Register Ajax handlerfactory

stay web.config The configuration is as follows:


<httpHandlers>
 <add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/>
</httpHandlers>

In short, if the URL of the request conforms to the format of ajaxpro / *. Ashx, it will be processed by ajaxhandlerfactory, which is a factory class that implements the ihandlerfactory interface and is used to obtain the ihandler handler. The format of type is: “name control. Class name, assembly name”.

  2. In the page class page_ Load event to register


protected void Page_Load(object sender, EventArgs e)
{
 AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage));
}

We pass the type of the page object to the resistertypofoajax method. This method is used to register the script in the foreground. Specifically, it will call the registerclientscriptblock of the current page object for registration. Therefore, there must be a < form runat = “server” > < / form > in the. ASPX file, otherwise the script will not be registered. (the type is passed here. In practice, it can be passed internally without passing HttpContext.Current.Handler . gettype(). Basetype can also get this type)

  3. Mark the method with Ajax method 


[AjaxMethod]
public List<string> GetList(string input1,string input2)
{
 return new List<string> { input1, input2 };
}

Ajax method is a tag attribute, which indicates that this method is used to process Ajax requests, and it is finally executed through reflection. It has several pairs of constructors. For some data that need to be cached, the cache time can be set. If our request does not need to use session, we can set httpsessionstaterequirement. If the request needs to be asynchronous, for example, to request a time-consuming web service You can also set the handler to an asynchronous state.

The return value of a method can be a simple type or a complex type; for example, a collection type obtained in the foreground is an array.

  4. Foreground call

The configuration and use of the background are very simple. Next, let’s see how the foreground initiates a request.


function GetList() {
 //var result = AjaxProNamespace.AjaxProPage.GetList("a", "b").value;
 //console.log(result);
 AjaxProNamespace.AjaxProPage.GetList("a", "b", function (result) {
  console.log(result);
 });  
}

Here, ajaxpronamespace is the name space of the page class, AJAX propage is the name of the page class, and GetList is the method of marking. Why can you write like this? As mentioned above, ajaxpro will register a script in the foreground, and it will generate the following script according to the relevant information of our page objects, so we can call it in this way, instead of writing JS or using jQuery library.


if(typeof AjaxProNamespace == "undefined") AjaxProNamespace={};
if(typeof AjaxProNamespace.AjaxProPage_class == "undefined") AjaxProNamespace.AjaxProPage_class={};
AjaxProNamespace.AjaxProPage_class = function() {};
Object.extend(AjaxProNamespace.AjaxProPage_class.prototype, Object.extend(new AjaxPro.AjaxClass(), {
 GetList: function(input1, input2) {
  return this.invoke("GetList", {"input1":input1, "input2":input2}, this.GetList.getArguments().slice(2));
 },
 url: '/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx'
}));
AjaxProNamespace.AjaxProPage = new AjaxProNamespace.AjaxProPage_class();

The parameter of GetList corresponds to the parameter of background method. The type must be convertible, otherwise the call will fail. The last parameter is the callback function. The parameter of the callback function is the object that encapsulates the return result. Its value attribute is the value returned after successful execution. For example, the above return is an array object. The error contains information about the failure.

Note that the part commented out above is the practice of synchronous request, which is often not what we want. I have seen people use it incorrectly.

2、 Principle of ajaxpro processing request

This paper mainly focuses on the process of processing Ajax requests by components, and other auxiliary functions are not introduced.

 1. Generate auxiliary script

On page_ In the load event, we call AjaxPro.Utility.RegisterTypeForAjax (typeof (Ajax propage)); used to register the required scripts. We noticed that the following script was introduced in the foreground page:

That is, each page will initiate these requests. These are files ending in. Ashx, but they are actually JS code. Some of these JS are nested in the DLL as resources, and some are generated automatically. They mainly encapsulate Ajax request related methods, and let us call methods with: namespace, page class name, tag method name. Why use. Ashx instead of. JS? As the internal resource file of the component, the external cannot directly request the. JS file, while. Ashx can be intercepted and then used Response.Write Output the content.

If the efficiency of generating and sending these scripts every time is very low, the internal processing of ajaxpro is to judge if none math and if modified since of the request header. If both of them are the same as the cached ones, a 304 status code will be returned. Therefore, only the first request from the client, the server will return the contents of the file, and the subsequent requests will only return 304, indicating that the local cache is used. We can verify this process by refreshing the page:

 2. Intercept requests

HttpHandler (ihttphandler) and HttpModule (ihttpmodule) are asp.net Two important components allow us to asp.net It is very convenient to expand on the basis of. HttpHandler corresponds to a specific request, such as. Ashx,. ASPX, etc.; HttpModule is an interceptor that can intercept all requests in a certain event of the pipeline. To put it simply, in the pipeline, httpapplication will trigger a series of events. We register an event through the HttpModule. For example, we can intercept the request before the handler object is generated, and then map it to our own handler. However, the actual result of processing the request return is HttpHandler, such as page, which is used to generate HTML.

with asp.net The MVC framework, for example, is built on the asp.net Based on the routing mechanism, asp.net The routing system intercepts the request through a urlroutingmodule, specifically intercepts the postresolverequestcache event, parses the URL, encapsulates the corresponding routing data, and finally hands the request to a MVC handler for processing, which implements the ihttphandler interface.

Previously, we configured the following: < add verb = “post, get” path = “ajaxpro / *. Ashx” type=“ AjaxPro.AjaxHandlerFactory , AJAX Pro / > this means that any post / get request that ends with ajaxpro / any name. Ashx is handed over to AjaxPro.AjaxHandlerFactory It is a handler factory that implements ihandlerfactory and is used to generate specific ihttphandlers. The component defines several classes to implement ihttphandler, some of which are used to generate JS scripts. For processing Ajax requests, they are mainly divided into two categories: ihttpasynchandler and ihttphandler. On the basis of these two classes, the support for session state is divided into three types: handler supporting read-write (implementing irequiressessionstate tag interface) and read-only( Implements the ireadonlysessionstate tag interface) and handler that does not support session. The specific handler generated is judged by Ajax method.

Ihttphandler’s processrequest (asynchronous is beginprocessrequest) is used to execute the request and return the output result. If we only need one kind of handler, we can implement ihttphandler. Ihandlerfactory is defined as follows:


public interface IHttpHandlerFactory
{
 IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
 void ReleaseHandler(IHttpHandler handler);
} 

Therefore, all requests of ajaxpro will conform to the format of ajaxpro / *. Ashx. Then, in the gethandler method, specific processing can be carried out, and the return result is ihttphandler. Take the non asynchronous state as an example, if we configure the need session, we will generate a handler that implements ihttphandler and irequiressessionstate, If a read-only session is required, a handler that implements ihttphandler and ireadonlysessionstate will be generated; this information can be obtained from the Ajax method tag attribute through reflection. The main code of ajaxhandlerfactory is as follows:

public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
 string filename = Path.GetFileNameWithoutExtension(context.Request.Path);
 Type t = null;
 Exception typeException = null;
 bool isInTypesList = false;
 switch (requestType)
 {
  //Get request, get the previous four scripts
  case "GET": 
   switch (filename.ToLower())
   {
    case "prototype":
     return new EmbeddedJavaScriptHandler("prototype");
    case "core":
     return new EmbeddedJavaScriptHandler("core");
    case "ms":
     return new EmbeddedJavaScriptHandler("ms");
    case "prototype-core":
    case "core-prototype":
     return new EmbeddedJavaScriptHandler("prototype,core");
    case "converter":
     return new ConverterJavaScriptHandler();
    default:
     return new TypeJavaScriptHandler(t);
   }
  case "POST":
   IAjaxProcessor[] p = new IAjaxProcessor[2];
   p[0] = new XmlHttpRequestProcessor(context, t);
   p[1] = new IFrameProcessor(context, t);
   for (int i = 0; i < p.Length; i++)
   {
    if (p[i].CanHandleRequest)
    {
     //Gets the Ajax method property of the tag method
     AjaxMethodAttribute[] ma = (AjaxMethodAttribute[])p[i].AjaxMethod.GetCustomAttributes(typeof(AjaxMethodAttribute), true);
     bool useAsync = false;
     HttpSessionStateRequirement sessionReq = HttpSessionStateRequirement.ReadWrite;
     if (ma.Length > 0)
     {
      useAsync = ma[0].UseAsyncProcessing;
      if (ma[0].RequireSessionState != HttpSessionStateRequirement.UseDefault)
       sessionReq = ma[0].RequireSessionState;
     }
     //There are six types of handler. The session status returns the specified handler according to whether it is asynchronous or not
     switch (sessionReq)
     {
      case HttpSessionStateRequirement.Read:
       if (!useAsync)
        return new AjaxSyncHttpHandlerSessionReadOnly(p[i]);
       else
        return new AjaxAsyncHttpHandlerSessionReadOnly(p[i]);
      case HttpSessionStateRequirement.ReadWrite:
       if (!useAsync)
        return new AjaxSyncHttpHandlerSession(p[i]);
       else
        return new AjaxAsyncHttpHandlerSession(p[i]);
      case HttpSessionStateRequirement.None:
       if (!useAsync)
        return new AjaxSyncHttpHandler(p[i]);
       else
        return new AjaxAsyncHttpHandler(p[i]);
      default:
       if (!useAsync)
        return new AjaxSyncHttpHandlerSession(p[i]);
       else
        return new AjaxAsyncHttpHandlerSession(p[i]);
     }
    }
   }
   break;
 }
 return null;
}

 3. Reflection execution method

After obtaining a handler to handle the request, the specified method can be executed in its processrequest (beginprocessrequest asynchronously). To execute a method of a page object, we must know the assembly in which the specified page is located, the namespace, the name of the page class, and the name of the method. This seems to be in line with our previous method of calling: namespace. Class name. Method name. In order to distinguish from general requests and make components independent enough, ajaxpro only intercepts requests in the format of “ajaxpro / *. Ashx”, which indicates that our Ajax requests should also conform to this format. For example:http://localhost:50712/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashxThis format is automatically generated by the foreground script and does not require us to construct it. If you look closely, you will find that AjaxProNamespace.AjaxProPage Testajaxprosourcecode is the fully qualified name of the page class: namespace. Class name, assembly name. Through this, we can generate specific type, and then conduct reflection to obtain information. What about the name of the method? Ajaxpro places it in the HTTP header with the name: x-ajax Pro method. With this information, you can reflect the execution method. The core code here is:

internal void Run()
{
 try
 {
  //Set the output to not cache (this is not necessarily what we want)
  p.Context.Response.Expires = 0;
  p.Context.Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
  p.Context.Response.ContentType = p.ContentType;
  p.Context.Response.ContentEncoding = System.Text.Encoding.UTF8;
  //Verifying Ajax requests
  if (!p.IsValidAjaxToken())
  {
   p.SerializeObject(new System.Security.SecurityException("The AjaxPro-Token is not valid."));
   return;
  }
  //Array of method parameter objects
  object[] po = null;
  //Request processing results
  object res = null;
  try
  {
   //Get parameters
   po = p.RetreiveParameters();
  }
  catch (Exception ex){}
  //Get cached key
  string cacheKey = p.Type.FullName + "|" + p.GetType().Name + "|" + p.AjaxMethod.Name + "|" + p.GetHashCode();
  if (p.Context.Cache[cacheKey] != null)
  {
   //If the cache exists, the cache is used directly
   p.Context.Response.AddHeader("X-" + Constant.AjaxID + "-Cache", "server");
   p.Context.Response.Write(p.Context.Cache[cacheKey]);
   return;
  }
  try
  {
   if (p.AjaxMethod.IsStatic)
   {
    //Using reflection to call static methods
    try
    {
     res = p.Type.InvokeMember(
      p.AjaxMethod.Name,
      System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod,
      null, null, po);
    }
    catch (Exception ex){}
   }
   else
   {
    try
    {
     //Create the instance object and call the instance method by reflection
     object c = (object)Activator.CreateInstance(p.Type, new object[] { });
     if (c != null)
     {
      res = p.AjaxMethod.Invoke(c, po);
     }
    }
    catch (Exception ex){}
   }
  }
  catch (Exception ex){}
  try
  {
   //Judge whether the result is XML or not. If so, set contenttype
   if (res != null && res.GetType() == typeof(System.Xml.XmlDocument))
   {
    p.Context.Response.ContentType = "text/xml";
    p.Context.Response.ContentEncoding = System.Text.Encoding.UTF8;
    ((System.Xml.XmlDocument)res).Save(p.Context.Response.OutputStream);
    return;
   }
   string result = null; ;
   System.Text.StringBuilder sb = new System.Text.StringBuilder();
   try
   {
    result = p.SerializeObject(res);
   }
   catch (Exception ex){}
   //If a cache is required, the result is written to the cache
   if (p.ServerCacheAttributes.Length > 0)
   {
    if (p.ServerCacheAttributes[0].IsCacheEnabled)
    {
     p.Context.Cache.Add(cacheKey, result, null, DateTime.Now.Add(p.ServerCacheAttributes[0].CacheDuration), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
    }
   }
  }
  catch (Exception ex){}
 }
 catch (Exception ex){}
}

3、 Summary

Let’s summarize the core processing flow of ajaxpro. It intercepts the URL in the specified format through an ihttphandlefactory, and then obtains the fully qualified name of the type to generate the type object. Then, it obtains the characteristics of the marking method through reflection to generate a custom object that implements the ihttphandler interface. In its processrequest method, the Headers get the method name, map the parameters through reflection and execute the function.

  Ajaxpro has the following advantages:

1. Simple configuration.

2. It can be used with other components.

3. Encapsulate foreground script, we don’t need to encapsulate or use other script libraries.

4. For return value processing, we can return simple type or complex type, which will be automatically serialized.

  The disadvantages are:

1. There will be 4 more requests on the page. Although the 304 cache will be utilized, the request needs to be sent to the server.

2. Ajax cannot use get request. Due to the custom URL format, we can’t use get request using this format. We know that get request can be cached by browser. One of the Yahoo front-end optimization suggestions is to use get request more. In fact, you should put the namespace. Class name, assembly in the HTTP header, and provide a parameter of type for us to choose freely.

3. Bind with < form runat = “server” >. The purpose is to generate foreground script for us, but if we want to use. HTML file + aspx.cs We can’t use this way (some pages in the blog Garden use this way); even our interface may be used by mobile terminals, and this convenience has become a limitation.

4. Reflection. This is relatively inefficient. It does not even cache methodinfo like our previous page classes.

As you can see, this component is worth using if you don’t care about efficiency. Here is just a core introduction, there are many other functions, this is the source code of ajaxpro components, interested friends can study.