Контроллер инъекции Castle Windsor с двумя экземплярами одного и того же интерфейса
У меня есть мой контроллер, как это
public class MyController : Controller
{
private IEntityRepository accountsRepo;
private IEntityRepository dataRepo;
public MyController(IEntityRepository accs, IEntityRepository data)
{
accountsRepo = accs;
dataRepo = data;
}
.....
}
И я установил контейнер таким образом:
public class RepositoriesInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IEntityRepository>()
.ImplementedBy<AccountsRepository>()
.Named("accs")
.LifestyleTransient(),
Component.For<IEntityRepository>()
.ImplementedBy<DataRepository>()
.Named("data")
.LifestyleTransient());
}
}
Также у меня есть настроенные объекты:
public class PersistenceFacility : AbstractFacility
{
protected override void Init()
{
Kernel.Register(
Component.For<DbContext>()
.ImplementedBy<AccountsContext>()
.LifestylePerWebRequest(),
Component.For<DbContext>()
.ImplementedBy<DataContext>()
.LifestylePerWebRequest());
}
}
}
... и установлено:
public class PersistenceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility<PersistenceFacility>();
}
}
Поэтому, когда я использую свой контроллер, оба параметра вводятся в экземпляр AccountsRepository (который был зарегистрирован первым). Конечно, я хочу, чтобы "данные" были соответственно хранилищем данных. Пожалуйста, объясните мне, как правильно бороться с такого рода инъекциями.
РЕДАКТИРОВАТЬ
Как предложил @roman, я внедрил универсальные репозитории:
public interface IRepository : IDisposable
{
void SaveChanges();
void ExecuteProcedure(String procedureCommand, params SqlParameter[] sqlParams);
}
public interface IEntityRepository<T> : IRepository
{
T Context { get; set; }
DbSet<TEntity> Set<TEntity>() where TEntity : class;
}
public class AccountsRepository : IEntityRepository<AccountsContext>
{
public AccountsContext Context { get; set; }
public AccountsRepository(AccountsContext c)
{
Context = c;
}
public DbSet<TEntity> Set<TEntity>() where TEntity : class
{
return Context.Set<TEntity>();
}
public virtual void ExecuteProcedure(String procedureCommand, params SqlParameter[] sqlParams)
{
Context.Database.ExecuteSqlCommand(procedureCommand, sqlParams);
}
public virtual void SaveChanges()
{
Context.SaveChanges();
}
public void Dispose()
{
if (Context != null)
Context.Dispose();
}
}
DataRepository выглядит так же, но в какой-то момент я решу иметь только один конкретный класс EntityRepository, но он не имеет отношения к полученным исключениям. Так что после косметических изменений интерфейса мой контроллер стал:
public class HomeController : Controller
{
private IEntityRepository<AccountsContext> accountsRepo;
private IEntityRepository<DataContext> dataRepo;
public HomeController(IEntityRepository<AccountsContext> accs, IEntityRepository<DataContext> data)
{
accountsRepo = accs;
dataRepo = data;
}
....
}
Также я изменил код установщика:
container.Register(
Component.For<IEntityRepository<AccountsContext>>()
.ImplementedBy<AccountsRepository>()
.LifestyleTransient(),
Component.For<IEntityRepository<DataContext>>()
.ImplementedBy<DataRepository>()
.LifestyleTransient());
А теперь во время процесса разрешения контроллера
return (IController) kernel.Resolve(controllerType);
Я ловил
Не удается создать компонент "MyMVCProj.DAL.AccountsRepository", так как он имеет зависимости, которые должны быть удовлетворены.
'MyMVCProj.DAL.AccountsRepository' is waiting for the following dependencies:
- Service 'MyMVCProj.DAL.AccountsContext' which was not registered.
Castle.MicroKernel.Handlers.HandlerException: Can't create component 'MyMVCProj.DAL.AccountsRepository' as it has dependencies to be satisfied.
'MyMVCProj.DAL.AccountsRepository' is waiting for the following dependencies:
- Service 'MyMVCProj.DAL.AccountsContext' which was not registered.
Но я установил AccountsContext в логике объекта.
РЕДАКТИРОВАТЬ ++ В соответствии с предложением @Roman я настроил свой объект следующим образом:
public class PersistenceFacility : AbstractFacility
{
protected override void Init()
{
Kernel.Register(
Component.For<DbContext>()
.ImplementedBy<AccountsContext>()
.Named("accctx")
.LifestylePerWebRequest(),
Component.For<DbContext>()
.ImplementedBy<DataContext>()
.Named("datactx")
.LifestylePerWebRequest());
}
}
а также установщик репозиториев:
public class RepositoriesInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IEntityRepository<AccountsContext>>()
.ImplementedBy<AccountsRepository>()
.Named("accs")
.LifestyleTransient()
.DependsOn(Dependency.OnComponent(typeof (DbContext), "accctx")),
Component.For<IEntityRepository<DataContext>>()
.ImplementedBy<DataRepository>()
.Named("data")
.LifestyleTransient()
.DependsOn(Dependency.OnComponent(typeof (DbContext), "datactx")));
}
}
Это исключение, которое я получаю сейчас:
Can't create component 'accs' as it has dependencies to be satisfied.
'accs' is waiting for the following dependencies:
- Service 'MyMVCProj.DAL.AccountsContext' which was not registered.
Но пытаясь решить эту грубую форсировку кода, я закончил с рабочим решением, просто установив конкретные реализации DBContext:
public class PersistenceFacility : AbstractFacility
{
protected override void Init()
{
Kernel.Register(
Component.For<AccountsContext>().LifestylePerWebRequest(),
Component.For<DataContext>().LifestylePerWebRequest());
}
}
И компоненты ядра сейчас:
AccountsContext PerWebRequest
AccountsRepository / IEntityRepository<AccountsContext> Transient
DataContext PerWebRequest
DataRepository / IEntityRepository<DataContext> Transient
И прежде чем они были:
AccountsContext / DbContext PerWebRequest
AccountsRepository / IEntityRepository<AccountsContext> Transient
DataContext / DbContext PerWebRequest
DataRepository / IEntityRepository<DataContext> Transient
Итак, новые вопросы:
Я сделал все вещи идиоматически? Почему такое поведение - там уже был AccountContext с небольшим упоминанием его зависимостей.
1 ответ
Тот факт, что вы ожидаете два экземпляра одного и того же интерфейса, но вам требуется другое поведение для них (путем введения их к двум разным параметрам), подразумевает - на мой взгляд - что они не должны быть одним и тем же интерфейсом, потому что у них разные роли, или обязанности. Для меня было бы больше смысла, если бы IEntityRepository был универсальным классом, и тогда в MyController вам потребовалось бы два разных универсальных типа интерфейса:
public class MyController(IEntityRepository<Account> acc, IEntityRepository<Data> data)
Тем не менее, если вы все еще хотите делать подобные вещи, я предлагаю вам использовать CollectionResolver, который позволит классу MyController получить IEnumerable. Таким образом, вы получите оба экземпляра, но вам нужно выбрать подходящий для использования в зависимости от ваших потребностей, что я еще раз подчеркну, я думаю, что это неправильный подход для этого.
Чтобы использовать CollectionResolver, вам нужно зарегистрировать его в контейнере Windsor следующим образом:
var container = new WindsorContainer();
container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));
И тогда MyController будет выглядеть так:
public class MyController(IEnumerable<IEntityRepository> repositories)
{
accountsRepo = repositories.Where(...);
dataRepo = repositories.Where(...);
}