Dependency Injection Unity - Conditional Resolving

12,718

Solution 1

A simple way to solve this is with the strategy pattern. Note that you can add or remove login providers without changing the design - you simply need to change the DI configuration.

Interfaces

public interface IAuthenticate{
    bool Login(string user, string pass);
    bool AppliesTo(string providerName);
}

public interface IAuthenticateStrategy
{
    bool Login(string providerName, string user, string pass);
}

Authenticate Providers

public class TwitterAuth : IAuthenticate
{
    bool Login(string user, string pass)
    {
        //connect to twitter api
    }
    
    bool AppliesTo(string providerName)
    {
        // I used the type name for this example, but
        // note that you could use any string or other
        // datatype to select the correct provider.
        return this.GetType().Name.Equals(providerName);
    }
}

public class FacebookAuth: IAuthenticate
{
    bool Login(string user, string pass)
    {
        //connect to fb api
    }

    bool AppliesTo(string providerName)
    {
        return this.GetType().Name.Equals(providerName);
    }
}

Strategy

public class AuthenticateStrategy: IAuthenticateStrategy
{
    private readonly IAuthenticate[] authenticateProviders;
    
    public AuthenticateStrategy(IAuthenticate[] authenticateProviders)
    {
        if (authenticateProviders == null)
            throw new ArgumentNullException("authenticateProviders");
            
        this.authenticateProviders = authenticateProviders;
    }

    public bool Login(string providerName, string user, string pass)
    {
        var provider = this.authenticateProviders
            .FirstOrDefault(x => x.AppliesTo(providerName));

        if (provider == null)
        {
            throw new Exception("Login provider not registered");
        }

        return provider.Login(user, pass);
    }
}

Unity Registration

// Note that the strings used here for instance names have nothing 
// to do with the strings used to select the instance in the strategy pattern
unityContainer.RegisterType<IAuthenticate, TwitterAuth>("twitterAuth");
unityContainer.RegisterType<IAuthenticate, FacebookAuth>("facebookAuth");
unityContainer.RegisterType<IAuthenticateStrategy, AuthenticateStrategy>(
    new InjectionConstructor(
        new ResolvedArrayParameter<IAuthenticate>(
            new ResolvedParameter<IAuthenticate>("twitterAuth"),
            new ResolvedParameter<IAuthenticate>("facebookAuth")
        )
    ));

Usage

private readonly IAuthenticateStrategy _authenticateStrategy;

public AuthenticateController(IAuthenticateStrategy authenticateStrategy)
{
    if (authenticateStrategy == null)
        throw new ArgumentNullException("authenticateStrategy");
        
    _authenticateStrategy = authenticateStrategy;
}



// login with twitter
public virtual ActionResult Twitter(string user, string pass)
{
    bool success =
            _authenticateStrategy.Login("TwitterAuth", user, pass);
}



// login with fb
public virtual ActionResult Facebook(string user, string pass)
{
    bool success =
            _authenticateStrategy.Login("FacebookAuth", user, pass);
}

unity.config

Instead of "Unity Registration" you could do this on your unity.config

<register type="IAuthenticate" mapTo="TwitterAuth" name="twitterAuth" />
<register type="IAuthenticate" mapTo="FacebookAuth" name="facebookAuth" />
<register type="IAuthenticateStrategy" mapTo="AuthenticateStrategy" />

Solution 2

Unity won't without your help. You could provide a name when you register your IAuthenticate types:

unityContainer.RegisterType<IAuthenticate, TwitterAuth>("Twitter");
unityContainer.RegisterType<IAuthenticate, FacebookAuth>("Facebook");

You'll no longer want to directly inject an IAuthenticate instance into your AuthenticateController. You'll either get the instance you want based on a condition right out of unity (service locator style):

myContainer.Resolve<IAuthenticate>("Twitter");

or you'll inject a Factory that does this for you (if you like a strict DI style).

Share:
12,718
sensei
Author by

sensei

I like coding universe.

Updated on June 05, 2022

Comments

  • sensei
    sensei almost 2 years

    Conditional resolving is the last thing I don't understand at the moment.

    Lets say we have an interface IAuthenticate:

    public interface IAuthenticate{
        bool Login(string user, string pass);
    }
    

    Now I have two types of authentication.

    Twitter auth

    public class TwitterAuth : IAuthenticate
    {
      bool Login(string user, string pass)
    {
       //connect to twitter api
    }
    
    }
    

    Facebook Auth

    public class FacebookAuth: IAuthenticate
    {
      bool Login(string user, string pass)
    {
       //connect to fb api
    }
    
    }
    

    Registering types in unity config:

    unityContainer.RegisterType<IAuthenticate, TwitterAuth>();
    unityContainer.RegisterType<IAuthenticate, FacebookAuth>();
    

    inject objects via DI in our controller:

    private readonly IAuthenticate _authenticate;
    
    public AuthenticateController(IAuthenticate authenticate)
    {
        _authenticate = authenticate;
    }
    
    
    
    // login with twitter
    public virtual ActionResult Twitter(string user, string pass)
    {
        bool success =
                _authenticate.Login(user, pass);
    }
    
    
    
    // login with fb
    public virtual ActionResult Facebook(string user, string pass)
    {
        bool success =
                _authenticate.Login(user, pass);
    }
    
    
    
    // login with google
    public virtual ActionResult Google(string user, string pass)
    {
        bool success =
                _authenticate.Login(user, pass);
    }
    

    How exactly will unity know which object does it have to resolve for different types of authentication? How do I do conditional resolving in this case?

    I spoke with friend of mine, and he explained if this situation appears it is wrong design, but this is just factory pattern used.

  • Jayendran
    Jayendran over 5 years
    could you please provide me the equivalent unity registration using web.config since we aren't supposed to change the code/unityconfig.cs everytime and planning to change the config without build/deployment
  • Jayendran
    Jayendran over 5 years
    I created a separate thread for my above request, you are most welcome to provide the solution
  • Rez.Net
    Rez.Net about 5 years
    how do you do this with .net core?
  • Appsum Solutions
    Appsum Solutions over 3 years
    The unity registration has a slight error in it. It registers 2 arrays to be injected in the strategy's constructor. This should be: unityContainer.RegisterType<IAuthenticateStrategy, AuthenticateStrategy>( new InjectionConstructor( new ResolvedArrayParameter<IAuthenticate>( new ResolvedParameter<IAuthenticate>("twitterAuth"), new ResolvedParameter<IAuthenticate>("facebookAuth") ) ));