下图显示了组建之间的基本控制流程
1.1控制器工厂、动作调用器
控制器工厂负责创建对请求进行服务的控制器实例
动作调用其负责查找并调用控制器类中的动作方法。
1.2自定义控制器工厂
namespace System.Web.Mvc{ // 摘要:定义控制器工厂所需的方法。 public interface IControllerFactory{ // 摘要:使用指定的请求上下文来创建指定的控制器。 // 参数:requestContext: // 请求上下文。 // controllerName: // 控制器的名称。 // 返回结果:控制器。 IController CreateController(RequestContext requestContext, string controllerName); // 摘要:获取控制器的会话行为。 // 参数: // requestContext: // 请求上下文。 // controllerName: // 你想要获取器其会话行为的控制器的名称。 // 返回结果: // 控制器的会话行为。 SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); // 摘要: // 释放指定的控制器。 // 参数: // controller: // 控制器。 void ReleaseController(IController controller); } }
public class CustomControllerFactory : IControllerFactory{ /// <summary> /// 创建能够对当前请求进行处理的控制器实例 /// </summary> public IController CreateController(RequestContext requestContext, string controllerName) { Type targetTtpe = null; switch (controllerName) { case "Product": targetTtpe = typeof(ProductController); break; case "Customer": targetTtpe = typeof(CustomerController); break; default: //默认情况下mvc会根据路由数据中controller的值来选择视图,而不是控制器名称 requestContext.RouteData.Values["controller"] = "Product"; targetTtpe = typeof(ProductController); break; } //通过依赖关系解析器(DependencyResolver)注册对象 return targetTtpe == null ? null : (IController)DependencyResolver.Current.GetService(targetTtpe); } public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) { //SessionStateBehavior枚举类型 //Default 使用默认 ASP.NET 逻辑来确定请求的会话状态行为 //Required 为请求启用完全的读写会话状态行为 //ReadOnly 为请求启用只读会话状态。 //Disabled 未启用会话状态来处理请求。 return SessionStateBehavior.Default; } public void ReleaseController(IController controller) { IDisposable disposable = controller as IDisposable; if (disposable != null) { disposable.Dispose(); } } }
这个接口中最重要的是CreateController(),当MVC框架需要控制器对请求进行服务时,便会调用这个方法。该方法的一个参数是RequestContext对象,它让工厂能够检测请求的细节;另一个参数是字符串,它包含了从路由的URL哪里所得到的controller的值
RequestContext属性
名称 |
类型 |
描述 |
HttpContext |
HttpContextBase |
提供关于HTTP请求信息 |
RouteData |
RouteData |
提供与请求匹配的路由信息 |
不建议这样创建自定义控制器的原意之一是,在Web应用程序中查找控制器类并对他们实例化是复杂的,且有潜在风险的eg,消除不同命名空间中同名类之间的歧义、构造函数异常。
默认情况下MVC框架会根据路由数据中controller的值来选择视图,而不是控制器类的名称(如果请求时一个不正确的URL,则路由数据中controller的值所指的就可能是一个不存在的控制器)。
1.3使用内建的控制器工厂--DefaultControllerFactory
该类维护者应用程序中这些类的一个列表,因此,一个请求到达时,它并不需要每次都执行一个搜索(不需要每次都通过搜索来简历这个控制器类的列表)。如果找到一个合适的类,便用控制器激活器(ControllerActivator)创建一个实例。
注意:遵循约定由于配置模式
定制DefaultControllerFactory的控制器实例化
①使用依赖性注解器:通过第三方的Ninject,NinjectDependencyResolver类实现IDependencyResolver接口以提供Ninject的DI支持。
②使用控制器激活器:使用控制器激活器(Controller Activator)的办法将DI引入到控制器中。实现IControllerActivator接口创建激活器
namespace System.Web.Mvc{ // 摘要:对使用依赖关系注入来实例化控制器的方式进行精细控制。 public interface IControllerActivator{ // 摘要:在类中实现时创建控制器。 // 参数: // requestContext: // 请求上下文。 // controllerType: // 控制器类型。 // 返回结果: // 创建的控制器。 IController Create(RequestContext requestContext, Type controllerType); } }
实现IControllerActivator
public class CustomControllerActivator : IControllerActivator { public IController Create(RequestContext requestContext, Type controllerType) { if (controllerType == typeof(ProductController)) { controllerType = typeof(CustomerController); } return (IController)DependencyResolver.Current.GetService(controllerType); } }
此伪代码很简单,如果请求的是ProductController将以CustomerController类的实例作为响应。仅用于演示了如何利用IControllerActivator接口在控制器工厂和依赖性解析器之间截取请求。
为了使用自定义激活器,需要为构造器传递一个实现类的实例并在application_start()中注册
protected void Application_Start(){ AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new CustomControllerActivator())); }
③重写DefaultControllerFactory()
1.4创建自定义动作调用器
控制器控场创建了一个控制器类的实例,框架就需要一种办法来调用这个实例上的一个动作。如果控制器是通过Controller类派生而来的,那么将由动作调用其(Action Invoker)调用动作。
备注:如果直接通过IController创建控制器,则要自己去负责执行动作
namespace System.Web.Mvc{ public interface IActionInvoker{ // 摘要: // 使用指定的控制器上下文来调用指定动作。 // 参数: // controllerContext: // 控制器上下文。 // actionName: // 操作的名称。 // 返回结果: // 如果找到了指定动作,则为 true;否则为 false。 bool InvokeAction(ControllerContext controllerContext, string actionName); } }
注意:注释中的动作对应上边的动作,动作与动作方法不同。
动作时一种行为
动作方法时实现这种行为的代码
动作调用其的作用是实现对一个动作的调用,而控制器中才是实现这个动作的动作方法。
即动作名与方法名默认情况下对应,但也可以让他们不同。
一个自定义动作调用器
public class CustomActionInvoker : IActionInvoker{ public bool InvokeAction(ControllerContext controllerContext, string actionName){ if (actionName == "Index"){ controllerContext.HttpContext.Response.Write("This is output from the Index Action"); return true; } else { return false; } } }
这个动作调用器并不关心控制器类中的方法。他自己处理动作。如果true则Response.Write如果false则404
与控制器相关联的动作调用器是通过Controller.ActionInvoker属性获得的。
namespace WebApplication1.Controllers{ public class ActionInvokerController : Controller{ public ActionInvokerController() { this.ActionInvoker = new CustomActionInvoker(); } } }
在这个控制器中没有动作方法,它依靠动作调用器去处理请求并导航到ActionInvoker/Index。
备注:不建议用户实现自己的动作调用器。缺点在于缺乏可扩展性、贫乏的职责分离、而且缺乏对各种视图的支持
1.5内建动作调用器--ControllerActionInvoker
默认情况下,ControllerActionInvoker查找一个具有与请求动作同名的方法。Eg,如果路由系统产生的action值为Index那么ControllerActionInvoker将查找符合动作条件的名称为Index的方法。
1.5.1自定义动作名
ActionName特性;注意如果使用这个特性创建的视图名应与ActionName的名相同否则路由不到。
1.5.2动作方法选择
[HttpPost]
[HttpGet]
[NonAction]:不会被暴露成一个外部可用的动作
1.5.3自定义动作方法选择器
namespace System.Web.Mvc{ // 表示一个特性,该特性用于影响操作方法的选择。 public abstract class ActionMethodSelectorAttribute : Attribute { // 摘要: // 确定操作方法选择对指定的控制器上下文是否有效。 // 参数: // controllerContext: // 控制器上下文。 // methodInfo: // 获取运用了选择器的方法信息 // 返回结果: // 如果操作方法选择对指定的控制器上下文有效,则为 true;否则为 false。 public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo); } }
自定义方法选择器
/// <summary> /// 是否是本地请求 /// </summary> public class LocalAttribute : ActionMethodSelectorAttribute { public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { return controllerContext.HttpContext.Request.IsLocal; } }
1.5.4处理位置动作
如果动作调用其找不到一个要调用的动作方法,便从他的InvokeAction方法返回false。当这种请求发生时,Controller类会调用它的HandleUnknownActiopn()。默认情况下,这个方法将一个404返回给浏览器。
此时如果想做一些特殊的事情,可以在控制器类中重写HandleUnknownAction()
public class HomeController : Controller { protected override void HandleUnknownAction(string actionName) { Response.Write("aaaaaaaaaaaaaaa"); } }
1.6用特殊控制器改善性能
MVC框架提供两种可以改善MVC web应用程序性能的特殊控制器。
1.6.1使用无会话控制器
默认情况下,控制器支持会话状态,这可以用来跨请求地存取数据值,使MVC程序员的工作更轻松。创建和维护会话状态是一个棘手的过程,必须对数据进行存储和接收,且必须对会话进行管理,以使他们能适当地终止。会话数据会消耗服务器内存或存储单元空间,而且多个web服务器之间的数据同步的需求,使得在运行一个大型应用程序的多个服务器上运行应用程序更加困难。
为了简化会话状态,asp.net对一个给定的会话在某一时刻只处理一个查询。如果客户端形成多个重叠请求,他们将被排成队列。并由服务器依序处理。好处:不需要担忧多个请求对同一暑假进行修改的情况,缺点:得不到所希望的请求吞吐量。
并非所有控制器都需要这种会话状态特性。在这种情况下,能够改善应用程序的性能,而又避免了棘手的会话状态维护工作。这可以通过无会话控制器来实现。它与规则控制器一样,但有两个方面不同:在把他们用于处理一个请求时,MVC框架不加载或不存储会话状态;重叠请求可以同时处理。
①在自定义IControllerFactory中管理会话状态:
在GetControllerSessionBehavior()中会返回SessionStateBehavior枚举值用于管理会话状态
②DefaultControllerFactory管理会话状态:
当使用内建的控制器工厂时,可以将SessionState注解属性运用于每个控制器类,以便对控制器的会话状态进行控制
1.6.2使用异步控制器
核心ASP.NET平台维护着一个用来处理客户端请求的.NET线程池--工作线程池,而这些县城叫做工作线程。当接收到一个请求时,将占用线程池中的一个工作线程,以进行这个请求的处理工作。当请求处理完毕后,该工作线程被返回给线程池,以便用于新请求的处理。对ASP.NET应用程序使用线程池有两个关键好处:
①通过重用工作线程,避免了每次处理一个请求时,都要new一个新线程的开销
②通过具有固定数目的可用工作线程,避免了超出服务器处理能力的并发请求情况。
在请求可以被短时间处理完毕的情况下,工作线程池会工作的最好。这也是大多MVC应用程序的情况。但如果有一些依赖于其他服务器,且占用较长时间才能完成的动作,那么可能会遇到所有工作线程都别绑定于等待其他系统完成其工作的情况。
此刻服务器有能力做更多的工作,毕竟这只是在等待,只占用很少资源,但因为所有线程都被绑定,传入的请求都被排成队列。这会陷入应用程序处理停顿,而服务器大片限制的状态。
该问题的解决方案是使用异步控制器。这会提高应用程序的整体性能,但并不利于执行异步操作
备注:异步控制器只能对占用I/O或占用带宽而非CPU密集型的动作有用。
CPU密集型:需要CPU高负荷运转(占用较多内存,执行大量处理)才能完成的动作
异步控制器视图解决的问题应当是线程池与所处理的请求类型之间搭配不当的情况。线程池意在确保每个请求得到一片服务器资源,但很可能最终停滞于一组无所事事的工作线程上。
如果对CPU密集型动作使用额外的后台线程,那么会因为涉及太多的并发请求而消弱服务器资源
创建异步控制器
创建异步控制器有两种方法:
①IAsyncController接口这与IController对等的异步接口,缺点显而易见。
②System.Web.Mvc.AsyncController对控制器进行派
public class RemoteDataController : AsyncController { // 在控制器中使用异步方法 public async Task<ActionResult> Data() { string data=await Task<string>.Factory.StartNew(() => { return new RemoteService().GetRemoteData(); }); return View((object)data); } //在后台类中实现异步方法 public async Task<ActionResult> ConsumeAsyncMethod() { string data = await new RemoteService().GetRemoteDataAsync(); return View("Data", (Object)data); } }
RemoteService类
public class RemoteService { public string GetRemoteData() { Thread.Sleep(5000); return "Hello Word"; } public async Task<string> GetRemoteDataAsync() { return await Task<string>.Factory.StartNew( () => { Thread.Sleep(5000); return "Hello word"; }); } }
两种方法效果一样,只是实现的异步方法位置不同