Как разрешить интерфейс, основанный на сервисе, в котором он передавался
У меня есть интерфейс.
public interface ISomeInterface {...}
и две реализации (SomeImpl1 и SomeImpl2):
public class SomeImpl1 : ISomeInterface {...}
public class SomeImpl2 : ISomeInterface {...}
У меня также есть две службы, в которые я вставляю ISomeInterface (через contructor):
public class Service1 : IService1
{
public Service1(ISomeInterface someInterface)
{
}
...
}
и
public class Service2 : IService2
{
public Service2(ISomeInterface someInterface)
{
}
...
}
Я использую Autofac в качестве своего инструмента IoC.
Вопрос. Как я могу настроить регистрацию Autofac, поэтому SomeImpl1 будет автоматически вводиться в Service1, а SomeImpl2 будет автоматически введен в Service2.
Спасибо!
Ответы
Ответ 1
Autofac поддерживает идентификацию сервисов по имени. Используя это, вы можете зарегистрировать свои реализации с именем (используя метод расширения Named
). Затем вы можете разрешить их по имени в делегатах регистрации IServiceX, используя метод расширения ResolveNamed
. Следующий код демонстрирует это.
var cb = new ContainerBuilder();
cb.Register(c => new SomeImpl1()).Named<ISomeInterface>("impl1");
cb.Register(c => new SomeImpl2()).Named<ISomeInterface>("impl2");
cb.Register(c => new Service1(c.ResolveNamed<ISomeInterface>("impl1"))).As<IService1>();
cb.Register(c => new Service2(c.ResolveNamed<ISomeInterface>("impl2"))).As<IService2>();
var container = cb.Build();
var s1 = container.Resolve<IService1>();//Contains impl1
var s2 = container.Resolve<IService2>();//Contains impl2
Альтернатива с использованием RegisterType
(в отличие от Register
)
Вы можете добиться того же результата с помощью метода расширения RegisterType
в сочетании с WithParameter
и ResolvedParameter
. Это полезно, если конструктор, принимающий именованный параметр, также принимает другие неименованные параметры, которые вы не хотите указывать в делетете регистрации:
var cb = new ContainerBuilder();
cb.RegisterType<SomeImpl1>().Named<ISomeInterface>("impl1");
cb.RegisterType<SomeImpl2>().Named<ISomeInterface>("impl2");
cb.RegisterType<Service1>().As<IService1>().WithParameter(ResolvedParameter.ForNamed<ISomeInterface>("impl1"));
cb.RegisterType<Service2>().As<IService2>().WithParameter(ResolvedParameter.ForNamed<ISomeInterface>("impl2"));
var container = cb.Build();
var s1 = container.Resolve<IService1>();//Contains impl1
var s2 = container.Resolve<IService2>();//Contains impl2
Ответ 2
Если вы можете переключиться с инсталляции конструктора на инъекцию свойств и позволить обеим службам получить один и тот же базовый класс (или реализовать тот же интерфейс), вы можете сделать следующее:
builder.RegisterType<ServiceBase>().OnActivating(e =>
{
var type = e.Instance.GetType();
// get ISomeInterface based on instance type, i.e.:
ISomeInterface dependency =
e.Context.ResolveNamed<ISomeInterface>(type.Name);
e.Instance.SomeInterface = dependency;
});
Для этого вам нужно определить свойство в базовом типе (или интерфейсе). Это решение является очень гибким и даже позволит вам создавать сложные вещи, такие как ввод типичного типа на основе родительского типа, как показано ниже:
builder.RegisterType<ServiceBase>().OnActivating(e =>
{
var type =
typeof(GenericImpl<>).MakeGenericType(e.Instance.GetType());
e.Instance.SomeInterface = (ISomeInterface)e.Context.Resolve(type);
});
Этот подход имеет несколько недостатков:
- Нам нужна инъекция свойств.
- Нам нужен базовый тип или интерфейс, который содержит это свойство.
- Нам нужно отражение, чтобы создать тип, который может повлиять на производительность (вместо создания контейнера эффективным делегатом) (хотя мы могли бы ускорить процесс путем кэширования типа).
С другой стороны, этот дизайн прост и работает практически для любого контейнера.
Ответ 3
Четыре варианта выполнения этого описаны в документации по автозагрузке:
Вариант 1: переработайте свои интерфейсы
Когда вы столкнетесь с ситуацией, когда у вас есть куча компонентов которые реализуют идентичные услуги, но не могут быть обработаны идентично, это, как правило, проблема с дизайном интерфейса.
С точки зрения объектно-ориентированного развития вы хотите, чтобы ваш объекты, чтобы придерживаться принципа подписи Лискова и такого рода разрывов, которые.
Выполняя некоторую переработку интерфейса, вам не нужно "выбирать зависимость от контекста" - вы используете типы для дифференциации и во время разрешения происходит автоматическая проводка.
Если у вас есть возможность повлиять на изменение вашего решения, это рекомендуемый вариант.
Вариант 2. Изменение регистраций
Вы можете вручную связать соответствующий тип с потребляющим компонентом таким образом:
var builder = new ContainerBuilder();
builder.Register(ctx => new ShippingProcessor(new PostalServiceSender()));
builder.Register(ctx => new CustomerNotifier(new EmailNotifier()));
var container = builder.Build();
// Lambda registrations resolve based on the specific type, not the
// ISender interface.
builder.Register(ctx => new ShippingProcessor(ctx.Resolve<PostalServiceSender>()));
builder.Register(ctx => new CustomerNotifier(ctx.Resolve<EmailNotifier>()));
var container = builder.Build();
Вариант 3: использование ключевых служб
builder.RegisterType<PostalServiceSender>()
.As<ISender>()
.Keyed<ISender>("order");
builder.RegisterType<EmailNotifier>()
.As<ISender>()
.Keyed<ISender>("notification");
builder.RegisterType<ShippingProcessor>()
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(ISender),
(pi, ctx) => ctx.ResolveKeyed<ISender>("order")));
builder.RegisterType<CustomerNotifier>();
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(ISender),
(pi, ctx) => ctx.ResolveKeyed<ISender>("notification")));
Вариант 4. Использование метаданных
builder.RegisterType<PostalServiceSender>()
.As<ISender>()
.WithMetadata("SendAllowed", "order");
builder.RegisterType<EmailNotifier>()
.As<ISender>()
.WithMetadata("SendAllowed", "notification");
builder.RegisterType<ShippingProcessor>()
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(ISender),
(pi, ctx) => ctx.Resolve<IEnumerable<Meta<ISender>>>()
.First(a => a.Metadata["SendAllowed"].Equals("order"))));
builder.RegisterType<CustomerNotifier>();
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(ISender),
(pi, ctx) => ctx.Resolve<IEnumerable<Meta<ISender>>>()
.First(a => a.Metadata["SendAllowed"].Equals("notification"))));