Search code examples
dependency-injectionsimple-injector

How do I make Simple Injector prefer implementations of the "most derived" interface?


In my data access layer I have a repository hierarchy that looks like this:

    <TEntity>                                 
IEntityRepository<---------+ICustomerRepository
        ^                            ^        
        |                            |        
        |                            |        
        |                            |        
        +                            |        
    <TEntity>                        +        
 EntityRepository<----------+CustomerRepository

The IEntityRepository<TEntity> interface defines basic CRUD operations that will be useful regardless of entity type. EntityRepository<TEntity> is a concrete implementation of these operations.

In addition, there are repository types for operations that are specific to a particular entity. In the example above, I have a Customer entity, and the ICustomerRepository interface defines operations such as GetByPhoneNumber. The ICustomerRepository also derives from IEntityRepository<Customer>, so that the common CRUD operations will also be available for an instance of ICustomerRepository. Finally, CustomerRepository is the concrete implementation for the ICustomerRepository operations, and it also inherits from EntityRepository<Customer> for the common operations implementation.

So, going over to my actual question: I use Simple Injector to inject instances into my application. I register each of the specialized repository types in my container: CustomerRepository as the implementation of ICustomerRepository and so on.

To ensure new entity types can be added to the system and used without needing to create a new, concrete repository implementation as well, I would like to be able to serve the base EntityRepository<> implementation when an IEntityRepository<> of the new entity is requested. I've understood I can use the RegisterOpenGeneric method for this.

What I can't figure out is, when a generic repository is requested, how can I serve the specialized repository for that type if it exists, and the generic repository only as a fallback?

For example, let's say I do this in my application:

container.Register<ICustomerRepository, CustomerRepository>();
container.RegisterOpenGeneric(typeof(IEntityRepository<>), typeof(EntityRepository<>));

Most of the classes relying on repositories would request the ICustomerRepositorydirectly. However, there could be a class in my application requesting the base interface, like this:

public ContractValidator(IEntityRepository<Customer> customerRepository,
                         IEntityRepository<Contract> contractRepository)
{
    ...

What happens in the example above is:

  • customerRepository gets an instance of EntityRepository<Customer>
  • contractRepository gets an instance of EntityRepository<Contract>

What I want to happen is:

  • customerRepository gets an instance of CustomerRepository
  • contractRepository gets an instance of EntityRepository<Contract>

Is there any way to inform Simple Injector's resolution that if a derivation of a particular interface exists, this should be served instead? So for IDerived : IBase, requests for IBase should return an implementation of IDerived if it exists. And I don't want this resolution across the board, just for these repositories. Can it be done in a reasonable way, or would I need to manually iterate through all the registrations in the RegisterOpenGeneric predicate and check manually?


Solution

  • Assuming your classes look like this

    public class CustomerRepository : 
        ICustomerRepository, 
        IEntityRepository<Customer> { }
    

    You can register all the generic implementations of IEntityRepository<> using RegisterManyForOpenGeneric and the fallback registration stays the same.

    UPDATE: Updated with v3 syntax

    // Simple Injector v3.x
    container.Register<ICustomerRepository, CustomerRepository>();
    container.Register(
        typeof(IEntityRepository<>), 
        new[] { typeof(IEntityRepository<>).Assembly });
    container.RegisterConditional(
        typeof(IEntityRepository<>),
        typeof(EntityRepository<>),
        c => !c.Handled);
    
    // Simple Injector v2.x
    container.Register<ICustomerRepository, CustomerRepository>();
    container.RegisterManyForOpenGeneric(
        typeof(IEntityRepository<>), 
        new[] { typeof(IEntityRepository<>).Assembly });
    container.RegisterOpenGeneric(
        typeof(IEntityRepository<>),
        typeof(EntityRepository<>));
    

    But you should note that if you use any lifestyle then these separate registrations may not resolve as you would expect. This is known as a torn lifestyle.