ajaxpro は古いコンポーネントですが、その実装アイデアとソース コードは依然として大きな参考価値があります。次に、この記事を通して、軽量の ajax コンポーネント 02 の書き方を紹介します -- AjaxPro の簡単な分析 興味のある方は以下を参照してください
前書き
前の記事では、Web フォームに Ajax を実装するいくつかの方法を紹介しました。プラットフォームを構築し、Base クラスを実装しました。この記事では、オープンソース コンポーネントである ajaxpro について説明します。これは古いコンポーネントですが、実装のアイデアとソース コードはまだ学ぶ価値があります。前回の記事での紹介により、ページ オブジェクトのメソッドの呼び出しはリフレクションによって実現されることがわかりました。鍵となるのは、リフレクション呼び出しメソッドやパラメーター マッピングなどを含む処理プロセス全体です。 Ajaxpro は、このプロセスをバックグラウンドで実装するのに役立つだけでなく、ajax 関連メソッドなどのリクエスト呼び出しメソッドをフォアグラウンドでカプセル化することもできます。ajaxpro メソッドを使用すると、js を自分でカプセル化したり、js ライブラリを使用したりせずに、非同期リクエストを送信できます。次に、このコンポーネントを簡単に分析します。
1. ajaxpro の使用
まず、このコンポーネントの使用方法を見てみましょう。
1. AjaxHandlerFactory を登録する
web.config に以下の設定を行います:
<httpHandlers> <add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/> </httpHandlers>
簡単に言うと、リクエストされた URL が ajaxpro/*.ashx 形式に準拠していれば、実装である AjaxHandlerFactory によって処理されます。 IHandlerFactory インターフェイスの Factory クラス。IHandler ハンドラーを取得するために使用されます。型の形式は、「名前コントロール。クラス名、アセンブリ名」です。
2. ページクラスの Page_Load イベントに登録します
protected void Page_Load(object sender, EventArgs e) { AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage)); }
このページオブジェクトの Type を ResisterTypoForAjax メソッドに渡します。具体的には、このメソッドはフォアグラウンドでスクリプトを登録するために使用されます。したがって、aspx ファイルに
が存在する必要があります。そうでない場合、スクリプトは登録されません。 (ここでは型を渡していますが、実際には渡さずに行うこともできます。この型は HttpContext.Current.Handler.GetType().BaseType を通じて内部的に取得することもできます)3. AjaxMethod でメソッドをマークします
[AjaxMethod] public List<string> GetList(string input1,string input2) { return new List<string> { input1, input2 }; }
AjaxMethod は、このメソッドが Ajax リクエストの処理に使用され、最終的にはリフレクションを通じて実行されることを示す Mark 属性です。キャッシュする必要のある一部のデータについては、キャッシュ時間を設定できます。リクエストに Session を使用する必要がない場合は、 HttpSessionStateRequirement を設定できます。時間のかかる Web サービスのリクエストなど、リクエストを非同期にする必要がある場合は、ハンドラーを非同期状態に設定することもできます。
メソッドの戻り値は、単純型または複合型のいずれかになります。たとえば、フォアグラウンドで取得されるコレクション型は配列です。
4. フロントエンドの呼び出し
バックエンドの構成と使用法は非常に簡単です。 次に、フロントエンドがリクエストを開始する方法を見てみましょう。
function GetList() { //var result = AjaxProNamespace.AjaxProPage.GetList("a", "b").value; //console.log(result); AjaxProNamespace.AjaxProPage.GetList("a", "b", function (result) { console.log(result); }); }
ここで、AjaxProNamespaceはページクラスが配置されている名前空間、AjaxProPageはページクラスの名前、GetListはマークされたメソッドです。なぜこのように書くことができるのでしょうか?前述したように、ajaxpro はフォアグラウンドでスクリプトを登録し、ページ オブジェクトの関連情報に基づいて次のスクリプトを生成するため、自分で js を記述したり、jquery ライブラリ メソッドを使用したりしなくても、このように呼び出すことができます。
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();
GetList のパラメータは、バックグラウンド メソッドのパラメータに対応している必要があります。変換可能でない場合、呼び出しは失敗します。最後のパラメータはコールバック関数です。コールバック関数のパラメータは、戻り値をカプセル化するオブジェクトです。たとえば、上記の戻り値は配列オブジェクトです。そのエラーには障害情報が含まれます。
上記のコメントアウトされた部分は同期リクエストのメソッドであり、多くの場合、これが誤って使用されているのを見たことがあります。
2. ajaxproによるリクエスト処理の原理
ここでは主にajaxリクエストを処理するコンポーネントの処理に焦点を当て、その他の補助的な機能については紹介しません。
1.補助スクリプトの生成
Page_Loadイベントで、AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage));を呼び出して、必要なスクリプトを登録しました。次のスクリプトがフロント ページに導入されていることに気付きました:
つまり、各ページがこれらのリクエストを開始します。これらはすべて .ashx で終わるファイルですが、実際にはすべて js コードです。これらの js の一部は dll 内にリソースとしてネストされており、一部は主に ajax リクエスト関連のメソッドをカプセル化しており、次のものが使用できます。ページクラス名。メソッドの呼び出しに使用されるタグメソッド名。 .js ではなく .ashx を使用するのはなぜですか? .js ファイルはコンポーネント内のリソース ファイルなので、外部から直接リクエストすることはできませんが、.ashx はインターセプトでき、その内容は 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には以下の利点があります:
1. 構成が簡単。
2. 他のコンポーネントと組み合わせて使用できます。
3. フロントエンド スクリプトをカプセル化します。自分でカプセル化したり、他のスクリプト ライブラリを使用したりする必要はありません。
4. 戻り値の処理では、単純型または複合型を返すことができ、これらは自動的にシリアル化されます。
欠点は次のとおりです:
1. ページにはさらに 4 つのリクエストがあります。 304 キャッシュが利用されますが、リクエストをサーバーに送信する必要があります。
2. AjaxではGetリクエストが使えません。 URL 形式はカスタマイズされているため、この形式を使用して Get リクエストを使用することはできません。Yahoo のフロントエンド最適化提案の 1 つは、より多くの Get リクエストを使用することです。実際には、名前空間、クラス名、アセンブリを http ヘッダーに配置し、自由に選択できる型パラメーターを提供する必要があります。
3.