ASP.NET Core: Abstracting External Login Providers with Autofac and the Factory Design Pattern

I’ve been implementing external login providers for the Humanitarian Toolbox’s AllReady humanitarian and disaster response open source project. I’ve already blogged about a mishap I had with User Secrets and a deep-dive on how to get email and name from Twitter’s external login provider.

In this blog post, I want to show the design and implementation changes I made to the surrounding codebase while implementing each of the external login providers. I’ll show you how I started with switch statements in a controller action method, slowly refactored to decompose, encapsulate and abstract the implementation of the different external login providers, and finally, how I wired it all up using the factory design pattern and Autofac.

And we’re off!


The First Attempt (Keeping it Local in the Action Method)

In AllReady, the external login work is handled by three action methods on the AccountController:

  • ExternalLogin
  • ExternalLoginCallback
  • ExternalLoginConfirmation

So, starting on the login screen:

LoginToAllReady

Clicking on any of the service logins will result in a request to the ExternalLogin action method.

This action method invokes SignInManager’s ExternalLoginSignInAsync method and returns a new ChallengeResult()

var redirectUrl = Url.Action(new UrlActionContext { Action = nameof(ExternalLoginCallback), Values = new { ReturnUrl = returnUrl }});  
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);  
return new ChallengeResult(provider, properties);  

The ExternalLogin action method then redirects to the ExternalLoginCallback action method, which depending on the external login provider you picked on the login screen, will attempt to populate four pieces of information that are required to register with the AllReady site.

  • First Name
  • Last Name
  • Email
  • Phone Number

RegisterWithGoogle

This page will attempt to fill in those fields depending on the external login provider that has been picked.

But there is a problem.

Each external login provider has different Claims it uses to try to retrieve those four pieces of information. For example, Microsoft and Facebook only provide the ability to get at the first and last name via a “Name” claim:

var name = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Name);  

In order to get the first name separated from the last name, I split the Name claim on a space and “hope” I get a first name and last name value makes sense:

var name = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Name);  
if (string.IsNullOrEmpty(name))  
{  
    return Task.FromResult(externalUserInformation);  
}

var array = name.Split(‘ ‘);  
if (array.Length < 2)  
{  
    return Task.FromResult(externalUserInformation);  
}

externalUserInformation.FirstName = array[0];  
externalUserInformation.LastName = array[1];

return Task.FromResult(externalUserInformation);  

Google populates both a GivenName and Surname claim, so grabbing this information to show on the UI is very easy (thanks for being awesome Google):

var externalUserInformation = new ExternalUserInformation  
{  
    Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email),  
    FirstName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.GivenName),  
    LastName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Surname)  
};

return Task.FromResult(externalUserInformation);  

Twitter … well, Twitter doesn’t make it easy to get at ANY of this information. In order to get at the user’s email, first or last name, read my blog post on the implementation. I’ll provide a very brief code snippet here as there are some strange claims that need to be accessed:

var externalUserInformation = new ExternalUserInformation();

var userId = externalLoginInfo.Principal.FindFirstValue("urn:twitter:userid");  
var screenName = externalLoginInfo.Principal.FindFirstValue("urn:twitter:screenname");

//(then a whole bunch of code dealing with LinqToTwitter…)  

My initial code to pick the correct Claim(s) from which to pull the information for the Register UI in the ExternalLoginCallback action method ended up looking like this:

if (externalLoginInfo.LoginProvider == "Google")  
{  
    //access the claims to get email, first name and last name  
}

if (externalLoginInfo.LoginProvider == "Facebook" || externalLoginInfo.LoginProvider == "Microsoft")  
{  
    //access the claims to get email, first name and last name  
}

if (externalLoginInfo.LoginProvider == "Twitter")  
{  
    //access the claims to get email, first name and last name  
}  

I would need to add another “if” block for each external login provider we wanted to add in the future. Seeing how this violated SRPand OCP, I immediately started to think how I could abstract away the retrieval of the needed values for each different external login provider from from the ExternalLoginCallback action method.

A factory was the first thing that came to mind.


Refactoring #1 (Factory to the Rescue)

