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

半城伤御伤魂 提交于 2020-03-04 04:55:42

问题


I am struggling with a weird case. I have a .NET Core console application that is setup like this:

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

I then have extensions on IServiceCollection that sets up AddGatewayServers() like this:

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;
        }
    });
}

And then in my class I inject the dependency like this:

private readonly Func<ServerType, IGatewayServer> _gatewayAccessor;

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

But when I call the _gatewayAccessor later on in GatewayServerCollection to get me an instance of the IGatewayServer it returns null. I call it like:

var server = _gatewayAccessor(ServerType.Type1);

What am i missing?


回答1:


Change your registration to the following:

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));
        }
    });
}

The most important change is from this:

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

To this:

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

The registrations in MS.DI from a simple dictionary mapping from a service type (IGatewayServer) to an implementation (Server1 or Server2 respectively). When you request Server1, it can't find typeof(Server1) in its dictionary. The solution, therefore, is to register those types by their concrete type.

On top of that, I made use of the GetRequiredService method:

provider.GetRequiredService<Server1>()

Instead of GetService:

provider.GetService<Server1>()

GetRequiredService will throw an exception when a registration does not exist, which allows your code to fail-fast.

I changed the registration of the delegate from Transient:

services.AddTransient<Func<ServerType, IGatewayServer>>

to Scoped:

services.AddScoped<Func<ServerType, IGatewayServer>>

This prevents it from being injected into any Singleton consumer, as MS.DI only prevents Scoped services to be injected into Singleton consumers, but does not prevent Transient instances from being injected into Scoped or Singleton consumers (but do make sure 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. Or it could even cause memory leaks, when you resolve Transient components that implement IDisposable (yuck!). Registering the delegate as Singleton, however, would also cause the same issues with Captive Dependencies. So Scoped is the only sensible option.

Instead of returning null for an unknown ServerType:

default:
    return null;

I throw an exception, allowing the application to fail fast:

default:
    throw new InvalidEnumArgumentException(...);


来源:https://stackoverflow.com/questions/55494896/net-core-di-resolving-keyed-services-using-custom-delegate-returns-null

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!