21

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.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
sensei
  • 7,044
  • 10
  • 57
  • 125
  • Your friend might be right. Take a close look if you aren't violating the [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). If the implementations of `IAuthenticate` are not interchangeable for one another (which means that a consumer such as the `AuthenticateController` will not work correctly if it gets the wrong implementation), you are violating LSP. The solution in that case is often to give each implementation its own abstraction. – Steven Aug 30 '15 at 12:42
  • 1
    @Steven I would argue LSP is about using 'child' instead of 'parent', not about using 'child1' instead of 'child2' (or does "every property provable about 'parent' should be true for every child" imply "every property provable about 'child1..n' should be true for 'child1..n'" ?) – Vladi Pavelka Jul 03 '18 at 16:08

2 Answers2

47

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" />
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • 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 Nov 15 '18 at 06:34
  • I created a [separate thread](https://stackoverflow.com/questions/53320911/dependency-injection-unity-conditional-resolving-using-web-config) for my above request, you are most welcome to provide the solution – Jayendran Nov 16 '18 at 02:09
  • how do you do this with .net core? – Rez.Net Mar 18 '19 at 05:17
  • 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( new InjectionConstructor( new ResolvedArrayParameter( new ResolvedParameter("twitterAuth"), new ResolvedParameter("facebookAuth") ) )); – Appsum Solutions Nov 25 '20 at 08:10
8

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).

Robert Moskal
  • 21,737
  • 8
  • 62
  • 86