ASP.NET MVC 3:正确实现 UnityDependencyResolver
2011-08-16 00:24 鹤冲天 阅读(8125) 评论(43) 编辑 收藏 举报前日,dudu 写了篇文章 《想爱容易,相处难:当ASP.NET MVC 爱上 IoC》,介绍了在 MVC 中如何使用 Unity,不过 dudu 犯了一个错误:错误地使用了 Unity。
这要先从 Unity 使用说起:
Unity 基本使用
假定程序中有如下两个接口:
1 2 |
public interface ICustomerRepository { /*...*/ } public interface IOrderRepository { /*...*/ } |
和两个实现类:
1 2 |
public class CustomerRepository : ICustomerRepository { /*...*/ } public class OrderRepository : IOrderRepository { /*...*/ } |
可以如下使用:
1 2 3 4 5 6 7 |
var container = new UnityContainer(); //注册 container.RegisterType<ICustomerRepository, CustomerRepository>(); container.RegisterType<IOrderRepository, OrderRepository>(); //使用 var customerRepository = container.Resolve<ICustomerRepository>(); var orderRepository = container.Resolve<IOrderRepository>(); |
(可在配置文件中注册,请参考相关文档)
但是实际使用中,情况要复杂的多,如下面这个接口和类(如何注入?):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public interface IOrderService { /*...*/ } public class OrderService: IOrderService { private ICustomerRepository customerRepository; private IOrderRepository orderRepository; public OrderService(ICustomerRepository customerRepository, IOrderRepository orderRepository) { this.customerRepository = customerRepository; this.orderRepository = orderRepository; } /*...*/ } |
有朋友会说,可以使用构造函数注入:
1 2 3 4 |
container.RegisterType<IOrderService, OrderService>( new InjectionConstructor(new CustomerRepository(), new OrderRepository()) ); var orderService = container.Resolve<IOrderService>(); |
很好,这个这样可以解决这个问题。
但是,这种方式是存在一些缺陷的:
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 IOrderController { /*...*/ } public class OrderController: IOrderController { private IOrderService orderService; private IStoreService storeService; public OrderController(IOrderService orderService, IStoreService 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 UnityContainer(); container.RegisterType<ICustomerRepository, CustomerRepository>(); container.RegisterType<IOrderRepository, OrderRepository>(); container.RegisterType<IOrderService, OrderService>(); container.RegisterType<IOrderController, OrderController>(); var orderController = container.Resolve<IOrderController>(); |
说明(相当啰嗦,明白人可跳过):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 UnityContainer(); container.RegisterType<ICustomerRepository, CustomerRepository>(); container.RegisterType<IOrderRepository, OrderRepository>(); container.RegisterType<IOrderService, OrderService>(); bool isRegistered = container.IsRegistered<OrderController>(); //false var controller = container.Resolve<OrderController>(); |
源码下载: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 UnityDependencyResolver : IDependencyResolver { IUnityContainer container; public UnityDependencyResolver(IUnityContainer container) { this.container = container; } public object GetService(Type serviceType) { if (!this.container.IsRegistered(serviceType)) { return null; } return container.Resolve(serviceType); } public IEnumerable<object> GetServices(Type 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 = serviceResolver ?? new SingleServiceResolver<IControllerFactory>( () => _factoryThunk(), new DefaultControllerFactory { ControllerBuilder = this }, "ControllerBuilder.GetControllerFactory" ); } public static ControllerBuilder Current { get { return _instance; } } public IControllerFactory GetControllerFactory() { return _serviceResolver.Current; } 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, new DefaultControllerActivator(dependencyResolver), "DefaultControllerFactory contstructor" ); } private class DefaultControllerActivator : IControllerActivator { Func<IDependencyResolver> _resolverThunk; public DefaultControllerActivator() : this(null) { } public DefaultControllerActivator(IDependencyResolver resolver) { if (resolver == null) _resolverThunk = () => DependencyResolver.Current; else _resolverThunk = () => resolver; } //... public IController Create(RequestContext requestContext, Type controllerType) { try { return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType)); } 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 UnityDependencyResolver : IDependencyResolver { private IUnityContainer container; public UnityDependencyResolver(IUnityContainer container) { this.container = container; } public object GetService(Type serviceType) { try { return container.Resolve(serviceType); } catch (ResolutionFailedException) { //额外操作,如日志 return null; } } public IEnumerable<object> GetServices(Type 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 ViewPageActivator : IViewPageActivator { public object Create(ControllerContext controllerContext, Type type) { return Activator.CreateInstance(type); } } |
在 Global.asax 文件中,注册如下:
1 2 3 4 5 6 7 8 9 |
protected void Application_Start() { //... IUnityContainer container = new UnityContainer(); container.RegisterType<IServiceA, ServiceA>(); container.RegisterType<IServiceB, ServiceB>();; container.RegisterType<IViewPageActivator, ViewPageActivator>(); DependencyResolver.SetResolver(new UnityDependencyResolver(container)); } |
不用担心 UnityDependencyResolver 异常处理带来的效率问题,因为就目前代码,启动之后 UnityDependencyResolver 中只会发生两次异常:获取 IControllerFactory 和 IControllerActivator 时各一次,损失可以忽略不计了。
修正后的源码:MvcIocDemo.rar(485KB)
后记
人非圣贤,难免犯错。
但截止到本文发布时,dudu 这篇文章却有 18 个推荐,只有 1 个反对(我的了),我们不得不反思了。
-------------------
思想火花,照亮世界