One of the main reasons I like ASP.NET Core Identity is that it provides all the tools you need to handle user registration: customizable password strength, e-mail verification system, configurable lockout strategies, third-party auth providers support, and so on. All these things - and much more - are already available out of the box, the only thing we need to do is to configure the Identity service to suit our needs - possibly using a couple of (official) NuGet packages to bring some additional juice in.
That said, sometimes you need to do something that isn't natively supported - such as allowing your users to register without requiring them to enter a given e-mail address. Although this isn't a typical requirement, there are some edge-case scenarios where you would want to do that. In my personal experience, this usually happens when those users are added by back-end operators: in other words, non-interactive users that only need to be present in the database (for various reasons), without actually being able to perform the login; however, there could be a lot of other suitable scenarios where you would want to overcome the default ASP.NET Core behavior where the user e-mail address is a required field.
In this post, we'll see how we can do that using a custom (yet simple) OptionalEmailUserValidator class. Are we ready? Let's start!
The issue
Before starting to work on our assignment, let's briefly reproduce the issue we're talking about.
Suppose we try to create our user in the following way:
1 2 |
var user = new ApplicationUser { UserName = model.Name }; var result = await UserManager.CreateAsync(user); |
If we try to do that, we will get a result variable equal to "InvalidEmail", which will be translated into an "Email cannot be null or empty" readable error. This happens because the ASP.NET Identity system wants the user.Email property to be filled with a non-null, non-empty, valid, and unique e-mail address.
Now that we know what we need to overcome, let's see how we can do that.
The (wrong) way to do that
The first thing we might be tempted to do is to configure the identity options in the following way:
1 2 3 4 |
services.AddIdentity<ApplicationUser>( options=> { options.User.RequireUniqueEmail = false; }); |
This would do something quite similar to what we want: we would be able to specify a null/empty e-mail address. However, the above settings will also disable the dupe-check of the existing e-mail addresses, which is something we might want to preserve for those users that will register (or "be registered") with a valid e-mail address.
The solution
Instead of disabling the dupe-check, we might think of creating a custom email validator and using that instead of the default one. That custom validator should behave in the following way:
- If the Email property is null or empty, return a IdentityResult.Success (instead of InvalidEmail).
- If the Email property is present, perform the dupe-check just like the default validator.
Here's the C# source code for a OptionalEmailUserValidator that does just that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class OptionalEmailUserValidator<TUser> : UserValidator<TUser> where TUser : class { public OptionalEmailUserValidator(IdentityErrorDescriber errors = null) : base(errors) { } public override async Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user) { var result = await base.ValidateAsync(manager, user); if (!result.Succeeded && string.IsNullOrWhiteSpace(await manager.GetEmailAsync(user))) { var errors = result.Errors.Where(e => e.Code != "InvalidEmail"); result = errors.Count() > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } return result; } } |
And here's how we can inject it into our web application's ASP.NET Identity service, replacing the default validator:
1 |
builder.Services.AddTransient<IUserValidator<User>, OptionalEmailUserValidator<User>>(); |
Be sure to put the above one-liner before the existing builder.Services.AddIdentity() or AddDefaultIdentity() line, otherwise it wouldn't work.
Conclusion
That's it: I sincerely hope that this simple, yet useful technique will help other ASP.NET Core developers looking for a neat and efficient way to allow the use of optional (yet unique) email addresses for their ASP.NET Core Identity users.