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
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: '/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx' })); 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接口。
前面我们进行了如下配置:
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