在 Aps.net mvc 應用中對請求的處理最終都是轉換為對某個 Controller 中的某個 Action 方法的呼叫,因此,要對一個請求進行處理,第一步,需要根據請求解析出對應的 ControllerAction 的名稱,這是 Asp.net mvc 中的路由 的職責所在,第二步,需要根據第一步解析出來的內容定位對請求進行處理的 Action 方法所屬的 Controller 型別,定位的過程稱為 Asp.net mvc 中 Controller 的啟用,第三步,就是根據請求的的內容到第二步定位的 Controller 中查詢最終用來處理請求的 Action 方法並執行。

  要執行一個方法,首先要根據方法簽名從多個過載(如果有)中選出最為匹配的一個,然後對方法的引數進行繫結,Action 方法的執行亦是如此。

  這一篇主要介紹一下 Action 方法執行過程的一些關鍵的元件。

關鍵元件

ActionInvoker

  在 Asp.net mvc 中的 Controller 型別是指那些直接或間接的實現了 IController 介面的型別,在該介面中僅定義了一個 void Execute(RequestContext requestContext) 方法,在通過 Controller 的啟用過程成功建立 Controller 型別的例項後,後續的 Action 方法的執行便是由該方法來執行的,該方法是一個同步的方法,但預設情況下,建立 Controller 例項後的後續操作是非同步實現的,這些非同步的操作是通過 IAsyncController 介面來實現的,該介面繼承自 IController,定義如下:

    public interface IAsyncController : IController
{
IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
void EndExecute(IAsyncResult asyncResult);
}
}

  Action 方法的執行是通過 IActionInvoker 來實現的,該介面中只定義了一個 bool InvokeAction(ControllerContext controllerContext, string actionName) 方法,該介面同樣具有一個非同步的版本 IAsyncActionInvoker,該介面繼承自 IActionInvoker,其定義如下:

    public interface IAsyncActionInvoker : IActionInvoker
{
IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state);
bool EndInvokeAction(IAsyncResult asyncResult);
}
}

  具體使用同步還是非同步的版本來執行 Action,如果 Controller 執行的是同步的 Execute(RequestContext requestContext) 方法,則便使用同步的 IActionInvoker,否則使用的是非同步的 IAsyncActionInvoker

ActionInvoker 的建立

  ActionInvoker 例項的建立是通過 Controller 類的 IActionInvoker CreateActionInvoker() 方法來建立的, IActionInvoker 的建立也是使用了 IOC 和工廠模式的思想,首先判斷內部 IOC 容器(IDependencyResolver 型別)是否存在 IAsyncActionInvokerFactory型別的繫結,如果存在則建立該型別的例項,然後呼叫其 CreateInstance() 建立 IActionInvoker 型別的例項並返回,否則,判斷 IOC 容器內是否存在 IActionInvokerFactory 型別的繫結,如果存在則建立該型別的例項,然後呼叫其 CreateInstance() 方法建立 IActionInvoker 型別的例項並返回。如果不存在,則判斷 IOC 容器內是否存在 IAsyncActionInvoker 型別的繫結,如果存在,則建立例項並返回,否則判斷是否存在 IActionInvoker 型別的繫結,如果存在,則建立例項並返回,如果不存在上述的任何型別的繫結,則建立一個 AsyncControllerActionInvoker 型別的例項並返回。

  

ControllerDescriptor

  該類從名字上就可以看出其是對 Controller 型別的一個描述,是一個抽象類,其定義(主要成員)如下:

    public abstract class ControllerDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{
//根據 ControllerContext 和 Action的名稱獲取指定的 Action的描述資訊
public abstract ActionDescriptor FindAction(ControllerContext controllerContext, string actionName) //獲取指定Controller 下定義的所有的有效的 Action的資訊
public abstract ActionDescriptor[] GetCanonicalActions(); //ICustomAttributeProvider 介面方法,獲取指定Controller型別上使用的所有特性(Attribute) 的資訊,引數 inherit 表示是否進行遞迴查詢
public virtual object[] GetCustomAttributes(bool inherit);
//同屬 ICustomAttributeProvider 介面方法,返回指定型別標識的自定義屬性陣列
public virtual object[] GetCustomAttributes(Type attributeType, bool inherit); //獲取 Controller 型別上使用的過濾器屬性
public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache); //ICustomAttributeProvider介面方法,判斷指定型別的自定義屬性是否存在
public virtual bool IsDefined(Type attributeType, bool inherit); //控制器的名稱
public virtual string ControllerName;
//控制器的型別
public abstract Type ControllerType { get; }
//IuniquelyIdentifiable介面成員
public virtual string UniqueId;//當前型別的唯一標識,由控制器名稱、控制器型別以及當前類的型別生成
}

  上面的 GetCanonicalActions() 方法用來獲取有效的 Action 方法,那麼什麼是有效的 Action 方法?一個規範的 Action 方法應滿足如下的要求:

  • 所在型別必須直接或間接的繼承自抽象類 ControllerBase
  • 必須是一個公共的例項方法
  • 必須是一個可呼叫的方法(BindingFlags.InvokedMethod),例如,建構函式便不是有效的 Action 方法
  • 方法不具有泛型引數
  • 方法不具有 out 和 ref 引數

  ControllerDescriptor 是抽象型別,其具有兩個子類,ReflectedControllerDescriptorReflectedAsyncControllerDescriptor . 子類內部對父類中的方法進行重寫,從名稱上來看,一個是同步的,一個是非同步的,要說清楚這兩個類最大的不同,需要說明一個類 ActionSelectorBase,這是一個抽象類,該類的主要成員如下示:

