依赖注入按名称解析

Ily*_*lya 18 c# dependency-injection asp.net-core

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

例如,在统一中我可以:定义两个实现 IRepository

container.RegisterType<IRepository, TestSuiteRepositor("TestSuiteRepository");
container.RegisterType<IRepository, BaseRepository>(); 
Run Code Online (Sandbox Code Playgroud)

并致电所需的实施

public BaselineManager([Dependency("TestSuiteRepository")]IRepository repository)
Run Code Online (Sandbox Code Playgroud)

ade*_*lin 21

正如@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>();
    }

}
Run Code Online (Sandbox Code Playgroud)

注册所需的服务 ConfigureServices.cs

services.AddSingleton<IRepositoryResolver, RepositoryResolver>();
services.AddTransient<TestSuiteRepository>();
services.AddTransient<BaseRepository>(); 
Run Code Online (Sandbox Code Playgroud)

最后在任何类中使用它:

public class BaselineManager
{
    private readonly IRepository _repository;

    public BaselineManager(IRepositoryResolver repositoryResolver)
    {
        _repository = repositoryResolver.GetRepositoryByName("TestSuiteRepository");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是引擎盖下的[服务定位器反模式](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/)。看看[关于服务定位器与抽象工厂的良好解释](https://blog.ploeh.dk/2010/11/01/PatternRecognitionAbstractFactoryorServiceLocator/)。您“应该”永远不要在业务层中引用DI容器。而是传递带有容器所需参数的委托以获取所需的实例。[这里](/sf/answers/2236976241/)是一个示例 (3认同)
  • 您需要 Microsoft.Extensions.DependencyInjection 或 _serviceProvider.GetService(typeof(TestSuiteRepository)) (2认同)

nel*_*eus 19

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

更新现在它可以作为nuget包使用.

要注册您的服务,您需要在Startup课程中添加以下代码:

        services.AddTransient<ServiceA>();
        services.AddTransient<ServiceB>();
        services.AddTransient<ServiceC>();
        services.AddByName<IService>()
            .Add<ServiceA>("key1")
            .Add<ServiceB>("key2")
            .Add<ServiceC>("key3")
            .Build();
Run Code Online (Sandbox Code Playgroud)

然后你可以通过IServiceByNameFactory界面使用它:

public AccountController(IServiceByNameFactory<IService> factory) {
    _service = factory.GetByName("key2");
}
Run Code Online (Sandbox Code Playgroud)

扩展的完整代码在github中.

  • 似乎这违背了依赖注入的目的。现在您的常规类依赖于依赖框架。 (2认同)

Mus*_*tor 14

>= .NET 8:键控 DI 服务

通过 .NET 8,我们获得了扩展库的更新,它带来了键控 DI 服务,因此现在有了一个内置解决方案

这里引用了 Microsoft 文档:What's new in .NET 8? - 密钥 DI 服务


密钥 DI 服务

键控依赖注入 (DI) 服务提供了一种使用密钥注册和检索 DI 服务的方法。通过使用密钥,您可以确定注册和使用服务的方式。以下是一些新的 API:

以下示例向您展示如何使用密钥 DI 服务。

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.Addsingleton<SmallCacheConsumer>();

builder.Services.AddKeyedSingleton<IMemoryCache, BigCache>("big");
builder.Services.AddKeyedSingleton<IMemoryCache, SmallCache>("small");

var app = builder.Build();

app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());

app.Run();

class BigCacheConsumer([FromKeyedServices("big")] IMemoryCache cache)
{
    public object? GetData() => cache.Get("data");
}

class SmallCacheConsumer(IKeyedServiceProvider keyedServiceProvider)
{
    public object? GetData() => keyedServiceProvider.GetRequiredKeyedService<IMemoryCache>("small");
}
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅dotnet/runtime#64427



Tse*_*eng 5

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

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

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

public BaselineManager([WithKey("TestSuiteRepository")]IRepository repository)
Run Code Online (Sandbox Code Playgroud)


rad*_*tei 5

在阅读了依赖注入的官方文档后,我认为你不能用这种方式做到这一点。

但我的问题是:你是否同时需要这两个实现?因为如果不这样做,你可以通过环境变量创建多个环境,并根据当前环境在类中拥有特定的功能Startup,甚至创建多个Startup{EnvironmentName}类。

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

我还写了一篇关于从 JSON 文件注入依赖项的文章,这样您就不必每次想要在实现之间切换时重新编译整个应用程序。基本上,您可以使用如下服务保留一个 JSON 数组:

"services": [
    {
        "serviceType": "ITest",
        "implementationType": "Test",
        "lifetime": "Transient"
    }
]
Run Code Online (Sandbox Code Playgroud)

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

希望这可以帮助!

  • 堆叠一个接口的多个实现似乎是一个常见的用例,以至于我试图弄清楚为什么他们忽略了这一点,这让我很头疼。但这就是适合您的 Microsoft。 (2认同)