This is the final 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

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 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.

Final Solution

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<K,V>.

The simplest way to think of IIndex<K,V> is as an IDictionary<K,V>. 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<K,V> 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.