Home  >  Article  >  Web Front-end  >  Writing lightweight ajax components 02--A brief analysis of AjaxPro

Writing lightweight ajax components 02--A brief analysis of AjaxPro

亚连
亚连Original
2018-05-24 15:01:021682browse

Although ajaxpro is an older component, the implementation ideas and source code are still of great reference value. Next, through this article, I will introduce to you how to write lightweight ajax components 02--a brief analysis of AjaxPro. Interested friends can refer to the following

Preface

The previous article introduced the implementation on the webform platform Some ways of ajax and implement a base class. In this article we look at an open source component: ajaxpro. Although this is an older component, the implementation ideas and source code are still worth learning. Through the introduction in the previous article, we know that calling the page object method is achieved by reflection. The key is the entire processing process, including reflection calling method, parameter mapping, etc. Ajaxpro not only helps us implement this process in the background, but also encapsulates the request calling methods in the foreground, such as ajax-related methods. You can use ajaxpro methods to send asynchronous requests without the need to encapsulate js yourself or use a js library. Next, we will briefly analyze this component.

1. How to use ajaxpro

Let’s first look at how to use this component.

 1. Register AjaxHandlerFactory

Make the following configuration in web.config:

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

Simply put, the requested url complies with ajaxpro/*. ashx format 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. Register in the page class Page_Load event

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

We pass the Type of this page object to the ResisterTypoForAjax method. This method is used to register the script in the foreground. Specifically The RegisterClientScriptBlock of the current Page object will be called to register, so there must be a 9da45565527026e0988f6215b7b6a235f5a47148e367a6035fd7a2faa965022e in the .aspx file, otherwise the script will not be registered. (Type is passed here, but it can actually be done without passing it. This type can also be obtained internally through HttpContext.Current.Handler.GetType().BaseType)

 3. Mark the method with AjaxMethod 

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

AjaxMethod is a tag attribute, indicating that this method is used to process ajax requests, and it is ultimately executed through reflection; it has several constructor pairs, and for some data that needs 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, such as requesting a time-consuming web service, we can also set the handler to asynchronous state.

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

 4. Front-end call

The configuration and use of the back-end are very simple. Next, let’s see how the front-end 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 namespace where the page class is located, AjaxProPage is the name of the page class, and GetList is the marked method. Why can it be written like this? As mentioned earlier, ajaxpro will register the script in the foreground, and it will generate the following script based on the relevant information of our page object, so we can call it like this without having to write js ourselves or use the jquery library method.

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: &#39;/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx&#39;
}));
AjaxProNamespace.AjaxProPage = new AjaxProNamespace.AjaxProPage_class();

The parameters of GetList correspond to the parameters of the 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 an object that encapsulates the return result. Its value attribute is the value returned after successful execution. For example, the above returned object is an array object. Its error includes failure information.

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

2. Principle of ajaxpro processing requests

The main focus here is on the process of components processing ajax requests, and other auxiliary functions will not be introduced.

  1. Generate auxiliary scripts

In the Page_Load event we called AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage)); to register the required scripts. We noticed that the following script was introduced on the front page:

That is, each page will initiate these requests. These are all files ending in .ashx, but they are actually all js code; some of these js are nested inside dll as resources, and some are automatically generated. They mainly encapsulate ajax request-related methods and allow us to use: Namespace. Page class name. Tag method name is used to call the method. Why use .ashx instead of .js? Because as a resource file inside the component, the .js file cannot be requested directly from the outside, but the .ashx can be intercepted, and then the content is output using Response.Write.

  如果每次都生成和发送这些脚本的效率是很低的,ajaxpro内部的处理是判断请求头的If-None-Math和If-Modified-Since,如果两个都和缓存的一样,就返回一个304状态码。所以,客户端只有首次请求服务端会返回文件的内容,后续的都只返回304表示使用本地缓存。我们刷新页面可以验证这个过程:

  2. 拦截请求

  HttpHandler(IHttpHandler) 和 HttpModule(IHttpModule) 是asp.net 两个重要的组件,让我们可以在asp.net的基础上很方便的进行扩展。HttpHandler对应某种具体的请求,例如.ashx,.aspx等;HttpModule是一个拦截器,可以在管道的某个事件对所有请求进行拦截。简单的说,在管道中,HttpApplication会触发一系列事件,我们在通过HttpModule对某个事件进行注册,例如我们可以在处理程序对象生成前拦截请求,然后映射到自己的处理程序;而实际处理请求返回结果的是HttpHandler,例如Page用来生成html。

  以asp.net mvc框架为例,它是建立在asp.net 路由机制的基础上的,asp.net 路由系统通过一个UrlRoutingModule对请求进行拦截,具体是在PostResolveRequestCache事件进行拦截,对url进行解析,封装相应的路由数据后,最终将请求交给一个MvcHandler进行处理,MvcHandler实现了IHttpHandler接口。

  前面我们进行了如下配置:cf06240b37eddaeaaa5ffa482fe7b198 这表明了任何的以 ajaxpro/任意名称.ashx结尾的 Post/Get 请求,都交给AjaxPro.AjaxHandlerFactory进行处理,它是一个实现了IHandlerFactory的处理程序工厂,用来生成具体的IHttpHandler。组件内部定义了多个实现IHttpHandler的类,有的是为了生成js脚本的,对于处理ajax请求,主要分为两类:异步(IHttpAsyncHandler)和非异步(IHttpHandler);在这两类的基础上,对于Session的状态的支持又分为三种:支持读写(实现IRequiresSessionState标记接口)的Handler、只读(实现IReadOnlySessionState标记接口)的Handler和不支持Session的Handler。具体生成什么样的Handler是通过AjaxMethod进行判断的。

  IHttpHandler的ProcessRequest(异步就是BeginProcessRequest)就用来执行请求返回输出结果的。如果只需要一种处理程序我们也可以实现IHttpHandler。IHandlerFactory的定义如下:

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

  所以,ajaxpro的所有请求都会符合ajaxpro/*.ashx格式,然后在GetHandler方法,就可以进行具体的处理,返回结果是IHttpHandler;以非异步状态为例,如果我们配置了需要Session,就会生成一个实现IHttpHandler和IRequiresSessionState的Handler,如果需要只读的Session,就会生成一个实现IHttpHandler和IReadOnlySessionState的Handler;这些信息可以通过反射从AjaxMethod标记属性获得。AjaxHandlerFactory的主要代码如下:

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请求,获取前面的那4个脚本
  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)
    {
     //获取标记方法的AjaxMethod属性
     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;
     }
     //6种Handler,根据是否异步,session状态返回指定的Handler
     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. 反射执行方法

  当获得一个处理本次请求的Handler后,就可以在其ProcessRequest(异步为BeginProcessRequest)执行指定的方法。要执行一个页面对象的方法,我们必须知道指定页面所在的程序集,名称空间,页面类的名称以及方法的名称。这似乎符合我们前面:名称空间.类名称.方法名称的调用方式。为了与一般请求区分开,让组件具有足够的独立性,ajaxpro只拦截符合"ajaxpro/*.ashx格式的请求,这说明我们的ajax请求也要符合这个格式。如:http://localhost:50712/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx,这个格式由前台脚本自动生成,并不需要我们去构造。仔细观察,会发现AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode 就是页面类的完全限定名:名称空间.类名称,程序集名称,通过这个我们就可以生成具体的Type,然后进行反射获取信息。那么方法的名称呢?ajaxpro将其放在http header 中,名称为:X-AjaxPro-Method。有了这些信息,就可以反射执行方法了。这里核心代码为:

internal void Run()
{
 try
 {
  //设置输出结果不缓存(这不一定是我们想要的)
  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;
  //验证ajax请求
  if (!p.IsValidAjaxToken())
  {
   p.SerializeObject(new System.Security.SecurityException("The AjaxPro-Token is not valid."));
   return;
  }
  //方法参数对象数组
  object[] po = null;
  //请求处理结果
  object res = null;
  try
  {
   //获取参数
   po = p.RetreiveParameters();
  }
  catch (Exception ex){}
  //获取缓存的Key
  string cacheKey = p.Type.FullName + "|" + p.GetType().Name + "|" + p.AjaxMethod.Name + "|" + p.GetHashCode();
  if (p.Context.Cache[cacheKey] != null)
  {
   //如果缓存存在,则直接使用缓存
   p.Context.Response.AddHeader("X-" + Constant.AjaxID + "-Cache", "server");
   p.Context.Response.Write(p.Context.Cache[cacheKey]);
   return;
  }
  try
  {
   if (p.AjaxMethod.IsStatic)
   {
    //使用反射调用静态方法
    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
    {
     //创建实例对象,反射调用实例方法
     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
  {
   //判断结果是不是xml,如是设置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 (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){}
}

三、总结

  我们总结一下ajaxpro的核心处理流程,它通过一个IHttpHandlerFactory拦截指定格式的url,然后从中获取类型的完全限定名生成类型对象,接着通过反射获取标记方法的特性,生成一个自定义的实现IHttpHandler接口的对象;在其ProcessRequest方法中,从http headers获取方法名称,通过反射进行参数映射并执行函数。

 ajaxpro has the following advantages:

 1. Simple configuration.

 2. Can be used in conjunction with other components.

 3. Encapsulate the front-end script. We don’t need to encapsulate it ourselves or use other script libraries.

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

 The disadvantages are:

 1. The page will have 4 more requests. Although 304 caching will be utilized, a request still needs to be sent to the server.

 2. Ajax cannot use Get request. Since the URL format is customized, Get requests cannot be used using this format. We know that Get requests can be cached by browsers. One of Yahoo's front-end optimization suggestions is to use more get requests. In fact, we should put the namespace, class name, and assembly in the http header, and then provide a type parameter for us to choose freely.

 3. Bind with 9da45565527026e0988f6215b7b6a235. The purpose is to generate the front-end script for us, but if we want to use the .html file .aspx.cs method, it cannot be used (some pages in the blog park use this method); even our interface may need to be provided to the mobile terminal With use, this convenience becomes a limitation.

 4. Reflection. This is relatively inefficient, and it does not even cache MethodInfo like our previous page class.

It can be seen that this component is still worth using if you don’t care about efficiency. Here is just a core introduction, there are many other functions in it. This is the source code of the ajaxpro component. Interested friends can study it.

The above is what I compiled for everyone. I hope it will be helpful to everyone in the future.

Related articles:

A brief analysis of the difference between json and jsonp and the format conversion after obtaining json data through ajax

Django framework uses ajax Implementing the function of batch importing data

Detailed explanation of AJAX XMLHttpRequest object

The above is the detailed content of Writing lightweight ajax components 02--A brief analysis of AjaxPro. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn