调用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";
}
}