閱讀378 返回首頁    go 阿裏雲 go 技術社區[雲棲]


ASP.NET MVC三個重要的描述對象:ActionDescriptor

在Model綁定過程中會通過激活的Controller類型創建用於描述它的ControllerDescriptor對象。Controller是一組Action方法的集合,而每一個Action通過ActionDescriptor對象來表示,在這篇文章中我們就來著重談談不同類型的ActionDescriptor。[本文已經同步到《How ASP.NET MVC Works?》中]

目錄
一、ActionDescriptor
二、AsyncActionDescriptor
三、ReflectedActionDescriptor
四、ReflectedAsyncActionDescriptor
五、TaskAsyncActionDescriptor

用於描述定義在Controller類中的Action方法的ActionDescriptor定義如下。屬性ActionName和ControllerDescriptor表示Action的名稱和描述所在Controller的ControllerDescriptor對象。表示唯一標識的UniqueId屬性由、與三者派生。

   1: public abstract class ActionDescriptor : ICustomAttributeProvider
   2: {
   3:     public virtual object[] GetCustomAttributes(bool inherit);
   4:     public virtual object[] GetCustomAttributes(Type attributeType,  bool inherit);
   5:     public virtual bool IsDefined(Type attributeType, bool inherit);
   6:     public virtual IEnumerable<FilterAttribute> GetFilterAttributes( bool useCache);
   7:     
   8:     public abstract ParameterDescriptor[] GetParameters();
   9:     public abstract object Execute(ControllerContext controllerContext,  IDictionary<string, object> parameters);
  10:     public virtual ICollection<ActionSelector> GetSelectors();
  11:     public virtual FilterInfo GetFilters();    
  12:  
  13:     public abstract string ActionName { get; }
  14:     public abstract ControllerDescriptor ControllerDescriptor { get; }
  15:     public virtual string UniqueId { get; }
  16: }

與ControllerDescriptor一樣,ActionDescriptor同樣實現了定義在ICustomAttributeProvider接口中的方法,我們可以通過相應的方法得到應用在Action方法上的相關特性,或者判斷某個指定的特性是否應用在對應的Action方法上。GetFilterAttributes方法用於返回應用在Action方法上的所有篩選器特性。用於描述Action方法中所有參數的ParameterDescriptor數組通過方法GetParameters返回。,該方法的兩個參數controllerContext和parameters分別代表Action方法執行所在的Controller上下文和傳入的參數。

GetSelectors方法用於返回一組表示Action選擇器的類型為ActionSelector的對象,而ActionSelector是一個委托類型。如下麵的代碼片斷所示,ActionSelector委托具有唯一的類型為ControllerContext的參數,布爾類型的返回值表示目標Action方法是否與指定的Controller上下文相匹配。該方法默認返回的是一個空的ActionSelector集合。

   1: public delegate bool ActionSelector(ControllerContext controllerContext);

ActionDescriptor的GetFilters方法返回的是一個FilterInfo類型的對象,我們通過這個對象可以得到應用在該Action方法上所有的篩選器。如下麵的代碼所示,FilterInfo具有四個隻讀的集合屬性,分別代碼應用在該Action方法上的四種類型的篩選器(ActionFilter、AuthorizationFilter、ExceptionFilter和ResultFilter)。

   1: public class FilterInfo
   2: {
   3:     public IList<IActionFilter>         ActionFilters { get; }
   4:     public IList<IAuthorizationFilter>  AuthorizationFilters { get; }
   5:     public IList<IExceptionFilter>      ExceptionFilters { get; }
   6:     public IList<IResultFilter>         ResultFilters { get; }
   7: }

異步版本的ActionDescriptor通過AsyncActionDescriptor類型表示,它用於描述定義在AsyncController中的異步方法。如下麵的代碼片斷所示,AsyncActionDescriptor是一個繼承自ActionDescriptor的抽象類,它重寫了Execute方法,並且定義了兩個用於異步執行Action方法的抽象方法。

   1: public abstract class AsyncActionDescriptor : ActionDescriptor
   2: {
   3:     public abstract IAsyncResult BeginExecute( ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback,  object state);
   4:     public abstract object EndExecute(IAsyncResult asyncResult);
   5:     public override object Execute(ControllerContext controllerContext,  IDictionary<string, object> parameters);
   6: }

