ASP. NET Core DI в зависимости от типа запроса - PullRequest
0 голосов
/ 07 апреля 2020

Как настроить внедрение зависимостей в ASP. NET Ядро для возврата определенного экземпляра в зависимости от типа, в который он внедряется?

Допустим, у меня есть простой интерфейс,

public interface IHello
{
    string SayHello();
}

И две разные реализации:

public class Hello : IHello
{
    public string SayHello() => "Hello...";
}

public class Hey : IHello
{
    public string SayHello() => "HEY!";
}

И, наконец, у меня есть несколько классов, которые все зависят от экземпляра IHello:

public class Class1
{
    public Class1(IHello hello)
    {
    }
}

public class Class2
{
    public Class2(IHello hello)
    {
    }
}

Теперь в ConfigureServices Я бы сделал что-то вроде этого:

services.AddSingleton<IHello, Hello>();

для настройки любого класса в зависимости от IHello, чтобы всегда получать один и тот же экземпляр Hello.

НО: Что Я действительно хочу, чтобы Class1 всегда получал один и тот же экземпляр синглтона Hey, а все другие классы должны просто получать экземпляр Hello. Это может выглядеть так в ConfigureServices (не работает очевидно):

services.AddSingleton<IHello, Hello>();
services.AddSingleton<IHello, Hey, Class1>(); // Doesn't work, but would be neat if it did...

1 Ответ

1 голос
/ 08 апреля 2020

Вот простой подход. Ему не хватает определенной элегантности, но он будет делать то, что вам нужно:

public static void Register(IServiceCollection serviceCollection)
{
    serviceCollection.AddSingleton<Hello>();
    serviceCollection.AddSingleton<Hey>();

    serviceCollection.AddSingleton<ClassThatDependsOnIHello1>(serviceProvider =>
        new ClassThatDependsOnIHello1(serviceProvider.GetService<Hello>()));

    serviceCollection.AddSingleton<ClassThatDependsOnIHello2>(serviceProvider =>
        new ClassThatDependsOnIHello2(serviceProvider.GetService<Hey>()));
}

Есть два класса, которые зависят от IHello. Регистрация для каждого из них включает в себя функцию. Эта функция разрешает либо Hello, либо Hey от поставщика услуг и передает его конструктору каждого соответствующего класса. Таким образом, вы получаете контроль над тем, какая реализация передается какому классу.

(Это не относится к вопросу о том, что поставщик услуг еще не создан. Предоставляемая вами функция будет выполнена позже, а служба провайдер, переданный ему, будет тем, который был построен из коллекции сервисов.)

Недостатком этого является то, что теперь ваша регистрация DI явно вызывает ваши конструкторы. Это может быть неприятно, потому что если конструкторы меняются (возможно, вы вводите другие зависимости), вам придется редактировать этот код. Это не здорово, но это не редкость.


План Б будет таким, как Microsoft предлагает , и использует другой контейнер.

Autofa c

Сначала добавьте Autofa c .Extensions.DependencyInjection NuGet. Это относится к Autofa c, а также предоставляет расширения, необходимые для добавления контейнера Autofa c в коллекцию сервисов.

Я организовал это так, чтобы сосредоточиться на способах регистрации зависимостей в Autofa c. Это похоже на IServiceCollection и IServiceProvider. Вы создаете ContainerBuilder, регистрируете зависимости и затем строите из него Container:

static void RegisterDependencies(this ContainerBuilder containerBuilder)
{
    containerBuilder.RegisterType<Hello>().Named<IHello>("Hello");
    containerBuilder.RegisterType<Hey>().Named<IHello>("Hey");

    containerBuilder.RegisterType<ClassThatDependsOnIHello1>().WithParameter(
        new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(IHello),
            (parameter, context) => context.ResolveNamed<IHello>("Hello")
        ));

    containerBuilder.RegisterType<ClassThatDependsOnIHello2>().WithParameter(
        new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(IHello),
            (parameter, context) => context.ResolveNamed<IHello>("Hey")
        ));
}

Это тоже не очень красиво, но это обходит проблему вызова конструкторов.

