为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
三国纷争 2022-11-22 16:42:50

我正在为一个奇怪的案例而苦苦挣扎。我有一个 .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;

}

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


var server = _gatewayAccessor(ServerType.Type1);

我错过了什么?


查看完整描述

1 回答

?
aluckdog

TA贡献1558条经验 获得超6个赞

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


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) 到实现(Server1或Server2分别)的简单字典映射。当你请求时,它在它的字典中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实例被注入到Scoped消费者中Singleton(但请确保启用验证)。如果您将其注册为Transient,委托将被注入到消费者中,但是当您调用所请求的服务取决于生活方式Singleton时,这最终会在运行时失败,因为这会导致Captive Dependencies。或者它甚至可能导致内存泄漏,当您解析实现的组件时(糟糕!)。将代表注册为GetRequiredServiceScopedTransientIDisposableSingleton,但是,也会导致与俘虏依赖项相同的问题。所以Scoped是唯一明智的选择。


而不是返回null一个未知数ServerType:


default:

    return null;

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


default:

    throw new InvalidEnumArgumentException(...);


查看完整回答
反对 回复 6天前
  • 1 回答
  • 0 关注
  • 5 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信