调用Action的流程
IIS 接收到一个http请求,进入w3wp进程(如果是webgarden,先找到一个压力小的的w3wp),找到applicationpool,进入global.asax,进入路由,从controllerfactory找到一个controller,如果我们用了默认的controller,会解析出action名称和参数调用action(也可以手动直接在controller处理掉请求),返回并执行result,如图:
自定义controller factory
在上一章节,介绍了如果customize 一个controller(实现IController接口),现在介绍一个如何customizecontroller factory。
接口:
public interface IControllerFactory { IController CreateController(RequestContextrequestContext, string controllerName); SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); void ReleaseController(IControllercontroller); }
说明:
CreateController : 接收request context和controller name(不包含”Controller”后缀),返回一个IController对象,用于创建Controller对象
GetControllerSessionBehavior:参数同上,返回一个SessionStateBehavior,控制创建出的controller的sessionstate对象
ReleaseController:
如果controller包含了一些需要手动释放的资源,可以在这个函数里统一释放
代码示例:
public class CustControllerFactory : IControllerFactory { private string_nsFind; private string_assembly; public CustControllerFactory(string namespaceFind, string assemblyName) { if(namespaceFind.Last() == ‘.‘) namespaceFind =namespaceFind.Remove(namespaceFind.Length - 1); _nsFind =namespaceFind; _assembly= assemblyName; } public IController CreateController(RequestContext requestContext, string controllerName) { var fullName = _nsFind + "." + controllerName + "Controller"; var type =GetType().Assembly.GetTypes().FirstOrDefault(t => t.FullName == fullName); if (type== null) { return new TestController(); } return (IController)Activator.CreateInstance(_assembly, fullName).Unwrap(); } public SessionStateBehavior GetControllerSessionBehavior(RequestContextrequestContext, string controllerName) { return SessionStateBehavior.Default; } public void ReleaseController(IController controller) { var disposable = controller as IDisposable; if(disposable != null) { disposable.Dispose(); } } }
代码说明:factory接收一个命名空间名称和assembly名称,在指定的范围内检索controller对象通过反射动态创建对象,如果找不到,返回一个默认的controller。
下一步,在global文件中注册factory:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ControllerBuilder.Current.SetControllerFactory(new CustControllerFactory("MVCRouteStudy.Controllers","MVCRouteStudy")); }
可以看到,在最后一行,我注册了我手动创建的factory。
测试一下这个factory,先访问一个不存在controller:
访问一个存在的:
注意:此例只作为一个简单的实现,甚至没有捕捉异常。现实应用中,场景要复杂的多,自定义的controller工厂在创建controller对象时,一般不仅仅简单的创建一个实例,可能会考虑性能维护一个缓存,手动维护session的状态,异常控制,以及一些安全性的考虑等等。
创建工厂时拿到的两个对象:
HttpContext |
当前Http请求对象 |
RouteData |
和当前请求匹配的那条route信息 |
Controller重置:
当前请求的controller是可以被覆盖的,
requestContext.RouteData.Values["controller"] ="Test";
这样一来,MVC Framework就会去查找新的View并覆盖本来的View名称了。
使用自带的controllerfactory
如果不自己实现controller factory,默认都会使用MVC 自带的factory,但是有几点是需要注意的,为了确保controller会被factory正确找到并实例化:
- 必须public
- 不可以是抽象类
- 不能是泛型类
- 必须以 ”Controller” 为结尾
- 必须实现了IController接口(如果继承了Controller就不用了,只针对自定义Controller的情况)
优先查找命名空间
打开global文件,添加:
ControllerBuilder.Current.DefaultNamespaces.Add("MyControllerNamespace"); ControllerBuilder.Current.DefaultNamespaces.Add("MyProject.*");
代码目的很显然,希望MVC framework创建controller时候,添加的这些命名空间中被优先查找,注意最后传递了一个MyProject.*的SearchPattern,而不是正则表达式。另外,被添加的命名空间不会因为先后顺序导致被先后查找,他们的优先顺序是平行的(就像前面章节介绍的限制Route命名空间一样)。
Customize 默认ControllerFactory的行为
Controller Activator
对于一个工厂,最主要的职责就是创建对象,而在MVC Framework中,默认的工厂是支持自己实现创建过程的。
接口:
public interface IControllerActivator { IController Create(RequestContext requestContext, Type controllerType); }
参数:
RequestContext 对象:当前http请求的上下文信息,前面章节说了,这个对象包含的信息非常丰富,可以做很多事情(客户端ip,cookie,session,querystring,form, Files, Request ,Response等等)
Controller 类型:和刚才手动创建factory不同,这里我们直接得到了一个Type,这样就不用关心也不用反射出类型了,创建对象方便多了。
示例的实现:
public class CustomControllerActivator :IControllerActivator { public IController Create(RequestContext requestContext, Type controllerType) { if (controllerType == typeof(HomeController)) { controllerType = typeof(TestController); } return (IController)DependencyResolver.Current.GetService(controllerType); } }
代码说明:如果进来的是Home,重写为Test ,调用DependencyResolver创建并返回实例。
在global文件的Application_Start方法中配置一下:
ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new CustomControllerActivator()));
如果还不能满足需要,继承DefaultControllerFactory,选择的重写以下的函数一个或多个:
public override IController CreateController(RequestContextrequestContext, string controllerName) { return base.CreateController(requestContext, controllerName); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return base.GetControllerInstance(requestContext, controllerType); } protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType) { return base.GetControllerSessionBehavior(requestContext, controllerType); } protected override Type GetControllerType(RequestContext requestContext, string controllerName) { return base.GetControllerType(requestContext, controllerName); } public override void ReleaseController(IController controller) { base.ReleaseController(controller); }
Customize Action Invoker
我们知道,MVC 通过controller工厂找到并创建一个controller实例之后,下一步就是查找并调用action了,这一步也是支持customize的。
接口:
public interface IActionInvoker { bool InvokeAction(ControllerContext controllerContext,string actionName); }
示例实现:
public class CustomActionInvoker : IActionInvoker { public bool InvokeAction(ControllerContext controllerContext, string actionName) { if(actionName == "Index") { controllerContext.HttpContext. Response.Write("I‘d like over write all the Index ActionBehavior."); return true; } return false; } }
代码说明:重写所有名称为index的action的行为。
在Controller中指向这个actioninvoker:
public TestController() { ActionInvoker = new CustomActionInvoker(); }
查看结果:
发现TestController中的IndexAction的行为已经被重写了。
使用自带的ActionInvoker
- 方法必须是public
- 不支持static
- 方法不能和System.Web.Mvc.Controller中的函数重名
- 不支持泛型
Customize Action的名字
可以使用[ActionName] 来指定Action的名称:
[ActionName("Enumerate")] public ViewResult List() { return View("Result",new Result { ControllerName ="Customer", ActionName = "List" }); } }
注意:一旦改了名字,旧的名字将被覆盖,意味将不会被找到,如果访问将返回404。
如果有场景需要同样的Action名称出现多次在同一个Controller,例如,支持httppost和httpget的行为想要分在不同的action里面,那么可以使用ActionName属性:
[ActionName("Index2")] [HttpPost] public ActionResult Index1() { return Content("FromPost"); } [ActionName("Index")] [HttpGet] public ActionResult Index2() { return Content("FromGet"); }
阻止一个Action被访问
[NonAction] public ActionResult MyAction() { return View(); }
访问这个Action,会返回404
Customize Action Selector
可以自定义Selector,来决定这个Action是否被select
抽象类:
[AttributeUsage(AttributeTargets.Method,AllowMultiple = false, Inherited = true)] public abstract class ActionMethodSelectorAttribute : Attribute { public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo); }
说明:
拿到的是ControllerContext和MethodInfo
ControllerContext中包含了http请求的信息
MethodInfo可以取的方法信息
示例实现:
public class LocalAttribute :ActionMethodSelectorAttribute { public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { return controllerContext.HttpContext.Request.IsLocal; } }
代码目的很明确,限制这个Action只能被本地的请求访问。
使用:
[Local] public ActionResult LocalIndex() { return Content("From Local "); }
控制unkownAction
如果本次请求向controller要一个不存在的action,可能希望返回到一个特殊的错误页面或者View,这时可以重写Controller中的HandleUnkownAction函数:
protected override void HandleUnknownAction(string actionName) { Response.Write(string.Format("You requested the {0} action", actionName)); }
查看结果:
Controller性能考虑
1. 考虑设置controller的session为SessionStateLess
如果没必要开启Session,可以考虑把一个Session状态关闭:
[SessionState(SessionStateBehavior.Disabled)] Public class FastController :Controller { Public ActionResult Index() { return View("Result",new Result { ControllerName = "Fast ",ActionName = "Index" }); } }
2. 考虑异步Controller
场景:和第三方系统交互,并且客户端不需要等待这个结果就可以进行当前的活动。可以结合使用TPL完成:
public class RemoteDataController : AsyncController{ public async Task<ActionResult> Data(){ string data = await Task<string>.Factory.StartNew(() => { return new RemoteService().GetRemoteData(); }); return View((object)data); } }
RemoteService模拟实现
public class RemoteService { public string GetRemoteData() { Thread.Sleep(2000); return "Hello from the other side of the world"; } }