TIL #3 - Consuming interface implementations as an IEnumerable collection in .NET Core

Tonight I had a situation where I wanted to provide multiple different implementations of an interface via dependency injection in .NET Core 5. I've written about a LinkedIn solution I created a while ago, and tonight I was extending the solution to include Salesforce authentication.  

I am using ASP.NET Contrib's OAuth Providers to enable logging in with LinkedIn and was adding Salesforce to the application.  Part of the solution persists tokens to storage using an ITokenService defined for the persistence / refreshing.  The issue was when a user logged in to a specific OAuth provider, I wanted to use the correct ITokenService implementation somewhat dynamically.    

Steve Gordon's Dependency Injection in ASP.NET Core PluralSight course covers registering multiple interface implementations and the fact that when injecting a requested interface, the last registration wins.  This is not what I wanted, but I could not remember the ways to provide multiple implementations or whether there was a good way of handling which implementation should be used.  

I rewatched portions of Steve's PluralSight course and he has a sample using the IEnumerable<T> type as a dependency.  Steve's sample enables processing a collection of rules, but I wanted a single instance from the collection.  Using a bit of a factory pattern enables use of the User.Identity.AuthenticationType of the logged in user to then drive getting the correct ITokenService instance by providerName.

using System.Collections.Generic;
using System.Linq;
using Metrics.Core.Interfaces;

namespace Metrics.Core.Services
{
    public class TokenServiceProvider : ITokenServiceProvider
    {
        private readonly IEnumerable<ITokenService> _tokenServices;

        public TokenServiceProvider(IEnumerable<ITokenService> tokenServices)
        {
            _tokenServices = tokenServices;
        }

        public ITokenService GetTokenProvider(string providerName)
        {
            return _tokenServices.FirstOrDefault(x => x.ProviderName == providerName);
        }
    }
}

With the ITokenService implementations, and the ITokenServiceProvider registered during Startup, a controller can have the all the services injected for use based on the OAuth provider used.

  public class HomeController : Controller
    {
        private readonly IConfiguration _configuration;
        private readonly ILogger<HomeController> _logger;
        private readonly ITokenServiceProvider _tokenServiceProvider;
        

        public HomeController(ILogger<HomeController> logger, ITokenServiceProvider tokenServiceProvider, IConfiguration configuration)
        {
            Guard.Against.Null(logger, nameof(logger));
            Guard.Against.Null(tokenServiceProvider, nameof(tokenServiceProvider));
            Guard.Against.Null(configuration, nameof(configuration));

            _configuration = configuration;
            _logger = logger;
            _tokenServiceProvider = tokenServiceProvider;
        }
    //... content ellided
    }

Once the user is logged in, using the User.identity.AuthenticationType enables interacting with tokens as needed similar to the code below.

   if (User.Identity.IsAuthenticated)
            {
                var providerName = User.Identity.AuthenticationType;
                 try {
                    var token = _tokenServiceProvider.GetTokenProvider(providerName).GetCurrrentToken();
                    return View(token);
                }
                catch (RequestFailedException ex)
                {
                    _logger.LogError(ex.Message);
                }
            }

I would be curious if there are other or better ways to handle this. Perhaps there is something standard in the ASP.NET Core token management that should be used? If you know of a better way, please let me know.

Tweet Post Update Email

My name is Pete Skelly. I write this blog. I am the VP of Technology at ThreeWill, LLC in Alpharetta, GA.

Tags:
til aspnet c# core dotnet oauth
comments powered by Disqus