Search code examples
c#design-patternsinterfaceinversion-of-controlsimple-injector

How to register service conditional based on top most parent namespace


I have the following classes which I'm having issues with resolving using SimpleInjector.

Config Interface

Namespace Infrastructure.Base.Interfaces
public interface ILdapConfigService 
{
    ..... 
}

Session Interface

Namespace Infrastructure.Base.Interfaces
public interface ISessionService 
{
    LdapConnection GetConnection(); 
}

Session Implementation

Namespace Infrastructure.Base.Services
public class SessionService : ISessionService {

    ILdapConfigService _ldapConfigService;        
    public SessionService (ILdapConfigService ldapConfigService) 
    {
        _ldapConfigService =  ldapConfigService;
    }

    public LdapConnection GetConnection()
    {
        .........
    }
}

Config Service Implementation 1

namespace Infrastructure.Old.Services
public class OldConfigService : ILdapConfigService 
{
    .... 
}

Config Service Implementation 2

namespace Infrastructure.New.Services
public class NewConfigService : ILdapConfigService 
{
    .... 
}

The idea is to have the ISessionService and SessionService is in a base project. Then have different projects which implements the LdapConfigService witch implements ILdapConfigService from the base project.

The different projects will have services which makes use of a repository in the base class too.

I need to know how to register these interfaces, or is it even possible to register it like this?

Infrastructure.New.Service.SomeService uses Infrastructure.Base.Interfaces.IRepository. Infrastructure.Old.Service.SomeOtherService uses Infrastructure.Base.Interfaces.IRepository.

Infrastructure.Base.Repositories.Repository implements Infrastructure.Base.Interfaces.IRepository. Infrastructure.Base.Repositories.Repository uses Infrastructure.Base.Interfaces.ISessionService.

Infrastructure.Base.Services.SessionService implements Infrastructure.Base.Interfaces.ISessionService. Infrastructure.Base.Services.SessionService uses Infrastructure.Base.Interfaces.ILdapConfigService.

Infrastructure.New.Services.LdapConfigService implements Infrastructure.Base.Interfaces.ILdapConfigService.

Infrastructure.Old.Services.LdapConfigService implements Infrastructure.Base.Interfaces.ILdapConfigService.

How can I register this logic with Simple Injector. I used RegisterConditional but that did not quite work, because the consumer of ILdapConfigService sits in base.

container.RegisterConditional<
     ILdapConfigService,
     Old.Services.LdapConfigService>(
     c =>  c.Consumer.ImplementationType.Namespace.Contains("Old"));

container.RegisterConditional<
     ILdapConfigService,
     New.Services.LdapConfigService>(
     c =>  c.Consumer.ImplementationType.Namespace.Contains("New"));

container.Register<ISessionService, SessionService>();
container.Register<ILdapRepository, LdapRepository>(); 

Each LdapConfigService talks to an app.config where all the important information is loaded into.

Base does the whole implementation to OpenLdap with exception with the Config with needs to be injected into the SessionService so it talks to the right server. Who knew abstraction could get so complicated.

Is my design pattern perhaps flawed?


Solution

  • Is my design pattern perhaps flawed?

    As long as you are sure you aren't violating the Liskov Substitution Principle, I can't find any flaws in it, although this design does make it a bit harder to configure the container.

    I think the trick here is to create two ISessionService implementations; one for the New stuff, an one for the Old stuff. This allows you to differentiate based on the types. This is needed, because Simple Injector's RegisterConditional has the limitation that only allows you to look at the registration's direct consumer type. You are registering ILdapConfigService conditionally, but it always gets injected into SessionService. By giving each ILdpConfigService its own ISessionService, you allow making the condition based on the consumers of ISessionSerice.

    This limitation of Simple Injector is deliberate and exists to prevent the user from making invalid configurations. For instance, if SessionService is registered as singleton, it will be impossible for it to have two different ILdapConfigServices.

    Since creating two ISessionService implementation is a 'configuration trick', you can simply define the second implementation as part of your Composition Root and inherit it from the first. By doing so you can match based on types as shown below:

    class NewSessionService : SessionService {
        public NewSessionService(ILdabConfigService s) { ... }
    }
    
    container.RegisterConditional<ISessionService, SessionService>(
        c => c.Consumer.ImplementationType.Namespace.Contains("Old"));
    
    container.RegisterConditional<ILdabConfigServices, Old.LdapConfigService>(
        c => c.Consumer.ImplementationType == typeof(SessionService));
    
    
    container.RegisterConditional<ISessionService, NewSessionService>(
        c => c.Consumer.ImplementationType.Namespace.Contains("New"));
    
    container.RegisterConditional<ILdabConfigServices, New.LdapConfigService>(
        c => c.Consumer.ImplementationType == typeof(NewSessionService));