MVC 2.0+Entity Framework在使用依赖注入时遇到的问题
前言:
最近在看MVC 3.0 Beta Release Note的时候,发现了IDependencyResolver接口,又看到了两篇关于MVC下使用依赖注入的文章(传送门1,传送门2)。
于是乎,对依赖注入进行了学习,并对以前做的一个网站进行了重构(使用Unity)。
其实完全没有必要,因为无法充分体现依赖注入的优势,反而在性能上有点损失,就当练手一下吧~
在这个过程中(网站是MVC2.0做的),发现MVC2.0和Entity Framework在依赖注入过程中出现了一些问题,所以在此,详解一下。
MVC 2.0下实现依赖注入:
MVC2.0对依赖注入的支持度很小,理解依赖注入的原理后大家都知道,这里的关键就是需要找到一个切入点,分析对象之间的关系和生命周期后发现,从Controller切入是一个明智的选择。
那怎么拦截原本的Controller创建过程,替代成我们自己的创建过程呢?
看了RoRoWoBlog的源码和传送门1这篇文章后,发现MVC中的Controller是由 DefaultControllerFactory 这个类创建的,很明显,这是一个Controller工厂类,职责就是用来创建Controller对象的。
反编译之:
可以发现内部有大量的虚方法,而且从名字中可以看出,这是这个工厂的关键,继承并重写之即可。
public class MyConrtollerFctory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (requestContext == null || controllerType == null) { throw new ArgumentNullException("controller"); } return MvcApplication.Container.Resolve(controllerType) as IController; } }
接下来利用一个静态方法在Global.asax中替换默认的Controller工厂
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); //替换默认Controller工厂 ControllerBuilder.Current.SetControllerFactory(new MyConrtollerFctory()); }
如何正常地抛出异常:
一切看似是那么地顺利,运行一下,成功了!
但是,忽然发现问题了!
当初输入一个不存在的Controller的时候,竟然报错了!而不是返回404错误!
高亮的那段话,显示问题出在这,看上去是在替代这个方法的时候,没有正确地抛出异常
继续反编译,查看这个方法:
protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) { throw new HttpException(0x194, string.Format(CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_NoControllerFound, new object[] { requestContext.HttpContext.Request.Path })); } if (!typeof(IController).IsAssignableFrom(controllerType)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase, new object[] { controllerType }), "controllerType"); } return this._activatorResolver.Current.Create(requestContext, controllerType); }
看上去问题就出在这了,由于我们抛出了自定的异常,导致外层没有Catch到,所以它认为这不是404错误
既然知道了原因,那解决起来岂不是很简单?直接把这段代码拷贝过来就行了~
但是问题又来了,这段代码中有几个对象是 internal(程序集可见)
我们的代码和它不在同一个程序集,那当然也不能访问了… 怎么办?继续反编译?把代码复制过来?
这个方案恐怕很不友好,其实,有一个小技巧,虽然看起来不怎么样,但是它的确有效~
public class MyConrtollerFctory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (requestContext == null || controllerType == null) { //关键 base.GetControllerInstance(requestContext, controllerType); } return MvcApplication.Container.Resolve(controllerType) as IController; } }
将重写Controller工厂类的时候,如果需要抛出异常,就直接访问基类的方法,让基类去抛吧~ 这样就不用担心什么了~
Entity Framework中遇到的问题:
不仅是MVC2.0,EF对依赖注入的支持度也不高。
主要体现在,ObjectContext 是由工具自动生成的,可以修改,但不建议修改它。
而 ObjectContext 又是有多个构造函数的,要么写配置文件,要么给它的构造函数上加上 InjectionConstructor 这个Attribute。
前者太麻烦,我只是一个小项目,没必要写配置文件;后者要修改核心代码,不符合上面的原则。
其实还有一种很方便的办法可以解决这个问题,只要在 UnityContainer 中注册一下 ObjectContext 的构造函数即可。
/// <summary> /// Unity容器 /// </summary> static private UnityContainer container; static public UnityContainer Container { get { if (container == null) { container = new UnityContainer(); //注册构造函数 container.RegisterInstance<ModelContainer>(new ModelContainer()); } return container; } }
后记:
据VC 3.0 Beta对依赖注入有了很好的支持(传送门2),期待~ 希望在正式版中能确定一下用法,不要一直变了~