Вот простой подход. Ему не хватает определенной элегантности, но он будет делать то, что вам нужно:
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 есть способ сделать это тоже. Я не знаю.)