I'm coding a multi-tenant app using ASP.NET Core 2.1
.
I would like to override the default user-creation-related validation mechanism.
Currently I cannot create several users with the same UserName
.
My ApplicationUser
model has a field called TenantID
.
What I'm trying to achieve: UserName
& EmailAddress
must be unique per tenant.
I've been googling a solution, but haven't found much info for asp.net core
on this one.
Most of the results would only cover Entity Framework
aspects, as if it's just a matter of overriding OnModelCreating(...)
method. Some are related to NON-core edition of ASP.NET Identity.
I wonder if I should keep investigating OnModelCreating
approach?
Or perhaps, there's something else that needs to be overridden around Identity
?
First of all, you need to disable the Identity's built-in validation mechanism:
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// disable the built-in validation
options.User.RequireUniqueEmail = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Then, assuming you are registering users with the ASP.NET Core with Identity template, you could just do:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
return View(model);
}
// check for duplicates
bool combinationExists = await _context.Users
.AnyAsync(x => x.UserName == model.UserName
&& x.Email == model.Email
&& x.TenantId == model.TenantId);
if (combinationExists)
{
return View(model);
}
// create the user otherwise
}
If you don't want to do that kind of checking in the controller and would rather keep the Identity flow, you can create your own IUserValidator<ApplicationUser>
pretty simply:
public class MultiTenantValidator : IUserValidator<ApplicationUser>
{
public async Task<IdentityResult> ValidateAsync(UserManager<ApplicationUser> manager, ApplicationUser user)
{
bool combinationExists = await manager.Users
.AnyAsync(x => x.UserName == user.UserName
&& x.Email == user.Email
&& x.TenantId == user.TenantId);
if (combinationExists)
{
return IdentityResult.Failed(new IdentityResult { Description = "The specified username and email are already registered in the given tentant" });
}
// here the default validator validates the username for valid characters,
// let's just say all is good for now
return IdentityResult.Success;
}
}
And you would then tell Identity to use your validator:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddUserValidator<MultiTenantValidator>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
With this, when you call UserManager.CreateAsync
, the validation will take place before creating the user.