Separating Data Access in Asp.Net Core 2

June 30, 2018

In Asp.Net Core 2, like in previous incarnations of Asp.Net there is a wizard that gives you a head-start with a a simple user log-in / registration system:

aspnet core2 id 4

If you set-up a new project using the wizard to create an individual user account, you may notice in the generated project, the lack of seemingly any code to achieve this. The reason being that all the code for the identity system is tucked away inside the razor pages. I see this as mainly a good thing, but with one exception*: I don’t like having the DB access code inside the main web project; it makes DI very difficult. So this is the story of how you can extricate the DB Access portion of this into a separate project.

Context

The crux of this is to move the context into a separate project; so let’s start with a new project:

aspnet core2 id 1

If you just want the identity access, then you’ll only need to move the ApplicationIdentityDbContext, however, in real life, you’re probably going to end up with two contexts:

aspnet core2 id 2

The contexts themselves need to be separate because the identity context inherits from IdentityDbContext**:



public class ApplicationIdentityDbContext : IdentityDbContext
{
    
    public ApplicationIdentityDbContext(DbContextOptions<ApplicationIdentityDbContext> options)
        : base(options)
    {
    }
}

Your second context should just inherit from DbContext.

NuGet

There’s a couple of gotcha’s with this; but the libraries that you need in the DataAccess project are:



Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.Entensions.Identity.Stores

Startup.cs

Finally, you’ll need to change the DI to register both contexts:




services.Configure<IdentityOptions>(options =>
{
    options.Password.RequireNonAlphanumeric = false;                
});
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(
        Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<ApplicationIdentityDbContext>(options =>
    options.UseSqlServer(
        Configuration.GetConnectionString("DefaultConnection")));
 
services                
    .AddDefaultIdentity<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationIdentityDbContext>();

I’m using SqlServer here, so if you’re not then you’ll obviously need to change the bits around that. You’ll notice that I switched the requirement to have your password have a non-alphanumeric character - especially for development, this can be annoying. I also don’t necessarily accept that it increases security for the site***.

Migrations

Now that you have multiple contexts, when you add a migration, you’ll need to specify the context to use; for example:



Add-Migration "InitialUserSetup" -context ApplicationIdentityDbContext

The same is true for Update-Database:



Update-Database -context ApplicationIdentityDbContext

Footnotes

* Okay - there may be other pitfalls; but if this works for 60% of the authentication cases, why not have it all inside a magic black box? When you need something more customised, you can always rip this out and replace it with your own.

** There’s nothing stopping you having the main DbContext inherit from IdentityDbContext, or just using IdentityDbContext as the main context.

*** Obviously, it does improve security for the site if everyone is using a 20 digit code and they start using non-alpha-numeric characters in that code; however, if they’re using a password manager, they probably are already generating such a code, and if not then you’ll just force “Password123” to “!Password123”, so you probably don’t gain much!

References

https://github.com/aspnet/EntityFrameworkCore/issues/7891

An excellent intro to Asp.Net Core 2 default structure



Profile picture

A blog about one man's journey through code… and some pictures of the Peak District
Twitter

© Paul Michaels 2024