#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.