Validation

Data validation

To validate data we use DataAnnotation on the DTO classes.

Open UserForRegistration.cs and add a property decorator Required to both the properties. This will ensure that both properties are not null. Also add a minimum length for the password.

[Required]
[StringLength(30, MinimumLength = 8, ErrorMessage = "The password must be between 8 and 30 characters.")]
public string Password { get; set; }

Check in the controller action Register using the model validation.

if(!ModelState.IsValid) {
    return BadRequest(ModelState);
}

The code that check if the Username is already taken is a Business Validation, so we move that code above the check ModelState.IsValid and if there is an error we add the error to the model state. Doing this we obtain a consistent response in case of errors so that client applications can use a single method to display validation errors.

public async Task<IActionResult> Register([FromBody] UserForRegistration userForRegistration)
{
    // Convert username to lowercase to avoid multiple user with similar names like "John" and "john"
    // Use invariant to avoid conflicts for users from different cultures
    userForRegistration.Username = userForRegistration.Username.ToLowerInvariant();

    // Business Validation
    if (await _repo.UserExists(userForRegistration.Username))
    {
        ModelState.AddModelError("Username", "Username already in use.");
    }

    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var userToCreate = new Models.User { Username = userForRegistration.Username };
    var createdUser = await _repo.Register(userToCreate, userForRegistration.Password);

    // TODO this 201 is just a temporary solution, we should return a path to the new entity
    return StatusCode(201);
}

A personal note on what constitutes Business Validation: strictly speaking testing that the password length is in the required leght is a business rule, but I like to differentiate between simple Model Validation where we check if the objects have valid data and more complex validation rules. For example a bank transaction can have a valid amount of 100 (it is an integer greater than zero) but we also do a Business Validation to check if the customer have at least 100 in his account.