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


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

一、ControllerActionInvoker與AsyncControllerActionInvoker

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以IAsyncActionInvoker接口去獲取注冊的ActionInvoker,如果返回對象不為Null,則將其作為默認的ActionInvoker
  • 通過當前的DependencyResolver以IActionInvoker接口去獲取注冊的ActionInvoker,如果返回對象不為Null,則將其作為默認的ActionInvoker
  • 創建AsyncControllerActionInvoker對象作為默認的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

二、ActionInvoker與ControllerDescriptor/ActionDescriptor

ControllerDescriptor和ActionDescriptor最終是為Model綁定服務的,而Model綁定是Action執行的一個環節,所以ControllerDescriptor最終是由相應的ActionInvoker創建的。總的來說,用於同步Action執行的ControllerActionInvoker創建ReflectedControllerDescriptor,而用於異步Action執行的AsyncControllerActionInvoker則創建ReflectedAsyncControllerDescriptor。ActionInvoker和ControllerDescriptor之間的關系可以通過如下圖所示的UML來表示。

image

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

具體來說,如果Action方法的返回類型為Task,對應的ActionDescriptor是一個TaskAsyncActionDescriptor對象如果Action方法名稱包含“Async”后綴,並且存在一個對應的以“Completed”后綴的方法,則對應的ActionDescriptor是一個ReflectedAsyncActionDescriptor對象;如果對應Completed方法不存在,對應的FindAction方法會直接拋出一個InvalidOperationException異常。在其它情況下的Action方法均是同步的,所以對應的ActionDescriptor類型為ReflectedActionDescriptor。ControllerDescriptor與ActionDescriptor之間的關系如下圖所示的UML來表示。

image

三、實例演示:AsyncActionInvoker對ControllerDescriptor的創建

為了讓讀者對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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM