Using IUserValidator to provide additional user validation rules in ASP.NET Core Identity
In my recent series of posts I extended the UserManager class from ASP.NET Core Identity (specifically its CreateAsync method) in order to confirm that the user was permitted to register and also to update a property in the User being created from data in the application database. If I hadn't needed to also modify the user I could've taken the simpler route of creating a class that implements IUserValidator. This interface contains a single method, ValidateAsync, which does all the work:
public Task<IdentityResult> ValidateAsync (UserManager<TUser> manager, TUser user);
Implementing this as a custom validator that performs the same work as the code I placed into my custom UserManager looks something like this:
public class MyUserValidator : IUserValidator<ApplicationIdentityUser> { public HumanResourcesDatabaseContext DatabaseContext { get; set; } public MyUserValidator(HumanResourcesDatabaseContext databaseContext) { DatabaseContext = databaseContext; } public Task<IdentityResult> ValidateAsync(UserManager<ApplicationIdentityUser> manager, ApplicationIdentityUser user) { var matchingUser = DatabaseContext.Employees.FirstOrDefault(candidateUser => candidateUser.EmailAddress.Equals(user.Email, StringComparison.OrdinalIgnoreCase)); if (matchingUser == null) { return Task.FromResult(IdentityResult.Failed(new IdentityError { Description = "Sorry, your email address isn't recognised as an employee by ValidateAsync" })); } else { return Task.FromResult(IdentityResult.Success); } } }
The ASP.NET Core runtime will take care of injecting the database context that it depends on, once its been wired into Identity in the ConfigureServices method:
services.AddDefaultIdentity<ApplicationIdentityUser>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddUserValidator<MyUserValidator>();
With that done, hitting F5 in Visual Studio to go to the web application and attempting to register with an email address that isn't present in the database gives the expected result, registration is rejected by the validator:
That's all there is to it, really! It's definitely a lighter-touch way of implementing a way of checking whether a new user meets additional criteria or not. In my use-case I could even "hijack" the validator to add the extra values I want to persist to the user being created, but there's something that leaves me quite uneasy having something that's supposed to validate have a side-effect.