要理解控制器工厂如何工作,最好的办法是创建一个自定义实现。但并不建议在项目中采取这种做法,因为通过内建的工厂进行扩展,有更容易的方式创建自定义行为,但这里是演示MVC框架如何创建控制器实例的一种很好的方式。控制器工厂是由IControllerFactory接口定义的,如下所示。
using System.Web.Routing; using System.Web.SessionState; namespace System.Web.Mvc { public interface IControllerFactory { IController CreateController(RequestContext requestContext, string controllerName); SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); void ReleaseController(IController controller); } }
接下来,将创建一个简单的控制器工厂,并完成IControllerFactory接口中各个方法的实现。创建一个
Infrastructure文件夹,并创建一个新的名称为CustomControllerFactory.cs的类文件,如下所示。
using ContorllerExtensibility.Controllers; using System; using System.Web.Mvc; using System.Web.Routing; using System.Web.SessionState; namespace ContorllerExtensibility.Infrastructure { public class CustomContorllerFactory:IControllerFactory { public IController CreateController(RequestContext requestContext,string controllerName) { Type targetType = null; switch (controllerName.ToLower()) { case "product": targetType = typeof(ProductController); break; case "customer": targetType = typeof(CustomerController); break; default: requestContext.RouteData.Values["controller"] = "Product"; targetType = typeof(ProductController); break; } return targetType == null ? null : (IController)DependencyResolver.Current.GetService(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(); } } } }
IControllerFactory接口最重要的方法是CreateController,当MVC需要控制器低请求进行服务时,便会调用这个方法。该方法的一个参数是一个RequestContext对象,它让工厂能够检测请求的细节;另一个参数是字符串,它包含了从路由的URL那里所得到的controller值。
本例中,只有两个控制器,作为最简单直接的演示目的,打算直接对它们进行实例化,即将类名强行写入控制器工厂,对于实际项目而言,这是不可取的。
CreateController方法的目的是,创建能够对当前请求进行处理的控制器实例。至于该怎么做,没有任何限制。唯一的规则是,作为该方法的结果,必须返回一个IController接口的对象。
处理备用控制器
自定义控制器工厂必须返回IController接口的一个实现,以其作为CreateController方法的返回结果,否则会向用户显示错误。这意味着,当处理的请求不以项目中的任何一个控制器为目标时,需要有一个备用位置。读者可以创建任何自己喜欢的策略来处理这种情况。例如,可以定义一个特定的控制器来渲染错误消息,或是像本例这样,将请求映射到一个已经存在的控制器。
实例化控制器类
如何对控制器进行实例化,没有特别的规则,但使用依赖性解析是一个良好做法。它能够在自定义控制器工厂中专注于请求与控制器类之间的映射,而将依赖性注入这样的问题留下来单独进行处理,并用于整个应用程序。从以下代码可以看出如何使用DependencyResolver类去创建控制器实例。
return targetType == null ? null : (IController)DependencyResolver.Current.GetService(targetType);
静态的DependencyResolver.Current属性返回IDependencyResolver接口的实现,该接口定义了GetService方法,为方法传递的是一个System.Type对象,又转而得到它的一个实例。GetService方法还有一个强类型的版本,但是因为事先不知道要处理的是什么类型,因此,这里只能使用这个返回Object的版本,然后将它明确地转换成IController。
实现其他接口方法
IControllerFactory接口中的另外两个方法如下。
GetControllerSessionBehavior方法由MVC框架用来确定是否应该为控制器维护会话数据。
当不再需要CreateController方法创建的控制器对象时,会调用ReleaseController方法,以便释放安歇可以被释放的资源。
注册自定义控制器工厂
通过ControllerBuilder类,可以告诉MVC框架使用这个自定义控制器工厂。在应用程序启动时,必须注册自定义控制器工厂,即在Global.asax.cs文件中使用Application_Start方法,如下所示。
using ContorllerExtensibility.Infrastructure; using System.Web.Mvc; using System.Web.Routing; namespace ContorllerExtensibility { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new CustomContorllerFactory()); } } }
一旦注册了控制器工厂,将由它负责处理应用程序接收到的所有请求。