如何使用Autofac将任意/更改的服务集注入对象?

时间:2016-12-30 04:25:37

标签: c# dependency-injection architecture autofac

使用Autofac,在构造函数参数中给出多个接口而不是我想要实现的,让我们说我有;

public class SomeController : ApiController
{
    private readonly IDomainService _domainService;
    private readonly IService1 _service1;
    private readonly IService2 _service2;
    private readonly IService3 _service3;

    public SomeController(IDomainService domainService,
                              Iservice1 service1,
                              IService2 service2,
                              IService2 service3, ...)
    {
        _domainService = domainService;
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
        ...
    }
}

或者,我们可以做一个界面并具有多个属性,例如;

public interface IAllServices
{
    IDomainService DomainService { get; set; }
    IService1 Service1 { get; set; }
    IService2 Service2 { get; set; }
    IService3 Service3 { get; set; }
}

public class SomeController : ApiController
{
    private readonly IAllServices _allServices;

    public SomeController(IAllServices allServices)
    {
        _allServices = allServices;

        var domainService1 = _allServices.DomainService;
        var service1 = _allServices.Service1;
        etc...
    }
}

但是,我希望有一个服务列表,这个代码对我有用,即。;

public interface IMyApp
{
    IEnumerable<dynamic> Services { get; set; }
}

public class SomeController : ApiController
{
    private readonly IMyApp _myapp;

    public SomeController(IMyApp myapp)
    {
        _myapp = myapp;

        foreach (var item in _myapp.Services)
        {
            if (item is IService1) { // do something... }
            if (item is IService2) { // do something... }
            if (item is IWhatever) { // do whatever something... }
        }
    }
}

但是,我没有更好的最佳实践如何创建模块,这是我的模块;

public class MainModule : Autofac.Module
{
    private readonly string[] _serviceNames;
    private readonly IDomainService _domainService;

    public MainModule(IDomainService domainService, params string[] serviceNames)
    {
        _serviceNames = serviceNames;
        _domainService = domainService;
    }

    protected override void Load(ContainerBuilder builder)
    {
        List<dynamic> _services = new List<dynamic>();

        _services.Add(_domainService);

        foreach (var serviceName in _serviceNames)
        {
            switch (serviceName)
            {
                case "MyService1":
                    IService1 service1 = new Service1();
                    _modules.Add(service1);
                    break;
                case "MyService2":
                    IService2 service2 = new Service2();
                    _modules.Add(service2);
                    break;
                case "SomeWhateverService":
                    IWhatever whateverService = new WhateverService();
                    _modules.Add(whateverService);
                    break;                      
            }

        }

        builder.RegisterType<MyApp>()
            .As<IMyApp>()
            .WithParameter(new TypedParameter(typeof(IEnumerable<dynamic>), _services));

    }
}

所以,这段代码有效,但我想让我的DomainService和所有服务也在容器中注册。也就是说,我想在没有 new 关键字的情况下替换switch语句中的任何内容。

IService1 service1 = new Service1();
_modules.Add(service1);

我也想注册域名服务。所以,我的Bootstrapper里面是这样的;

public static class Initializer
{
    public static IContainer BuildContainer(
        HttpConfiguration config, Assembly assembly, IDomainService domainService, params string[] services)
    {
        var builder = new ContainerBuilder();
        builder.RegisterApiControllers(assembly);
        builder.RegisterWebApiFilterProvider(config);
        builder.RegisterModule(new MainModule(domainService, services));

        var container = builder.Build();
        config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

        return container;
    }
}

发生了什么,我需要在启动时创建域服务,即。;

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);

        MyDomainService domainService = new MyDomainService();

        var container =
            Initializer.BuildContainer(
            GlobalConfiguration.Configuration,
            Assembly.GetExecutingAssembly(),
            domainService,
            "MyService1", "MyService2", "SomeWhateverService");

    }
}

您可以看到我必须首先创建域服务,而不是使用IoC;

MyDomainService domainService = new MyDomainService();

并添加到模块。

最大的问题是,如何使用Autofac以正确的方式执行此操作。我的Bootstrapper在另一个项目中,所有接口也在其他项目中。

非常感谢你的帮助。抱歉这个长期的问题。

解决方案: 在测试了几个模型之后,似乎最好的方法是将域事件模型用于此类场景,而不是将服务注入域中。

2 个答案:

答案 0 :(得分:1)

进行依赖注入的正确方法是使用Constructor Injection。构造函数注入应始终是您的首选,并且只有在高例外情况下,您才应该回到另一种方法。

您建议使用属性注入作为替代,但这会导致Temporal Coupling,这意味着可以在缺少必需的依赖项时初始化类,以后会导致空引用异常。

注入包含构造函数负责获取所需依赖项的所有服务的集合的方法是Service Locator pattern的变体。这种模式充满了问题和is considered to be an anti-pattern

将依赖项分组到新类中并注入该类仅在类封装逻辑并隐藏依赖项的情况下才有用。此模式称为Facade Service。有一个大型服务公开其他人使用的依赖项可以被认为是Service Locator反模式的一种形式,特别是当这个类暴露的服务数量开始增长时。它将成为获取服务的常见目标。一旦发生这种情况,它就会出现与其他形式的服务定位器相同的缺点。

将依赖项提取到另一个类中,同时允许使用者直接使用这些依赖项并不能帮助降低消费者的复杂性。该消费者将保持相同数量的逻辑和相同数量的依赖。

这里的核心问题似乎是你的类有太多的依赖。然而,关于构造函数注入的好处是,当类有太多依赖项时,它会非常清楚。寻求其他方法来获得依赖关系并不会使类变得更简单。不要尝试其他注射方法,请尝试以下方法:

  • 应用Single Responsibility Principle。课程应该有一个改变的理由。
  • 尝试将具有其依赖关系的逻辑提取到 Facade Service
  • 从类中删除处理交叉问题(例如日志记录和安全检查)的逻辑和依赖项,并将它们放在基础结构中(例如装饰器,拦截器或依赖于您的框架,处理器,中间件,消息管道等) )。

答案 1 :(得分:0)

在测试了几个模型之后,似乎最好的办法就是在这种情况下使用域事件模式,而不是将服务注入域中。

我参考Udi Dahan关于域名事件的文章: http://udidahan.com/2009/06/14/domain-events-salvation/