IoC - Multiple implementations support for a single interface
Asked Answered
S

7

17

I am wondering why .Net IoC containers do not easily support multiple implementations for a single interface! May be I am wrong, but as far I have seen, frameworks like Ninject partially supports this feature using annotations (how?). I do not think other frameworks like Windsor or simple injector have an easy mechanism to support this scenario.

Is there any reason why this is not supported by many frameworks? AFAIK, one of the most important reasons to use interfaces is to achieve loose coupling. If the frameworks designed to improve loose coupling, do not fluently support multiple implementations for a single interface, I do not understand why!

P.S. Of course I understand that there will be a resolution issue during run time, and the container would be confused which implementation to choose, but that is something which has to be considered in the design, right?

Swop answered 29/7, 2012 at 10:1 Comment(3)
Autofac supports it. Register all your IFoo implementations and resolve them as an IEnumerable<IFoo>.Leucocratic
In fact, all IoC containers allow you to register and resolve IEnumerable<IFoo> collections, so any container allows you to do this.Liborio
Bala, please show us an example of what you are trying to achieve. Your question is currently too vaque.Liborio
S
10

Unity has the same functionality

Register named dependency

    var container = new UnityContainer();
    container.RegisterType<IConnector, Connector>("TestConnector");

Resolve by name

    container.Resolve<IConnector>("TestConnector");

the same approach

    [Dependency("TestConnector")]
    public IConnector Connector { get; set; }

Windsor has the same

class Program
{
    static void Main(string[] args)
    {
        var container = new WindsorContainer()
            .Register(Component.For<IConnector>().ImplementedBy<ConnectorA>().Named("ConnectorA"))
            .Register(Component.For<IConnector>().ImplementedBy<ConnectorB>().Named("ConnectorB"));

        var connectorA = container.Resolve<IConnector>("ConnectorA");
        Console.WriteLine("Connector type: {0}", connectorA.GetType());
        var connectorB = container.Resolve<IConnector>("ConnectorB");
        Console.WriteLine("Connector type: {0}", connectorB.GetType());
        Console.ReadKey();
    }
}

public interface IConnector
{
}

public class ConnectorA : IConnector
{

}

public class ConnectorB : IConnector
{

}
Surinam answered 29/7, 2012 at 10:29 Comment(0)
O
8

I advice to look at Convention over Configuration and especially Convention Based Dependency Injection and context-based dependency injection. Most of IoC if not all supports both approaches. You could find many interesting samples with different IoC libraries, when several implementation bind to one interface, and how useful it could be.

For example does support binding of several implementation of one interface: depends on context or attributes, by names, and so one.

By context

Following snippet binds on implemetation depends on target type automaticaly:

Bind<IWarrior>().To<Samurai>().WhenInjectedInto(typeof(OnLandAttack));
Bind<IWarrior>().To<SpecialNinja>().WhenInjectedInto(typeof(AmphibiousAttack));

By name

Very helpful when you configuration is in XML or database. Take into account InNamedScope also:

Bind<IWeapon>().To<Shuriken>().Named("Strong");
Bind<IWeapon>().To<Dagger>().Named("Weak");

By convention

With different dependency configuration at different parts of your project.

Ova answered 29/7, 2012 at 16:47 Comment(1)
This is actually very useful, didn't know about context based injection. Do other IoC containers support it?Steinmetz
G
6

Your premise is wrong.

Windsor quite happily accepts registrations of multiple implementations of the same service. In addition to the named component resolution support mentioned by GSerjo, in Windsor (by default), the first registered implementation will win but you can override this by using IsDefault() method when registering an alternative implementation. Please see http://docs.castleproject.org/Windsor.Registering-components-one-by-one.ashx for more details.

If you wish to exercise more control over the selection from multiple implementations you can create an IHandlerSelector implementation to do so. Please see http://stw.castleproject.org/Windsor.Handler-Selectors.ashx for more details.

Gourd answered 30/7, 2012 at 0:9 Comment(0)
B
2

My container Griffin.Container supports it.

registrar.RegisterConcrete<OneImplementation>();
registrar.RegisterConcrete<AnotherImplementation>();

And to fetch:

var services = container.Resolve<ITheService>();

However, you can't get one specific implementation. It's a design decision. It's much better to register a factory in the container if have to get a specific implementation. Read more here in the best practices section.

Griffin.Container can be found at github: https://github.com/jgauffin/griffin.container

Bernardinebernardo answered 29/7, 2012 at 10:35 Comment(0)
S
1

StructureMap provides these abilities:

