Unity: распространение именованной регистрации

#c# #dependency-injection #inversion-of-control #castle-windsor #unity-container

#c# #внедрение зависимости #инверсия управления #замок-Виндзор #unity-контейнер

Вопрос:

С помощью Unity (2.0) я регистрирую два именованных интерфейса IFoo , сопоставленных двум разным реализациям:

 Container
   .RegisterType<IFoo, Foo1>("first")
   .RegisterType<IFoo, Foo2>("second");
  

Ну, у меня есть такой сервис:

 public class ServiceImpl : IService {

    public ServiceImpl(IFoo foo, IDependency dep, IJustAnother stuff) { ... }

    public MyValueObject[] FindVOs() { ... }

}
  

Если хотите зарегистрировать две именованные службы, каждая из которых зависит от одной IFoo , я пишу это:

  Container
   .RegisterType<IService, ServiceImpl>(
      "first", 
      new InjectionConstructor(
         new ResolvedParameter<IFoo>("first"),
         new ResolvedParameter<IDependency>(),
         new ResolvedParameter<IJustAnother>()
      )
   )
   .RegisterType<IService, ServiceImpl>(
      "second", 
      new InjectionConstructor(
         new ResolvedParameter<IFoo>("second"),
         new ResolvedParameter<IDependency>(),
         new ResolvedParameter<IJustAnother>()
      )
   )
  

Это работает, но я нахожу это уродливым и неумным, потому что для каждой новой реализации IFoo мне нужно добавлять два RegisterType вызова.
Есть ли способ получше (возможно, с расширением)?

Просто еще один вопрос: как Windsor container справляется с этим? с автоматической регистрацией?

* РЕДАКТИРОВАТЬ *

Например, если мне нужен CompositeService, подобный этому:

 public class CompositeService : IService {

    public ServiceImpl(IService[] services) { _services = services; }

    public MyValueObject[] FindVOs() { 
       return _services.SelectMany(_ => _.FindVOs() ); 
    }
}
  

Я мог бы зарегистрироваться таким образом:

 Container
   .RegisterType<IService, CompositeService>();
  

и Unity введет всю именованную регистрацию в составной конструктор. Проблема в том, что таким образом это работает, только присваивая имя всем IService . И я не могу ввести IFoo[] вместо IService , потому что IFoo используется только ServiceImpl которая является конкретной реализацией IService . Именно по этой причине я спросил, есть ли лучший способ зарегистрировать их, избегая избыточности, в Unity.

Комментарии:

1. Можете ли вы объяснить, зачем вам нужны две ServiceImpl регистрации?

2. @Steven это то, чего я хотел бы избежать. Я хочу, чтобы make ServiceImpl использовал первую или вторую реализацию IFoo в зависимости от контекста. Например, в веб-приложении Foo1 может быть реализацией для административных страниц, а Foo2 — для обычных пользователей. Или я мог бы внедрить всю именованную регистрацию IService в CompositeService посредством внедрения массива

3. Извините, но я все еще не понимаю вас. В чем именно заключается избыточность, которой вы пытаетесь избежать? И почему вы хотите внедрить IFoo[] ?

4. Я хочу избежать регистрации ServiceImpl дважды, каждый раз меняя только имя. Я бы хотел что-то вроде «foreach (IFoo(имя) в контейнере) регистрирует ServiceImpl(имя)»

5. Я не понимаю, где и как вы хотите использовать IFoo[] . Возможно, вы можете сделать пример более конкретным.

Ответ №1:

Ваш ответ в комментариях дал важную подсказку о том, чего вы пытаетесь достичь:

Foo1 может быть реализацией для административных страниц, в то время как Foo2 — для обычных пользователей

Вы можете эффективно решить эту проблему, зарегистрировав InjectionFactory . Самый короткий пример, который я могу придумать, следующий:

 container.Register<IFoo>(new InjectionFactory(c =>
{
    if (HttpContext.User.IsInRole("SUPERUSERS"))
        return container.Resolve<Foo1>();

    return container.Resolve<Foo2>();
}));
  

Вы также можете решить эту проблему, определив декоратор / прокси IFoo, который это делает. Это делает вашу конфигурацию DI немного чище, но требует немного больше кода. Вот пример:

 using System.Security.Principal;

public class UserBasedFoo : IFoo
{
    private readonly IPrincipal currentUser;
    private readonly IFoo normalUserFoo;
    private readonly IFoo superUserFoo;

    public UserBasedFoo(IPrincipal currentUser, 
        IFoo normalUserFoo, IFoo superUserFoo)
    {
        this.currentUser = currentUser;
        this.normalUserFoo = normalUserFoo;
        this.superUserFoo = superUserFoo;
    }

    object IFoo.FooMethod()
    {
        return this.CurrentUserFoo.FooMethod();
    }

    private IFoo CurrentUserFoo
    {
        get
        {
            if (this.currentUser.IsInRole("SUPERUSERS"))
                return this.superUserFoo;

            return this.normalUserFoo;
        }
    }
}
  

Вы можете настроить это следующим образом:

 container.Register<IPrincipal>(
    new InjectionFactory(c => HttpContext.User));

container.Register<IFoo>(new InjectionFactory(c =>
{
    return new UserBasedFoo(container.Resolve<IPrincipal>(),
        container.Resolve<Foo1>(),
        container.Resolve<Foo2>());
}));
  

Недостатком этого последнего подхода является то, что он требует больше кода, но он более гибкий, многоразовый и тестируемый. Сам этот класс не зависит от ASP.NET , что важно, если вы хотите провести модульное тестирование.

Я надеюсь, что это поможет.

Комментарии:

1. 1 спасибо. это очень хорошо работает с первым примером, который я привел в комментарии, но во втором? что, если мне действительно нужно внедрить два экземпляра IService в ServiceComposite, используя IService[] в конструкторе? Является ли мой подход единственным в таком случае?

2. Трудно ответить на этот вопрос, не зная контекста, так чего же вы пытаетесь достичь, вводя IService[] ?

3. Пусть IService будет сервисом, извлекающим данные из некоторого источника. С помощью составного шаблона вы могли бы составить некоторые его реализации и позволить клиенту не знать о том, что он запрашивает более чем из одного источника.

4. Итак, в чем проблема с использованием композитов?

Ответ №2:

Я решил написать UnityContainerExtension (со своим собственным BuilderStrategy ), который позволяет мне это делать:

 Container
   .RegisterType<IFoo, Foo1>("first")
   .RegisterType<IFoo, Foo2>("second")

   .RegisterType<IService, ServiceImpl>(
       new InjectionPropagator<IFoo>()
   )
;
  

таким образом, мне не нужно регистрировать новое имя IService для каждого именованного IFoo , для меня это очень полезно, потому что я могу добавлять именованные IFoo регистрации в файл конфигурации XML.