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

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

This is the second post in a series on Abstracting External Login Providers with Autofac and the Factory Design Pattern

  • Part 1: Keeping it Local in the Action Method
  • Part 2: Factory to the Rescue
  • Part 3: Twitter Provider
  • Part 4: Autofac and Factory

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.

Show Comments