6

I have AuthenticationStrategy class which I'm going to inject in controller constructor.

I have two IAuthenticationProviders: InternalAuthenticationProvider and ExternalAuthenticationProvider.
In AuthenticationStrategy constructor I want to inject all providers.
Sample code:

public class AuthenticationStrategy
{
    private readonly Dictionary<string, IAuthenticationProvider> _authenticationProviders;

    public AuthenticationStrategy(IAuthenticationProvider[] authenticationProviders)
    {
        if (authenticationProviders == null)
        {
            throw new ArgumentNullException("AuthenticationProviders");
        }

        _authenticationProviders = authenticationProviders
            .ToDictionary(x => nameof(x), x => x);
    }
}

How can I inject multiple providers using depency injection? Sample code:

services.AddScoped<IAuthenticationProvider, InternalAuthenticationProvider>();
services.AddScoped<IAuthenticationProvider, ExternalAuthenticationProvider>();
services.AddScoped<AuthenticationStrategy>();

Any ideas?

VMAtm
  • 27,943
  • 17
  • 79
  • 125
Algirdas
  • 1,065
  • 1
  • 9
  • 21

5 Answers5

2

I think that storing the Dictionary inside your strategy is quite a code smeel, as it looks like an anti-pattern Service Locator. You probably need to introduce the factory for your authentication providers based on key. This is a desired approach in .Core dependency injection, however you can use other IoC containers with similar features (named dependencies, for example).

So, your code could be like this:

public enum AuthType
{
    Internal,
    External,
}

public interface IAuthenticationProviderResolver
{
    IAuthenticationProvider GetAuthByType(AuthType type);
}

public class ProviderResolver : IAuthenticationProviderResolver
{
    private readonly IServiceProvider _serviceProvider;

    public RepositoryResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IAuthenticationProvider GetAuthByName(AuthType type)
    {
         switch (type) 
         {
             case AuthType.Internal:
                 return _serviceProvider.GetService<InternalAuthenticationProvider>();
             case AuthType.External:
                 return _serviceProvider.GetService<ExternalAuthenticationProvider>();
             default:
                 throw new ArgumentException("Unknown type for authentication", nameof(type))
         }
    }
}

Now all you need is to register your classes as usual:

services.AddSingleton<IAuthenticationProviderResolver, ProviderResolver>();
services.AddScoped<InternalAuthenticationProvider>();
services.AddScoped<ExternalAuthenticationProvider>();
services.AddScoped<AuthenticationStrategy>();

And usage inside the strategy:

public class AuthenticationStrategy
{
    private readonly IAuthenticationProviderResolver _resolver;

    public AuthenticationStrategy(IAuthenticationProviderResolver resolver)
    {
        if (resolver== null)
        {
            throw new ArgumentNullException("Provider Resolver");
        }

        _resolver = resolver;
    }

    public void MakeDecision()
    {
        _resolver.GetAuthByType(authType).Authenticate();
    }
}
Community
  • 1
  • 1
VMAtm
  • 27,943
  • 17
  • 79
  • 125
  • 2
    2 Things: 1) Using name (string) is loosely typed in IAuthenticationProvider GetAuthByName(string name), im not sure if its a better approach. 2) What if there are more than 2 concrete implementations of IAuthenticationProvider? - Thanks in advance! – Yawar Murtaza Apr 04 '17 at 08:16
  • Named dependencies is a common approach, and is quite similar with a `Distionary`. You still can define your own signature, with more arguments or [with a enum](https://lostechies.com/seanchambers/2009/08/11/refactoring-day-11-switch-to-strategy/) – VMAtm Apr 04 '17 at 12:32
  • @YawarMurtaza updated to `enum`, as it's more type-safe approach, and added some `switch`. If you have some more thoughts regarding the solution, please share it. For me, it probably better to switch off to some other IoC container rather than native. – VMAtm Apr 04 '17 at 14:34
  • That makes it strongly typed which is good and answers my first point. Now what if we have another implementation of IAuthenticationProvider, we will have to edit this class to add another switch-case statement and add another enum value which violates open-close principle. – Yawar Murtaza Apr 04 '17 at 15:28
  • @YawarMurtaza I disagree with you here. Strategy should define the `AuthType`, and resolver should provide authentication needed by that type. If we'll store all of that inside strategy, after some time it will be like `God` object. Still think that some third-party IoC is more freferrable for this rather than such workaround – VMAtm Apr 04 '17 at 15:48
  • Interesting. Could you please post some references in support of your idea? – Yawar Murtaza Apr 04 '17 at 16:53
  • @YawarMurtaz that's only my understanding, based on Mark Seaman' book `DI in .Net`, with samples about named dependencies. – VMAtm Apr 04 '17 at 18:42
1

One option would be to make AuthenticationStrategy generic. Then you could differ with type

config.Scan(assembly =>
{
    assembly.AssemblyContainingType(typeof(AuthenticationProvider));
    assembly.ConnectImplementationsToTypesClosing(typeof(IAuthenticationProvider<>));
});

Code above scans the dll so you also don't have to register each and every one.

Thomas Koelle
  • 3,416
  • 2
  • 23
  • 44
1

If you're sticking with the OOTB dependency injection setup i.e. not using a third party container then one option would be to be explicit in your constructor args like this:

public class AuthenticationStrategy
{
    public AuthenticationStrategy(
        IInternalAuthenticationProvider internal,
        IExternalAuthenticationProvider external)
    {
        ...
    }
}

IInternalAuthenticationProvider and IExternalAuthenticationProvider interfaces are nothing more than marker interfaces like so:

public interface IInternalAuthenticationProvider : IAuthenticationProvider { }
public interface IExternalAuthenticationProvider : IAuthenticationProvider { }

So your DI setup will now look like this:

services.AddScoped<IInternalAuthenticationProvider , InternalAuthenticationProvider>();
services.AddScoped<IExternalAuthenticationProvider , ExternalAuthenticationProvider>();
services.AddScoped<AuthenticationStrategy>();
Naeem Sarfraz
  • 7,360
  • 5
  • 37
  • 63
  • This is a solution that I'm using right now. But still I want to get rid of explicit arguments in constructor because it could be more concrete providers in the future. – Algirdas Apr 04 '17 at 08:26
1

Assuming you are using Asp.Net Core project type with Visual Studio 2017

Lets say you have the following defination of an interface:

   public interface IAuthenticationProvider 
    {
    }

with implementing classes like so:

public class WindowsAuthentication : IAuthenticationProvider { }


public class NTMLAuthentication : IAuthenticationProvider { }


public class KerberosAuthentication : IAuthenticationProvider { }


public class CustomAuthentication : IAuthenticationProvider { }

So far so good. Now to resolve the dependencies for the types implementing the same interface I would use the custom resolver class with its interface:

public interface IAuthenticationResolver
{
    IAuthenticationProvider GetProvider(Type type);
}

and its implementation:

public class AuthenticationResolver : IAuthenticationResolver
    {
        private readonly IServiceProvider services;
        public AuthenticationResolver(IServiceProvider services)
        {
            this.services = services;
        }

        public IAuthenticationProvider GetProvider(Type type)
        {            
            return this.services.GetService(type) as IAuthenticationProvider;
        }
    }

In your Startup class, under ConfigureServices register these types

 services.AddTransient<IAuthenticationResolver, AuthenticationResolver>();
            services.AddTransient<WindowsAuthentication>();
            services.AddTransient<KerberosAuthentication>();
            services.AddTransient<NTMLAuthentication>();
            services.AddTransient<CustomAuthentication>();

Of course you can use Scopped if thats what you need.

Once this is all set, go back to your controller / client class where the dependencies are injected:

 public class HomeController : Controller
 {
        private readonly Dictionary<string, IAuthenticationProvider> authProvidersDictionary;

        public HomeController(IAuthenticationResolver resolver)
        {
            System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly();
            this.authProvidersDictionary = new Dictionary<string, IAuthenticationProvider>();

            foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes)
            {
                if (ti.ImplementedInterfaces.Contains(typeof(IAuthenticationProvider)))
                {                   

                    this.authProvidersDictionary.Add(ti.Name, resolver.GetProvider(ti.UnderlyingSystemType));
                }
            }            
        }
}

Hope this helps!

Yawar Murtaza
  • 3,655
  • 5
  • 34
  • 40
0

The question is in itself an anti-pattern example of dependency injection being the "Golden Hammer" tool of choice for .net core.

Your class should be able to access the two authentication providers without the disjoint code of dependency injection.

Disjoint code in .net dependency injection:

  • Registering the injected object in the application's startup
  • Object construction of injected object separated from that object's implementation in an anonymous method
  • Object construction never directly called by the rest of the .net core's application code. The .net core framework calls the constructor.

A junior fresh out of school developer should be able to look at any line in any method and quickly find how that method is called, where the method is called and why the method is called -without having to know dozens (hundreds?) of small micro-features of the .net core framework.

Unit testing with mocked classes is easily achievable in ways other than using dependency injection.

JohnT
  • 69
  • 4