代码改变世界

ASP.NET MVC 3:放弃 Unity

2011-08-16 17:34  鹤冲天  阅读(12768)  评论(29编辑  收藏  举报

续接前文《ASP.NET MVC 3:正确实现 UnityDependencyResolver》。

Unity 很多时候也不尽人意,我们来看下:

multi-injection

Unity 支持 multi-injection,如果想当然的写成下面的样子,那就错了:

1
2
3
4
IUnityContainer container = new UnityContainer();
container.RegisterType<INotify, EmailNotify>();
container.RegisterType<INotify, SMSNotify>();
container.RegisterType<INotify, PhoneNotify>();

后面的注册会替换掉前面的。要在同一类型上注册多次,必须在 RegisterType 方法中指定不同的名称:

1
2
3
4
IUnityContainer container = new UnityContainer();
container.RegisterType<INotify, EmailNotify>();
container.RegisterType<INotify, SMSNotify>("sms");
container.RegisterType<INotify, PhoneNotify>("phone");

第 2 行的注册没有指定名称,会创建 INotify 接口的默认映射,第 3、4 行会创建命名映射(非默认)。

可如下获取实例:

1
2
3
var n1 = container.Resolve<INotify>();
var n2 = container.Resolve<INotify>("sms");
var n3 = container.Resolve<INotify>("phone");

也可以通过 ResolveAll 方法获取多个实例:

IUnityContainer.ResolveAll 方法

使用:

1
var ns = container.ResolveAll<INotify>();

但 ResolveAll 方法“名不符实”,它只会返回命名(非默认)映射,官方文档中如此解释:

When you want to obtain a list of all the registered objects of a specific type, you can use the ResolveAll method. The two overloads of this method accept either an interface or a type name, and they return an instance of IEnumerable that contains references to all registered objects of that type that are not default mappings. The list returned by the ResolveAll method contains only named instance registrations.

不禁心寒!不看文档,好多人都会以后这个 ResolveAll 会返回所有的(命名的和默认的)。也难怪没人能指出 前文 中 UnityDependencyResolver 参考实现 中的问题。

改进 UnityDependencyResolver 之前,还要看下 MVC 对 IDependencyResolver 接口的要求:

MVC 对 IDependencyResolver 接口的要求

MSDN 中如是说:

Implementations of this interface should delegate to the underlying dependency injection container to provide the registered service for the requested type. When there are no registered services of the requested type, the ASP.NET MVC framework expects implementations of this interface to return null from GetService and to return an empty collection from GetServices.

For more information about IDependencyResolver, see the entry ASP.NET MVC 3 Service Location on Brad Wilson's blog.

原文链接:http://msdn.microsoft.com/zh-cn/library/system.web.mvc.idependencyresolver(v=vs.98).aspx

大意是说,实现这个接口应将注册服务和请求类型委托给底层的 DI 容器处理,如果没有请求类型的服务注册,GetService 方法应当返回 null,GetServices 应当返回空集合。

正确实现 UnityDependencyResolver

Brad Wilson 文章 ASP.NET MVC 3 Service Location 回复中有这么一段实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UnityDependencyResolver : IDependencyResolver {
    private readonly IUnityContainer _UnityContainer;

    public UnityDependencyResolver(IUnityContainer UnityContainer) {
        _UnityContainer = UnityContainer;
    }

    public object GetService(Type serviceType) {
        return (serviceType.IsClass && !serviceType.IsAbstract) ||
            _UnityContainer.IsRegistered(serviceType) ?
            _UnityContainer.Resolve(serviceType) : null;
    }
    public IEnumerable<object> GetServices(Type serviceType) {
        return (serviceType.IsClass && !serviceType.IsAbstract) ||
            _UnityContainer.IsRegistered(serviceType) ?
            _UnityContainer.ResolveAll(serviceType) : new object[] { };
    }
}

回复人 Chris Foster 补充说:

The code I posted above does not work well for ResolveAll as Unity does not behave as expected. Maybe I should have unit tested before posting :). Ninject maybe a better choice after all.

Apologies.

大意: Unity 的 ResolveAll 并不像我们期望的那样,Ninject 或许是一个更好的选择。抱歉。

根据前面的说明和分析,正确实现 UnityDependencyResolver 已经没有困难了。

相信你能把它写出来,但我已经失去耐心,因为后面还会遇到问题。

Circular References 与 IEnumerable<T>

很遗憾,Circular References 在处理 IEnumerable<T> 上又遇到了问题。

如下面类,构造函数有一个 IEnumerable<INotify> 类型的参数:

1
2
3
4
5
6
public class OrderController {
    private IEnumerable<INotify> notifies;
    public OrderController(IEnumerable<INotify> notifies) {
        this.notifies = notifies;
    }
}

如下,在获取 OrderController 的实例抛出异常:

image

解决办法也是有的,要注册下 IEnumerable<INotify>:

1
container.RegisterType<IEnumerable<INotify>, INotify[]>();

参照自:http://stackoverflow.com/questions/1961549/resolving-ienumerablet-with-unity

加上这么个注册,感觉怪麻烦的。

不过 Unity 对 Func<IEnumerable<INotify>> 反道是支持的:

1
2
3
4
5
6
7
IUnityContainer container = new UnityContainer();
var resolver = container.Resolve<Func<IEnumerable<INotify>>>();
container.RegisterType<INotify, EmailNotify>();
container.RegisterType<INotify, SMSNotify>("sms");
container.RegisterType<INotify, PhoneNotify>("phone");

var ns = resolver();

奇怪吧,我是想不通了。

放弃 Unity

Unity 如此诡异,不按常理出牌,终于逼我产生了放弃的念头。

也突然间,明白了《Pro ASP.NET MVC 3 Framework》一书中为什么选用 Ninject 作为例子,而没有选用 Unity。

初试了下 Ninject,感觉还不错,不存在以上问题。感觉 Ninject 是完全针对 Unity 的缺点而写的。

 

本文源码下载:UnityDemo2.rar(523KB)