Home > Article > Web Front-end > Writing lightweight ajax components (detailed graphic and text explanation)
This time I will bring you how to write lightweight ajax components (detailed explanations in pictures and texts). What are the precautions for writing lightweight ajax components? Here are practical cases, let’s take a look.
Through the previous introduction, we know how to execute the method of the page object. The core is reflection, which is the process of obtaining parameters from the request and executing the specified method. In fact, this is very similar to the core idea of the ASP.NET MVC framework. It will parse the URL, get the controller and action names from it, then activate the controller object, get the action parameters from the request and execute the action. On the web form platform, we write the method in .aspx.cs. What we want to achieve is to execute the specified method before the page object is generated, and then return the result.
Let’s first look at a few call examples after implementation. These functions can also be used in combination:
[AjaxMethod] public void Test1(int index) { //简单调用 } [AjaxMethod] public string Test2(Test test) { return "参数为一个Test实例"; } [AjaxMethod(OutputCache = 20)] public string Test3(int index) { return "输出结果缓存20秒"; } [AjaxMethod(ServerCache = 20)] public string Test4() { return "在服务端缓存20秒"; } [AjaxMethod(SessionState=SessionState.None)] public void Test5() { //Session未被加载 } [AjaxMethod(SessionState = SessionState.ReadOnly)] public void Test6() { //Session只能读不能写 } [AjaxMethod(SessionState = SessionState.ReadWrite)] public void Test7() { //Session可以读写 } [AjaxMethod(IsAsync = true)] public void Test8() { //异步调用 }
We have already been familiar with the basic execution process, and now we go directly to the topic.
Ajax Convention
Usually when mainstream browsers use ajax to send asynchronous requests, the request header will carry a mark: X-Requested-With:XMLHttpRequest. We can also directly use this tag to determine whether it is an ajax request, but other components may be useful in the project. In order not to affect each other, we add a custom request header. Here is:
internal static class AjaxConfig { /// <summary> /// 请求头Ajax标记键 /// </summary> public const string Key = "AjaxFlag"; /// <summary> /// 请求头Ajax标记值 /// </summary> public const string Value = "XHR"; /// <summary> /// 请求头Ajax方法标记 /// </summary> public const string MethodName = ""; }
It means that if the http request header contains an AjaxFlag: XHR, that is what we want to process. In addition, the MethodName of the http header indicates the name of the method we want to execute.
AjaxMethodAttribute mark attribute
Mark attribute is used for reflection. Here we define some functions we need. We hope to have:
1. Can configure Session status
2. Support asynchronous Handler
3. Support Get cache
4. Support server-side cache
The definition is as follows. The tag marked with AttributeUsag can only be used on methods.
/// <summary> /// ajax方法标记属性 /// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class AjaxMethodAttribute : Attribute { public AjaxMethodAttribute() { } private SessionState _sessionState = SessionState.None; private int _outputCache = 0; private int _serverCache = 0; private ContentType _contentType = ContentType.Plain; private bool _isUseAsync = false; /// <summary> /// session状态 /// </summary> public SessionState SessionState { get { return _sessionState; } set { _sessionState = value; } } /// <summary> /// 客户端缓存时间,以秒为单位。该标记只对get请求有效 /// </summary> public int OutputCache { get { return _outputCache; } set { _outputCache = value; } } /// <summary> /// 服务端缓存时间,以秒为单位 /// </summary> public int ServerCache { get { return _serverCache; } set { _serverCache = value; } } /// <summary> /// 输出类型(默认为text/plain) /// </summary> public ContentType ContentType { get { return _contentType; } set { _contentType = value; } } /// <summary> /// 使用启用异步处理 /// </summary> public bool IsAsync { get { return _isUseAsync; } set { _isUseAsync = value; } } } /// <summary> /// Session状态 /// </summary> public enum SessionState { None, ReadOnly, ReadWrite } /// <summary> /// 输出内容类型 /// </summary> public enum ContentType { Plain, Html, XML, Javascript, JSON }
Various handlers and AjaxHandlerFactory
According to the previous article, specific Handlers are mainly divided into two categories, asynchronous and non-asynchronous; under these two categories, the status of the Session is different There are three types: not supported, only supported for reading (implementing the IReadOnlySessionState interface), and supporting reading and writing (implementing the IRequiresSessionState interface). IReadOnlySessionState and IRequiresSessionState are just mark interfaces (without any methods, it is more reasonable to use mark attributes to implement them). Asynchronous Handler needs to implement the IHttpAsyncHandler interface, which in turn implements IHttpHandler. The ProcessRequest method (or BeginProcessRequest) of Handler is where we want to execute the method. The definition is as follows:
Handler in non-asynchronous state:
//不支持Session internal class SyncAjaxHandler : IHttpHandler { private Page _page; private CacheMethodInfo _cacheMethodInfo; internal SyncAjaxHandler(Page page, CacheMethodInfo cacheMethodInfo) { _page = page; _cacheMethodInfo = cacheMethodInfo; } public void ProcessRequest(HttpContext context) { //执行方法(下面详细介绍) Executor.Execute(_page, context, _cacheMethodInfo); } public bool IsReusable { get { return false; } } public static SyncAjaxHandler CreateHandler(Page page, CacheMethodInfo cacheMethodInfo, SessionState state) { switch (state) { case SessionState.ReadOnly: return new SyncAjaxSessionReadOnlyHandler(page, cacheMethodInfo); case SessionState.ReadWrite: return new SyncAjaxSessionHandler(page, cacheMethodInfo); default: return new SyncAjaxHandler(page, cacheMethodInfo); } } } //支持只读Session internal class SyncAjaxSessionReadOnlyHandler : SyncAjaxHandler, IReadOnlySessionState { internal SyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo) : base(page, cacheMethodInfo) { } } //支持读写Session internal class SyncAjaxSessionHandler : SyncAjaxHandler, IRequiresSessionState { internal SyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo) : base(page, cacheMethodInfo) { } }
Handler in asynchronous state:
//不支持Session internal class ASyncAjaxHandler : IHttpAsyncHandler, IHttpHandler { private Page _page; private CacheMethodInfo _cacheMethodInfo; internal ASyncAjaxHandler(Page page, CacheMethodInfo cacheMethodInfo) { _page = page; _cacheMethodInfo = cacheMethodInfo; } public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) { //执行方法(下面详细介绍) Action<Page, HttpContext, CacheMethodInfo> action = new Action<Page, HttpContext, CacheMethodInfo>(Executor.Execute); IAsyncResult result = action.BeginInvoke(_page, context, _cacheMethodInfo, cb, action); return result; } public void EndProcessRequest(IAsyncResult result) { Action<Page, HttpContext, CacheMethodInfo> action = result.AsyncState as Action<Page, HttpContext, CacheMethodInfo>; action.EndInvoke(result); } public void ProcessRequest(HttpContext context) { throw new NotImplementedException(); } public bool IsReusable { get { return false; } } public static ASyncAjaxHandler CreateHandler(Page page, CacheMethodInfo cacheMethodInfo, SessionState state) { switch (state) { case SessionState.ReadOnly: return new ASyncAjaxSessionReadOnlyHandler(page, cacheMethodInfo); case SessionState.ReadWrite: return new ASyncAjaxSessionHandler(page, cacheMethodInfo); default: return new ASyncAjaxHandler(page, cacheMethodInfo); } } } //支持只读Session internal class ASyncAjaxSessionReadOnlyHandler : ASyncAjaxHandler, IReadOnlySessionState { internal ASyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo) : base(page, cacheMethodInfo) { } } //支持读写Session internal class ASyncAjaxSessionHandler : ASyncAjaxHandler, IRequiresSessionState { internal ASyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo) : base(page, cacheMethodInfo) { } }
AjaxHandlerFactory implements the IHandlerFactory interface, which is used to generate specific Handlers based on requests. Need to register in web.config. AjaxHandlerFactory's GetHandler is our first step in intercepting requests. Use the AjaxFlag:XHR in the request header to determine whether we need to process it. If so, create a Handler, otherwise proceed in the normal way. Since our method is written in .aspx.cs, our request is of .aspx suffix, which is the type of page (Page, which implements IHttpHandler). Page is created through PageHandlerFactory, which also implements the IHandlerFactory interface, which means It is used to create handlers. So we need to use PageHandlerFactory to create an IHttpHandler, but the constructor of PageHandlerFactory is of protected internal type. We cannot create a new one directly, so we need to inherit it through a CommonPageHandlerFactory.
After obtaining the Page through PageHandlerFactory, combined with the method name, we can reflect to obtain the AjaxMethodAttribute mark attribute. Then generate a specific Handler based on its related properties. The specific code is as follows:
internal class CommonPageHandlerFactory : PageHandlerFactory { } internal class AjaxHandlerFactory : IHttpHandlerFactory { public void ReleaseHandler(IHttpHandler handler) { } public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) { HttpRequest request = context.Request; if (string.Compare(request.Headers[AjaxConfig.Key], AjaxConfig.Value, true) == 0) { //检查函数标记 string methodName = request.Headers[AjaxConfig.MethodName]; if (methodName.IsNullOrEmpty()) { Executor.EndCurrentRequest(context, "方法名称未正确指定!"); return null; } try { CommonPageHandlerFactory ajaxPageHandler = new CommonPageHandlerFactory(); IHttpHandler handler = ajaxPageHandler.GetHandler(context, requestType, url, pathTranslated); Page page = handler as Page; if (page == null) { Executor.EndCurrentRequest(context, "处理程序类型必须是aspx页面!"); return null; } return GetHandler(page, methodName, context); } catch { Executor.EndCurrentRequest(context, url + " 不存在!"); return null; } } if (url.EndsWith(".aspx", StringComparison.CurrentCultureIgnoreCase)) { CommonPageHandlerFactory orgPageHandler = new CommonPageHandlerFactory(); return orgPageHandler.GetHandler(context, requestType, url, pathTranslated); } return null; } /// <summary> /// 获取自定义处理程序 /// </summary> /// <param name="page">处理页面</param> /// <param name="methodName">处理方法</param> /// <param name="context">当前请求</param> private IHttpHandler GetHandler(Page page, string methodName, HttpContext context) { //根据Page和MethodName进行反射,获取标记属性(下面详细介绍) CacheMethodInfo methodInfo = Executor.GetDelegateInfo(page, methodName); if (methodInfo == null) { Executor.EndCurrentRequest(context, "找不到指定的Ajax方法!"); return null; } AjaxMethodAttribute attribute = methodInfo.AjaxMethodAttribute; if (attribute.ServerCache > 0) { //先查找缓存 object data = CacheHelper.TryGetCache(context); if (data != null) { Executor.EndCurrentRequest(context, data); return null; } } if (attribute.IsAsync) { //异步处理程序 return ASyncAjaxHandler.CreateHandler(page, methodInfo, attribute.SessionState); } return SyncAjaxHandler.CreateHandler(page, methodInfo, attribute.SessionState); } }
上面的CacheMethodInfo是用于缓存调用方法的相关信息的,第一篇我们有提到过优化缓存的一些方法,其中就包括缓存+委托。但这里我们并不直接缓存方法的MethodInfo,因为缓存MethodInfo的话,需要通过Invoke去执行,这样的效率比较低。这里我缓存的是方法的委托,该委托的签名为:Func
/// <summary> /// 缓存方法信息 /// </summary> sealed class CacheMethodInfo { /// <summary> /// 方法名称 /// </summary> public string MethodName { get; set; } /// <summary> /// 方法委托 /// </summary> public Func<object, object[], object> Func { get; set; } /// <summary> /// 方法参数 /// </summary> public ParameterInfo[] Parameters { get; set; } /// <summary> /// Ajax标记属性 /// </summary> public AjaxMethodAttribute AjaxMethodAttribute { get; set; } }
核心方法
1. Eexcutor.GetDelegateInfo 获取方法相关信息
该方法用于遍历页面类,获取所有AjaxMethodAttribute标记的方法信息,生成一个CacheMethodInfo对象,包括标记信息、方法名称、参数信息,以及最重要的方法委托。该对象会缓存在一个哈希表中,下次获取时,直接从内存获得。
/// <summary> /// 获取页面标记方法信息 /// </summary> /// <param name="page">页面对象</param> /// <param name="methodName">方法名称</param> internal static CacheMethodInfo GetDelegateInfo(Page page, string methodName) { if (page == null) { throw new ArgumentNullException("page"); } Type type = page.GetType(); //ajaxDelegateTable是一个Hashtable Dictionary<string, CacheMethodInfo> dic = ajaxDelegateTable[type.AssemblyQualifiedName] as Dictionary<string, CacheMethodInfo>; if (dic == null) { dic = new Dictionary<string, CacheMethodInfo>(); //遍历页面的所有MethodInfo IEnumerable<CacheMethodInfo> infos = (from m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) let ca = m.GetCustomAttributes(typeof(AjaxMethodAttribute), false).FirstOrDefault() where ca != null select new CacheMethodInfo { //方法标记属性 AjaxMethodAttribute = ca as AjaxMethodAttribute, //方法名称 MethodName = m.Name, //方法参数信息 Parameters = m.GetParameters() }); if (infos.IsNullOrEmpty()) { return null; } for (int i = 0, length = infos.Count(); i < length; i++) { CacheMethodInfo cacheMethodInfo = infos.ElementAt(i); string name = cacheMethodInfo.MethodName; MethodInfo methodInfo = type.GetMethod(name); if (!dic.ContainsKey(name)) { //根据MethodInfo获取方法委托 cacheMethodInfo.Func = ReflectionUtil.GetMethodDelegate(methodInfo); dic.Add(name, cacheMethodInfo); } } ajaxDelegateTable[type.AssemblyQualifiedName] = dic; } CacheMethodInfo currentMethodInfo = null; dic.TryGetValue(methodName, out currentMethodInfo); return currentMethodInfo; }
获取方法的委托的是通过一个ReflectionUtil获得的,该类主要用来优化反射,它通过Expression,可以将MethodInfo编译成Func