這次為大家帶來編寫輕量級ajax組件(圖文詳解),編寫輕量級ajax組件的注意事項有哪些,下面就是實戰案例,一起來看一下。
透過先前的介紹,我們知道要執行頁面物件的方法,核心就是反射,是從請求取得參數並執行指定方法的過程。實際上這和asp.net mvc框架的核心思想很類似,它會解析url,從中獲取controller和action名稱,然後激活controller對象,從請求獲取action參數並執action。在web form平台上,我們把方法寫在.aspx.cs中,要實現的就是在頁面物件還未產生的情況下,執行指定的方法,然後回傳結果。
我們先看實作後幾個呼叫例子,這些功能也可以組合使用:
[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()
{
//异步调用
}
前面我們已經熟悉基本的執行流程,現在直接進入主題。
Ajax約定
通常現在主流瀏覽器在使用ajax發送非同步請求時,請求頭都會帶上一個:X-Requested-With:XMLHttpRequest 的標記。我們也可以直接透過這個標記來判斷是不是ajax請求,不過專案中可能有用其它的元件,為了不互相影響,我們加入一個自訂的請求頭。這裡為:
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 = "";
}
意思是如果http 的請求頭包含一個 AjaxFlag : XHR,就是我們要處理的。另外http header的MethodName就表示我們要執行的方法的名稱。
AjaxMethodAttribute標記屬性
標記屬性是給反射用的,在這裡定義我們需要的一些功能。我們希望有:
1. 可以設定Session狀態
2. 支援非同步Handler
3. 支援Get快取
4. 支援服務端快取
定義如下,用AttributeUsag標記該標記只能用於方法上。
/// <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
}
各種處理程序和AjaxHandlerFactory
依照上一篇的說法,具體的Handler主要分為兩類,異步和非非同步;這兩類下,對於Session的狀態又有3三種,不支援、只支援讀(實作IReadOnlySessionState介面)、支援讀寫(實作IRequiresSessionState介面)。 IReadOnlySessionState和IRequiresSessionState都只是標記介面(無任何方法,其實應該用標記屬性實作比較合理)。非同步的Handler需要實作IHttpAsyncHandler接口,該接口又實作了IHttpHandler。 Handler的ProcessRequest方法(或BeginProcessRequest)就是我們要執行方法的地方。定義如下:
非異步狀態的Handler:
//不支持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:
//不支持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實現了IHandlerFactory接口,用來根據請求生成具體的Handler,它需要在web.config進行註冊使用。 AjaxHandlerFactory的GetHandler是我們攔截請求的第一步。透過請求頭的AjaxFlag:XHR來判斷是否需要我們處理,如果是,則建立一個Handler,否則按照普通的方式進行。由於我們的方法是寫在.aspx.cs內的,我們的請求是.aspx後綴的,也就是頁面(Page,實現了IHttpHandler)類型,Page是透過PageHandlerFactory創建的,PageHandlerFactory也實現了IHandlerFactory接口,表示它是用來創建處理程序的。所以我們需要用PageHandlerFactory來建立一個IHttpHandler,不過PageHandlerFactory的建構子是protected internal類型的,我們無法直接new一個,所以需要透過一個CommonPageHandlerFactory繼承它來實現。
透過PageHandlerFactory取得Page後,結合方法名稱,我們就可以反射取得AjaxMethodAttribute標記屬性了。然後根據它的相關屬性生成具體的Handler。具體程式碼如下:
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