Adding a custom '.AddThisOrThat()' method to configure ASP.NET Core Identity in one

A tiny bit more ASP.NET Core Identity, simplifying the code in startup.cs and also making the custom Identity library more re-usable

In my recent series of posts about ASP.NET Core Identity I made several customisations to Identity that are bespoke to the application I'm building. This does mean that the code for configuring Identity in Startup.cs has started to get quite verbose. As one of the steps I undertook was to migrate all of the "core" Identity code into a separate library, it makes a certain amount of sense to consider moving the service configuration code into that library and then simply invoking it as a one-liner from the main application.

Any application that consumes this library, and after all the reason for moving it to a library was so that multiple applications can use the same Identity store / configuration, just need to make one call to configure Identity, without needing to know exactly what the correct combination of settings are to do so.

Moving service configuration out to the library

When you look at the content of the ConfigureServices method, it's almost exclusively made up of a series of calls to methods on, or that appear to be on, the services object:

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(
        Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationIdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddUserValidator<MyUserValidator>()
    .AddUserManager<ApplicationIdentityUserManager>();

These are actually extensions methods, both on IServiceCollection and also on the custom Builder classes that some of these methods return, all returning either the IServiceCollection they're operating on, or the builder they generate, so that service configuration can be written using method chaining to reduce the complexity of the code required. Imagine for one moment if this convention hadn't been adopted, the above code would be a lot more long-winded and would be considerably more brittle and prone to failure if an additional line of configuration was added between several of the calls to configure Identity, i.e. between the calls to AddEntityFrameworkStores and AddUserValidator. Because of the use of method chaining, interposing a line of code requires thought as both are extension methods on the IdentityBuilder returned by the first call to AddDefaultIdentity.

First up is creating a new static class in the custom Identity library, I've named this ApplicationIdentityExtensions as this matches the apparent convention for the classes that host the extension methods being used in the snippet of code above. Then it's a very simple matter of adding a new edtension method on IServiceCollection, that looks a little like this:

namespace RW.HumanResourcesPortal.Identity
{
    public static class ApplicationIdentityExtensions
    {
        public static IServiceCollection AddApplicationIdentity(this IServiceCollection serviceCollection)
        {
            throw new NotImplementedException();
        }
    }
}

Once that's done, it's decision time. Just how configurable should the configuration extension method be? In this instance it makes sense to only allow the name of the connection string to be configurable, so that means tweaking the method signature so it looks like this:

public static IServiceCollection AddApplicationIdentity(this IServiceCollection serviceCollection, string identitySqlServerConnectionString)

With that done it's time to move the code across and update the content of Startup.cs, first up is the code once it's been moved across:

public static class ApplicationIdentityExtensions
{
    public static IServiceCollection AddApplicationIdentity(this IServiceCollection services, string identitySqlServerConnectionString)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(identitySqlServerConnectionString));
        services.AddDefaultIdentity<ApplicationIdentityUser>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddUserValidator<MyUserValidator>()
            .AddUserManager<ApplicationIdentityUserManager>();

        return services;
    }
}

Moving this code across did mean adding a reference to the Microsoft.AspNetCode.Identity.UI package as that's the one that contains the AddDefaultIdentity extension method

Then there's the simplified code in Startup.cs:

services.AddApplicationIdentity(
    Configuration.GetConnectionString("DefaultConnection"));

That's all any application that consumes the Identity library requires to correctly, and completley, configure ASP.NET Core Identity so that it's ready to run. The only other thing worth mentioning here is that the connection string for the Identity database context is passed in, rather than having the name of the connection string in appsettings.json hard-coded, or even the fact that the setting comes from a configuration file locked in. 

About Rob

I've been interested in computing since the day my Dad purchased his first business PC (an Amstrad PC 1640 for anyone interested) which introduced me to MS-DOS batch programming and BASIC.

My skillset has matured somewhat since then, which you'll probably see from the posts here. You can read a bit more about me on the about page of the site, or check out some of the other posts on my areas of interest.

No Comments

Add a Comment