Who doesn’t love the Factory pattern? It’s been around for ages, it’s one of the most easily understood design patterns out there and this scenario fit the usage well.

To implement the factory, I needed a contract I could use across external login providers to retrieve email, first name and last name, the factory class itself and the code in the factory class that would create the appropriate external login provider for the provider’s name.

To start with, here’s the contract:

public interface IProvideExternalUserInformation  
{  
    Task<ExternalUserInformation> GetExternalUserInformation(ExternalLoginInfo externalLoginInfo);  
}

public class ExternalUserInformation  
{  
    public string FirstName { get; set; }  
    public string LastName { get; set; }  
    public string Email { get; set; }  
}  

ExternalUserInformation is a “DTO” that will contain the information the UI needs.

Now I needed to break out each external login provider implementation (currently sitting in the ExternalLoginCallback action method as conditional statements) into a separate class that implements IProvideExternalUserInformation.

public class GoogleExternalUserInformationProvider : IProvideExternalUserInformation  
{  
    public Task<ExternalUserInformation> GetExternalUserInformation(ExternalLoginInfo externalLoginInfo)  
    {  
        var externalUserInformation = new ExternalUserInformation  
        {  
             Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email),  
             FirstName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.GivenName),  
             LastName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Surname)  
        };  
        return Task.FromResult(externalUserInformation);  
    }  
}

public class MicrosoftAndFacebookExternalUserInformationProvider : IProvideExternalUserInformation  
{  
    public Task<ExternalUserInformation> GetExternalUserInformation(ExternalLoginInfo externalLoginInfo)  
    {  
        var externalUserInformation = new ExternalUserInformation { Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) };  
        var name = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Name);  
        if (string.IsNullOrEmpty(name))  
        {  
            return Task.FromResult(externalUserInformation);  
        }

        var array = name.Split(‘ ‘);  
        if (array.Length < 2)  
        {  
            return Task.FromResult(externalUserInformation);  
        }  
        externalUserInformation.FirstName = array[0];  
        externalUserInformation.LastName = array[1];

        return Task.FromResult(externalUserInformation);  
    }  
}  

(I’m leaving the TwitterExternalLoginProvider out of this example for now. We’ll pick it back up later in this blog post)

Now that I have my contract and external login providers classes, I need a factory class to to instantiate them:

public class ExternalUserInformationProviderFactory : IExternalUserInformationProviderFactory  
{  
    public IProvideExternalUserInformation GetExternalUserInformationProviderFor(string loginProvider)  
    {  
        switch (loginProvider)  
        {  
            case "Facebook":  
                return new MicrosoftAndFacebookExternalUserInformationProvider();  
            case "Google":  
                return new GoogleExternalUserInformationProvider();  
            case "Microsoft":  
                return new MicrosoftAndFacebookExternalUserInformationProvider();  
            case "Twitter":  
                return new TwitterExternalUserInformationProvider();  
        }   
        return null;  
    }  
}  

Two things to note here.

  • I have one class that will retrieve the needed information for both Microsoft and Facebook, because these two providers work the exact same way.
  • The factory itself implements an interface, IExternalUserInformationProviderFactory.
public interface IExternalUserInformationProviderFactory  
{  
    IProvideExternalUserInformation GetExternalUserInformationProvider(string loginProvider);  
}  

The reason the factory implements an interface is to allow it to be injected into AccountController for the ExternalLoginCallback action method to use. After adding the IExternalUserInformationProviderFactory to AccountController’s constructor:

public AccountController(IExternalUserInformationProviderFactory externalUserInformationProviderFactory)  
{  
    _externalUserInformationProviderFactory = externalUserInformationProviderFactory;  
}  

I changed my code in the ExternalLoginCallback action method to delegate retrieving the Registration information to the provider returned by the injected external user information provider factory:

