今回は軽量なajaxコンポーネントの書き方(画像と文章で詳しく解説)をお届けします。軽量なajaxコンポーネントを書く際の注意点について、実際の事例を紹介します。
これまでの説明で、ページ オブジェクトのメソッドを実行するための中心となるのはリフレクションであることがわかりました。リフレクションとは、リクエストからパラメーターを取得し、指定されたメソッドを実行するプロセスです。実際、これは ASP.NET MVC フレームワークの中心的な考え方に非常に似ており、URL を解析し、そこからコントローラーとアクション名を取得し、コントローラー オブジェクトをアクティブ化し、リクエストからアクション パラメーターを取得します。アクションを実行します。 Web フォーム プラットフォームでは、.aspx.cs にメソッドを記述します。目的は、ページ オブジェクトが生成される前に指定されたメソッドを実行し、結果を返すことです。
まず、実装後のいくつかの呼び出し例を見てみましょう。これらの関数は組み合わせて使用することもできます:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | [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()
{
}
[AjaxMethod(SessionState = SessionState.ReadOnly)]
public void Test6()
{
}
[AjaxMethod(SessionState = SessionState.ReadWrite)]
public void Test7()
{
}
[AjaxMethod(IsAsync = true)]
public void Test8()
{
}
|
ログイン後にコピー
基本的な実行プロセスについてはすでに理解したので、ここから直接トピックに進みます。
Ajax 規約
通常、主流ブラウザが ajax を使用して非同期リクエストを送信する場合、リクエスト ヘッダーには X-Requested-With:XMLHttpRequest というマークが付けられます。このタグを直接使用して Ajax リクエストであるかどうかを判断することもできますが、相互に影響を与えないように、プロジェクト内で他のコンポーネントが役立つ場合があります。これは次のとおりです:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | internal static class AjaxConfig
{
public const string Key = "AjaxFlag" ;
public const string Value = "XHR" ;
public const string MethodName = "" ;
}
|
ログイン後にコピー
http リクエスト ヘッダーに AjaxFlag: XHR が含まれている場合、それを処理する必要があることを意味します。さらに、http ヘッダーの MethodName は、実行するメソッドの名前を示します。
AjaxMethodAttribute mark属性
Mark属性はリフレクションに使用されます。ここで必要な関数をいくつか定義します。期待したいのは:
1.セッションステータスを設定できる
2.非同期ハンドラーをサポート
3.キャッシュの取得をサポート
4.サーバーサイドキャッシュをサポート
定義は以下の通りで、AttributeUsagでマークされたタグメソッドでのみ使用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
[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;
public SessionState SessionState
{
get { return _sessionState; }
set { _sessionState = value; }
}
public int OutputCache
{
get { return _outputCache; }
set { _outputCache = value; }
}
public int ServerCache
{
get { return _serverCache; }
set { _serverCache = value; }
}
public ContentType ContentType
{
get { return _contentType; }
set { _contentType = value; }
}
public bool IsAsync
{
get { return _isUseAsync; }
set { _isUseAsync = value; }
}
}
public enum SessionState
{
None,
ReadOnly,
ReadWrite
}
public enum ContentType
{
Plain,
Html,
XML,
Javascript,
JSON
}
|
ログイン後にコピー
さまざまなハンドラーと AjaxHandlerFactory
前の記事によると、特定のハンドラーは主に非同期と非同期の 2 つのカテゴリに分けられ、これら 2 つのカテゴリでは、サポートされていないセッション状態のみがサポートされています。読み取り (IReadOnlySessionState インターフェイスの実装) と読み取りと書き込み (IRequiresSessionState インターフェイスの実装)。 IReadOnlySessionState と IRequiresSessionState は単なるマーク インターフェイスです (メソッドを使用しない場合は、マーク属性を使用して実装する方が合理的です)。非同期ハンドラーは IHttpAsyncHandler インターフェイスを実装する必要があり、このインターフェイスが IHttpHandler を実装します。 Handler の ProcessRequest メソッド (または BeginProcessRequest) でメソッドを実行します。定義は次のとおりです:
非同期状態のハンドラー:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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);
}
}
}
internal class SyncAjaxSessionReadOnlyHandler : SyncAjaxHandler, IReadOnlySessionState
{
internal SyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo)
: base(page, cacheMethodInfo)
{
}
}
internal class SyncAjaxSessionHandler : SyncAjaxHandler, IRequiresSessionState
{
internal SyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo)
: base(page, cacheMethodInfo)
{
}
}
|
ログイン後にコピー
非同期状態のハンドラー:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
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);
}
}
}
internal class ASyncAjaxSessionReadOnlyHandler : ASyncAjaxHandler, IReadOnlySessionState
{
internal ASyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo)
: base(page, cacheMethodInfo)
{
}
}
internal class ASyncAjaxSessionHandler : ASyncAjaxHandler, IRequiresSessionState
{
internal ASyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo)
: base(page, cacheMethodInfo)
{
}
}
|
ログイン後にコピー
AjaxHandlerFactory は、リクエストに基づいて特定のハンドラーを生成するために使用される IHandlerFactory インターフェイスを実装します。Web に登録する必要があります。 .config。 AjaxHandlerFactory の GetHandler は、リクエストをインターセプトするための最初のステップです。リクエスト ヘッダーの AjaxFlag:XHR を使用して、処理する必要があるかどうかを判断し、必要な場合はハンドラーを作成します。それ以外の場合は、通常の方法で処理します。このメソッドは .aspx.cs で記述されているため、リクエストは .aspx サフィックスであり、これはページ (Page、IHttpHandler を実装) タイプです。ページは PageHandlerFactory を通じて作成されます。これは、IHandlerFactory インターフェイスも実装します。つまり、次の目的で使用されます。ハンドラーを作成します。したがって、IHttpHandler を作成するには PageHandlerFactory を使用する必要がありますが、PageHandlerFactory の constructor は、新しいものを直接作成できないため、CommonPageHandlerFactory を通じて継承する必要があります。
PageHandlerFactoryでPageを取得した後、メソッド名と組み合わせてリフレクションによりAjaxMethodAttributeのマーク属性を取得できます。次に、関連するプロパティに基づいて特定のハンドラーを生成します。具体的なコードは次のとおりです:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 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)
{
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
sealed class CacheMethodInfo
{
public string MethodName { get; set; }
public Func<object, object[], object> Func { get; set; }
public ParameterInfo[] Parameters { get; set; }
public AjaxMethodAttribute AjaxMethodAttribute { get; set; }
}
|
ログイン後にコピー
核心方法
1. Eexcutor.GetDelegateInfo 获取方法相关信息
该方法用于遍历页面类,获取所有AjaxMethodAttribute标记的方法信息,生成一个CacheMethodInfo对象,包括标记信息、方法名称、参数信息,以及最重要的方法委托。该对象会缓存在一个哈希表中,下次获取时,直接从内存获得。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
internal static CacheMethodInfo GetDelegateInfo(Page page, string methodName)
{
if (page == null)
{
throw new ArgumentNullException( "page" );
}
Type type = page. GetType ();
Dictionary<string, CacheMethodInfo> dic = ajaxDelegateTable[type.AssemblyQualifiedName] as Dictionary<string, CacheMethodInfo>;
if (dic == null)
{
dic = new Dictionary<string, CacheMethodInfo>();
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))
{
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