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分別是ControllerActionInvoker和AsyncControllerActionInvoker。如下麵的代碼片斷所示,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來表示。
對於ReflectedControllerDescriptor來說,包含其中的ActionDescriptor類型均為ReflectedActionDescriptor。而ReflectedAsyncControllerDescriptor描述的Controller可以同時包含同步和異步的Action方法,所以它可以包含任何類型的ActionDescriptor。
具體來說,。;如果對應Completed方法不存在,對應的FindAction方法會直接拋出一個InvalidOperationException異常。在其它情況下的Action方法均是同步的,所以對應的ActionDescriptor類型為ReflectedActionDescriptor。ControllerDescriptor與ActionDescriptor之間的關係如下圖所示的UML來表示。
為了讓讀者對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