Dependency Injection Unity - Conditional Resolving

Dependency Injection Unity - Conditional Resolving

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" />

Unity: default and conditional resolving

I want to confirm that the best way is to have container configured from the code and even not just from the code, but from wise reach code.
Ultimate sample is the EntLib's configuration from "constructors lambda expressions library":

http://msdn.microsoft.com/en-us/magazine/ee335709.aspx

yield return new TypeRegistration<Database>(
() => new SqlDatabase(
ConnectionString,
Container.Resolved<IDataInstrumentationProvider>(Name)))
{
Name = Name,
Lifetime = TypeRegistrationLifetime.Transient
};

such constructions will be later interpreted to build container configuration!

Unit DI to Microsoft DI - Conditional Resolving

Based on what you are trying to do, note the following

Default service container replacement

The built-in service container is designed to serve the needs of the framework and most consumer apps. We recommend using the built-in container unless you need a specific feature that the built-in container doesn't support, such as:

  • Property injection
  • Injection based on name (emphasis mine)
  • Child containers
  • Custom lifetime management
  • Func<T> support for lazy initialization
  • Convention-based registration

Reference Default service container replacement

Unity does have an extension that can plug into Microsoft DI

Install the nuget package

Install-Package Unity.Microsoft.DependencyInjection

And use the relevant extensions.

For example

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseUnityServiceProvider() //<---- Add this line
.ConfigureContainer<IUnityContainer>( container => {
container.RegisterType<IService, ConcreteServiceA>("MyString");

container.RegisterType<IStrategy, Strategy>(
new InjectionConstructor(
new ResolvedArrayParameter<IService>(
new ResolvedParameter<IService>("MyString")
)
));
})
//...

Conditionally inject dependency using Unity

Tell the container what dependency it should inject into RenewalController

container.RegisterType<RenewalController>(new InjectionConstructor(
new ResolvedParameter<RenewalService>(),
new ResolvedParameter<IRenewalTypedFactory>()));

During constructor injection can the Unity DI Container recognize the type that is being constructed, and use it when resolving a reference?

You can define a name for each ILogger instance and use the [Dependency] attribute to specify which one you want. Example:

public static class Constants
{
public const string LOGGER_MYCLASS_UNITY_KEY = "ILogger<MyClass>";
}

In your Unity registration:

IUnityContainer container = new UnityContainer();
container.RegisterType<ILogger, Logger<MyClass>>(Constants.LOGGER_MYCLASS_UNITY_KEY,
new ContainerControllerLifetimeManager());

In the constructor of the class that needs the logger:

public class MyClass
{
private readonly ILogger<MyClass> _logger;

public MyClass([Dependency(Constants.LOGGER_MYCLASS_UNITY_KEY)] ILogger logger)
{
_logger = (ILogger<MyClass>)logger;
}
}

HTH.

C# Unity - change concrete implementation at runtime

Your question is fair enough. Resolving dependencies at runtime, based on some user input or configuration settings, is a well-known problem.
Mark Seemann devotes separate section of his great book Dependency Injection in .NET to this problem - "6.1 Mapping runtime values to abstractions".
And I can't fully agree with NightOwl888 that this is not a DI problem.

There are 2 main solutions for this problem.

First one, described by Mark Seeman in his book, is to have the factory that takes indication of selected implementation as parameter. Here is basic description how it works.

First of all, you should pass somehow which implementation you want to use. It could be just a string (e.g. "gmail", "yahoo"), but it's better to do it via enum:

public enum EmailTarget
{
Gmail,
Yahoo,
}

Then you should define the factory itself. In common, it will look like this:

public interface IEmailSenderFactory
{
IEmailSender CreateSender(EmailTarget emailTarget);
}

Then you should provide implementation for the factory. It could be as simple as:

public class EmailSenderFactory : IEmailSenderFactory
{
public IEmailSender CreateSender(EmailTarget emailTarget)
{
switch (emailTarget)
{
case EmailTarget.Gmail:
return new GmailEmailSender();

case EmailTarget.Yahoo:
return new YahooEmailSender();

default:
throw new InvalidOperationException($"Unknown email target {emailTarget}");
}
}
}

However, in more complex cases, instances of IEmailSender should also be created via DI container. In this case you could use factory based on IUnityContainer:

public class EmailSenderFactory : IEmailSenderFactory
{
private readonly IUnityContainer diContainer;

public EmailSenderFactory(IUnityContainer diContainer)
{
this.diContainer = diContainer;
}

public IEmailSender CreateSender(EmailTarget emailTarget)
{
switch (emailTarget)
{
case EmailTarget.Gmail:
return diContainer.Resolve<GmailEmailSender>();

case EmailTarget.Yahoo:
return diContainer.Resolve<YahooEmailSender>();

default:
throw new InvalidOperationException($"Unknown email target {emailTarget}");
}
}
}

Then you should adjust EmailSender and inject IEmailSenderFactory in it. Send() method should be extended with value of EmailTarget enum that specifies selected sender:

public class EmailSender
{
private readonly IEmailSenderFactory senderFactory;

public EmailSender(IEmailSenderFactory senderFactory)
{
this.senderFactory = senderFactory;
}

public void Send(EmailTarget emailTarget)
{
var sender = senderFactory.CreateSender(emailTarget);
sender.SendEmail();
}
}

The last thing is proper composition root:

IUnityContainer container = new UnityContainer();
container.RegisterType<IEmailSender, GmailEmailSender>("gmail");
container.RegisterType<IEmailSender, YahooEmailSender>("yahoo");
container.RegisterType<IEmailSenderFactory, EmailSenderFactory>();

And finally when you need to send e-mail:

var sender = container.Resolve<EmailSender>();
sender.Send(EmailTarget.Gmail);

The second approach is more simple. It doesn't use the factory and is based on Unity named dependencies. With this approach your classes could be left as is. Here is the composition root:

IUnityContainer container = new UnityContainer();
container.RegisterType<IEmailSender, GmailEmailSender>("gmail");
container.RegisterType<IEmailSender, YahooEmailSender>("yahoo");
container.RegisterType<EmailSender>("gmail", new InjectionConstructor(new ResolvedParameter<IEmailSender>("gmail")));
container.RegisterType<EmailSender>("yahoo", new InjectionConstructor(new ResolvedParameter<IEmailSender>("yahoo")));

Sender is created in the following way:

var emailSender = container.Resolve<EmailSender>("gmail");
emailSender.Send();

It's up to you to decide which of these approaches to use. Purists will say that the first one is better because you don't mix specific DI container with your application logic. Actually you do, if the factory is based on DI container, but it's concentrated in one place and could be easily replaced. The second approach is however much simpler and could be used for simplest scenarios.



Related Topics



Leave a reply



Submit