当路由系统找到与当前请求匹配的路由信息(RouteData
),而路由信息中的RouteHandler
为MvcRouteHandler
,那么此请求将由MvcRouteHandler
返回的MvcHandler
来负责处理。默认情况下MvcHandler
将根据请求上下文(RequestContext
)中的信息找到对应的控制器和活动方法,通过调用活动方法,返回应答内容,将其返回给客户端。
MvcHandler
类通过使用单例类ControllerBuilder
的GetControllerFactory
方法获取当前指定的IControllerFactory
对象,通过此对象来生成具体的IController
控制器。对于MvcHandler
来说,它只关心IController
对象,即只负责调用IController的Execute方法来产生应答内容。Action
方法是MVC框架默认Controller
类中实现的一种机制,它通过路由信息中的action参数来确定调用Controller
类中的某个对应方法,以此产生应答内容。
默认情况下,MVC框架使用DefaultControllerFactory
控制器工厂,它使用路由信息中的controller
参数来确定具体的控制器类,并通过反射机制生成控制器实例。
从MvcRouteHandler
接收到请求到最终确定使用那个控制器,涉及到的类,如下图所示:
通过ControllerBuilder
的SetControllerFactory
方法我们可以指定自定义的控制器工厂,自定义工厂可以直接实现IControllerFactory
接口,也可以从DefaultControllerFactory
类继承,如果直接实现IControllerFactory
,则我们必须自己实现一种机制将路由信息与控制器对应起来。所以为了使用MVC
中默认的路由解析功能,我们通常从DefaultControllerFactory
继承,然后根据实际情况覆写GetControllerInstance
与GetControllerType
方法,其中GetControllerInstance
用于返回一个IController控制器,GetControllerType
用于根据路由信息获取合适的控制器类型。显然,在默认控制器工厂中,CreateController
方法先调用GetControllerType
找出类型,然后调用GetControllerInstance
方法创建一个控制器实例。
DefaultControllerFactory
使用Activator.CreateInstance
方法来创建控制器对象,所以MVC
默认的控制器必须具有无参构造函数。下面我们使用依赖注入(DI)技术使我们的自定义控制器工厂具有实例化有参控制器。
设想以下情景:领域模型中有个Product
类,我们为其定义了一个IProductsRepository
的存储管理类。首先,我们实现了一个SqlProductsRepository
类,用于从SqlServer
数据库中获取产品资料,然后,为方便测试,我们还实现了一个MockProductsRepository
类,用于返回一个简单的产品列表。我们建立一个ProductsController
控制器,为确定使用哪种存储实现,我们要求在生成控制器实例时,必须传入一个IProductsRepository
对象:
我们选用NInject开源库来实现DI(下载地址:http://ninject.org/download):
1、下载NInject,并解压
2、新建一个空的MVC工程
3、添加引用Ninject.dll、System.Data.Linq
4、在SQL Server Management Studio Express中附加上源代码中的Test数据库
5、按上述类图实现各个接口及类
6、创建一个
ProductsController
,增加一个带IProductsRepository
参数的构造函数
ProductsController
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace CnBlogs.ControllerFactory.Controllers { public class ProductsController : Controller { private IProductsRepository _repository; public ProductsController(IProductsRepository repository) { _repository = repository; } public ActionResult Index() { return View(_repository.GetProductsList()); } } }
7、创建与
Index Action
方法对应的视图Index.aspx
8、创建一个控制器工厂:
NinjectControllerFactory
NinjectControllerFactory
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Ninject.Modules; using Ninject; using CnBlogs.ControllerFactory.Models; using System.Configuration; namespace CnBlogs.ControllerFactory.Infrastructure { public class NinjectControllerFactory : DefaultControllerFactory { private IKernel _kernel = new StandardKernel(new MyModule()); protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) { if (controllerType == null) return null; return (IController)_kernel.Get(controllerType); } private class MyModule : NinjectModule { public override void Load() { //Bind<IProductsRepository>() // .To<MockProductsRepository>(); Bind<IProductsRepository>() .To<SqlProductsRepository>() .WithConstructorArgument("connectstring", ConfigurationManager.ConnectionStrings["TestDb"].ConnectionString); } } } } ``` 9、在`Global.asax.cs`中设置自定义工厂
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); }
“`
Ok,要在虚拟产品列表与真实的SqlServer
中的产品列表中切换,我们只需修改MyModule
中的Load
方法,将Controller
构造函数中的特定参数绑定到一个具体的实现。
注意:本文涉及到有关的Linq、DI
方面的知识,请参考相关资料。要运行本文示例,请先将源代码中的mdf文件附加到SqlServer Express
中,并修改App.config
中TestDb
连接字符串的定义。