public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null)  
{  
…  
    var externalUserInformationProvider = _externalUserInformationProviderFactory.GetExternalUserInformationProvider(externalLoginInfo.LoginProvider);  
    var externalUserInformation = await externalUserInformationProvider.GetExternalUserInformation(externalLoginInfo);

then I populate the ViewModel with the values from the returned ExternalUserInformation and return it to the view:

return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel  
{  
    Email = externalUserInformation.Email,  
    FirstName = externalUserInformation.FirstName,  
    LastName = externalUserInformation.LastName,  
    ReturnUrl = returnUrl,  
    LoginProvider = externalLoginInfo.LoginProvider  
});  

Lastly, I registered the factory with ASP.NET Core’s built-in container in Startup.cs as a singleton so it could be injected into AccountController at run-time.

private IContainer CreateIoCContainer(IServiceCollection services)  
{  
    services.AddSingleton<IExternalUserInformationProviderFactory, ExternalUserInformationProviderFactory>();  
 }  

I ran the code and it worked.

I had successfully encapsulated the different external login provider implementations in different classes and provided AccountController with an injected factory that would allow it to get a hold of the correct provider by passing it the provider name (“Google”, “Facebook”, “Microsoft”, etc…)

All was right with the world, until I had to finish implementing the Twitter external login provider.


Refactoring #2 (Twitter Provider Requires A Singleton Dependency)

At this point in the refactoring, I was still in the process of figuring out/finalizing the Twitter provider. I won’t show you the whole provider here (you can check it out directly on AllReady’s GitHub), but the provider needed four pieces of information that were being kept in the config.json file.

  • ConsumerKey
  • ConsumerSecret
  • OAuthToken
  • OAuthSecret

I needed to get a hold of these values because the Twitter provider used LinqToTwitter to make an additional HTTP call after the initial authentication request had succeeded to get the Name and Email claim. The code looked like this:

public class TwitterExternalUserInformationProvider : IProvideExternalUserInformation  
{  
    public async Task<ExternalUserInformation> GetExternalUserInformation(ExternalLoginInfo externalLoginInfo)  
    {  
    …  
        var authTwitter = new SingleUserAuthorizer  
        {  
            CredentialStore = new SingleUserInMemoryCredentialStore  
            {  
                ConsumerKey = //need this value from config.json file  
                ConsumerSecret = //need this value from config.json file,  
                OAuthToken = //need this value from config.json file,  
                OAuthTokenSecret = //need this value from config.json file,  
            }  
        };

        await authTwitter.AuthorizeAsync();  
        var twitterCtx = new TwitterContext(authTwitter);  
        …  
    }  
}  

For those of you new to ASP.NET Core, you are no longer able to access configuration values statically (more information about this, see my post ASP.NETCore: User Secrets, Secret Manager and External Logins, oh my!).

To get at configuration values in your code, you need to inject IOptions into the class that needs the configuration values where T is a strongly typed configuration class that “wraps” the values in config.json (for more information on strongly typed configuration classes in ASP.NET Core, please see this post).

I created a strongly typed configuration class to wrap the values in config.json for the Twitter provider.

public class TwitterAuthenticationSettings  
{  
    public string ConsumerKey { get; set; }  
    public string ConsumerSecret { get; set; }  
    public string OAuthToken { get; set; }  
    public string OAuthSecret { get; set; }  
}  

and injected IOptions into TwitterExternalInformationProvider so I could pull those values from configuration:

public TwitterExternalUserInformationProvider(IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings)  

But now I had a problem.

Since my factory was instantiating TwitterExternalUserInformationProvider, I needed a way to provide an instance of IOptions to it. The simplest thing that worked was injecting IOptions into the factory class’s constructor. So my factory class now looked like this:

public class ExternalUserInformationProviderFactory : IExternalUserInformationProviderFactory  
{  
    private readonly IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings;

    public ExternalUserInformationProviderFactory(IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings)  
    {  
        this.twitterAuthenticationSettings = twitterAuthenticationSettings;  
    }

    public IProvideExternalUserInformation GetExternalUserInformationProviderFor(string loginProvider)  
    {  
        switch (loginProvider)  
        {  
            case "Facebook":  
                return new FacebookExternalUserInformationProvider();  
            case "Google":  
                return new GoogleExternalUserInformationProvider();  
            case "Microsoft":  
                return new MicosoftExternalUserInformationProvider();  
            case "Twitter":  
                return new TwitterExternalUserInformationProvider(twitterAuthenticationSettings);  
       }  
    return null;
    }  
}  

