Search code examples
c#linqasp.net-corevisual-studio-codeember-data

Unexpected behavior when checking for existing records in the database


I have an ASP.NET CORE 2.1 API back-end with an Ember front-end (created in VSCode). I am following an online video tutorial from Embercasts for new-user registration. The logic for checking for duplicate users is not working properly:

if (context.Users.Where(u => u.Username.Equals((string) value, StringComparison.OrdinalIgnoreCase)).Count() > 1)
{
     return new ValidationResult("Username is already taken", new [] { "Username" });
}

For some reason, the code above allows for exactly one duplicate user before reporting a duplicate user. In other words, the code to return a new ValidationResult alerting the user of a duplicate record is not reached. I figured that this may be related to some bizarre array indexing issue, where the first record is in position 0. After testing that hypothesis, I was proven correct. The following code prevents duplicates, while still allowing for one record to be created:

if (context.Users.Where(u => u.Username.Equals((string) value, StringComparison.OrdinalIgnoreCase)).Count() > 0)
{
     return new ValidationResult("Username is already taken", new [] { "Username" });
}

Does anyone know why this is happening? Any help is appreciated.

LibraryApi\Model\User.cs

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using JsonApiDotNetCore.Models;

namespace LibraryApiNew.Models
{
    public class User : Identifiable
    {
        [Attr("email"), UniqueEmail, Required(AllowEmptyStrings = false)]public string Email {get; set; }
        [Attr("username"), UniqueUsername, Required(AllowEmptyStrings = false)]public string Username { get; set; }
        [Attr("password"), NotMapped, Required(AllowEmptyStrings = false), Compare("PasswordConfirmation")]public string Password { get; set; }
        [Attr("password-confirmation"), NotMapped, Required(AllowEmptyStrings = false)]public string PasswordConfirmation { get; set; }
        public string PasswordHash { get; set; }
    }

    public class UniqueUsername : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var context = (AppDbContext) validationContext.GetService(typeof(AppDbContext));

            if (context.Users.Where(u => u.Username.Equals((string) value, StringComparison.OrdinalIgnoreCase)).Count() > 0) // Why?
            {
                return new ValidationResult("Username is already taken", new [] { "Username" });
            }

            return ValidationResult.Success;
        }
    }

    public class UniqueEmail : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var context = (AppDbContext) validationContext.GetService(typeof(AppDbContext));

            if (context.Users.Where(u => u.Email.Equals((string) value, StringComparison.OrdinalIgnoreCase)).Count() > 0) // Why?
            {
                return new ValidationResult("Email is already taken", new [] { "Email" });
            }

            return ValidationResult.Success;
        }
    }
}

Solution

  • Your code relates to user registration and the if clause with context.Users.Where(u => u.Username.Equals((string) value, StringComparison.OrdinalIgnoreCase)).Count() > 0 does check if the username is already present.

    If it is present, creating a second one would create a duplicate, hence the error message "Username is already taken".

    If the count is 0, that simply means, that name is not yet taken and can be used and the validation succeeds.

    Now would you check for > 1, that would mea, that you would willingly allow for one duplicate