實際上AsyncActionDescriptor重寫的Execute方法並沒有實現任何Action方法執行的邏輯,而是直接拋出一個InvalidOperationException異常,意味用於。

上麵我們介紹的ReflectedControllerDescriptor的FindAction和GetCanonicalActions方法返回的ActionDescriptor對象實際上是一個ReflectedActionDescriptor對象。顧名思義,ReflectedActionDescriptor針對Action方法元數據信息的解析同樣通過針對目標方法成員的反射來實現。如下麵的代碼片斷所示,ReflectedActionDescriptor直接繼承自ActionDescriptor,表示Action名稱、所在Controller的描述以及Action方法的隻讀屬性ActionName、ControllerDescriptor和MethodInfo均在構造函數中初始化。

   1: public class ReflectedActionDescriptor : ActionDescriptor
   2: {   
   3:     public ReflectedActionDescriptor(MethodInfo methodInfo, string actionName, ControllerDescriptor controllerDescriptor);    
   4:     
   5:     public override object[] GetCustomAttributes(bool inherit);
   6:     public override object[] GetCustomAttributes(Type attributeType,  bool inherit);
   7:     public override bool IsDefined(Type attributeType, bool inherit);
   8:     public override IEnumerable<FilterAttribute> GetFilterAttributes( bool useCache);
   9:  
  10:     public override ParameterDescriptor[] GetParameters();
  11:     public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);
  12:     public override ICollection<ActionSelector> GetSelectors();
  13:    
  14:     public override string ActionName { get; }
  15:     public override ControllerDescriptor ControllerDescriptor { get; }
  16:     public MethodInfo MethodInfo { get;}
  17:     public override string UniqueId { get; }
  18: }

ReflectedControllerDescriptor通過對應用在Action方法上所有特性的反射實現了定義在ICustomAttributeProvider接口中的三個方法。對於方法GetFilterAttributes返回的應用在Action方法上的篩選器特性,同樣是通過相同的方式獲得。

ReflectedControllerDescriptor重寫了UniqueId屬性,在現有的基礎上將表示Action方法的MethodInfo對象作為了決定元素之一。也就是說,表示ReflectedControllerDescriptor對象唯一標識的UniqueId屬性通過。

對於通過方法GetParameters返回的用於描述所有參數的ParameterDescriptor數組,也是通過對Action方法的參數列表進行反射來創建的。Execute方法最終傳入參數列表調用MethodInfo對象執行Action方法。

ReflectedControllerDescriptor的GetSelectors返回的ActionSelector集合涉及到一個類型為ActionMethodSelectorAttribute的特性。ActionMethodSelectorAttribute特性應用於Action方法,會影響到通過ControllerDescriptor的FindAction方法實現的基於當前Controller上下文的Action方法的選擇。如下麵的代碼片斷所示,ActionMethodSelectorAttribute是一個抽象類型,其唯一的抽象方法IsValidForRequest用於判斷目標Action方法是否與當前請求(即指定的Controller上下文)相匹配。

   1: [AttributeUsage(AttributeTargets.Method, AllowMultiple = false,  Inherited = true)]
   2: public abstract class ActionMethodSelectorAttribute : Attribute
   3: {
   4:     public abstract bool IsValidForRequest(ControllerContext controllerContext,  MethodInfo methodInfo);
   5: }

在ASP.NET MVC應用編程接口中定義了如下四個基於HTTP方法(GET、POST、PUT和DELETE)的ActionMethodSelectorAttribute,當我們將它們應用到某個Action方法上時,隻有在當前請求的HTTP方法與之相匹配的情況下目標Action方法才會被選擇。

  • System.Web.Mvc.HttpGetAttribute
  • System.Web.Mvc.HttpPostAttribute
  • System.Web.Mvc.HttpPutAttribute
  • System.Web.Mvc.HttpDeleteAttribute

除了上麵四個基於某種HTTP方法的ActionMethodSelectorAttribute特性之外,還定義了一個AcceptVerbsAttribute特性。AcceptVerbsAttribute的不同之處在於它可以動態地指定一個或者多個匹配的HTTP方法。如下麵的的代碼片斷所示,AcceptVerbsAttribute具有一個字符串集合類型的隻讀屬性Verbs,用於表示目標Action方法支持的HTTP方法(HTTP Method又被稱為HTTP Verb),該屬性在構造函數中被初始化。

   1: [AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
   2: public sealed class AcceptVerbsAttribute : ActionMethodSelectorAttribute
   3: {    
   4:     public AcceptVerbsAttribute(HttpVerbs verbs);
   5:     public AcceptVerbsAttribute(params string[] verbs); 
   6:   
   7:     public override bool IsValidForRequest(ControllerContext controllerContext,  MethodInfo methodInfo);
   8:     public ICollection<string> Verbs { get;  }
   9: }
  10:  
  11: [Flags]
  12: public enum HttpVerbs
  13: {
  14:     Get     = 1,
  15:     Post    = 2,
  16:     Put     = 4,
  17:     Delete  = 8,
  18:     Head    = 16,
  19: }

從上麵的代碼片斷可以看出AcceptVerbsAttribute具有兩個構造函數,其參數類型分別是HttpVerbs枚舉和字符串數組,由於AcceptVerbsAttribute枚舉應用了FlagsAttribute特性,我們可以使用操作符“|”指定多個HTTP方法。如下所示的兩種應用AcceptVerbsAttribute的方式是等效的。順便提一下,通過字符串指定的HTTP方式是不區分大小寫的;實際上述的四個ActionMethodSelectorAttribute在內部使用了AcceptVerbsAttribute特性實現了具體的Action方法選擇邏輯。

   1: //使用HttpVerbs枚舉表示HTTP方法
   2: public class ContactController
   3: {
   4:     [AcceptVerbs(HttpVerbs.Put|HttpVerbs.Post|HttpVerbs.Delete)]
   5:     public ActionResult UpdateContact(Contact contact)
   6:     { 
   7:         //省略實現
   8:     }
   9: }
  10:  
  11: //使用字符串表示HTTP方法
  12: public class ContactController
  13: {
  14:     [AcceptVerbs("PUT","POST","DELETE")]
  15:     public ActionResult UpdateContact(Contact contact)
  16:     {
  17:         //省略實現
  18:     }
  19: }

除了上麵5個基於HTTP方法的ActionMethodSelectorAttribute特性之外,還具有另一個具有如下定義的NonActionAttribute特性。顧名思義,應用了NonActionAttribute特性的方法將不會被認為是一個Action方法,所以在根據請求進行目標Action方法選擇 的時候,這樣的方法總是被排除在候選範圍之內,所以IsValidForRequest方法直接返回False。

   1: [AttributeUsage(AttributeTargets.Method, AllowMultiple = false,  Inherited = true)]
   2: public sealed class NonActionAttribute : ActionMethodSelectorAttribute
   3: {    
   4:     public override bool IsValidForRequest(ControllerContext controllerContext,  MethodInfo methodInfo)
   5:     {
   6:         return false;
   7:     }
   8: }

異步的ReflectedControllerDescriptor由ReflectedAsyncActionDescriptor類型表示。它用於描述以XxxAsync/XxxCompleted方式定義的異步Action方法,所以一個ReflectedAsyncActionDescriptor對象通過代表著兩個方法的MethodInfo對象來創建。如下麵的代碼片斷所示,ReflectedAsyncActionDescriptor的構造的參數asyncMethodInfo和completedMethodInfo就代碼這兩個MethodInfo。在構造函數中初始化的這兩個MethodInfo對象愛分別通過隻讀屬性AsyncMethodInfo和CompletedMethodInfo返回。

   1: public class ReflectedAsyncActionDescriptor : AsyncActionDescriptor
   2: {    
   3:     public ReflectedAsyncActionDescriptor(MethodInfo asyncMethodInfo,  MethodInfo completedMethodInfo, string actionName,  ControllerDescriptor controllerDescriptor);
   4:     
   5:     public override IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state);
   6:     public override object EndExecute(IAsyncResult asyncResult);
   7:  
   8:     public override object[] GetCustomAttributes(bool inherit);
   9:     public override object[] GetCustomAttributes(Type attributeType, bool inherit);
  10:     public override IEnumerable<FilterAttribute> GetFilterAttributes( bool useCache);
  11:     public override ParameterDescriptor[] GetParameters();
  12:     public override ICollection<ActionSelector> GetSelectors();
  13:     public override bool IsDefined(Type attributeType, bool inherit);
  14:  
  15:     public override string ActionName { get; }
  16:     public MethodInfo AsyncMethodInfo {  get;  }
  17:     public MethodInfo CompletedMethodInfo {  get; }
  18:     public override ControllerDescriptor ControllerDescriptor { get; }
  19:     public override string UniqueId { get; }
  20: }

