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


ASP.NET MVC三個重要的描述對象:ControllerDescriptor和ActionDescriptor的創建

不論是用於描述Controller的ControllerDescriptor,還是用於描述Action方法的ActionDescriptor,都具有同步和異步兩個版本,那麼這些不同類型的ControllerDescriptor的ActionDescriptor是在什麼情況下創建的呢?

ControllerDescriptor的創建設計到一個重要的名為ActionInvoker的組件,顧名思義,ActionInvoker專門用於Action方法的執行。我們會在本書第7章“Action方法的執行”中對ActionInvoker進行深入介紹,在這裏我們隻需要對其作一個簡單的了解。

ActionInvoker實現了具有如下定義的IActionInvoker接口,唯一的方法實現了對指定Action方法的執行,而作為Controller的默認基類的Controller具有一個ActionInvoker屬性,該屬性表示的ActionInvoker被真正用於定義在該Controller類型中的所有Action方法的執行。

   1: public interface IActionInvoker
   2: {
   3:     bool InvokeAction(ControllerContext controllerContext, string actionName);
   4: }
   5:  
   6: public abstract class Controller
   7: {   
   8:     //其它成員
   9:     public IActionInvoker ActionInvoker { get; set; }
  10: }

而具有如下定義的System.Web.Mvc.Async.IAsyncActionInvoker接口是ActionInvoker的異步版本。IAsyncActionInvoker繼承了IActionInvoker接口,並在此基礎上定義了兩個BeginInvokeAction/EndInvokeAction方法用於Action方法的異步執行。

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

ASP.NET MVC真正用於Action方法同步和異步執行的ActionInvoker分別是ControllerActionInvokerAsyncControllerActionInvoker。如下麵的代碼片斷所示,ControllerActionInvoker定義了一個受保護的方法GetControllerDescriptor用於根據指定的Controller上下文獲取相應的ControllerDescriptor,它的子類AsyncControllerActionInvoker對這個方法進行了重寫。

   1: public class ControllerActionInvoker : IActionInvoker
   2: {
   3:     //其它成員
   4:     protected virtual ControllerDescriptor GetControllerDescriptor( ControllerContext controllerContext);
   5: }
   6:  
   7: public class AsyncControllerActionInvoker : ControllerActionInvoker,  IAsyncActionInvoker, IActionInvoker
   8: {
   9:     //其它成員
  10:     protected override ControllerDescriptor GetControllerDescriptor( ControllerContext controllerContext);
  11: }

我們所有要了解的是在默認情況下(沒有對Controller類型的ActionInvoker屬性進行顯式設置)采用的ActionInvoker類型是哪個。ASP.NET MVC對Conroller采用的ActionInvoker類型的選擇機製是這樣的:

在默認的情況下,當前的DependencyResolver直接通過對指定的類型進行反射來提供對應的實例對象,所以對於前麵兩個步驟返回的對象均為Null,所以默認創建出來的ActionInvoker類型為AsyncControllerActionInvoker。我們可以通過如下一個簡單的實例來驗證這一點。在通過Visual Studio的ASP.NET MVC項目模板創建的空Web應用中,我們創建了如下一個默認的HomeController,在Action方法Index中直接通過ContentResult將ActionInvoker屬性的類型名稱呈現出來。

   1: public class HomeController : Controller
   2: {  
   3:     public ActionResult Index()
   4:     {
   5:         return Content("默認ActionInvoker類型:" + this.ActionInvoker.GetType().FullName);
   6:     }
   7: }   

當運行該Web應用時,會在瀏覽器上產生如下的輸出結果,我們可以清楚地看到默認采用的ActionInvoker類型正是AsyncControllerActionInvoker。

   1: 默認ActionInvoker類型:System.Web.Mvc.Async.AsyncControllerActionInvoker

為了進一步驗證基於DependencyResolver對ActionInvoker的提供機製,我們將在《ASP.NET MVC Controller激活係統詳解:IoC的應用[下篇]》創建的基於Ninject的自定義NinjectDependencyResolver應用在這裏。如下麵的代碼片斷所示,在初始化NinjectDependencyResolver的時候,我們將IActionInvoker和IAsyncActionInvoker影射到兩個自定義ActionInvoker類型,即FooActionInvoker和FooAsyncActionInvoker,它們分別繼承自ControllerActionInvoker和AsyncControllerActionInvoker。

   1: public class NinjectDependencyResolver : IDependencyResolver
   2: {
   3:     public IKernel Kernel { get; private set; }
   4:     public NinjectDependencyResolver()
   5:     {
   6:         this.Kernel = new StandardKernel();
   7:         AddBindings();
   8:     }
   9:     private void AddBindings()
  10:     {
  11:         this.Kernel.Bind<IActionInvoker>().To<FooActionInvoker>();
  12:         this.Kernel.Bind<IAsyncActionInvoker>().To<FooAsyncActionInvoker>();
  13:     }
  14:     public object GetService(Type serviceType)
  15:     {
  16:         return this.Kernel.TryGet(serviceType);
  17:     }
  18:     public IEnumerable<object> GetServices(Type serviceType)
  19:     {
  20:         return this.Kernel.GetAll(serviceType);
  21:     }
  22: }
  23: public class FooActionInvoker : ControllerActionInvoker
  24: {}
  25: public class FooAsyncActionInvoker : AsyncControllerActionInvoker
  26: {}

在Global.asax中對NinjectDependencyResolver進行注冊後運行我們的程序,會在瀏覽器中得到如下的輸出結果。IAsyncActionInvoker和FooAsyncActionInvoker進行了影射,NinjectDependencyResolver可以通過IAsyncActionInvoker提供一個FooAsyncActionInvoker實例。

   1: 默認ActionInvoker類型:Artech.Mvc.FooAsyncActionInvoker

