.NET Core DI 使用自定义委托解析键控服务 returns null

.NET Core DI resolving keyed services using custom delegate returns null

我正在处理一个奇怪的案例。我有一个 .NET Core 控制台应用程序,其设置如下:

private static async Task Main(string[] args)
{
    var runAsService = !(Debugger.IsAttached || args.Contains("console"));
    var builder = new HostBuilder()
        .ConfigureServices((hostContext, services) =>
        {
            services.AddLogging(loggingBuilder => { loggingBuilder.AddConsole(); });

            services.AddGatewayServers();
            services.AddHostedService<GatewayService>();
        });

    if (runAsService)
        await builder.RunServiceAsync();
    else
        await builder.RunConsoleAsync();
}

然后我在 IServiceCollection 上设置了如下扩展 AddGatewayServers()

public static void AddGatewayServers(this IServiceCollection services)
{
    services.AddTransient<IGatewayServer, Server1>();
    services.AddTransient<IGatewayServer, Server2>();
    services.AddTransient<Func<ServerType, IGatewayServer>>(provider => key =>
    {
        switch (key)
        {
            case ServerType.Type1: return provider.GetService<Server1>();
            case ServerType.Type2: return provider.GetService<Server2>();
            default: return null;
        }
    });
}

然后在我的 class 中注入这样的依赖项:

private readonly Func<ServerType, IGatewayServer> _gatewayAccessor;

public GatewayServerCollection(Func<ServerType, IGatewayServer> gatewayAccessor)
{
    _gatewayAccessor = gatewayAccessor;
}

但是当我稍后在 GatewayServerCollection 中调用 _gatewayAccessor 来获取 IGatewayServer 的实例时,它 returns null。我这样称呼它:

var server = _gatewayAccessor(ServerType.Type1);

我错过了什么?

将您的注册更改为以下内容:

public static void AddGatewayServers(this IServiceCollection services)
{
    services.AddTransient<Server1>();
    services.AddTransient<Server2>();
    services.AddScoped<Func<ServerType, IGatewayServer>>(provider => (key) =>
    {
        switch (key)
        {
            case ServerType.Type1: return provider.GetRequiredService<Server1>();
            case ServerType.Type2: return provider.GetRequiredService<Server2>();
            default: throw new InvalidEnumArgumentException(
                typeof(ServerType), (int)key, nameof(key));
        }
    });
}

最重要的变化来自于:

services.AddTransient<IGatewayServer, Server1>();
services.AddTransient<IGatewayServer, Server2>();

为此:

services.AddTransient<Server1>();
services.AddTransient<Server2>();

MS.DI 中的注册来自从服务类型 (IGatewayServer) 到实现(分别为 Server1Server2)的简单字典映射。当您请求 Server1 时,它无法在其字典中找到 typeof(Server1)。因此,解决方案是通过具体类型注册这些类型。

最重要的是,我使用了 GetRequiredService 方法:

provider.GetRequiredService<Server1>()

而不是GetService

provider.GetService<Server1>()

GetRequiredService 将在注册不存在时抛出异常,这允许您的代码快速失败。

我把委托的注册改成了Transient:

services.AddTransient<Func<ServerType, IGatewayServer>>

Scoped:

services.AddScoped<Func<ServerType, IGatewayServer>>

这可以防止它被注入任何 Singleton 消费者,因为 MS.DI 只能防止 Scoped 服务被注入 Singleton 消费者,但不会阻止 Transient 实例被注入 ScopedSingleton 消费者(但一定要确保 validation is enabled). In case you register it as Transient, the delegate would be injected into Singleton consumers, but this would eventually fail at runtime when you call GetRequiredService when the requested service depends on a Scoped lifestyle, as that would cause Captive Dependencies。或者它甚至可能导致内存泄漏,当你解析 Transient 组件时实施 IDisposable(糟糕!)。然而,将委托注册为 Singleton 也会导致与俘虏依赖项相同的问题。因此 Scoped 是唯一明智的选择。

而不是为未知的 ServerType 返回 null

default:
    return null;

我抛出异常,让应用程序快速失败:

default:
    throw new InvalidEnumArgumentException(...);