通過之前的介紹,我們知道要執行頁面對象的方法,核心就是反射,是從請求獲取參數并執行指定方法的過程。實際上這和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)
{
//執行方法(下面詳細介紹)
ActionPage, HttpContext, CacheMethodInfo> action = new ActionPage, HttpContext, CacheMethodInfo>(Executor.Execute);
IAsyncResult result = action.BeginInvoke(_page, context, _cacheMethodInfo, cb, action);
return result;
}
public void EndProcessRequest(IAsyncResult result)
{
ActionPage, HttpContext, CacheMethodInfo> action = result.AsyncState as ActionPage, 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去執行,這樣的效率比較低。這里我緩存的是方法的委托,該委托的簽名為:Funcobject, object[], object>,該委托的返回值為object類型,表示可以返回任意的類型(我們可以在組件內部進行處理,例如如果是引用類型(非string),就將其序列化為json,但這里并沒有實現)。該委托接收兩個參數,第一個參數是方法所屬的對象,如果是靜態方法就是null;第二個參數是方法的參數,定義為object[]表示可以接收任意類型的參數。通過委托執行方法,與直接調用方法的效率差別就不是很大(對委托不熟悉的朋友可以參見:委托)。CacheMethodInfo的定義如下:
/// summary>
/// 緩存方法信息
/// /summary>
sealed class CacheMethodInfo
{
/// summary>
/// 方法名稱
/// /summary>
public string MethodName { get; set; }
/// summary>
/// 方法委托
/// /summary>
public Funcobject, 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
Dictionarystring, CacheMethodInfo> dic = ajaxDelegateTable[type.AssemblyQualifiedName] as Dictionarystring, CacheMethodInfo>;
if (dic == null)
{
dic = new Dictionarystring, CacheMethodInfo>();
//遍歷頁面的所有MethodInfo
IEnumerableCacheMethodInfo> 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編譯成Funcobject,object[],object>委托,為Type編譯一個Funcobject>委托,用于創建實例對象。
通過Expression優化反射
Expression(表達式樹)允許我們將代碼邏輯以表達式的形式存儲在樹狀結構里,然后在運行時去動態解析,實現動態編輯和執行代碼。熟悉ORM框架的朋友對Expression肯定很熟悉,因為大部分方法都有一個ExpressionTDelegate>類型的參數。訪問關系型數據庫的本質還是sql語句,orm的工作就是為開發人員屏蔽這個過程,以面向對象的方式去讀寫數據庫,而不是自己編寫sql語句。例如,Users.Where(u => u.Age > 18) 就可查詢年齡大于18的用戶。這里不對應用在orm的過程進行詳解,下面我們介紹如何用Expression并利用它來生成委托。
.net定義了許多表達式類型,這些類型都派生自Expression,Expression是一個抽象類,而且是一個工廠類,所有類型的表達式都通過它來創建。如圖:
先看一個 1 * 2 + 2 例子,我們用表達樹來描述來描述它:
/*
* a * b + 2
*/
/*
直接操作
int a = 1, b = 2;
int result = a * 2 + 2;
*/
/*
通過委托調用
Funcint, int, int> func = new Funcint, int, int>((a, b) => { return a * b + 2; });
func(1, 2);
*/
/*通過Expression調用*/
//定義兩個參數
ParameterExpression pe1 = Expression.Parameter(typeof(int), "a");
ParameterExpression pe2 = Expression.Parameter(typeof(int), "b");
//定義一個常量
ConstantExpression constExpression = Expression.Constant(2);
//參數數組
ParameterExpression[] parametersExpression = new ParameterExpression[]{pe1,pe2};
//一個乘法運算
BinaryExpression multiplyExpression = Expression.Multiply(pe1, pe2);
//一個加法運算
BinaryExpression unaryExpression = Expression.Add(multiplyExpression, constExpression);
//將上面的表達式轉換為一個委托表達式
LambdaExpression lambdaExpression = Expression.LambdaFuncint, int, int>>(unaryExpression, parametersExpression);
//將委托編譯成可執行代碼
Funcint,int,int> func = lambdaExpression.Compile() as Funcint,int,int>;
Console.WriteLine(func(1, 2));
可以看到我們最終將其編譯為一個具體類型的委托了。下面看我們真正用到的方法是如何實現的,代碼如下:
public static Funcobject, object[], object> GetMethodDelegate(MethodInfo methodInfo)
{
if (methodInfo == null)
{
throw new ArgumentNullException("methodInfo");
}
//定義參數表達式,它表示委托的第一個參數
ParameterExpression instanceExp = Expression.Parameter(typeof(object), "instance");
//定義參數表達式,它表示委托的第二個參數
ParameterExpression paramExp = Expression.Parameter(typeof(object[]), "parameters");
//獲取方法的參數信息數組
ParameterInfo[] paramInfos = methodInfo.GetParameters();
//參數表達式集合
ListExpression> paramExpList = new ListExpression>();
int length = paramInfos.Length;
for (int i = 0; i length; i++)
{
//獲取paramExp參數數組的第i個元素
BinaryExpression valueObj = Expression.ArrayIndex(paramExp, Expression.Constant(i));
//將其轉換為與參數類型一致的類型
UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);
//添加到參數集合
paramExpList.Add(valueCast);
}
//方法所屬的實例的表達式,如果為靜態則為null
UnaryExpression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceExp, methodInfo.ReflectedType);
//表示調用方法的表達式
MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, paramExpList);
//將表達式目錄描述的lambda編譯為可執行代碼(委托)
if (methodCall.Type == typeof(void))
{
ExpressionActionobject, object[]>> lambda = Expression.LambdaActionobject, object[]>>(methodCall, instanceExp, paramExp);
Actionobject, object[]> action = lambda.Compile();
return (instance, parameters) =>
{
action(instance, parameters);
return null;
};
}
else
{
UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
ExpressionFuncobject, object[], object>> lambda = Expression.LambdaFuncobject, object[], object>>(castMethodCall, instanceExp, paramExp);
return lambda.Compile();
}
}
具體代碼都有注釋解釋,最終我們獲得了一個Funcobject,object[],object>類型的委托,它會作為CacheMethodInfo的屬性進行緩存。有興趣測試反射性能的朋友,也不妨去測試對比一下這幾種方式執行的效率差別:1.直接執行方法 2.Emit 3. 緩存+委托 4.Delegate.DynamicInvoke。
2. Executor.Execute 執行委托
在執行委托前,我們需要先從請求獲取參數,映射到方法。參數可以是簡單的類型,如 string Test(int i,int j); 也可以是一個對象,如 string Test(User user); 如果是 string Test(User user1, User user2) 也行,提交參數時只需要加上 user1或 user2 前綴即可,例如 user1.Name,user2.Name。這里沒有支持更多的匹配方式,像mvc,它還支持嵌套類型等等,這些可以自己去實現。如果參數是一個對象,我們可能需要為它的字段進行賦值,也可能為它的屬性進行賦值。這里我們定義一個DataMember,用來表示字段或屬性的父類。如:
internal abstract class DataMember
{
public abstract string Name { get; }
public abstract Type MemberType { get; }
public abstract void SetValue(object instance,object value);
public abstract object GetValue(object instance);
}
接著定義屬性類型PropertyMember和字段類型FieldMember,分別繼承了DataMember。
PropertyMember定義:
internal class PropertyMember : DataMember
{
private PropertyInfo property;
public PropertyMember(PropertyInfo property)
{
if (property == null)
{
throw new ArgumentNullException("property");
}
this.property = property;
}
public override void SetValue(object instance, object value)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
this.property.SetValue(instance, value, null);
}
public override object GetValue(object instance)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
return this.property.GetValue(instance,null);
}
public override string Name
{
get { return this.property.Name; }
}
public override Type MemberType
{
get { return this.property.PropertyType; }
}
}
FieldMember定義:
internal class FieldMember : DataMember
{
private FieldInfo field;
public FieldMember(FieldInfo field)
{
if (field == null)
{
throw new ArgumentNullException("field");
}
this.field = field;
}
public override void SetValue(object instance, object value)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
this.field.SetValue(instance, value);
}
public override object GetValue(object instance)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
return this.field.GetValue(instance);
}
public override string Name
{
get { return this.field.Name;}
}
public override Type MemberType
{
get { return this.field.FieldType; }
}
}
定義一個DataMemberManager,用來遍歷Type,獲取所有字段和屬性的,實現如下:
internal static class DataMemberManager
{
/// summary>
/// 獲取實例字段/屬性集合
/// /summary>
/// param name="type">類型/param>
/// returns>/returns>
public static ListDataMember> GetDataMember(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
IEnumerablePropertyMember> propertyMembers = from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
select new PropertyMember(property);
IEnumerableFieldMember> fieldMembers = from field in type.GetFields(BindingFlags.Instance | BindingFlags.Public)
select new FieldMember(field);
ListDataMember> members = new ListDataMember>();
foreach(var property in propertyMembers)
{
members.Add(property);
}
foreach (var field in fieldMembers)
{
members.Add(field);
}
return members;
}
}
在前面我們定義的Handler的ProcessRequest方法中,我們調用了Executor.Execute,該方法用于執行委托,實現如下:
/// summary>
/// 核心函數,執行Handler的方法
/// /summary>
/// param name="page">頁面對象/param>
/// param name="context">請求上下文/param>
/// param name="cacheMethodInfo">緩存方法原數據/param>
internal static void Execute(Page page, HttpContext context, CacheMethodInfo methodInfo)
{
if (page == null)
{
throw new ArgumentNullException("page");
}
try
{
if (methodInfo != null)
{
HttpRequest request = context.Request;
object[] parameters = GetParametersFromRequest(request, methodInfo.Parameters);
object data = methodInfo.Func(page, parameters);
int serverCache = methodInfo.AjaxMethodAttribute.ServerCache;
if (serverCache > 0)
{
CacheHelper.Insert(context, methodInfo.AjaxMethodAttribute.ServerCache, data);
}
EndCurrentRequest(context, data, methodInfo.AjaxMethodAttribute.OutputCache);
}
else
{
EndCurrentRequest(context, "找不到合適的Ajax方法!");
}
}
catch (FormatException)
{
EndCurrentRequest(context, "調用方法匹配到無效的參數!");
}
catch (InvalidCastException)
{
EndCurrentRequest(context, "參數轉換出錯!");
}
catch (System.Threading.ThreadAbortException)
{
//do nothing
}
catch (Exception ex)
{
EndCurrentRequest(context, ex.Message);
}
}
CacheMethodInfo我們已經獲得了,現在只要獲得參數我們就可以執行方法。
GetParameterFromRequest用于從請求獲取object[]參數數組。根據上面所說的,如果參數是一個簡單類型,那么直接進行轉換;如果是實例對象,那么我們先要創建new一個實例對象,然后為其字段或屬性賦值。實現如下:
/// summary>
/// 從請求獲取參參數
/// /summary>
/// param name="request">HttpRequest/param>
///param name="parameters">參數信息/param>
/// returns>參數數組/returns>
private static object[] GetParametersFromRequest(HttpRequest request, ParameterInfo[] parameters)
{
if (parameters.IsNullOrEmpty())
{
return null;
}
int length = parameters.Length;
object[] realParameters = new object[length];
for (int i = 0; i length; i++)
{
ParameterInfo pi = parameters[i];
Type piType = pi.ParameterType.GetRealType();
object value = null;
if (piType.IsValueType())
{
//值類型
value = ModelUtil.GetValue(request, pi.Name, piType);
value = value ?? Activator.CreateInstance(piType);
}
else if (piType.IsClass)
{
//引用類型
object model = ModelUtil.CreateModel(piType);
ModelUtil.FillModelByRequest(request, pi.Name, piType, model);
value = model;
}
else
{
throw new NotSupportedException(pi.Name + " 參數不被支持");
}
realParameters[i] = value;
}
return realParameters;
}
ModelUtil會從Http Request獲取參數,并進行類型轉換處理:
internal static class ModelUtil
{
/// summary>
/// 緩存構造函數
/// /summary>
private static Hashtable constructorTable = Hashtable.Synchronized(new Hashtable());
/// summary>
/// 根據名稱從HttpRequest獲取值
/// /summary>
/// param name="request">HttpRequest/param>
/// param name="name">鍵名稱/param>
/// param name="type">參數類型/param>
/// returns>/returns>
public static object GetValue(HttpRequest request, string name, Type type)
{
string[] values = null;
if (string.Compare(request.RequestType, "POST", true) == 0)
{
values = request.Form.GetValues(name);
}
else
{
values = request.QueryString.GetValues(name);
}
if (values.IsNullOrEmpty())
{
return null;
}
string data = values.Length == 1 ? values[0] : string.Join(",", values);
return Convert.ChangeType(data, type);
}
/// summary>
/// 創建實例對象
/// /summary>
/// param name="type">實例類型/param>
/// returns>/returns>
public static object CreateModel(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
Funcobject> func = constructorTable[type.AssemblyQualifiedName] as Funcobject>;
if (func == null)
{
func = ReflectionUtil.GetConstructorDelegate(type);
constructorTable[type.AssemblyQualifiedName] = func;
}
if (func != null)
{
return func();
}
return null;
}
/// summary>
/// 填充模型
/// /summary>
/// param name="request">HttpRequest/param>
/// param name="name">鍵名稱/param>
/// param name="prefix">參數類型/param>
/// parparam name="model">實例對象/parparam>
public static void FillModelByRequest(HttpRequest request, string name, Type type, object model)
{
if (model == null)
{
return;
}
IEnumerableDataMember> members = DataMemberManager.GetDataMember(type);
if (members.IsNullOrEmpty())
{
return;
}
object value = null;
foreach (DataMember member in members)
{
value = GetValue(request, string.Format("{0}.{1}", name, member.Name), member.MemberType);
value = value ?? GetValue(request, member.Name, member.MemberType);
member.SetValue(model, value);
}
}
}
如果是引用類型,需要通過構造函數創建對象,像前面用于,這里我們也用Expression來構建一個Funcobject>類型的委托來優化,它調用了ReflectionUtil.GetConstructorDelegate方法。實現如下:
/// summary>
/// 獲取構造函數委托
/// /summary>
/// param name="type">實例類型/param>
/// returns>/returns>
public static Funcobject> GetConstructorDelegate(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
if (ci == null)
{
throw new MissingMemberException("類型必須有一個無參public構造函數!");
}
NewExpression newExp = Expression.New(type);
ExpressionFuncobject>> lambda = Expression.LambdaFuncobject>>(newExp);
return lambda.Compile();
}
最后再輸出結果時,如果是Get請求,并且需要緩存,我們還需要設置一下Response.Cache。如下:
private static void EndRequest(HttpContext context, object data, int outPutCache, ContentType contentType)
{
HttpResponse response = context.Response;
if (outPutCache != 0)
{
if (string.Compare(context.Request.HttpMethod, "GET", true) == 0)
{
if (outPutCache > 0)
{
response.Cache.SetCacheability(HttpCacheability.Public);
response.Cache.SetMaxAge(new TimeSpan(0, 0, outPutCache));
response.Cache.SetExpires(DateTime.Now.AddSeconds(outPutCache));
}
else
{
response.Cache.SetCacheability(HttpCacheability.NoCache);
response.Cache.SetNoStore();
}
}
}
response.ContentType = GetContentType(contentType);
response.ContentEncoding = System.Text.Encoding.UTF8;
if (data != null)
{
response.Write(data);
}
response.End();
}
總結
現在不管我們前臺用什么腳本庫,只要按照約定就可以調用標記方法。上面已經介紹了組件的核心部分,您也可以按照自己的想法進行擴展,也歡迎共同學習交流。
您可能感興趣的文章:- Ajax核心XMLHTTP組件資料
- 編寫輕量ajax組件02--淺析AjaxPro
- 編寫輕量ajax組件01-與webform平臺上的各種實現方式比較