Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is UserManage.CreateAsync throwing an EntityValidationError when duplicate username is used rather than returning result with failure?

I have the following register method, and I swear I (manually) tested this a while back and noted that if a username already existed, the result simply had a false value for result.Succeeded and would append the error message to the ModelState (using the build in AddErrors(result) helper method). I'm pretty sure this method (Register(...)) comes out of the box with ASP.NET mvc 5, but I think I changed the user to include a username (whereas out of the box, the email is simply used as the username).

public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Username, Email = model.Email };

        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);

            return RedirectToAction("Index", "Home");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Instead, I am currently getting the error as an EntityValidationError being thrown and uncaught.

I know I could simply catch this error and move on with my day, but I want to make sure that something else is causing this issue if it is not the correct behavior.

Update:

After creating a new MVC project, I can confirm that the typical behavior (when a duplicate username is registered) is that CreateAsync should return a result with a false value for result.Succeeded and an error message of "Username is taken" should be appended to the ModelState. Clearly something is amiss in my code or config, but I haven't the foggiest idea of where to start exploring. If it helps, I have been seeing EntityValidationErrors in other places of my code lately in situations that shouldn't warrant it either. See: Unable to SaveChanges on a db update. Weird lazy loading behavior possibly?

like image 325
NicholasFolk Avatar asked Oct 29 '22 06:10

NicholasFolk


1 Answers

I found my own solution. As I mentioned, I had altered the user to include a username (as well as make email optional). A part of this task involved creating a custom user validator class. In the custom user validator's ValidateAsync method, I had forgotten to check if a username existed already (and did not belong to the user). Like so:

    async Task<IdentityResult> IIdentityValidator<TUser>.ValidateAsync(TUser item)
    {
        var errors = new List<string>();

        // ...

        // Piece of code I have now added  
        var owner = await _manager.FindByNameAsync(item.UserName);
        if (owner != null && !EqualityComparer<string>.Default.Equals(owner.Id, item.Id))
        {
            errors.Add($"Username {item.UserName} is already taken");
        }
        // End of code I added            

        // ...

        return errors.Any()
            ? IdentityResult.Failed(errors.ToArray())
            : IdentityResult.Success;
    }

I believe the lesson learned for me is the difference between the App layer validation, where validation occurs in the CreateAsync method by the UserManager. In the case of the App layer validation, the error will present itself exactly as prescribed. If that layer of validation is omitted, and the DB faces the same constraint, then when the context is saved, it will throw its own error. In this case, a slightly more cryptic EntityValidationError.

like image 125
NicholasFolk Avatar answered Nov 15 '22 04:11

NicholasFolk