C# tutorials > Frameworks and Libraries > ASP.NET Core > Model Binding and Validation (data annotations, custom validation)

Model Binding and Validation (Data Annotations, Custom Validation) in ASP.NET Core

This tutorial explores Model Binding and Validation in ASP.NET Core, focusing on Data Annotations and Custom Validation. Model Binding automatically maps incoming HTTP request data to the properties of your model. Validation ensures that the bound data meets specific requirements before being processed. We'll cover how to use Data Annotations for common validation scenarios and how to create custom validation attributes for more complex requirements.

Introduction to Model Binding

Model Binding is the process of converting incoming HTTP request data (e.g., from form fields, query strings, or route parameters) into .NET objects that your controller actions can work with. ASP.NET Core handles this automatically, simplifying data access within your application.

Introduction to Validation

Validation ensures that the data received through Model Binding is in a valid state before being used by your application. This helps prevent errors, improves data quality, and enhances security. Validation can be implemented using Data Annotations or custom validation logic.

Data Annotations: A Simple Example

This code snippet demonstrates basic Data Annotations. [Required] ensures the Name property is not empty. [StringLength] limits the length of the Name. [Range] specifies the acceptable range for the Price. [EmailAddress] validates that the SupportEmail is a valid email format. The ErrorMessage properties provide user-friendly feedback when validation fails.

public class Product
{
    [Required(ErrorMessage = "Product Name is required")]
    [StringLength(100, MinimumLength = 3, ErrorMessage = "Product Name must be between 3 and 100 characters")]
    public string Name { get; set; }

    [Range(0.01, 1000, ErrorMessage = "Price must be between 0.01 and 1000")]
    public decimal Price { get; set; }

    [EmailAddress(ErrorMessage = "Invalid Email Address")]
    public string SupportEmail { get; set; }
}

Using Data Annotations in a Controller

In your controller action, check the ModelState.IsValid property. If it's true, the model is valid and you can proceed with your logic (e.g., saving the product to a database). If it's false, the ModelState contains validation errors that you should display in your view.

[HttpPost]
public IActionResult Create(Product product)
{
    if (ModelState.IsValid)
    {
        // Save the product to the database
        return RedirectToAction("Index");
    }

    // If validation fails, return the view with validation errors
    return View(product);
}

Displaying Validation Errors in a View

In your Razor view, use the asp-validation-summary tag helper to display a summary of all validation errors. Use the asp-validation-for tag helper next to each input field to display specific error messages for that field. The text-danger CSS class (often from Bootstrap) styles the errors in red.

@model Product

<form asp-action="Create" method="post">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>

    <div class="form-group">
        <label asp-for="Name" class="control-label"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>

    <div class="form-group">
        <label asp-for="Price" class="control-label"></label>
        <input asp-for="Price" class="form-control" />
        <span asp-validation-for="Price" class="text-danger"></span>
    </div>

    <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" />
    </div>
</form>

Creating Custom Validation Attributes

To create a custom validation attribute, inherit from the ValidationAttribute class. Override the IsValid method, which contains your validation logic. The value parameter is the value being validated, and the validationContext provides access to the model and other context information. Return a ValidationResult to indicate success or failure.

using System.ComponentModel.DataAnnotations;

public class ValidAgeAttribute : ValidationAttribute
{
    private readonly int _minAge;

    public ValidAgeAttribute(int minAge)
    {
        _minAge = minAge;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            DateTime birthDate = (DateTime)value;
            int age = DateTime.Now.Year - birthDate.Year;

            if (age < _minAge)
            {
                return new ValidationResult($"Age must be at least {_minAge} years.");
            }
        }

        return ValidationResult.Success;
    }
}

Using the Custom Validation Attribute

Apply your custom validation attribute to a property just like any other Data Annotation. Pass any necessary parameters to the attribute's constructor. In this example, we require that the BirthDate results in an age of at least 18 years.

public class Person
{
    [Required]
    public string Name { get; set; }

    [ValidAge(18, ErrorMessage = "You must be at least 18 years old")]
    public DateTime BirthDate { get; set; }
}

Concepts Behind the Snippet

The core concepts are: declarative validation through attributes, leveraging the ModelState to track validation status, and the ability to extend validation with custom logic when the built-in attributes are insufficient.

Real-Life Use Case Section

Imagine an e-commerce application where users register and provide their shipping addresses. Model Binding and Validation can ensure that the address fields are correctly populated (e.g., zip codes have the correct format, required fields are not empty) before processing the order. Similarly, in a banking application, ensuring valid account numbers, amounts, and transaction details is crucial for financial integrity.

Best Practices

  • Keep your Data Annotations concise and focused.
  • Provide clear and user-friendly error messages.
  • Consider using a validation library like FluentValidation for more complex scenarios.
  • Perform validation both on the client-side (for immediate feedback) and server-side (for security).
  • Avoid overly complex custom validation logic within the attribute; move complex logic to a separate service if needed.

Interview Tip

Be prepared to explain the difference between Data Annotations and custom validation, and when you might choose one over the other. Also, be ready to discuss the role of ModelState.IsValid in ASP.NET Core controllers and how it ties into the validation process. Being able to discuss client-side vs. server-side validation is also key.

When to Use Them

Use Data Annotations for simple, common validation scenarios like required fields, string length constraints, and range checks. Use custom validation attributes when you need to implement more complex validation logic that cannot be easily expressed using Data Annotations alone.

Memory Footprint

Data Annotations themselves have a minimal memory footprint. The memory impact comes from storing the validated data and the validation error messages. Custom validation attributes have a slightly larger footprint due to the additional code they execute, but this is typically negligible unless you have a very large number of custom attributes or the validation logic is computationally expensive.

Alternatives

Alternatives to Data Annotations include:

  • FluentValidation: A popular library for building strongly-typed validation rules using a fluent interface.
  • Manual Validation: Writing validation logic directly in your controller actions (less maintainable).
  • Third-party Validation Libraries: Other validation libraries that offer different features and approaches.

Pros of Data Annotations

  • Easy to use and understand for common validation scenarios.
  • Declarative and keep validation logic close to the model properties.
  • Integrated with ASP.NET Core's Model Binding and Validation pipeline.

Cons of Data Annotations

  • Can become verbose for complex validation scenarios.
  • Less flexible than other validation approaches for highly customized logic.
  • Difficult to unit test in isolation without extra setup.

FAQ

  • What happens if ModelState.IsValid is false?

    If ModelState.IsValid is false, it means that the model has failed validation. The ModelState property in your controller contains a collection of errors that you can display to the user, typically in your view, to indicate which fields have invalid data.

  • Can I use Data Annotations and Custom Validation together?

    Yes, you can absolutely use Data Annotations and Custom Validation attributes together within the same model. This allows you to leverage the simplicity of Data Annotations for common validation tasks while using custom attributes for more specific or complex validation requirements.

  • How do I perform client-side validation with Data Annotations?

    ASP.NET Core automatically generates client-side validation scripts based on your Data Annotations. Ensure you have included the necessary JavaScript libraries (e.g., jQuery Validation and jQuery Validation Unobtrusive) in your view. These scripts will perform validation in the browser before the form is submitted to the server.