How to use .NET data annotation attributes

How to use .NET data annotation attributes

The following data annotations can be mixed and matched depending on the type of validation you're performing in your models. They also integrate well with MVC's model binding and validation features to provide feedback to the user

1. Range Attribute

This attribute can be used to ensure a property value falls within a specified range (for example, for numeric values, dates, or other types that implement IComparable).

[Range(18, 99, ErrorMessage = "Age must be between 18 and 99.")]
public int Age { get; set; }

For dates:

[Range(typeof(DateTime), "1/1/2000", "12/31/2099", ErrorMessage = "Date must be between 1/1/2000 and 12/31/2099.")]
public DateTime BirthDate { get; set; }

2. Compare Attribute

This attribute is used to compare two properties, often for fields like password and confirm password.

[Required]
[DataType(DataType.Password)]
public string Password { get; set; }

[Required]
[DataType(DataType.Password)]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }

3. Phone Attribute

Use this attribute to validate phone numbers. It uses a relatively lenient regex pattern for phone numbers, but you can also combine it with a custom regular expression.

[Phone(ErrorMessage = "Invalid phone number.")]
public string PhoneNumber { get; set; }

4. CreditCard Attribute

For validating credit card numbers.

[CreditCard(ErrorMessage = "Invalid credit card number.")]
public string CreditCardNumber { get; set; }

5. Url Attribute

To validate URLs.

[Url(ErrorMessage = "Invalid URL.")]
public string WebsiteUrl { get; set; }

6. Custom RegularExpression Attribute

For more specialized validation needs. For example, validating a strong password:

[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$", 
    ErrorMessage = "Password must be at least 8 characters long and include one uppercase letter, one lowercase letter, and one number.")]
public string Password { get; set; }

Drill down on the regex :

^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$

This regular expression is often used for validating passwords. It ensures the following:

  1. The password contains at least one lowercase letter.
  2. The password contains at least one uppercase letter.
  3. The password contains at least one digit.
  4. The password is at least 8 characters long.

Explanation:

  1. ^

    • Anchors the pattern to the start of the string. This ensures that the validation checks from the beginning of the string.
  2. (?=.*[a-z])

    • (?= ... ) is a positive lookahead. Lookaheads assert that a certain pattern is present without including it in the final match.
    • .*[a-z] means "zero or more characters (.*) followed by a lowercase letter ([a-z])".
    • This ensures the string contains at least one lowercase letter anywhere in the string.
  3. (?=.*[A-Z])

    • Another positive lookahead.
    • .*[A-Z] means "zero or more characters followed by an uppercase letter ([A-Z])".
    • This ensures the string contains at least one uppercase letter anywhere in the string.
  4. (?=.*\d)

    • Yet another positive lookahead.
    • .*\d means "zero or more characters followed by a digit (\d)".
    • This ensures the string contains at least one digit anywhere in the string.
  5. [a-zA-Z\d]{8,}

    • This is the main part of the regex that matches the content of the string itself.
    • [a-zA-Z\d] specifies that the string can contain lowercase letters (a-z), uppercase letters (A-Z), or digits (\d).
    • {8,} specifies that this segment must be at least 8 characters long. There is no upper limit specified here, so the string can be longer than 8 characters.
  6. $

    • Anchors the pattern to the end of the string. This ensures the entire string follows the rules without any additional characters before or after.

7. MinLength and MaxLength Attributes

To set minimum or maximum length constraints on string inputs, such as passwords or descriptions.

[MinLength(6, ErrorMessage = "Password must be at least 6 characters long.")]
[MaxLength(50, ErrorMessage = "Password cannot exceed 50 characters.")]
public string Password { get; set; }

8. DataType Attribute

Specifies the type of data that should be used. This is particularly useful for formatting or HTML rendering purposes (e.g., using the correct input type in forms).

[DataType(DataType.EmailAddress)]
public string Email { get; set; }

[DataType(DataType.PhoneNumber)]
public string PhoneNumber { get; set; }

[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }

9. Display Name Attribute

This is not strictly validation but is useful for providing a more user-friendly name for a field when rendering forms or validation messages.

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

10. Custom Validation Attribute

If you need more complex validation logic that cannot be handled by the built-in attributes, you can create custom validation methods.

  1. Define a custom method:

public class CustomValidationMethods
{
    public static ValidationResult ValidateAge(int age, ValidationContext context)
    {
        if (age < 18)
            return new ValidationResult("Age must be 18 or older.");
        return ValidationResult.Success;
    }
}

      2. Apply the custom validation to a property:

[CustomValidation(typeof(CustomValidationMethods), "ValidateAge")]
public int Age { get; set; }

