【问题标题】:.NET Core DI resolving keyed services using custom delegate returns null.NET Core DI 使用自定义委托解析键控服务返回 null
【发布时间】:2019-08-24 23:12:57
【问题描述】:

我正在与一个奇怪的案例作斗争。我有一个这样设置的 .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;
        }
    });
}

然后在我的课堂上注入这样的依赖:

private readonly Func<ServerType, IGatewayServer> _gatewayAccessor;

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

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

var server = _gatewayAccessor(ServerType.Type1);

我错过了什么?

【问题讨论】:

  • AddGatewayServers方法内的GetService&lt;T&gt;调用改为GetRequiredService&lt;T&gt;,然后重试。
  • 您不需要使函数成为临时依赖项。它不会从一个执行程序更改为下一个执行程序
  • 感谢@Steven 的评论。已经试过了。我得到了异常(因为如果找不到注册,GetRequiredService 方法应该抛出)
  • @Jinish:没错!您使用IGatewayServer 服务类型注册了这些类型。这意味着您可以按该类型解决它。你应该用它们的具体类型来注册它们。
  • 感谢@Steven 注意到这一点。就是这样。我完全忽略了将类型注册为 IGatewayServer 的这一点。将它们注册为具体类型解决了这个问题(并且非常有意义)。谢谢。如果您将其添加为答案,我将很乐意将其标记为

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


【解决方案1】:

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

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)。如果您将其注册为Transient,则委托将被注入Singleton消费者,但是当您调用GetRequiredService时,当请求的服务依赖于Scoped生活方式时,这最终会在运行时失败,因为这会导致Captive Dependencies。或者,当您解析实现IDisposableTransient 组件时,它甚至可能导致内存泄漏(糟糕!)。但是,将委托注册为 Singleton 也会导致与 Captive Dependencies 相同的问题。所以Scoped 是唯一明智的选择。

而不是为未知的ServerType 返回null

default:
    return null;

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

default:
    throw new InvalidEnumArgumentException(...);

【讨论】:

    猜你喜欢
    • 2020-06-19
    • 1970-01-01
    • 2012-04-07
    • 1970-01-01
    • 2017-12-27
    • 2017-05-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    相关资源
    最近更新 更多