This article mainly introduces the third implementation of writing lightweight ajax components in detail, which has certain reference value. Interested friends can refer to it
Through the previous introduction, we know 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 are already familiar with the basic execution process , now 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 { ////// 请求头Ajax标记键 /// public const string Key = "AjaxFlag"; ////// 请求头Ajax标记值 /// public const string Value = "XHR"; ////// 请求头Ajax方法标记 /// 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.
////// ajax方法标记属性 /// [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; ////// session状态 /// public SessionState SessionState { get { return _sessionState; } set { _sessionState = value; } } ////// 客户端缓存时间,以秒为单位。该标记只对get请求有效 /// public int OutputCache { get { return _outputCache; } set { _outputCache = value; } } ////// 服务端缓存时间,以秒为单位 /// public int ServerCache { get { return _serverCache; } set { _serverCache = value; } } ////// 输出类型(默认为text/plain) /// public ContentType ContentType { get { return _contentType; } set { _contentType = value; } } ////// 使用启用异步处理 /// public bool IsAsync { get { return _isUseAsync; } set { _isUseAsync = value; } } } ////// Session状态 /// public enum SessionState { None, ReadOnly, ReadWrite } ////// 输出内容类型 /// public enum ContentType { Plain, Html, XML, Javascript, JSON }
Various handlers and AjaxHandlerFactory
According to the previous article, the specific Handler Mainly divided into two categories, asynchronous and non-asynchronous; under these two categories, there are three types of Session states: 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) { //执行方法(下面详细介绍) Actionaction = new Action (Executor.Execute); IAsyncResult result = action.BeginInvoke(_page, context, _cacheMethodInfo, cb, action); return result; } public void EndProcessRequest(IAsyncResult result) { Action action = result.AsyncState as Action ; 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 and is used to generate specific Handlers based on requests. It needs to be registered 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 page (Page, implements IHttpHandler) type. 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. However, 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; } ////// 获取自定义处理程序 /// /// 处理页面 /// 处理方法 /// 当前请求 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); } }
The above CacheMethodInfo is used to cache the relevant information of the calling method, as we mentioned in the first article Some methods of over-optimizing caching include cache delegation. But here we do not directly cache the MethodInfo of the method, because if we cache the MethodInfo, we need to execute it through Invoke, which is relatively inefficient. What I cache here is the delegate of the method. The signature of the delegate is: Func
////// 缓存方法信息 /// sealed class CacheMethodInfo { ////// 方法名称 /// public string MethodName { get; set; } ////// 方法委托 /// public Func
核心方法
1. Eexcutor.GetDelegateInfo 获取方法相关信息
该方法用于遍历页面类,获取所有AjaxMethodAttribute标记的方法信息,生成一个CacheMethodInfo对象,包括标记信息、方法名称、参数信息,以及最重要的方法委托。该对象会缓存在一个哈希表中,下次获取时,直接从内存获得。
////// 获取页面标记方法信息 /// /// 页面对象 /// 方法名称 internal static CacheMethodInfo GetDelegateInfo(Page page, string methodName) { if (page == null) { throw new ArgumentNullException("page"); } Type type = page.GetType(); //ajaxDelegateTable是一个Hashtable Dictionarydic = ajaxDelegateTable[type.AssemblyQualifiedName] as Dictionary ; if (dic == null) { dic = new Dictionary (); //遍历页面的所有MethodInfo IEnumerable 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