簡體   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