//屬性部分
public MethodInfo[] ActionMethods { get; private set; }//當前Controller下所有Action方法集合 public HashSet<MethodInfo> StandardRouteMethods { get; private set; }//所有 Action 方法的 HashSet集合 public MethodInfo[] AliasedMethods{get;}//所有具有別名的Action方法的集合 public ILookup<string, MethodInfo> NonAliasedMethods{get;private set;};//所有沒有別名的方法的集合 private StandardRouteActionMethodCache CreateStandardRouteCache()//Action 方法快取,該類是該類內部的一個私有類,內部僅具有兩個屬性,分別表示無別名的 Action 方法的集合和具有別名方法的集合。 //方法部分
protected void Initialize(Type controllerType);//負責對上述的屬性進行初始化 protected abstract bool IsValidActionMethod(MethodInfo methodInfo);//判斷給定方法是否為有效的 Action方法,基本的判定標準即上面說的那些 //根據給定的 Action 名稱獲取對應的ActionMethod,在該方法中首先從快取的具有別名的方法集合和無別名的方法集合中篩選中具有指定名稱的方法,然後改根據請求的方法(Post、Get等)對上一步得到的集合進行篩選,移除不滿足的項
protected List<MethodInfo> FindActionMethods(ControllerContext controllerContext, string actionName); //獲取指定 Method 的 ActionName,如果方法沒有別名則直接返回它本身的名稱,如果方法有 ActionNameSelector 屬性修飾,則返回其Name屬性
public string GetActionName(MethodInfo methodInfo) //根據指定的 ActionName 獲取對應的 ActionMethod,內部呼叫 FindActionMethods,如果不存在,返回 null,具有多個時丟擲 AmbiguousMatchException 異常
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName)

  該類具有兩個子類:ActionMethodSelectorAsyncActionMethodSelector,前者處理一些普通的同步方法相關的操作,後者處理一些非同步方法相關的操作,這裡的非同步方法包括兩種,一種是具有 Aysnc 字首或具有 Completed 字尾的方法,此類方法主要是為了保持 Asp.net Mvc 的向後的相容性,是 Asp.net mvc 3 之前實現非同步的方式,另外一種是返回型別為 Task<ActionResult> 的方法。是 Asp.net mvc 3 之後提倡的非同步 Action 實現方式。

  在 ControllerDescriptor 的兩個子類中均有一個 ActionMethodSelectorBase 型別的變數 _selector,不同的是 ReflectedControllerDescriptor 的該變數是 ActionMethodSelector 型別的,而 ReflectedAsyncControllerDescriptor 的該變數是 AsyncMethodSelector 型別的。

  在 ReflectedControllerDescriptorReflectedAsyncController 中均對基類中的 ActionDescriptor FindAction(ControllerContext context,string actionName) 進行重寫,前者內部呼叫 _selector 的同名方法返回一個與 actionName 匹配的 MethodInfo,最終返回的是上一步的 MethodInfoactionName 等作為引數初始化的一個 ReflectedActionDescriptor。對於後者,呼叫 _selector 的同名方法返回的是一個如下所示的委託

    delegate ActionDescriptor ActionDescriptorCreator(string actionName, ControllerDescriptor controllerDescriptor);}

  針對上述Action 方法的兩種不同的非同步實現方法,返回的是不同的結果,對於具有 Async 字首Completed 字尾的方法,返回的是 ReflectedAsyncActionDescriptor 型別的例項,對於返回型別為 Task<ActionResult> 的 Action 方法,返回的是 TaskAsyncActionDescriptor 型別的例項。

ActionDescriptor

  顧名思義,這個類用於描述 Action 相關的一些資訊,該類是一個抽象類,其定義(主要成員)如下所示:

public abstract class ActionDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{
/*****************************屬性部分*********************************/
//當前 Action 方法的名稱
public abstract string ActionName { get; } //當前 Action 所在的 Controller相關的 ControllerDescriptor
public abstract ControllerDescriptor ControllerDescriptor { get; } //Asp.net mvc 自定義的基於 Dictinary 的執行緒安全的快取集合,後面會介紹
internal ActionMethodDispatcherCache DispatcherCache{get{..};set{..} public virtual string UniqueId{};//IUniquelyIdentifiable 介面成員,當前型別例項的唯一標識
由當前型別、ControllerDescriptor、ActionName三者計算產生 /***************************** 方法部分 ***********************************/ /ICustomAttributeProvider 介面方法,功能不在贅述,但這裡並沒有提供具體的實現,前兩個返回的均為空的 object[]
public virtual object[] GetCustomAttributes(bool inherit); public virtual object[] GetCustomAttributes(Type attributeType, bool inherit); public virtual bool IsDefined(Type attributeType, bool inherit); //其它方法
public abstract object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);//ActionDescriptor 型別的核心方法,Action 方法的返回的 ActionResult 便是由該方法的返回結果處理轉換產生的 //根據跟定的 ParameterInfo 去 parameters中尋找指定的引數值,key 為 parameterInfo.Name,查詢失敗、引數型別不匹配、查詢值為空但引數不能為空時丟擲異常
internal static object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, MethodInfo methodInfo); //作用同上面的方法,但在出現上述丟擲異常的情況時會使用預設值替代而不是丟擲異常,這裡的預設值有兩種,一種是直接在方法引數後使用等號賦值的,如Say(string name = "tom"),另外一種是在 model 類的屬性上使用 DefaultValueAttribute 設定的預設值,前者具有更高的優先順序
internal static object ExtractParameterOrDefaultFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters); //獲取所有定義在當前 Action 上的過濾器Filters
public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache); //獲取所有的引數資訊
public abstract ParameterDescriptor[] GetParameters();

  下面說一下 ActionMethodDispatcherCache ** 這個型別,該型別派生自 ReaderWriterCache<MethodInfo, ActionMethodDispatcher>,其中的ReaderWriterCache<TKey,TValue> 是一個 Asp.net mvc 自定義的一個基於 Dictinary 的執行緒安全的集合,ActionDescriptor** 可以看作是與當前 Action 方法相關的 MethodInfo 的封裝,在找到請求所對應的 Action 方法時正常的思路是使用反射的方式來呼叫對應的方法,但是對 Action 方法的訪問是相當的頻繁的,大量使用反射會嚴重的影響程式的效能,因此,Asp.net mvc 內部並沒有這麼實現,而是將對應的 Action 方法轉換為一棵表示式樹,然後將表示式樹編譯為委託,最終執行委託的方式來實現的。ActionMethodDispatcher 類最重要的功能就是根據傳入的 MethodInfo 編譯產生一個委託,最終產生的委託型別為 object ActionExecutor(ControllerBase controller,object[] parameters)。上面所說的 ActionDescriptor 的核心方法 object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters) 的返回結果便是內部呼叫該型別的委託產生的。

ActionDescriptor 的子類

  ActionDescriptor 本身為抽象類,不能直接例項化使用,其型別具有兩個直接子類:ReflectedActionDescriptorAsyncActionDescriptor,後者亦是一個抽象類,其具有兩個子型別:ReflectedAsyncActionDescriptorTaskAsyncActionDescriptor。這裡主要對 ReflectedActionDescriptorTaskAsyncActionDescriptor 進行說明。

  • ReflectedActionDescriptor

      該類主要看其 object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters) 方法的實現,其實現如下所示:
