Подтвердить что ты не робот

Как разрешить интерфейс, основанный на сервисе, в котором он передавался

У меня есть интерфейс.

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.

Спасибо!

4b9b3361

Ответ 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"))));