上节:ASP.NET MVC路由机制扩展及Area原理-MVC原理系列4中,我们讲解了ASP.NET MVC 是如何实现Area机制的及MVC自定义扩展路由机制。
本节,我们将MvcRouteHandler,即MV框架入口处,开展解说,且看下文:
MvcRouteHandler的实现仅仅是通过GetHttpHandler方法返回一个MvcHandler实例,MvcHandler从RouteData中获得controller名字负责创建一个ControllerBuilder的实例,并通过ControllerBuilder的GetControllerFactory返回一个IControllerFactory的实例,这个实例就是DefaultControllerFactory,它的 CreateController方法负责创建需要的Controller实例。下面这段代码来自MvcHandler:
2 | string controllerName = RequestContext.RouteData.GetRequiredString( "controller" ); |
4 | factory = ControllerBuilder.GetControllerFactory(); |
5 | controller = factory.CreateController(RequestContext, controllerName); |
DefaultControllerFactory创建Controller分两步:
1 | Type controllerType = GetControllerType(requestContext, controllerName); |
2 | IController controller = GetControllerInstance(requestContext, controllerType); |
GetControllerInstance最终是这样创建controller的:
1 | return (IController)Activator.CreateInstance(controllerType); |
这就使得要使用DI设计模式十分困难。好在我们可以重写GetControllerInstance,并应用各种DI。受书中的影响,个人使用的是Ninject。
另外,为了加快GetControllerType方法,DefaultControllerFactory内部运用缓存机制将当前程序和所有引用程序集的类型缓存成哈希表。
还可以通过如下方法,提供某些名字空间较高的优先级来优化搜索。
1 | protected void Application_Start() |
3 | RegisterRoutes(RouteTable.Routes); |
4 | ControllerBuilder.Current.DefaultNamespaces.Add( "MyApp.Controllers.*" ); |
5 | ControllerBuilder.Current.DefaultNamespaces.Add( "OtherAssembly.MyNamespace.*" ); |
上面这种设置名字空间的优先级仍然低于在设置路由表时为某个路由指定的名字空间,Areas机制就是靠在设置路由表时指定名字空间实现的,详见深入理解ASP.NET MVC(4)。
也可以在ControllerBuilder层面上替换ControllerFactory:
1 | protected void Application_Start() |
3 | RegisterRoutes(RouteTable.Routes); |
4 | ControllerBuilder.Current.SetControllerFactory( new MyControllerFactory()); |
MvcHandler依靠ControllerBuilder和IControllerFactory获得Controller的实例后,调用Controller实例的Execute方法,在该方法返回后再调用IControllerFactory的ReleaseController收尾。由此看出Controller的Execute做了所有的事情,看似“功能强大”,其实它只是.NET的普通类, MVC框架赋予其“非凡”的能力:
所有的Controller只要实现一个接口,IController,接口的定义了Execute方法:
1 | namespace System.Web.Mvc { |
2 | using System.Web.Routing; |
4 | public interface IController { |
5 | void Execute(RequestContext requestContext); |
从接口定义可以看出,当Controller被“调用”的时候,应该负责完成Execute方法,参数RequestContext封装了HttpContext,所以可以像下面这样直接实现一个Controller,同样可以工作:
1 | public class HelloWorldController : IController |
3 | public void Execute(RequestContext requestContext) |
5 | requestContext.HttpContext.Response.Write( "Hello, world!" ); |
然而,需要的逻辑和架构远比这个复杂的多,因此MVC框架提供了如下类关系:
IController->ControllerBase->Controller
其中ControllerBase实现Execute,Execute在内部调用ExecuteCore,ExecuteCore作为一个抽象方法,延迟到Controller中实现。ControllerBase只提供了诸如TempData、ViewData等,Controller的ExecuteCore方法真正invoke了action机制,是action的入口。下面的代码是ExecuteCore的实现:
01 | PossiblyLoadTempData(); |
03 | string actionName = RouteData.GetRequiredString( "action" ); |
04 | if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { |
05 | HandleUnknownAction(actionName); |
09 | PossiblySaveTempData(); |
可以看到RouteData在这里又提供了action参数,可以想象InvokeAction方法依靠这个action的名字调用action,并实现诸多验证机制。下节开始讨论action逻辑。
本节:ASP.NET MVC Controller创建及调用内幕-MVC原理系列5,到此收笔,谢谢欣赏。