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

Jin*_*ish 1 c# dependency-injection .net-core .net-core-2.2

我正在处理一个奇怪的案件。我有一个 .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();
}
Run Code Online (Sandbox Code Playgroud)

然后我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;
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

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

private readonly Func<ServerType, IGatewayServer> _gatewayAccessor;

public GatewayServerCollection(Func<ServerType, IGatewayServer> gatewayAccessor)
{
    _gatewayAccessor = gatewayAccessor;
}
Run Code Online (Sandbox Code Playgroud)

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

var server = _gatewayAccessor(ServerType.Type1);
Run Code Online (Sandbox Code Playgroud)

我缺少什么?

Ste*_*ven 5

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

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));
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

最重要的变化来自于此:

services.AddTransient<IGatewayServer, Server1>();
services.AddTransient<IGatewayServer, Server2>();
Run Code Online (Sandbox Code Playgroud)

对此:

services.AddTransient<Server1>();
services.AddTransient<Server2>();
Run Code Online (Sandbox Code Playgroud)

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

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

provider.GetRequiredService<Server1>()
Run Code Online (Sandbox Code Playgroud)

代替GetService

provider.GetService<Server1>()
Run Code Online (Sandbox Code Playgroud)

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

我将代表的注册更改为Transient

services.AddTransient<Func<ServerType, IGatewayServer>>
Run Code Online (Sandbox Code Playgroud)

Scoped

services.AddScoped<Func<ServerType, IGatewayServer>>
Run Code Online (Sandbox Code Playgroud)

这可以防止它被注入到任何Singleton消费者中,因为 MS.DI 仅阻止Scoped服务被注入到Singleton消费者中,但不会阻止Transient实例被注入到ScopedSingleton消费者中(但请确保启用验证)。如果您将其注册为Transient,委托将被注入到消费者中,但是当您在请求的服务依赖于生活方式时调用时,Singleton这最终会在运行时失败,因为这会导致Captive Dependencies。或者,当您解析实现的组件时,它甚至可能导致内存泄漏(恶心!)。但是,将委托注册为也会导致与强制依赖项相同的问题。所以这是唯一明智的选择。GetRequiredServiceScopedTransientIDisposableSingletonScoped

而不是返回null未知的ServerType

default:
    return null;
Run Code Online (Sandbox Code Playgroud)

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

default:
    throw new InvalidEnumArgumentException(...);
Run Code Online (Sandbox Code Playgroud)