.NET Core DI, разрешающий службы с ключами с использованием специального делегата, возвращает ноль - PullRequest
1 голос
/ 03 апреля 2019

Я борюсь со странным случаем.У меня есть консольное приложение .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 Ответ

1 голос
/ 03 апреля 2019

Измените вашу регистрацию следующим образом:

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 потребителей, но это в конечном итоге приведет к сбою во время выполнения, когда вы вызываете GetRequiredService, когда запрашиваемая служба зависит от Scoped образа жизни, поскольку это приведет к Пленные зависимости .Или это может даже вызвать утечку памяти, когда вы решаете Transient компонентов, которые реализуют IDisposable (чёрт!).Однако регистрация делегата с именем Singleton также вызовет те же проблемы с зависимостями Captive.Так что Scoped - единственный разумный вариант.

Вместо того, чтобы возвращать null для неизвестного ServerType:

default:
    return null;

Я выбрасываю исключение, позволяющее приложению быстро проваливаться:

default:
    throw new InvalidEnumArgumentException(...);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...