For<IMyInterface>().Add<MyInterfaceImpl1>().Named("MyInterfaceImpl1");
For<IUsingInterface>().Add<UsingInterfaceImpl>().Ctor<IMyInterface>().Is(i => i.GetInstance<IMyInterface>("MyInterfaceImpl1"));
Scharaga answered 29/7, 2012 at 10:11 Comment(1)
Thanks, but why does Windsor, a quite widely accepted and widely used framework not support this?Swop
L
1

Your question is a bit vague, since you don't supply a concrete example of when you think you need this. In most cases there is a problem in your application or design, or you aren't following DI best practices.

All containers allow you to register multiple dependencies with the same interface as an IEnumerable<ThatInterface>, even if they do not have deep support for multiple instances. Injecting lists of services into other services however, is a design smell, and it would be better to hide this list behind a Composite. This hides the fact that there are multiple implementations behind the abstraction, and allows you to easily change the way those multiple implementations are used, by changing just a single place in the application. I don't believe any IoC framework has any support for generating composites for you, since the there is no one default way of processing the wrapped implementations. You'll have to write this Composite yourself. However, since writing such a composite is really, really simple, this justifies not having such feature in the framework.

If you want to have multiple implementations, but always need one to be returned, based on some configuration, there are always ways to do this. Most containers allow you to configure those dependencies in an XML configuration file. But even if a container does not contain such feature, reading this value from the configuration file manually and registering the right type in the container is very easy.

If you have one implementation of a certain interface for production and another implementation for unit testing purposes, you should only register the production implementation in the container. Your unit tests should be clear of any DI container, and you should manually create a class under test, and inject fake dependencies in its constructor, by simply newing the type up. Using a DI container, pollutes and complicates your tests. To pull this off, you will need to design such type around the constructor injection pattern. Don't call the container (or any other facade over the container) inside the service under test, to retrieve dependencies.

Liborio answered 30/7, 2012 at 16:56 Comment(2)
Would you be able to take a shot at my problem here. Like you say, it seems smelly to pass multiple implementations to a single class, but I can't see another way of doing it in that case. I actually agree with you on pretty much everything though, mainly in regards to ctor injection and testing.Mastership
So you can't easily build a multi-apartment building with apartment using different locks, right? It is a smell, when different clients want locks of varying mechanisms? You may argue that nobody builds like this, bu it's just an issue of my slightly poor analogy. Ok: clients want to change a lock/door altogether to another type but with same dimensions. How is it possible they are forced to use same locks as others? You make a binding and a concept of polymorphism for participating interface vanishes, what was called interface effectively becomes that one concrete implementation everywhere.Tuppeny
I
0

If you want to access implementations with certain conditions, you can use Dictionary.

UC_Login: The user must validate their credentials according to the Authentication Mode (By Database or Active Directory), each authentication mode has different business logic.

My code: I have an Interface called IAuthService.cs I have two classes called DatabaseAuthService.cs and ActiveDirectoryAuthService.cs both with the same IsValidCredential (User user) method that depend on the same Interface.

public interface IAuthService
{
  Task<bool> IsValidCredentialAsync(User user);
}

public class DatabaseAuthService : IAuthService
{
  private readonly IDatabaseAuthRepository _databaseAuthRepository;
  // User IServiceProvider for access to any other interfaces
  // using Microsoft.Extensions.DependencyInjection; using System;
  public DatabaseAuthService(IServiceProvider serviceProvider)
  => _databaseAuthRepository = serviceProvider.GetService<IDatabaseAuthRepository>();

  public async Task<bool> IsValidCredentialAsync(User user)
  {
    // return await _databaseAuthRepository.something...
  }
}

public class LdapAuthService : IAuthService
{
  public LdapAuthService()
  {
  }
  public async Task<bool> IsValidCredentialAsync(User user)
  {
    // something...
  }
}

Condition Implemented: I use the AuthenticationAppServiceclass, with the LoginAsync (LoginDto dto) method.

public class AuthenticationAppService
{
  private readonly Dictionary<AuthenticationModeEnum, IAuthService> _authProviders =
      new Dictionary<AuthenticationModeEnum, IAuthService>();
  public AuthenticationAppService(IServiceProvider serviceProvider)
  {
    _authProviders.Add(AuthenticationModeEnum.Database, new DatabaseAuthService(serviceProvider));
    _authProviders.Add(AuthenticationModeEnum.ActiveDirectory, new LdapAuthService());
  }

  public Task<bool> LoginAsync(LoginDto dto)
  {
    var user = Mapper.Map<user, LoginDto>(dto);
    return await _authProviders[(AuthenticationModeEnum)dto.AuthMode].IsValidCredentialAsync(user);
  }
}

maybe not on topic, but hope it helps.

Illuviation answered 16/10, 2020 at 21:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.