ASP.NET Core MVC Validation

Mastering Data Integrity in Your Web Applications

Introduction to Validation

Data validation is a critical aspect of building robust and secure web applications. ASP.NET Core MVC provides a powerful and flexible framework for validating user input, ensuring that the data submitted to your application is accurate, complete, and conforms to expected formats. This guide will walk you through the various techniques and attributes available for implementing server-side and client-side validation.

Why is Validation Important?

Best Practice: Always perform validation on both the client-side and server-side. Client-side validation enhances UX, while server-side validation is essential for security and data integrity.

Model Validation Attributes

ASP.NET Core MVC leverages data annotation attributes from the System.ComponentModel.DataAnnotations namespace to define validation rules. These attributes can be applied directly to properties in your model classes.

Common Validation Attributes:

Example Model:


using System.ComponentModel.DataAnnotations;

public class ProductViewModel
{
    [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; }

    [Required]
    [Range(1, int.MaxValue, ErrorMessage = "Price must be a positive number.")]
    public decimal Price { get; set; }

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

    [RegularExpression(@"^\d{5}(-\d{4})?$", ErrorMessage = "Invalid ZIP code format. Use ##### or #####-####.")]
    public string ZipCode { get; set; }
}
            

Implementing Server-Side Validation

In your MVC controllers, you can check the validity of a model using the ModelState.IsValid property. This property is automatically populated by the framework when you bind the request to your model.

Controller Example:


using Microsoft.AspNetCore.Mvc;
// ... other using statements

public class ProductController : Controller
{
    [HttpGet]
    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Create(ProductViewModel product)
    {
        if (ModelState.IsValid)
        {
            // Process the valid product data (e.g., save to database)
            // Redirect to a success page
            return RedirectToAction(nameof(Success));
        }

        // If ModelState is not valid, return the view with validation errors
        return View(product);
    }

    public IActionResult Success()
    {
        return View();
    }
}
            

Displaying Validation Errors in the View:

Use the <div asp-validation-summary="ModelOnly"></div> helper to display a summary of all validation errors at the top of your form. Use the <span asp-validation-for="PropertyName"></span> helper to display validation errors for a specific property.

Form Example (Razor View - .cshtml):


@model ProductViewModel

@section Scripts { @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } }

Note: The _ValidationScriptsPartial.cshtml typically includes a reference to the necessary unobtrusive JavaScript validation libraries.

Client-Side Validation

ASP.NET Core MVC integrates seamlessly with client-side validation using libraries like jQuery Validate and the unobtrusive validation JavaScript package. When these are included and correctly configured, validation attributes on your model will automatically enable client-side checks.

Enabling Client-Side Validation:

  1. Ensure you have the necessary NuGet packages installed: Microsoft.AspNetCore.Mvc.NewtonsoftJson (often included) and Microsoft.jQuery.Unobtrusive.Ajax and Microsoft.jQuery.Unobtrusive.Validation.
  2. Include the validation scripts in your layout or view:
    
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
                        
    (These paths might vary depending on your project setup. The _ValidationScriptsPartial.cshtml often handles this.)
  3. The Razor helpers like <input asp-for="..." /> and <span asp-validation-for="..." /> automatically render the necessary HTML attributes and data attributes for the unobtrusive JavaScript to hook into.

Custom Validation Attributes:

For more complex validation scenarios not covered by built-in attributes, you can create custom validation attributes by inheriting from ValidationAttribute.


using System.ComponentModel.DataAnnotations;

public class MyCustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        // Implement your custom validation logic here
        // Return true if valid, false otherwise
        if (value is string strValue)
        {
            return strValue.Contains("ASP.NET");
        }
        return true; // Or handle other types
    }

    // Optional: Override FormatErrorMessage to customize error messages
}
            

Then apply it to your model property:


[MyCustomValidation(ErrorMessage = "This field must contain 'ASP.NET'.")]
public string Description { get; set; }
            

Remote Validation

For validation that requires checking against a database or external service (e.g., checking if a username is already taken), you can use the [Remote] attribute.

Remote Attribute Usage:

The [Remote] attribute specifies a controller action method that performs the validation.


using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

public class UserController : Controller
{
    // ... other actions

    [HttpGet]
    [Route("/users/checkusername")] // Example route
    public async Task<IActionResult> CheckUsername(string username)
    {
        // Simulate checking database for username existence
        bool usernameExists = await CheckIfUsernameExistsAsync(username);

        if (usernameExists)
        {
            return Json($"Username '{username}' is already taken.");
        }
        else
        {
            return Json(true); // True indicates the username is available
        }
    }

    private async Task<bool> CheckIfUsernameExistsAsync(string username)
    {
        // Replace with actual database check
        await Task.Delay(100); // Simulate async operation
        return username.ToLower() == "admin" || username.ToLower() == "testuser";
    }
}
            

Apply the attribute to your model:


using System.ComponentModel.DataAnnotations;

public class RegistrationViewModel
{
    [Required]
    [Remote(action: "CheckUsername", controller: "User", ErrorMessage = "Username is already taken.")]
    public string Username { get; set; }

    // ... other properties
}