Separating Data Access in Asp.Net Core 2
Paul Michaels
Posted on August 5, 2019
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:
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:
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:
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
Originally posted here.
Posted on August 5, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.