11. EmailAddress 

Note: here this example targets .NET 4.x where we need to enhance the strength of the validation with a regular expression, .NET Core has strengthened the edge cases and regex is not required. 

???????    public class ContactViewModel
    {
        [Required(ErrorMessage = "Email is required.")]
        [EmailAddress(ErrorMessage = "Invalid Email Address.")]
        [RegularExpression(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", ErrorMessage = "Invalid Email Address.")]
        public string email { get; set; }
    }

 

Enhancements in .NET Core

 

Nullable Reference Types Support

With the introduction of nullable reference types in .NET Core 3.0+, you can add nullability context directly to your properties, which makes the [Required] attribute more meaningful and reduces the need for the [Required] attribute for non-nullable types (like string).

In .NET Core 3.0 and later:

public string FirstName { get; set; }  // Required by default due to non-nullable type.

You can still use [Required] for additional clarity or customization of the error message:

[Required(ErrorMessage = "First Name is required.")]
public string FirstName { get; set; }

StringLength and MaxLength

In .NET Core, [StringLength] and [MaxLength] work the same as in .NET Framework, but they're also more tightly integrated with database schema generation when using Entity Framework Core. This allows EF Core to automatically apply constraints to the database.

[MaxLength(100, ErrorMessage = "First Name can't be longer than 100 characters.")]
public string FirstName { get; set; }

CreditCard, Phone, and EmailAddress

In .NET Core, the validation for attributes like [CreditCard], [Phone], and [EmailAddress] are the same, but the built-in validation rules have been updated to follow more modern standards.

For example, the [EmailAddress] attribute in .NET Core better handles edge cases for valid emails, and it's stricter about the format.

[EmailAddress(ErrorMessage = "Please provide a valid email address.")]
public string Email { get; set; }

 

New/Enhanced Attributes in .NET Core

 

Remote Validation

ASP.NET Core provides [Remote] validation that allows server-side validation without requiring a full page refresh. This attribute sends an asynchronous request to the server to validate the property.

[Remote(action: "ValidateEmail", controller: "Users", ErrorMessage = "Email already in use.")]
public string Email { get; set; }

On the server, the ValidateEmail action would check if the email is already in use.

FileExtensions Attribute

This attribute ensures that uploaded files have specific extensions.

[FileExtensions(Extensions = "jpg,jpeg,png,gif", ErrorMessage = "Only image files are allowed.")]
public string ImageFile { get; set; }

DataType Attribute Enhancements

In .NET Core, the [DataType] attribute has additional formatting and rendering options that integrate with Razor views and HTML helpers. For example, [DataType(DataType.Currency)] can apply automatic formatting to currency fields.

[DataType(DataType.Currency)]
public decimal Price { get; set; }

RegularExpression

Still present and can be used to enforce complex patterns in inputs like passwords or other specific formats. Works the same as in .NET Framework.

[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$", ErrorMessage = "Password must have at least 8 characters, one uppercase letter, and one number.")]
public string Password { get; set; }

Validation at the Model Level

In ASP.NET Core, you can also implement validation at the model level using the IValidatableObject interface. This allows custom validation logic that can't be handled by built-in attributes.

public class UserModel : IValidatableObject
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (BirthDate > DateTime.Now)
        {
            yield return new ValidationResult("BirthDate cannot be in the future.");
        }
    }
}

Dependency Injection in Custom Validation

In .NET Core, one powerful feature is the ability to inject dependencies into custom validation attributes using the IServiceProvider and IValidationAttributeAdapterProvider. This enables you to validate data with services, like checking a database for unique values in a custom attribute.

For example, you can inject a service into your custom attribute to check if an email already exists in the database.

Fluent Validation as an Alternative

A popular alternative to data annotations in .NET Core is FluentValidation, a separate library that offers more complex and expressive validation rules without using attributes. It’s common in .NET Core applications to use FluentValidation, especially when working with more complicated validation scenarios.

Example:

public class UserValidator : AbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("Invalid email.");
        RuleFor(x => x.Password).MinimumLength(8).WithMessage("Password must be at least 8 characters.");
    }
}

Key Differences in .NET Core

  • Built-in validation attributes remain mostly the same, but with enhanced validation logic.
  • The introduction of nullable reference types adds implicit validation to non-nullable types.
  • ASP.NET Core adds the ability to inject services into validation attributes.
  • FluentValidation is commonly used in .NET Core as a more flexible alternative to data annotations.
  • Remote validation is improved and widely used for asynchronous server-side validation.

Overall, the validation system is very similar between .NET Framework and .NET Core, but with better integration and a few enhanced capabilities in .NET Core.