Since IOptions are scoped as singletons by ASP.NET Core’s built-in dependency injection, I added my strongly typed configuration class in Startup.cs:

services.Configure<TwitterAuthenticationSettings>(Configuration.GetSection("Authentication:Twitter"));  

I then ran the code and the injection worked.

But it bothered me that I was passing in a dependency to the factory class that was only being utilized by one of the providers. That seemed to be a bit of a “wonky” workaround, but the code I had at this time worked.

This solution ended up breaking down when through further refactoring, I had to add another dependency to TwitterExternalUserInformationProvider. But this time, I needed to scope the dependency as a transient.


Refactoring #3 (Twitter Provider Requires a Transient Dependency)

In TwitterExternalUserInformationProvider, I was doing some work with claims:

…  
var externalUserInformation = new ExternalUserInformation();  
var userId = externalLoginInfo.ExternalPrincipal.FindFirstValue("urn:twitter:userid");  
var screenName = externalLoginInfo.ExternalPrincipal.FindFirstValue("urn:twitter:screenname");  
…  

invoking the LinqToTwitter API directly:

var authTwitter = new SingleUserAuthorizer  
{  
    CredentialStore = new SingleUserInMemoryCredentialStore  
    {  
        ConsumerKey = twitterAuthenticationSettings.Value.ConsumerKey,  
        ConsumerSecret = twitterAuthenticationSettings.Value.ConsumerSecret,  
        OAuthToken = twitterAuthenticationSettings.Value.OAuthToken,  
        OAuthTokenSecret = twitterAuthenticationSettings.Value.OAuthSecret, 
            UserID = ulong.Parse(userId), ScreenName = screenName  
    }  
};

await authTwitter.AuthorizeAsync();

var twitterCtx = new TwitterContext(authTwitter);

var verifyResponse = await (from acct in twitterCtx.Account  
    where (acct.Type == AccountType.VerifyCredentials) 
    && (acct.IncludeEmail == true) 
    select acct).SingleOrDefaultAsync();  

and finally using the data returned via LinqToTwitter to populate ExternalUserInformation:

…  
if (verifyResponse != null && verifyResponse.User != null)  
{  
    var twitterUser = verifyResponse.User;  
    if (twitterUser != null)  
    {  
        externalUserInformation.Email = twitterUser.Email;

        if (!string.IsNullOrEmpty(twitterUser.Name))  
        {  
            var array = twitterUser.Name.Split(‘ ‘);  
            if (array.Length > 1)  
            {  
                externalUserInformation.FirstName = array[0];  
                externalUserInformation.LastName = array[1];  
            }  
       }  
     }  
}

return externalUserInformation;  

In short, TwitterExternalUserInformationProvider was doing a lot of work.

Upon closer inspection, I saw this class was violating SRP. I really felt the friction with the class when I went to write a unit test and found I could not write it because of the hard dependency on LinqToTwitter.

As with most things in software development, adding another layer of abstraction helped me remove the LinqToTwitter dependency out of TwitterExternalUserInformationProvider. I did this by creating an abstraction for LinqToTwitter and moving the implementation to a separate class.

Here is the abstraction:

public interface ITwitterRepository  
{  
    Task<Account> GetTwitterAccount(string userId, string screenName);  
}  

and here is the implementation:

public class TwitterRepository : ITwitterRepository  
{  
    private readonly IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings;

    public TwitterRepository(IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings)  
    {  
        this.twitterAuthenticationSettings = twitterAuthenticationSettings;  
    }