public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (parameters == null)
{
throw new ArgumentNullException("parameters");
} // Performance sensitive so avoid Linq or delegates.
ParameterInfo[] parameterInfos = MethodInfo.GetParameters();
object[] parametersArray = new object[parameterInfos.Length];
for (int i = 0; i < parameterInfos.Length; i++)
{
ParameterInfo parameterInfo = parameterInfos[i];
object parameter = ExtractParameterFromDictionary(parameterInfo, parameters, MethodInfo);
parametersArray[i] = parameter;
} ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(MethodInfo);
object actionReturnValue = dispatcher.Execute(controllerContext.Controller, parametersArray);
return actionReturnValue;
}

  引數中的 ControllerContext 可以看作是 ControllerRouteDataRequestContext 三者的封裝,跟前面所說的一樣,根據傳入的 MethodInfo 例項獲取 ActionMethodDispatcher 型別例項,呼叫其 Execute 方法並 返回執行結果。

  • TaskAsyncActionDescriptor

      在說這個型別之前先看一下其直接基類:AsyncActionDescriptor,其定義如下:
public abstract class AsyncActionDescriptor : ActionDescriptor
{
public abstract IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state); public abstract object EndExecute(IAsyncResult asyncResult); public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);//這裡直接丟擲無效操作的異常 internal static AsyncManager GetAsyncManager(ControllerBase controller)
{
IAsyncManagerContainer helperContainer = controller as IAsyncManagerContainer;
if (helperContainer == null)
{
throw Error.AsyncCommon_ControllerMustImplementIAsyncManagerContainer(controller.GetType());
} return helperContainer.AsyncManager;
}
}

  前兩個方法沒有什麼好說的,標準的非同步操作方法,重點說一下最後一個方法,AsyncManager GetAysncManager(ControllerBase controller),該方法返回一個 AsyncManager型別,該型別主要功能有兩個,第一,在非同步操作和回撥操作之間傳遞引數,第二個是向系統傳送非同步操作開始和結束的通知。該類的定義如下:

//屬性部分
public event EventHandler Finished;//非同步操作完成時的回撥事件 public OperationCounter OutstandingOperations { get; private set; }//當前執行的非同步操作的計數器 public IDictionary<string, object> Parameters { get; private set; }//非同步操作與回撥操作之間引數傳遞的集合 public int Timeout{get;set;}//非同步操作的超時時間,單位為毫秒,預設值為 45*1000 //方法部分
public virtual void Finish();//非同步操作完成時的回撥函式 public virtual void Sync(Action action);非同步操作的執行函式,內部呼叫 SynchronizationContext 例項的同名方法

  OutstandingOperations 屬性是整個非同步操作的核心屬性,在非同步操作開始和結束,應分別呼叫該型別的 Increment 方法和 Decrement 方法增加和減少計數操作(內部執行原子運算),每次在呼叫上述的兩個方法時,都會檢驗當前的計數值,如果計數值為0,則表示非同步操作已完成,此時,該型別的 Completed 方法便會表呼叫,響應的 AsyncManager 型別的 Finish 方法也會被呼叫。抽象類 Controller 實現了 IAsyncManagerContainer 介面,該介面中僅定義了一個 AsyncManager 型別的屬性。因此,對一步操作超時進行設定的兩個屬性 :AsyncTimeoutAttributeNoAsyncTimeoutAttribute,其中前者繼承自 ActionFilter,重寫了過濾器的 void OnExecuting(ActionExecutingContext context) 方法,後者繼承自前者,相當於將超時時間 duration 設定為 -1,表示永不超時。在重寫的 void OnExecuting(ActionExecutingContext context) 便是對 Controller 的 AsyncManager 例項的 TimeOut 屬性進行設定。

ParameterDescriptor

  顧名思義,該型別是對 Action 方法的引數進行描述的,是一個抽象類,同樣的實現了 ICustomAttributeProvider 介面,該類的定義如下示:

public abstract class ParameterDescriptor : ICustomAttributeProvider
{
//引數所屬的 Action 的 ActionDescriptor
public abstract ActionDescriptor ActionDescriptor { get; } //引數繫結資訊,例如引數值的獲取路徑(Querstring、Form表單等)、引數是否需要進行繫結,是否具有某些字首等資訊
public virtual ParameterBindingInfo BindingInfo{get;} //引數預設值
public virtual object DefaultValue}{get;} //引數名稱
public abstract string ParameterName { get; } //引數型別
public abstract Type ParameterType { get; } //以下為 ICustomAttributeProvider 介面方法
public virtual object[] GetCustomAttributes(bool inherit); public virtual object[] GetCustomAttributes(Type attributeType, bool inherit); public virtual bool IsDefined(Type attributeType, bool inherit);
}

  該型別僅有一個實現類:ReflectedParameterDescriptor,在該類中對基類中的方法或屬性進行重寫或實現,增加了一個 ParameterInfo 型別的屬性,表示當前引數的一些資訊。

  至此,Action 執行過程中一些關鍵的元件介紹完畢。