Сначала он регистрирует две реализации IHello и дает им имена.

Затем он регистрирует два класса, которые зависят от IHello. WithParameter(new ResolvedParameter()) использует две функции:

  • Первая функция определяет, является ли данный параметр тем, который мы хотим разрешить. Таким образом, в каждом случае мы говорим: «Если параметр для разрешения равен IHello, то разрешите его, используя следующую функцию.»
  • Затем он разрешает IHello, указывая, какую именованную регистрацию использовать.

Меня не волнует, насколько это сложно, но это означает, что если в эти классы вводятся другие зависимости, они будут разрешены в обычном режиме. Вы можете разрешить ClassThatDependsOnIHello1 без фактического вызова его конструктора.

Вы также можете сделать это без имен:

static void RegisterDependencies(this ContainerBuilder containerBuilder)
{
    containerBuilder.RegisterType<Hello>();
    containerBuilder.RegisterType<Hey>();

    containerBuilder.RegisterType<ClassThatDependsOnIHello1>().WithParameter(
        new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(IHello),
            (parameter, context) => context.Resolve<Hello>()
        ));

    containerBuilder.RegisterType<ClassThatDependsOnIHello2>().WithParameter(
        new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(IHello),
            (parameter, context) => context.Resolve<Hey>()
        ));

    containerBuilder.RegisterType<SomethingElse>().As<ISomethingElse>();
}

Мы можем очистить это с помощью метода, который упрощает создание этого ResolvedParameter потому что это так отвратительно.

public static ResolvedParameter CreateResolvedParameter<TDependency, TImplementation>()
    where TDependency : class
    where TImplementation : TDependency
{
    return new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(TDependency),
        (parameter, context) => context.Resolve<TImplementation>());
}

Теперь предыдущая регистрация становится:

containerBuilder.RegisterType<ClassThatDependsOnIHello1>().WithParameter(
    CreateResolvedParameter<IHello, Hello>());

containerBuilder.RegisterType<ClassThatDependsOnIHello2>().WithParameter(
    CreateResolvedParameter<IHello, Hey>());

Лучше!

Это оставляет детали того, как вы интегрируете ее с вашим приложение, и это зависит от вашего приложения. Вот документация Autofa c , которая предоставляет более подробную информацию.

Для целей тестирования вы можете сделать это:

public static IServiceProvider CreateServiceProvider()
{
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterDependencies();
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}

Мне нравится писать модульные тесты для этого Такие вещи. Этот метод создаст IServiceProvider из контейнера Autofa c, а затем вы можете проверить разрешающие элементы из контейнера, чтобы убедиться, что они разрешены должным образом.

Если вы предпочитаете другой контейнер, посмотрите, если он имеет аналогичные интеграции, чтобы использовать его с контейнером Microsoft. Вы можете найти тот, который вам нравится больше.


Windsor

Вот аналогичный пример использования Castle.Windsor.MsDependencyInjection .

public static class WindsorRegistrations
{
    public static IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
    {
        var container = new WindsorContainer();
        container.RegisterDependencies();
        return WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection);
    }

    public static void RegisterDependencies(this IWindsorContainer container)
    {
        container.Register(
            Component.For<Hello>(),
            Component.For<Hey>(),

            Component.For<ClassThatDependsOnIHello1>()
                .DependsOn(Dependency.OnComponent<IHello, Hello>()),
            Component.For<ClassThatDependsOnIHello2>()
                .DependsOn(Dependency.OnComponent<IHello, Hey>())
        );
    }
}

Есть две вещи, которые мне нравятся в этом :

  • Намного легче читать! При разрешении ClassThatDependsOnIHello2 выполните зависимость от IHello с помощью Hey. Просто.
  • Это - WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection) - позволяет регистрировать зависимости с помощью IServiceCollection и включать их в поставщика услуг. Так что, если у вас есть код, который регистрирует множество зависимостей с IServiceCollection, вы все равно можете его использовать. Вы можете зарегистрировать другие зависимости с Windsor. Затем CreateServiceProvider смешивает их все вместе. (Может быть, у Autofa c есть способ сделать это тоже. Я не знаю.)
...