Register with unique email OR unique username in ASP.NET Core MVC

Viewed 1347

I am working on a system that should allow users to register by a unique username, or unique email. Register by both is possible as well.

I am using the default identity pages with some modifications, here is a sample of the register code:

    public class InputModel
    {
        //[Required] //Validated in server side based on role
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }

        //[Required] //Validated in server side based on role
        [Display(Name = "Username")]
        public string UserName { get; set; }

        [Required]
        [Display(Name = "Name")]
        public string Name { get; set; }
    }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        string username = Input.UserName ?? Input.Email;

        if (ModelState.IsValid)
        {
            var user = new ApplicationUser
            {
                UserName = username,
                Email = Input.Email,
                Name = Input.Name,
            };
        }
    }

Basically, if the user entered a username, Username column will be Input.UserName. if no username (only email), the Username = Input.Email, because obviously it cannot be empty. Now both username and email are equal as the default.

examples:

Username: a@a , Email: a@a , >> no username

Username: xyz , Email: null , >> no email

Username: abc , Email: a@b , >> user entered both username and email

For now, username is always unique and always required (required by identity not the user), but not the case for the email, it can be null as expected but its not unique, I added this line in the startup.cs for the uniqueness:

            services.AddIdentity<IdentityUser, IdentityRole>(options => {
            options.User.RequireUniqueEmail = true;
        })

but now it cannot be null, it give this validation error:

Email '' is invalid.

2 Answers

Please try to implement a setter for Email that converts empty string to null

I added this line in the startup.cs for the uniqueness: options.User.RequireUniqueEmail = true;

but now it cannot be null, it give this validation error: Email '' is invalid.

In source code of UserManager<TUser>.CreateAsync method, we can find that it will call ValidateUserAsync method to valid user before saving the user.

var result = await ValidateUserAsync(user);

in code of ValidateUserAsync method, we can find it call another method ValidateAsync, like below.

var result = await v.ValidateAsync(this, user);

and in ValidateAsync method, if we configured RequireUniqueEmail to true, it will valid Email of current user by calling ValidateEmail method.

if (manager.Options.User.RequireUniqueEmail)
{
    await ValidateEmail(manager, user, errors);
}

Go to definition of ValidateEmail method, we can easily find it will check if the email parameter is null or System.String.Empty, or if value consists exclusively of white-space characters.

// make sure email is not empty, valid, and unique
private async Task ValidateEmail(UserManager<TUser> manager, TUser user, List<IdentityError> errors)
{
    var email = await manager.GetEmailAsync(user);
    if (string.IsNullOrWhiteSpace(email))
    {
        errors.Add(Describer.InvalidEmail(email));
        return;
    }
    if (!new EmailAddressAttribute().IsValid(email))
    {
        errors.Add(Describer.InvalidEmail(email));
        return;
    }
    var owner = await manager.FindByEmailAsync(email);
    if (owner != null &&
        !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user)))
    {
        errors.Add(Describer.DuplicateEmail(email));
    }
}
Related