【问题标题】:Dependency injection resolving by name通过名称解析依赖注入
【发布时间】:2016-12-28 13:26:53
【问题描述】:

如何为特定类注入不同的对象实现?

例如在Unity中,我可以定义IRepository的两个实现

container.RegisterType<IRepository, TestSuiteRepositor("TestSuiteRepository");
container.RegisterType<IRepository, BaseRepository>(); 

并调用所需的实现

public BaselineManager([Dependency("TestSuiteRepository")]IRepository repository)

【问题讨论】:

标签: c# dependency-injection asp.net-core


【解决方案1】:

正如@Tseng 所指出的,命名绑定没有内置解决方案。但是,使用工厂方法可能对您的情况有所帮助。示例应如下所示:

创建存储库解析器:

public interface IRepositoryResolver
{
    IRepository GetRepositoryByName(string name);
}

public class RepositoryResolver : IRepositoryResolver 
{
    private readonly IServiceProvider _serviceProvider;
    public RepositoryResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    public IRepository GetRepositoryByName(string name)
    {
         if(name == "TestSuiteRepository") 
           return _serviceProvider.GetService<TestSuiteRepositor>();
         //... other condition
         else
           return _serviceProvider.GetService<BaseRepositor>();
    }

}

ConfigureServices.cs注册需要的服务

services.AddSingleton<IRepositoryResolver, RepositoryResolver>();
services.AddTransient<TestSuiteRepository>();
services.AddTransient<BaseRepository>(); 

最终在任何类中使用它:

public class BaselineManager
{
    private readonly IRepository _repository;

    public BaselineManager(IRepositoryResolver repositoryResolver)
    {
        _repository = repositoryResolver.GetRepositoryByName("TestSuiteRepository");
    }
}

【讨论】:

  • 我试试你说的,但是在GetRepositoryByName() 方法中,我得到了这个错误:he non-generic method 'IServiceProvider.GetService(Type)' cannot be used with type arguments
  • 你需要 Microsoft.Extensions.DependencyInjection 或 _serviceProvider.GetService(typeof(TestSuiteRepository))
  • 这个解决方案对我有用,因为我需要一个 DbContextFactory 对象。
  • 这是 service locator anti-pattern 在引擎盖下。看看this good explanation of service locator vs abstract factory。您“应该”永远不要在业务层中引用 DI 容器。而是传递一个带有容器所需参数的委托,以获取您想要的实例。 Here 就是一个例子
【解决方案2】:

除了@adem-caglin 的回答,我想在这里发布一些我为基于名称的注册创建的可重用代码。

更新现在可以使用nuget package

为了注册您的服务,您需要将以下代码添加到您的 Startup 类:

        services.AddTransient<ServiceA>();
        services.AddTransient<ServiceB>();
        services.AddTransient<ServiceC>();
        services.AddByName<IService>()
            .Add<ServiceA>("key1")
            .Add<ServiceB>("key2")
            .Add<ServiceC>("key3")
            .Build();

然后就可以通过IServiceByNameFactory接口使用了:

public AccountController(IServiceByNameFactory<IService> factory) {
    _service = factory.GetByName("key2");
}

或者你可以使用工厂注册来保持客户端代码干净(我更喜欢)

_container.AddScoped<AccountController>(s => new AccountController(s.GetByName<IService>("key2")));

扩展的完整代码在github

【讨论】:

  • 看起来这违背了依赖注入的目的。现在你的常规类依赖于依赖框架。
  • 作为替代方案,IoC 容器可以决定注入什么实例。尽管它需要更复杂的基于名称的注册规则,这可能是一个很好的解决方案,无需显式依赖。
  • @mac10688 最后我添加了这种可能性github.com/yuriy-nelipovich/DependencyInjection.Extensions
  • 有趣的是,默认情况下,ASP.NET Core 的默认 IOC 实现会为您在 Startup 中定义的所有实现实例创建一个 IEnumerable
  • 顺便说一句,图书馆的荣誉。我能够很容易地插入它。我喜欢它不需要任何复杂的接线这一事实。我发现最快的对象实例化发生在我仅针对启动时默认创建的 IEnumerable 运行一个简单的 LINQ 查询时。 Ninject 提供了一个命名实现,我不明白为什么微软不只是移植类似的东西。我看不出它是如何违反国际奥委会的原则的。干得好!
【解决方案3】:

您不能使用内置的 ASP.NET Core IoC 容器。

这是设计。内置容器有意保持简单且易于扩展,因此如果您需要更多功能,可以插入第三方容器。

您必须使用第三方容器来执行此操作,例如 Autofac(请参阅 docs)。

public BaselineManager([WithKey("TestSuiteRepository")]IRepository repository)

【讨论】:

    【解决方案4】:

    看了the official documentation for dependency injection之后,我觉得你不能这样。

    但我的问题是:您是否同时需要这两个实现?因为如果你不这样做,你可以create multiple environments through environment variablesspecific functionality in the Startup class based on the current environment,甚至创建多个Startup{EnvironmentName} 类。

    当 ASP.NET Core 应用程序启动时,Startup 类用于引导应用程序、加载其配置设置等(了解有关 ASP.NET 启动的更多信息)。但是,如果存在名为 Startup{EnvironmentName} 的类(例如 StartupDevelopment),并且 ASPNETCORE_ENVIRONMENT 环境变量与该名称匹配,则将使用该 Startup 类。因此,您可以配置 Startup 用于开发,但有一个单独的 StartupProduction 将在应用程序在生产中运行时使用。反之亦然。

    我也是wrote an article about injecting dependencies from a JSON file,因此您不必每次想要在实现之间切换时都重新编译整个应用程序。基本上,您保留一个 JSON 数组,其中包含如下服务:

    "services": [
        {
            "serviceType": "ITest",
            "implementationType": "Test",
            "lifetime": "Transient"
        }
    ]
    

    然后您可以在这个文件中修改所需的实现,而不必重新编译或更改环境变量。

    希望这会有所帮助!

    【讨论】:

    • 如果我们同时需要两个实现该怎么办?
    • 堆叠一个接口的多个实现似乎是一个常见的用例,以至于试图弄清楚为什么他们忽略了这一点让我很头疼。但这就是你的微软。
    【解决方案5】:

    首先,这可能仍然是个坏主意。您要实现的是将依赖项的使用方式和定义方式分开。但是你想使用依赖注入框架,而不是反对它。避免服务定位器反模式的发现能力差。为什么不以类似于ILogger&lt;T&gt; / IOptions&lt;T&gt; 的方式使用泛型?

    public BaselineManager(RepositoryMapping<BaselineManager> repository){
       _repository = repository.Repository;
    }
    
    public class RepositoryMapping<T>{
        private IServiceProvider _provider;
        private Type _implementationType;
        public RepositoryMapping(IServiceProvider provider, Type implementationType){
            _provider = provider;
            _implementationType = implementationType;
        }
        public IRepository Repository => (IRepository)_provider.GetService(_implementationType);
    }
    
    public static IServiceCollection MapRepository<T,R>(this IServiceCollection services) where R : IRepository =>
        services.AddTransient(p => new RepositoryMapping<T>(p, typeof(R)));
    
    services.AddScoped<BaselineManager>();
    services.MapRepository<BaselineManager, BaseRepository>();
    

    自 .net core 3 起,如果您未能定义映射,则应引发验证错误。

    【讨论】:

      猜你喜欢
      • 2011-11-13
      • 1970-01-01
      • 2015-05-27
      • 2013-01-22
      • 1970-01-01
      • 2012-09-08
      • 2019-12-03
      • 1970-01-01
      相关资源
      最近更新 更多