ReflectedAsyncActionDescriptor方法中用於相關特性(定義在ICustomAttributeProvider接口中的三個方法,用於獲取篩選器特性列表的GetFilterAttributes方法以及GetSelectors方法對ActionMethodSelectorAttribute特性的解析)和參數描述(GetParameters方法)都是通過針對XxxAsync方法(即AsyncMethodInfo屬性)的反射實現的。實現的BeginExecute/EndExecute最終對AsyncMethodInfo和CompletedMethodInfo的調用實現了對Action方法的異步執行。

異步Action除了以配對的XxxAsync/XxxCompleted方法進行定義之外,還可以通過一個返回類型為Task的方法來定義,基於後者的Action描述通過類型TaskAsyncActionDescriptor表示。如下麵的代碼片斷所示,TaskAsyncActionDescriptor具有一個名為TaskMethodInfo的隻讀屬性,正是表示的這個基於Task的方法,該屬性在構造函數中初始化。

   1: public class TaskAsyncActionDescriptor : AsyncActionDescriptor
   2: {   
   3:     public TaskAsyncActionDescriptor(MethodInfo taskMethodInfo, string actionName, ControllerDescriptor controllerDescriptor);
   4:     
   5:     public override IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state);
   6:     public override object EndExecute(IAsyncResult asyncResult);
   7:     public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);
   8:  
   9:     public override object[] GetCustomAttributes(bool inherit);
  10:     public override object[] GetCustomAttributes(Type attributeType, bool inherit);
  11:     public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache);
  12:     public override ParameterDescriptor[] GetParameters();
  13:     public override ICollection<ActionSelector> GetSelectors();
  14:     public override bool IsDefined(Type attributeType, bool inherit);
  15:  
  16:     public override string ActionName { get; }
  17:     public override ControllerDescriptor ControllerDescriptor { get; }
  18:     public MethodInfo TaskMethodInfo { get; }
  19:     public override string UniqueId { get; }
  20: }

TaskAsyncActionDescriptor對於涉及到特性和參數描述的方法都是通過針對TaskMethodInfo的反射來完成的。用於實現對Action操作的異步執行的BeginExecute/EndExecute通過Action方法返回的Task對象來完成(BeginExecute執行Action方法得到並異步執行Task,EndExecute方法獲取Task執行的結果)。TaskAsyncActionDescriptor重寫了Execute方法並在其中直接拋出異常。

ASP.NET MVC三個重要的描述對象:ControllerDescriptor
ASP.NET MVC三個重要的描述對象:ActionDescriptor
ASP.NET MVC三個重要的描述對象:ControllerDescriptor與ActionDescriptor的創建機製
ASP.NET MVC三個重要的描述對象:ParameterDescriptor


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 11:34:34

  上一篇:go  ASP.NET MVC三個重要的描述對象:ControllerDescriptor
  下一篇:go  ASP.NET MVC三個重要的描述對象:ControllerDescriptor和ActionDescriptor的創建