繁体   English   中英

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

[英]How do you inject an arbitrary/changing set of services into an object using 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和所有服务都注册到容器中。 也就是说,我要替换switch语句中的任何内容而无需new关键字。

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在另一个项目中,所有接口也在另一个项目中。

非常感谢您的帮助。 很抱歉,这个问题很长。

解决方案 :在测试了几种模型之后,似乎最好的方法是针对这种情况使用域事件模型,而不是将服务注入域中。

进行依赖项注入的正确方法是使用构造函数注入。 构造函数注入应该始终是您的首选,并且只有在例外情况下,您才应该使用其他方法。

您建议使用属性注入作为替代方法,但这会导致时间耦合 ,这意味着可以在缺少必需的依赖项的情况下初始化类,从而在以后导致空引用异常。

注入包含所有服务的集合的方法是Service Locator模式的一种变体,该集合包含构造函数负责获取其所需依赖关系的所有服务。 这种模式到处都是问题, 被认为是一种反模式

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

将依赖关系提取到不同的类中,同时允许使用者直接使用这些依赖关系,并不会降低使用者的复杂性。 该使用者将保持相同数量的逻辑和相同数量的依赖关系。

这里的核心问题似乎是您的类获得了太多的依赖关系。 但是,关于构造函数注入的伟大之处在于,当类具有过多的依赖关系时,它可以非常清楚地知道。 寻求其他方法来获取依赖关系不会使类变得不那么复杂。 代替尝试其他注入方法,请尝试以下操作:

  • 实行单一责任原则 班级应该有一个改变的理由。
  • 尝试将逻辑及其依赖项从类中提取到Facade Service中
  • 从类中删除处理交叉关注点(例如日志记录和安全检查)的逻辑和依赖项,并将它们放置在基础结构中(例如装饰器,拦截器或根据您的框架而定的处理程序,中间件,消息管道等)。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM