Asp.Net MVC4 系列--进阶篇之Controller(2)

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


 

 

Asp.Net MVC4 系列--进阶篇之Controller(2),古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。