    public async Task<Account> GetTwitterAccount(string userId, string screenName)  
    {  
        var authTwitter = new SingleUserAuthorizer  
        {  
            CredentialStore = new SingleUserInMemoryCredentialStore  
            {  
                ConsumerKey = twitterAuthenticationSettings.Value.ConsumerKey,  
                ConsumerSecret = twitterAuthenticationSettings.Value.ConsumerSecret,  
                OAuthToken = twitterAuthenticationSettings.Value.OAuthToken,  
                OAuthTokenSecret = twitterAuthenticationSettings.Value.OAuthSecret,  
                UserID = ulong.Parse(userId),  
                ScreenName = screenName  
            }  
        };

        await authTwitter.AuthorizeAsync();

        var twitterCtx = new TwitterContext(authTwitter);

        var account = await (from acct in twitterCtx.Account 
            where (acct.Type == AccountType.VerifyCredentials) 
            && (acct.IncludeEmail == true)  
            select acct).SingleOrDefaultAsync();

        return account;  
     }  
}  

Now the TwitterExternalUserInformationProvider class delegated the call to LinqToTwitter via the injected ITwitterRepository :

public class TwitterExternalUserInformationProvider : IProvideExternalUserInformation  
{  
    private readonly ITwitterRepository twitterRepository;

    public TwitterExternalUserInformationProvider(ITwitterRepository twitterRepository)  
    {  
        this.twitterRepository = twitterRepository;  
    }

    public async Task<ExternalUserInformation> GetExternalUserInformation(ExternalLoginInfo externalLoginInfo)  
    {  
        var externalUserInformation = new ExternalUserInformation();  
        var userId = externalLoginInfo.Principal.FindFirstValue("urn:twitter:userid");  
        var screenName = externalLoginInfo.Principal.FindFirstValue("urn:twitter:screenname");

        var twitterAccount = await twitterRepository.GetTwitterAccount(userId, screenName);

        if (twitterAccount != null && twitterAccount.User != null)  
        {  
            var twitterUser = twitterAccount.User;  
            externalUserInformation.Email = twitterUser.Email;

            if (!string.IsNullOrEmpty(twitterUser.Name))  
            {  
                var array = twitterUser.Name.Split(‘ ‘);  
                if (array.Length > 1)  
                {  
                    externalUserInformation.FirstName = array[0];  
                    externalUserInformation.LastName = array[1];  
                }  
            }  
        }  
        return externalUserInformation;  
    }  
}  

Much better! I was no longer violating SRP.

But now I faced the challenge of how to get a transient instance of ITwitterRepository injected into TwitterExternalUserInformationProvider. I initially set out to inject it via ExternalUserInformationProviderFactory (like I did for IOptions) and then pass it to the instantiation of the twitter provider, but the ITwitterRepository needed to be scoped as transient and my factory was scoped as a singleton.

How was I going to provide a transient scoped dependency to a class that itself, was also being instantiated by the container, but scoped as singleton?

The encapsulation of the LinqToTwitter code in TwitterRepository was the right thing to do,  but I needed a mechanism that could resolve the correct type at run-time that did NOT rely on a singleton scoped factory.

Enter Autofac’s IComponentContext…


Refactoring #4 (Autofac and Factory, Take 1)

What I needed was a way for the container to resolve the correct provider instance at run-time instead of my singleton-scoped factory class creating the instances. I started to poke around to see how this could be accomplished and discovered I could inject Autofac’s IComponentContext into the factory.

Injecting IComponentContext delegated the resolution of each provider to the Autofac container instead of my factory using the “new” keyword. Here is the new factory code:

public class ExternalUserInformationProviderFactory : IExternalUserInformationProviderFactory  
{  
    private readonly IComponentContext autofacContainer;

    public ExternalUserInformationProviderFactory(IComponentContext autofacContainer)  
    {  
        this.autofacContainer = autofacContainer;  
    }

    public IProvideExternalUserInformation GetExternalUserInformationProvider(string loginProvider)
    {  
        switch (loginProvider)  
        {  
            case "Facebook":  
                return autofacContainer.Resolve<MicrosoftAndFacebookExternalUserInformationProvider>();  
            case "Microsoft":  
                return autofacContainer.Resolve<MicrosoftAndFacebookExternalUserInformationProvider>(); ;  
            case "Google":  
                return autofacContainer.Resolve<GoogleExternalUserInformationProvider>();  
            case "Twitter":  
                return autofacContainer.Resolve<TwitterExternalUserInformationProvider>();  
        }  
        return null;  
    }  
}  

