Reputation: 30628
In my application, I want to take dependencies on multiple repositories in a class, where not all of them are required each time. Rather than constructing an instance of each one where unnecessary, I use the Typed Factory facility in Windsor.
However, registering a factory for each repository is a bit tiresome, and I would like to replace this with an open generic registration. What I want to do is something like the following:
container.Register(
Component.For<IFactory<IRepository<>>>().AsFactory()
);
However, this is a syntax error because of the missing type parameter for IRepository. Is there a syntax I can use which would make this work?
NB: I'm aware that I can register an untyped Factory interface and use this to create multiple components. I'm not interested in doing this as this is essentially taking a dependency on a service locator - if I've not registered a dependency then I won't know about it until the code tries to use it - with my approach I know about this in the constructor even though I'm not creating an instance yet.
Full (simplified) sample below:
public class TestA { }
public class TestB { }
public interface IRepository<T> { T Create(); }
public class Repository<T> : IRepository<T>
{
public T Create() { return Activator.CreateInstance<T>(); }
}
public interface IFactory<T>
{
T Create();
void Release(T instance);
}
class Program
{
static void Main(string[] args)
{
IWindsorContainer container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
// Individual registrations of repositories here are fine
Component.For<IRepository<TestA>>().ImplementedBy<Repository<TestA>>(),
Component.For<IRepository<TestB>>().ImplementedBy<Repository<TestB>>()
);
container.Register(
// Individual registrations of factories - works, but trying to avoid!
Component.For<IFactory<IRepository<TestA>>>().AsFactory(),
Component.For<IFactory<IRepository<TestB>>>().AsFactory()
);
container.Register(
// Generic Registration of Factories - syntax errors
// Component.For<IFactory<IRepository<>>>().AsFactory()
// Component.For(typeof(IFactory<IRepository<>>)).AsFactory()
);
var factoryA = container.Resolve<IFactory<IRepository<TestA>>>();
var factoryB = container.Resolve<IFactory<IRepository<TestB>>>();
var repoA = factoryA.Create();
var repoB = factoryB.Create();
Console.WriteLine("Everything worked");
}
}
Upvotes: 2
Views: 2199
Reputation: 7274
Your factory inteface definition is a little too "open". Change your factory interface as follows:
public interface IRepositoryFactory<T>
{
IRepository<T> Create();
void Release(IRepository<T> instance);
}
And you can then register:
container.Register(Component.For(typeof(IRepositoryFactory<>)).AsFactory());
And resolve:
var factoryA = container.Resolve<IRepositoryFactory<TestA>>();
var factoryB = container.Resolve<IRepositoryFactory<TestB>>();
Upvotes: 5
Reputation: 172835
There's a pattern for grouping repositories together. It is called unit of work. So, instead of creating a factory for creating repositories, create a unit of work class that references these repositories. For instance:
public abstract class UnitOfWork : IDisposable
{
// here is your factory
protected abstract IRepository<T> GetRepository<T>();
public IRepository<User> Users
{
get { return this.GetRepository<User>();
}
public IRepository<Customer> Customers
{
get { return this.GetRepository<Customer>();
}
// etc..
}
In your Composition Root you can define an UnitOfWork
implementation that holds a reference to Windsor and enables you to get IRepository<T>
implementations:
internal sealed class WindsorUnitOfWork : UnitOfWork
{
private WindsorContainer container;
public WindsorUnitOfWork(WindsorContainer container)
{
this.container = container;
}
protected override IRepository<T> GetRepository<T>()
{
return this.container.Resolve<IRepository<T>>();
}
}
And register it as follows:
container.Register(Component.For<UnitOfWork>()
.ImplementedBy<WindsorUnitOfWork>()
.LifeStyle.Transient);
Consumers now have a really convenient way of using the repositories:
private readonly UnitOfWork db;
public KarmaService(UnitOfWork db)
{
this.db = db;
}
public int CalculateKarmaForActiveUsersByName(string name)
{
var users =
from user in this.db.Users
where user.Name == name
where user.Active
select user;
return users.Sum(user => user.Karma);
}
Upvotes: 1