Как определить интерфейс на основе службы, куда он передается - PullRequest
25 голосов
/ 07 июня 2011

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

public interface ISomeInterface {...}

и две реализации (SomeImpl1 и SomeImpl2):

public class SomeImpl1 : ISomeInterface {...}
public class SomeImpl2 : ISomeInterface {...}

У меня также есть две службы, в которые я внедряю ISomeInterface (через конструктор):

public class Service1 : IService1 
{
   public Service1(ISomeInterface someInterface)
   {
   }
...
}

и

public class Service2 : IService2 
{
   public Service2(ISomeInterface someInterface)
   {
   }
...
}

Я использую Autofac в качестве инструмента IoC. Вопрос. Как настроить регистрацию Autofac, чтобы SomeImpl1 автоматически вводился в Service1, а SomeImpl2 автоматически вводился в Service2.

Спасибо!

Ответы [ 3 ]

32 голосов
/ 07 июня 2011

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
4 голосов
/ 07 июня 2011

Если вы можете переключиться с внедрения конструктора на внедрение свойства и позволить обеим службам наследовать один и тот же базовый класс (или реализовать один и тот же интерфейс), вы можете сделать следующее:

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);
});

У этого подхода есть несколько недостатков:

  1. Нам нужно внедрение свойства.
  2. Нам нужен базовый тип или интерфейс, который содержит это свойство.
  3. Нам нужно отражение, чтобы построить тип, который может оказать влияние напроизводительность (вместо того, чтобы контейнер создавал эффективный делегат) (хотя мы могли бы ускорить процесс за счет кэширования типа).

С другой стороны, этот дизайн прост и работает практически для любого контейнера.

2 голосов
/ 29 ноября 2016

Четыре варианта выполнения этого описаны в Документация по автозапуску :

Вариант 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();

OПункт 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"))));
...