前日,dudu 写了篇文章 《想爱容易,相处难:当ASP.NET MVC 爱上 IoC》,介绍了在 MVC 中如何使用 Unity,不过 dudu 犯了一个错误:错误地使用了 Unity。
这要先从 Unity 使用说起:
Unity 基本使用
假定程序中有如下两个接口:
1 2 | public interface { /*...*/ } public interface { /*...*/ } |
和两个实现类:
1 2 | public class : { /*...*/ } public class : { /*...*/ } |
可以如下使用:
1 2 3 4 5 6 7 | var container = new (); //注册 container.RegisterType<, CustomerRepository>(); container.RegisterType<, OrderRepository>(); //使用 customerRepository = container.Resolve<>(); orderRepository = container.Resolve<>(); |
(可在配置文件中注册,请参考相关文档)
但是实际使用中,情况要复杂的多,如下面这个接口和类(如何注入?):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public interface { /*...*/ } public class : { private customerRepository; private orderRepository; public OrderService( customerRepository, orderRepository) { this.customerRepository = customerRepository; this.orderRepository = orderRepository; } /*...*/ } |
有朋友会说,可以使用构造函数注入:
1 2 3 4 | container.RegisterType<, >( new (new (), new ()) ); var orderService = container.Resolve<>(); |
很好,这个这样可以解决这个问题。
但是,这种方式是存在一些缺陷的:
1、多次注册:我们已经将 CustomerRepository 注册给了 ICustomerRepository 接口,但在注册 IOrderService 接口时,还要 new CustomerRepository。多次注册会带来很多问题,假想有一天,我们需要将 CustomerRepository 统统换成 ImprovedCustomerRepository,使用这种方式不得不多处修改。
2、设计变动时,多处修改:设想 OrderService 的构造函数需要增加一个 IProductRepository 类型的参数,另外一个类 StoreService 的构造函数也要增加这么个参数。
3、更复杂的情况,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public interface { /*...*/ } public class : { private orderService; private storeService; public OrderController( orderService, storeService) { this.orderService = orderService; this.storeService = storeService; } } |
如何在 Unity 中注册 IOrderController? 别告诉我用构造函数注入,这可得两级构造注入。
其实 Unity 支持 Circular References,可以轻松解决这些问题:
使用 Circular References
Circular References 翻译过来是“循环引用“。
Unity 中,不需要任何设置就可以使用 Circular References,使用一个例子来说明 :
1 2 3 4 5 6 7 | var container = new (); container.RegisterType<, >(); container.RegisterType<, >(); container.RegisterType<, >(); container.RegisterType<, >(); var orderController = container.Resolve<>(); |
说明(相当啰嗦,明白人可跳过):Unity 在获取 IOrderController 的实例时(第 7 行),根据第 5 行的注册得知,应该创建 OrderController 的实例。但 OrderController 有两个参数,类型分别是 IOrderService、IStoreService,势必先创建 IOrderService 的实例。根据第 4 行得知应该去创建 OrderService 的实例,OrderServer 又有 ICustomerRepository、IOrderRepository 两个参数,再根据第 2、3 行分别创建 CustomerRepository 和 OrderRepository 的实例,因为这两个类构造函数无参,直接可实例化。获取 IOrderService 实例化后,用类似的方式再获取 IStoreService 的实例。这样根据构造函数的参数,一步步向前,称为 Circular References Resolve。
有了 Circular References,不再需要过多的注册,Unity 会智能判断并处理。所以,即使一个类型没有注册在容器中,依然可以获取它的实例:
1 2 3 4 5 6 7 | var container = new (); container.RegisterType<, >(); container.RegisterType<, >(); container.RegisterType<, >(); bool isRegistered = container.IsRegistered<>(); //false var controller = container.Resolve<>(); |
源码下载:UnityDemo.rar (522KB)
dudu 文中存在的问题
看了前面的部分,相信你一定能指出 dudu 文中 UnityDependencyResolver 存在问题,原代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class : { container; public UnityDependencyResolver( container) { this.container = container; } public object GetService( serviceType) { if (!this.container.IsRegistered(serviceType)) { return null; } return container.Resolve(serviceType); } public IEnumerable<object> GetServices( serviceType) { return container.ResolveAll(serviceType); } } |
没错,问题就在第 12~15 行,if (!this.container.IsRegistered(serviceType)) return null; 这句其实 ”屏蔽“ 了 Unity 的 Circular References,导致一系列问题,应该去除这四行代码。
另外,使用 Unity (或其它 DI 框架),不需要创建新的 ControllerFactory(如 dudu 文中的 UnityControllerFactory),除非有特殊需要。
从 ControllerBuilder 的源码中,可以看出,如果没有注册 IControllerFactory,MVC 将自动使用 DefaultControllerFactory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class ControllerBuilder { private Func<IControllerFactory> _factoryThunk = () => null; private static ControllerBuilder _instance = new ControllerBuilder(); private IResolver<IControllerFactory> _serviceResolver; public ControllerBuilder() : this(null) { } internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver) { serviceResolver ?? new SingleServiceResolver<IControllerFactory>( () => _factoryThunk(), , "ControllerBuilder.GetControllerFactory" ); } public static ControllerBuilder Current { get { return _instance; } } public IControllerFactory GetControllerFactory() { return ; } public void SetControllerFactory(IControllerFactory controllerFactory) { if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); _factoryThunk = () => controllerFactory; } //... } |
DefaultControllerFactory 在内部(DefaultControllerActivator 类)会尝试调用 DependencyResolver 来获取 Controller 的实例,如果不成功则使用 Activator.CreateInstance :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public class DefaultControllerFactory : IControllerFactory { private IResolver<IControllerActivator> _activatorResolver; private IControllerActivator _controllerActivator; public DefaultControllerFactory() : this(null, null, null) { } public DefaultControllerFactory(IControllerActivator controllerActivator) : this(controllerActivator, null, null) { } internal DefaultControllerFactory(IControllerActivator controllerActivator, IResolver<IControllerActivator> activatorResolver, IDependencyResolver dependencyResolver) { if (controllerActivator != null) _controllerActivator = controllerActivator; else _activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>( () => null, , "DefaultControllerFactory contstructor" ); } private class DefaultControllerActivator : IControllerActivator { Func<IDependencyResolver> _resolverThunk; public DefaultControllerActivator() : this(null) { } public DefaultControllerActivator(IDependencyResolver resolver) { if (resolver == null) ; else _resolverThunk = () => resolver; } //... public IController Create(RequestContext requestContext, Type controllerType) { try { return ?? ); } catch (Exception ex) { //... } } } } |
通过前面的说明和分析,可以写出 UnityDependencyResolver 的参考实现:
UnityDependencyResolver 参考实现及使用
参考代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class : { private container; public UnityDependencyResolver( container) { this.container = container; } public object GetService(Type serviceType) { try { return container.Resolve(serviceType); } catch (ResolutionFailedException) { //额外操作,如日志 return null; } } public IEnumerable<object> GetServices( serviceType) { try { return container.ResolveAll(serviceType); } catch (ResolutionFailedException) { //额外操作,如日志 return Enumerable.Empty<object>(); } } } |
第 19~26 行 GetServices 好像还是有些问题的,有谁能指出吗? (答案在此:《ASP.NET MVC 3:放弃 Unity》)
建议实现 IViewPageActivator 接口并注册:
1 2 3 4 5 6 7 | public class : { public object Create( controllerContext, type) { return Activator.CreateInstance(type); } } |
在 Global.asax 文件中,注册如下:
1 2 3 4 5 6 7 8 9 | protected void Application_Start() { //... container = new (); container.RegisterType<, >(); container.RegisterType<, >();; container.RegisterType<, >(); DependencyResolver.SetResolver(new (container)); } |
不用担心 UnityDependencyResolver 异常处理带来的效率问题,因为就目前代码,启动之后 UnityDependencyResolver 中只会发生两次异常:获取 IControllerFactory 和 IControllerActivator 时各一次,损失可以忽略不计了。
修正后的源码:MvcIocDemo.rar(485KB)
后记
人非圣贤,难免犯错。
但截止到本文发布时,dudu 这篇文章却有 18 个推荐,只有 1 个反对(我的了),我们不得不反思了。