現在我們對NinjectDependencyResolver的定義稍加修改,將針對IAsyncActionInvoker接口的類型影射刪除,隻保留針對IActionInvoker的映射。

   1: public class NinjectDependencyResolver : IDependencyResolver
   2: {
   3:     //其它成員
   4:     private void AddBindings()
   5:     {
   6:         this.Kernel.Bind<IActionInvoker>().To<FooActionInvoker>();
   7:         //this.Kernel.Bind<IAsyncActionInvoker>().To<FooAsyncActionInvoker>();
   8:     }
   9: }

再次運行我們的程序則會得到如下的輸出結果。由於NinjectDependencyResolver隻能通過IActionInvoker接口提供具體的ActionInvoker,所以最終被創建的是一個FooActionInvoker對象。這個實例演示告訴我們:當我們需要使用到自定義的ActionInvoker的時候,可以通過自定義DependencyResolver以IoC的方式提供具體的ActionInvoker實例。

   1: 默認ActionInvoker類型:Artech.Mvc.FooActionInvoker

ControllerDescriptor和ActionDescriptor最終是為Model綁定服務的,而Model綁定是Action執行的一個環節,所以ControllerDescriptor最終是由相應的ActionInvoker創建的。總的來說,。ActionInvoker和ControllerDescriptor之間的關係可以通過如下圖所示的UML來表示。

image

對於ReflectedControllerDescriptor來說,包含其中的ActionDescriptor類型均為ReflectedActionDescriptor。而ReflectedAsyncControllerDescriptor描述的Controller可以同時包含同步和異步的Action方法,所以它可以包含任何類型的ActionDescriptor。

具體來說,。;如果對應Completed方法不存在,對應的FindAction方法會直接拋出一個InvalidOperationException異常。在其它情況下的Action方法均是同步的,所以對應的ActionDescriptor類型為ReflectedActionDescriptor。ControllerDescriptor與ActionDescriptor之間的關係如下圖所示的UML來表示。

image

為了讓讀者對ActionInvoker對ControllerDescriptor的解析機製具有一個深刻的理解,同時也作為對該機製的驗證,我們做一個簡單的實例演示。通過前麵的介紹我們知道在默認的情況下Controller采用AsyncControllerActionInvoker進行Action方法的執行,這個例子就來演示一下它生成的ControllerDescriptor是個怎樣的對象。我們通過Visual Studio的ASP.NET MVC項目模板創建一個空Web應用,並創建一個默認的HomeController,然後對其進行如下的修改。

   1: public class HomeController : AsyncController
   2: {
   3:     public void Index()
   4:     {
   5:         MethodInfo method = typeof(AsyncControllerActionInvoker).GetMethod("GetControllerDescriptor", BindingFlags.Instance | BindingFlags.NonPublic);
   6:         ControllerDescriptor controllerDescriptor = (ControllerDescriptor)method.Invoke(this.ActionInvoker, new object[] { this.ControllerContext });
   7:         Response.Write(controllerDescriptor.GetType().FullName + "<br/>");
   8:         CheckAction(controllerDescriptor, "Foo");
   9:         CheckAction(controllerDescriptor, "Bar");
  10:         CheckAction(controllerDescriptor, "Baz");
  11:  
  12:     }
  13:     private void CheckAction(ControllerDescriptor controllerDescriptor, string actionName)
  14:     {
  15:         ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(this.ControllerContext, actionName);
  16:         Response.Write(string.Format("{0}:{1}<br/>",actionName, actionDescriptor.GetType().FullName));
  17:     }
  18:  
  19:     public void Foo() { }
  20:     public void BarAsync() { }
  21:     public void BarCompleted() { }
  22:     public Task Baz()
  23:     {
  24:         return new Task(DoNothing);
  25:     }
  26:     public void DoNothing() { }
  27: }

我們首先將HomeController的基類從Controller改為AsyncController,並定義了Foo、BarAsync/BarCompleted和Baz四個方法,我們知道它們對應著Foo、Bar和Baz三個Action,其中Foo是同步Action,Bar和Baz分別是兩種不同定義形式(XxxAsync/XxxCompleted和Task)的異步Action。

CheckAction用於根據指定的Action名稱從ControllerDescriptor對象中獲取用於表示對應Action的ActionDescriptor對象,最終將類型名稱呈現出來。在Index方法中,我們通過反射的方式調用當前ActionInvoker(一個AsyncControllerActionInvoker對象)的受保護方法GetControllerDescriptor或者用於描述當前Controller(HomeController)的ControllerDescriptor的對象,並將類型名稱呈現出來。最後通過調用CheckAction方法將包含在創建的ControllerDescriptor對象的三個ActionDescriptor類型呈現出來。

當我們運行該程序的時候,在瀏覽器中會產生如下的輸出結果,從中可以看出ControllerDescriptor類型為ReflectedAsyncControllerDescriptor。同步方法Foo對象的ActionDescriptor是一個ReflectedActionDescriptor對象;以XxxAsync/XxxCompleted形式定義的異步方法Bar對應的ActionDescriptor是一個ReflectedAsyncActionDescriptor對象;而返回類型為Task的方法Baz對應的ActionDescriptor類型則是TaskAsyncActionDescriptor。

   1: System.Web.Mvc.Async.ReflectedAsyncControllerDescriptor
   2: Foo:System.Web.Mvc.ReflectedActionDescriptor
   3: Bar:System.Web.Mvc.Async.ReflectedAsyncActionDescriptor
   4: Baz:System.Web.Mvc.Async.TaskAsyncActionDescriptor

 

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:32

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