In order to set up and configure the AspNetCore.Identity
framework, we need to install the required NuGet package and perform a number of code changes in some of our project's entity classes.
The first thing we're going to do is to check for the existence of the Microsoft.AspNetCore.Identity.EntityFrameworkCore
library package, which we should have already added in Chapter 4, The Data Model. If we missed it, we can fix the issue in a number of ways.
If we like to use the Package Manager Console, we can select the appropriate tab and write the following command:
> Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore
If we prefer the Package Manager GUI interface, right-click in Solution Explorer to the OpenGameListWebApp project node, select Manage NuGet Packages, and act accordingly:
As usual, we can also manage everything directly from the project.json
file by adding the following line to the dependencies
section:
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0"
Once done, we need to perform some changes to our project's classes to ensure a proper Identity support.
Open the Data/ApplicationDbContext.cs
class file and perform the following changes:
using
reference to Microsoft.AspNetCore.Identity.EntityFrameworkCore
, as required by the new base class:using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
DbContext
to IdentityDbContext<ApplicationUser>
:public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
DbSet<ApplicationUser> Users
property, as the IdentityDbContext
base class already has it built in: #region Properties
public DbSet<Item> Items { get; set; }
public DbSet<Comment> Comments { get; set; }
// public DbSet<ApplicationUser> Users { get; set; }
#endregion Properties
If we try to compile the project, this file will now produce an error, because our existing ApplicationUser
class does not extend the IdentityUser
type, which is a requirement for the TUser
, generic type required by the IdentityDbContext
class. To solve the error, switch to the /ApplicationUsers/ApplicationUser.cs
class and add the IdentityUser
base class in the following way:
namespace OpenGameListWebApp.Data.ApplicationUsers { public class ApplicationUser : IdentityUser {
Needless to say, we'll have to add a reference to the AspNetCore.Identity
namespace here as well:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
As soon as we save the file, we'll get three green compilation notices for the Id
, Email
, and UserName
properties, as they are all already present in the IdentityUser
base class:
We don't need them anymore, so we can comment (or just remove) them as well:
//[Key] //[Required] //public string Id { get; set; } //[Required] //[MaxLength(128)] //public string UserName { get; set; } //[Required] //public string Email { get; set; }
That's it! From now on, our ApplicationUser
entity class is also an IdentityUser
that can be used by ASP.NET Identity
for authentication and authorization purposes.
What we need to do now is to add the Identity-related services to our project's startup class. Open the Startup.cs
file and add the following to the ConfigureServices
method, right before the DbContext
(new lines are highlighted):
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); // Add EntityFramework's Identity support. services.AddEntityFramework(); // Add Identity Services & Stores services.AddIdentity<ApplicationUser, IdentityRole>(config => { config.User.RequireUniqueEmail = true; config.Password.RequireNonAlphanumeric = false; config.Cookies.ApplicationCookie.AutomaticChallenge = false; }) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // Add ApplicationDbContext. services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]) ); // Add ApplicationDbContext's DbSeeder services.AddSingleton<DbSeeder>(); }
In order to make it work, we also need to add the following namespaces:
using OpenGameListWebApp.Data.Users; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
Since we changed our ApplicationUser
class to make it extend the IdentityUser
base class, we most likely broke the seeding mechanism we set up back in Chapter 4, The Data Model. On top of that, we should also create some sample roles, since we now we can make good use of them. These are two good reasons to revise our current DbSeeder
class.
Let's open our /Data/DbSeeder.cs
file and update it accordingly. This is a fat class in terms of source code lines, so we'll just show the relevant changes.
The first thing we need to do is to add a UserManager and a RoleManager, as they are the required Asp.NetCore.Identity
handler classes to properly work with users and roles. We can define a private variable for each one of them within the #Private Members
region (new lines are highlighted):
#region Private Members private ApplicationDbContext DbContext; private RoleManager<IdentityRole> RoleManager; private UserManager<ApplicationUser> UserManager; #endregion Private Members
These references will require the following namespaces:
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
We can then instantiate these new properties within the Constructor
using the same dependency injection pattern we already used to instantiate our ApplicationDbContext
:
#region Constructor public DbSeeder(ApplicationDbContext dbContext, RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager) { DbContext = dbContext; RoleManager = roleManager; UserManager = userManager; } #endregion Constructor
Right after that, we need to change our CreateUsers
method to make use of these handlers. Since they all feature methods enforcing async
/await
programming pattern, we also need to make it async
and change its return type from void
to Task
. Therefore, we will also conveniently rename it CreateUsersAsync
as well. Here's the new method, rewritten from scratch:
private async Task CreateUsersAsync() { // local variables DateTime createdDate = new DateTime(2016, 03, 01, 12, 30, 00); DateTime lastModifiedDate = DateTime.Now; string role_Administrators = "Administrators"; string role_Registered = "Registered"; //Create Roles (if they doesn't exist yet) if (!await RoleManager.RoleExistsAsync(role_Administrators)) await RoleManager.CreateAsync(new IdentityRole(role_Administrators)); if (!await RoleManager.RoleExistsAsync(role_Registered)) await RoleManager.CreateAsync(new IdentityRole(role_Registered)); // Create the "Admin" ApplicationUser account (if it doesn't exist already) var user_Admin = new ApplicationUser() { UserName = "Admin", Email = "[email protected]", CreatedDate = createdDate, LastModifiedDate = lastModifiedDate }; // Insert "Admin" into the Database and also assign the "Administrator" role to him. if (await UserManager.FindByIdAsync(user_Admin.Id) == null) { await UserManager.CreateAsync(user_Admin, "Pass4Admin"); await UserManager.AddToRoleAsync(user_Admin, role_Administrators); // Remove Lockout and E-Mail confirmation. user_Admin.EmailConfirmed = true; user_Admin.LockoutEnabled = false; } #if DEBUG // Create some sample registered user accounts (if they don't exist already) var user_Ryan = new ApplicationUser() { UserName = "Ryan", Email = "[email protected]", CreatedDate = createdDate, LastModifiedDate = lastModifiedDate, EmailConfirmed = true, LockoutEnabled = false }; var user_Solice = new ApplicationUser() { UserName = "Solice", Email = "[email protected]", CreatedDate = createdDate, LastModifiedDate = lastModifiedDate, EmailConfirmed = true, LockoutEnabled = false }; var user_Vodan = new ApplicationUser() { UserName = "Vodan", Email = "[email protected]", CreatedDate = createdDate, LastModifiedDate = lastModifiedDate, EmailConfirmed = true, LockoutEnabled = false }; // Insert sample registered users into the Database and also assign the "Registered" role to him. if (await UserManager.FindByIdAsync(user_Ryan.Id) == null) { await UserManager.CreateAsync(user_Ryan, "Pass4Ryan"); await UserManager.AddToRoleAsync(user_Ryan, role_Registered); // Remove Lockout and E-Mail confirmation. user_Ryan.EmailConfirmed = true; user_Ryan.LockoutEnabled = false; } if (await UserManager.FindByIdAsync(user_Solice.Id) == null) { await UserManager.CreateAsync(user_Solice, "Pass4Solice"); await UserManager.AddToRoleAsync(user_Solice, role_Registered); // Remove Lockout and E-Mail confirmation. user_Solice.EmailConfirmed = true; user_Solice.LockoutEnabled = false; } if (await UserManager.FindByIdAsync(user_Vodan.Id) == null) { await UserManager.CreateAsync(user_Vodan, "Pass4Vodan"); await UserManager.AddToRoleAsync(user_Vodan, role_Registered); // Remove Lockout and E-Mail confirmation. user_Vodan.EmailConfirmed = true; user_Vodan.LockoutEnabled = false; } #endif await DbContext.SaveChangesAsync(); }
As we can see, we made some relevant changes here:
DbContext.Add
and DbContext.AddRange
methods have been replaced by those provided by the UserManager
. This allow us to specify a password that will be automatically hashed and also to avoid any explicit Id
assignment, as they will be auto-generated.RoleManager
to create two sample roles: administrators and registered.admin
user to the administrators role and all the other sample users to the registered role.Once done, we need to update the SeedAsync
method to reflect the rename we just did on CreateUsersAsync
and also handle the fact that the latter is now asynchronous as well:
#region Public Methods public async Task SeedAsync() { // Create default Users if (await DbContext.Users.CountAsync() == 0) await CreateUsersAsync(); // Create default Items (if there are none) and Comments if (await DbContext.Items.CountAsync() == 0) CreateItems(); } #endregion Public Methods
With this, we're done updating our project's classes.
Before going further, it might be wise to issue a whole project rebuild to make sure we're not getting build errors within our code.