在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
以下内容摘自:http://www.cnblogs.com/r01cn/archive/2012/02/19/2358753.html 感谢作者的翻译,这里只是译文。原书名:Pro ASP.NET MVC 3 Framework
第十四章 控制器可扩展性 PART1
本章第二部分演示两种类型的应用程序控制器,即无会话控制器和异步控制器。这些可以用来增强服务器的能力。我们演示如何生成和使用这些控制器类型,并解释你应该什么时候考虑在MVC应用程序中使用它们。 请求处理管道组件 图14-1显示了组件之间的基本控制流程。图中的有些元素你此刻应该是熟悉的。我们第11章讨论了路由系统,第12章我们描述了Controller类和动作方法之间的关系。
图14-1. 调用一个动作方法 本章第一部分我们关注的是控制器工厂(Controller Factory)和动作调用器(Action Invoker)。这些组件的名称暗示了它们的目的。控制器工厂负责生成对一个请求进行服务的控制器实例,动作调用器负责查找并调用控制器类中的动作方法。MVC框架含有这两个组件的默认实现,我们将向你演示如何配置它们,以控制它们的行为。我们也将向你演示如何完全替换这些组件而使用自定义逻辑。 生成一个控制器工厂 像MVC框架的大部分情况一样,要理解控制器工厂如何进行工作,最好的办法是生成一个自定义实现。我们不建议你在实际项目中这样做,因为,通过扩展内建的工厂,可以更容易地生成自定义行为。但这是演示MVC框架如何生成控制器实例的一种很好的办法。 定义一个自定义控制器工厂 控制器工厂是由IControllerFactory接口定义的,如清单14-1所示。 清单14-1. IControllerFactory接口
namespace System.Web.Mvc { using System.Web.Routing; using System.Web.SessionState; public interface IControllerFactory { IController CreateController(RequestContext requestContext, string controllerName); SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); void ReleaseController(IController controller); } } 这个接口中最重要的方法是CreateController,当MVC框架需要一个控制器对一个请求进行服务时,框架便会调用这个方法。该方法的参数是一个RequestContext对象,它允许该工厂检测该请求的细节;和一个字符串,它包含了通过路由的URL所得到的controller值。 我们不建议这样生成自定义控制器的原因之一是,查找web应用程序中的控制器类、并对它们实例化是复杂的。为了使我们的演示简单,我们将仅支持两个控制器,叫做FirstController和SecondController。作为CustomControllerFactory类的一部分,清单14-2演示了这个方法的实现。 清单14-2. CustomControllerFactory类
using System; using System.Web.Mvc; using System.Web.Routing; using System.Web.SessionState; using ControllerExtensibility.Controllers; namespace ControllerExtensibility.Infrastructure { public class CustomControllerFactory : IControllerFactory { public IController CreateController(RequestContext requestContext, string controllerName) { Type targetType = null; switch (controllerName) { case "Home": requestContext.RouteData.Values["controller"] = "First"; targetType = typeof(FirstController); break; case "First": targetType = typeof(FirstController); break; case "Second": targetType = typeof(SecondController); break; } return targetType == null ? null : (IController)Activator.CreateInstance(targetType); } public SessionStateBehavior GetControllerSessionBehavior( RequestContext requestContext, string controllerName) { return SessionStateBehavior.Default; } public void ReleaseController(IController controller) { IDisposable disposable = controller as IDisposable; if (disposable != null) { disposable.Dispose(); } } } } CreateController方法的目的,是生成一个能够对请求进行处理的控制器实例。控制器工厂如何做这种事是完全公开的。存在一些你在本书的例子中一直看到的约定,因为,这是默认控制器工厂的工作方式。完成我们这个自定义工厂之后,我们将讨论默认控制器工厂。对于清单14-2的工厂,我们忽略了所有这些约定,而是实现了我们自己的逻辑。这是所做的一件相当奇怪的事情,但它确实演示了MVC框架提供的充分的灵活性。 如果我们接收到controller值是First或Second的一个请求,我们就会生成FirstController或SecondController类的一个新实例。我们用System.Activator类生成实例,它让我们根据对象的类型生成对象的实例,像这样: (IController)Activator.CreateInstance(targetType); 如果我们收到controller值是Home的一个请求,我们把这个请求映射到FirstController类。同样,这是要做的一件奇怪的事情,但它表明请求与控制器之间的映射是控制器工厂自身的职责。我们无论如何不能把它说成是请求到视图的映射。 MVC框架基于路由数据中的controller值来选择视图,而不是基于控制器类的名字。例如,如果我们想把对Home控制器的请求映射到First控制器的一个实例,我们也需要修改请求中的controller值,像这样: requestContext.RouteData.Values["controller"] = "First"; 因此,不仅控制器工厂要独自地负责把请求匹配到控制器,而且它可以对请求进行修改,以改变请求处理管道中后继步骤的行为。这是MVC框架相当有力的要素和关键组件。 IControllerFactory接口中的另外两个方法是: · GetControllerSessionBehavior方法由MVC框架用来确定是否应该为控制器维护会话数据。我们将在本章稍后的“使用无会话控制器”小节中回到这一论题。 · ReleaseController方法,当不再需要CreateController方法生成的控制器对象时,调用这个方法。在我们的实现中,我们查看这个类是否实现IDisposable接口。如果是,我们调用Dispose方法以释放那些可以被释放的资源。 注册一个自定义控制器工厂 我们通过ControllerBuilder类来告诉MVC框架要使用我们的自定义控制器工厂,如清单14-3所示。 清单14-3. 注册一个自定义工厂
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory()); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } 用内建的控制器工厂进行工作 对于大多数应用程序,内建的控制器工厂,名为DefaultControllerFactory,是完全足够的。当它从路由系统接收到一个请求时,这个工厂查找路由数据,以找到controller属性的值(这是我们在第11章所描述的),并试图在这个web应用程序中找到满足以下条件的一个类: · 这个类必须是一个public类。 · 这个类必须是具体类(而不是抽象类)。 · 这个类必须没有泛型(generic)参数。 · 类名必须以Controller结尾。 · 这个类必须实现IController接口。 DefaultControllerFactory类维护着应用程序中这些类的一个列表,因此,每次一个请求到达时,它并不需要每次都执行一个搜索。如果找到一个合适的类,那么便用控制器激活器(controller activator)生成一个实例(我们将在马上要描述的“定制DefaultControllerFactory控制器的生成”小节中回到这一论题),控制器的工作便完成了。如果没有匹配的控制器,那么该请求便不能作进一步处理。 要注意DefaultControllerFactory类是如何遵循“约定优于配置”模式的。你不需要在配置文件中注册你的控制器,因为这个工厂会为你查找它们。你需要做的全部事情是,生成满足这个工厂查寻条件的类。 如果你想生成自定义控制器工厂的行为,你可以对默认工厂的设置进行配置,或重写它的一些方法。这样,你便能够建立有用的“约定优于配置”的行为,而不需要重新生成它。我们将在以下小节中向你演示定制控制器生成的不同方式。 安排命名空间优先级 在第11章中,我们向你演示了,在构建一条路由时,如何安排一个或多个命名空间的优先级。用它解决控制器的多义性问题,即,同名控制器类位于不同命名空间的情况。我们在第11章提到,这种信息(指命名空间优先级信息 — 译者注)被沿途传递给控制器工厂,而处理命名空间列表并对之排序的,正是这个DefaultControllerFactory。 如果你的应用程序有很多路由,指定全局命名空间优先级可能是更方便的,以使它们能用于你的所有路由。清单14-4演示了如何做这种事情。 清单14-4. 全局命名空间优先级
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ControllerBuilder.Current.DefaultNamespaces.Add("MyControllerNamespace"); ControllerBuilder.Current.DefaultNamespaces.Add("MyProject.*"); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } 我们用静态的ControllerBuilder.Current.DefaultNamespaces.Add方法来添加应该给予优先的命名空间。我们添加的命名空间的顺序并不暗示某种搜索顺序。所有默认命名空间都被用来搜索候选的控制器类,而且重复(指有多个相同的包含命名空间在内的控制器 — 译者注)仍然会引发异常,就像我们直接在路由定义中执行同样的事情那样。 ■ Tip Global prioritization is overridden by route-specific prioritization. This means you can define a global policy, and then tailor individual routes as required. 提示:Global优先级会被路由优先级所重写(override)。这意味着你可以定义一个全局策略,然后在必要时裁剪个别路由(原文如下:Global prioritization is overridden by route-specific prioritization. This means you can define a global policy, and then tailor individual routes as required.) 。 详见第11章的为个别路由指定命名空间。 如果这个控制器工厂在已经指定的命名空间中找不到一个合适的控制器类,那么将检测应用程序的其余部分。注意,我们在清单14-4中以黑体显示的第二条语句中使用了星号(*)。这允许我们指定控制器工厂应该查找MyProject命名空间及其任意子命名空间。(警告:这看上去像正则表达式语法,但它却不是,你可以用*作为命名空间的结尾,但你在这里不能使用任何其它正则表达式语法。) 定制DefaultControllerFactory的控制器构建 定制DefaultControllerFactory类如何生成控制器对象有许多种方式。但到目前为止,对控制器工厂进行定制最通常的理由是为了添加对DI(依赖注入)的支持。做这件事有几种不同的办法,最适合的技术取决于你在应用程序的其它地方如何使用DI。 使用依赖解析器(Resolver) DefaultControllerFactory类在依赖性解析器可用时,将用它来创建控制器。我们在第10章涉及了依赖解析器,并给你演示了我们的NinjectDependencyResolver类,它实现了IDependencyResolver接口,以提供Ninject的DI支持。 DefaultControllerFactory将调用IDependencyResolver.GetService方法来生成一个控制器实例,这给你解析并注入依赖提供了机会。 使用控制器激活器(Activator) 你也可以通过生成一个控制器激活器的办法,把DI引入到控制器中。通过实现IControllerActivator接口,你可以生成这个激活器,如清单14-5所示。 清单14-5. IControllerActivator接口
namespace System.Web.Mvc { using System.Web.Routing; public interface IControllerActivator { IController Create(RequestContext requestContext, Type controllerType); } } 该接口含有一个方法,名为Create,它传递一个描述请求的RequestContext对象和一个指定应该实例化哪个控制器的类型(Type)。清单14-6演示了这个接口的一个简单实现。 清单14-6. 实现IControllorActivator接口
using System; using System.Web.Mvc; using ControllerExtensibility.Controllers; namespace ControllerExtensibility.Infrastructure { public class CustomControllerActivator : IControllerActivator { public IController Create(System.Web.Routing.RequestContext requestContext, Type controllerType) { if (controllerType == typeof(FirstController)) { controllerType = typeof(SecondController); } return DependencyResolver.Current.GetService(controllerType) as IController; } } } 我们的实现便把请求传递给依赖性解析器,除非请求的是FirstController类型。在这种情况下,我们便请求SecondController类的一个实例。 只当你也使用一个依赖性解析器时,才可以使用这个IControllerActivator接口。这是因为DefaultControllerFactory通过调用IDependencyResolver.GetService方法查找(finds)一个IControllerActivator类型的控制器激活器。因此,我们需要用这个依赖性解析器来直接注册我们的激活器。在这个例子中,意味着使用NinjectDependencyResolver类的AddBindings方法,如清单14-7所示。 清单14-7. 注册一个控制器激活器
private void AddBindings() { // put bindings here Bind<IControllerActivator>().To<CustomControllerActivator>(); } 几乎总是简单地根据一个依赖解析器来创建控制器。然而,如果你想拦截并操纵请求,那么使用一个控制器激活器是一个有用的特定的特性(niche feature),在清单14-6中我们就是这样做的。 重写DefaultControllerFactory方法 你可以重写(override )DefaultControllerFactory类中的方法来定制控制器的构建。表14-1描述了你可以重写的三个方法,每一个都起着略有不同的作用。
|
请发表评论