I was no longer injecting dependencies into the factory that were being used by only the TwitterExternalUserInformationProvider and I was no longer instantiating the providers. Autofac was doing the work for me.

Another nice side effect of this change was dependencies were only being injected into the classes that needed them, rather my code passing dependencies through multiple class constructors. TwitterRepository now only needed IOptions injected:

public class TwitterRepository : ITwitterRepository  
{  
    private readonly IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings;

    public TwitterRepository(IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings)  
    {  
        this.twitterAuthenticationSettings = twitterAuthenticationSettings;  
    }

    public async Task<Account> GetTwitterAccount(string userId, string screenName)  
    {  
        var authTwitter = new SingleUserAuthorizer  
        {  
            CredentialStore = new SingleUserInMemoryCredentialStore  
            {  
                ConsumerKey = twitterAuthenticationSettings.Value.ConsumerKey,  
                ConsumerSecret = twitterAuthenticationSettings.Value.ConsumerSecret,                           
                OAuthToken = twitterAuthenticationSettings.Value.OAuthToken,                          
                OAuthTokenSecret = twitterAuthenticationSettings.Value.OAuthSecret,  
                UserID = ulong.Parse(userId),  
                ScreenName = screenName  
            }  
       };

       await authTwitter.AuthorizeAsync();

       var twitterCtx = new TwitterContext(authTwitter);

        var account = await (from acct in twitterCtx.Account 
            where (acct.Type == AccountType.VerifyCredentials) 
            && (acct.IncludeEmail == true)  
            select acct).SingleOrDefaultAsync();

        return account;  
        }  
    }  
}  

TwitterExternalUserInformationProvider only needed ITwitterRepository injected:

public class TwitterExternalUserInformationProvider : IProvideExternalUserInformation  
{  
    private readonly ITwitterRepository twitterRepository;

    public TwitterExternalUserInformationProvider(ITwitterRepository twitterRepository)  
    {  
        this.twitterRepository = twitterRepository;  
    }

    public async Task<ExternalUserInformation> GetExternalUserInformation(ExternalLoginInfo externalLoginInfo)  
    {  
        var externalUserInformation = new ExternalUserInformation();  
        var userId = externalLoginInfo.Principal.FindFirstValue("urn:twitter:userid");  
        var screenName = externalLoginInfo.Principal.FindFirstValue("urn:twitter:screenname");  
        var twitterAccount = await twitterRepository.GetTwitterAccount(userId, screenName);

        if (twitterAccount != null && twitterAccount.User != null)  
        {  
            var twitterUser = twitterAccount.User;  
            externalUserInformation.Email = twitterUser.Email;

            if (!string.IsNullOrEmpty(twitterUser.Name))  
            {  
                var array = twitterUser.Name.Split(‘ ‘);  
                if (array.Length > 1)  
                {  
                    externalUserInformation.FirstName = array[0];  
                    externalUserInformation.LastName = array[1];  
                }  
         }  
    }

    return externalUserInformation;  
    }  
}  

ExternalUserInformationProviderFactory was still being injected into AccountController’s constructor:

public class AccountController : Controller  
{  
    public AccountController(IExternalUserInformationProviderFactory externalUserInformationProviderFactory)  
    {  
        _externalUserInformationProviderFactory = externalUserInformationProviderFactory;  
    }  
}  

The big difference is how all this was registered in Startup.cs. The code used Autofac directly instead of using ASP.NET Core’s built-in container:

var containerBuilder = new ContainerBuilder();  
…  
containerBuilder.Register<IExternalUserInformationProviderFactory>(c =>  
    new ExternalUserInformationProviderFactory(c.Resolve<IComponentContext>()));  
containerBuilder.RegisterType<TwitterExternalUserInformationProvider>();  
containerBuilder.RegisterType<GoogleExternalUserInformationProvider>();  
containerBuilder.RegisterType<MicrosoftAndFacebookExternalUserInformationProvider>();  

Note the registration of ExternalUserInformationProviderFactory. It uses Resolve to give itself an instance of IComponentContext.

I ran the code, and it worked.

