代码改变世界

ASP.NET MVC 3:正确实现 UnityDependencyResolver

2011-08-16 00:24 by 鹤冲天, ... 阅读, ... 评论, 收藏, 编辑

前日,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 个反对(我的了),我们不得不反思了。