Dieses Mal werde ich Ihnen zeigen, wie man leichte Ajax-Komponenten schreibt (ausführliche Erklärung mit Bildern und Texten). Was sind die Vorsichtsmaßnahmen zum Schreiben leichter Ajax-Komponenten? sehen.
Durch die vorherige Einführung wissen wir, wie die Methode des Seitenobjekts ausgeführt wird. Der Kern ist die Reflexion, bei der Parameter aus der Anforderung abgerufen und die angegebene Methode ausgeführt werden. Tatsächlich ist dies der Kernidee des ASP.NET MVC-Frameworks sehr ähnlich. Es analysiert die URL, ruft daraus die Controller- und Aktionsnamen ab, aktiviert dann das Controller-Objekt und ruft die Aktionsparameter aus der Anfrage ab die Aktion ausführen. Auf der Webformularplattform schreiben wir die Methode in .aspx.cs. Unser Ziel ist es, die angegebene Methode auszuführen, bevor das Seitenobjekt generiert wird, und dann das Ergebnis zurückzugeben.
Schauen wir uns nach der Implementierung zunächst einige Aufrufbeispiele an. Diese Funktionen können auch in Kombination verwendet werden:
[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() { //异步调用 }
Den grundlegenden Ausführungsprozess haben wir bereits kennengelernt, jetzt geht es los direkt zum Thema.
Ajax-Konvention
Wenn Mainstream-Browser Ajax zum Senden asynchroner Anforderungen verwenden, trägt der Anforderungsheader normalerweise eine Markierung: X-Requested-With:XMLHttpRequest. Wir können dieses Tag auch direkt verwenden, um festzustellen, ob es sich um eine Ajax-Anfrage handelt, aber andere Komponenten können im Projekt nützlich sein. Um sich gegenseitig nicht zu beeinflussen, fügen wir einen benutzerdefinierten Anforderungsheader hinzu. Hier ist:
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 = ""; }
Das bedeutet, dass wir das verarbeiten müssen, wenn der HTTP-Anforderungsheader ein AjaxFlag: XHR enthält. Darüber hinaus gibt der MethodName des http-Headers den Namen der Methode an, die wir ausführen möchten.
AjaxMethodAttribute-Markierungsattribut
Das Markierungsattribut wird zur Reflexion verwendet. Hier definieren wir einige Funktionen, die wir benötigen. Wir hoffen auf Folgendes:
1. Kann den Sitzungsstatus konfigurieren
2. Unterstützt asynchrone Handler
3. Unterstützt Get-Cache
4. Unterstützt Server- Side Caching
ist wie folgt definiert. Das Markieren dieses Tags mit AttributeUsag kann nur für Methoden verwendet werden.
/// <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 }
Verschiedene Handler und AjaxHandlerFactory
Gemäß dem vorherigen Artikel werden bestimmte Handler hauptsächlich in zwei Kategorien unterteilt: asynchrone und nicht asynchrone. Für Sitzungen gibt es drei Zustandstypen: nicht unterstützt, nur zum Lesen unterstützt (Implementierung der IReadOnlySessionState-Schnittstelle) und zur Unterstützung des Lesens und Schreibens (Implementierung der IRequiresSessionState-Schnittstelle). IReadOnlySessionState und IRequiresSessionState sind nur Markierungsschnittstellen (ohne Methoden ist es sinnvoller, Markierungsattribute zu verwenden, um sie zu implementieren). Der asynchrone Handler muss die IHttpAsyncHandler-Schnittstelle implementieren, die wiederum IHttpHandler implementiert. In der ProcessRequest-Methode (oder BeginProcessRequest) von Handler möchten wir die Methode ausführen. Es ist wie folgt definiert:
Handler im nicht asynchronen Zustand:
//不支持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 im asynchronen Zustand:
//不支持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 implementiert die IHandlerFactory-Schnittstelle Wird zum Generieren spezifischer Daten basierend auf dem Anforderungshandler verwendet und muss in web.config registriert werden. Der GetHandler von AjaxHandlerFactory ist unser erster Schritt beim Abfangen von Anfragen. Verwenden Sie das AjaxFlag:XHR im Anforderungsheader, um zu bestimmen, ob wir es verarbeiten müssen. Wenn ja, erstellen Sie einen Handler, andernfalls gehen Sie wie gewohnt vor. Da unsere Methode in .aspx.cs geschrieben ist, hat unsere Anfrage das Suffix .aspx. Dies ist der Seitentyp (Page, implementiert IHttpHandler). Die Seite wird über PageHandlerFactory erstellt, das auch die IHandlerFactory-Schnittstelle implementiert, was bedeutet, dass dies der Fall ist Handler erstellen. Wir müssen also PageHandlerFactory verwenden, um einen IHttpHandler zu erstellen, aber der -Konstruktor von PageHandlerFactory ist ein geschützter interner Typ. Wir können keinen neuen erstellen, daher müssen wir ihn über einen CommonPageHandlerFactory erben.
Nachdem wir die Seite über PageHandlerFactory erhalten haben, können wir in Kombination mit dem Methodennamen das AjaxMethodAttribute-Markierungsattribut durch Reflektion erhalten. Generieren Sie dann einen bestimmten Handler basierend auf den zugehörigen Eigenschaften. Der spezifische Code lautet wie folgt:
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