Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection Unity - Conditional Resolving

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.

like image 597
sensei Avatar asked Aug 30 '15 11:08

sensei


2 Answers

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" />
like image 155
NightOwl888 Avatar answered Oct 16 '22 23:10

NightOwl888


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

like image 26
Robert Moskal Avatar answered Oct 16 '22 23:10

Robert Moskal