After stepping back and taking a look, it dawned on me that my factory code was now using Service Locator. I again, set out to see if there was a way to make this better.


Refactoring #5 (Autofac and Factory, Take 2)

Although the code was running, I could potentially run into scoping issues letting Autofac create the provider instances via IComponentContext. Also, I am using Service Locator here, which isn’t the biggest deal, considering the fact that the factory itself is an abstraction of how to retrieve the correct external login provider. I still didn’t like the fact that I was using Service Locator.

After further research through Autofac’s docs and on Stackoverflow, it appears Autofac has a way to get rid of the Service Locator pattern by using IIndex.

The simplest way to think of IIndex is as an IDictionary. The “K” represents the name of the provider (in this case, a string that says: “Google”, “Facebook”, etc…) and the “V” is the value to return. In this case, “V” is the correct implementation of IProvideExternalUserInformation interface for the given provider name.

What this effectively does is move the registration out of ExternalUserInformationProviderFactory and into the container registration code. So, my factory now looks like this:

public class ExternalUserInformationProviderFactory : IExternalUserInformationProviderFactory  
{  
    private readonly IIndex<string, IProvideExternalUserInformation> providers;

    public ExternalUserInformationProviderFactory(IIndex<string, IProvideExternalUserInformation> providers)  
    {  
        this.providers = providers;  
    }

    public IProvideExternalUserInformation GetExternalUserInformationProvider(string loginProvider)  
    {  
        IProvideExternalUserInformation provider;  
        if (!providers.TryGetValue(loginProvider, out provider))  
        {  
            throw new ApplicationException($"could not resolve external user information provider for login provider: {loginProvider}");  
        }  
        return provider;  
    }  
}  

Note that the case statements are gone. We instead use a dictionary-based approach via TryGetValue to retrieving the correct provider.

But where does mapping of the provider name (string) to the provider implementation live? It used to live in the factory as a bunch of switch statements, but that information has moved to Startup.cs:

 containerBuilder.RegisterType<TwitterExternalUserInformationProvider>().Named<IProvideExternalUserInformation>("Twitter");  
 containerBuilder.RegisterType<GoogleExternalUserInformationProvider>().Named<IProvideExternalUserInformation>("Google");  
 containerBuilder.RegisterType<MicrosoftAndFacebookExternalUserInformationProvider>().Named<IProvideExternalUserInformation>("Microsoft");  
 containerBuilder.RegisterType<MicrosoftAndFacebookExternalUserInformationProvider>().Named<IProvideExternalUserInformation>("Facebook");  
 containerBuilder.RegisterType<ExternalUserInformationProviderFactory>().As<IExternalUserInformationProviderFactory>();  

Here you can see we’re using “.Named(string providerName)”, where T is the interface implemented by all external login providers and “providerName” is a string that represents the provider (“Google”, “Microsoft”, etc…). Through these registrations along with the use of IIndex in the factory class, Autofac figures out the right instance to provide.

A nice benefit of this change is when we need to add another provider, we only need to add another line to the configuration instead of opening up the factory code.

Although we’re no longer using Service Locator, we have tied our factory to Autofac-specific code (IIndex). Some Composition Root purists my scoff at me here and see this solution as no better than the first solution that injected Autofac’s IComponentContext into the factory, but I beg to differ.

The real question to be asked here is not rooted in “best practices”, but “what is a better trade-off”?

The answer is since this approach can manage scope better, I choose this approach over the version of the factory using Service Locator. Yes, my factory is still cognizant of the Autofac library, but at least I’m no longer using Service Locator.

After all, it’s all about trade-off’s 😉


In Closing

Through multiple refactorings, you’ve seen my solution of abstracting external login providers go through fairly large changes. Some take-aways from these refactorings:

  • your first “solution” to  a problem is rarely the final solution
  • focusing on the SOLID principles and unit testability forces us to be honest about our design
  • when you start feeling friction with your solution, whether it’s via unit testing, the API, etc… it can help to go back to the drawing board and see if you can find a better way
  • don’t make implementation choices blindly.  Always examine the trade-offs.

Thanks

Michael McCarthy

Read more posts by this author.