Model validation is one of the most important parts of building secure and reliable web applications. It protects your application from invalid data, accidental mistakes, and even malicious inputs. In ASP.NET Core, model validation becomes much simpler with the help of Data Annotations. These small attributes, which you place on your model properties, allow you to define validation rules quickly without writing long validation logic manually.
In this article, we will explore model validation in ASP.NET Core using data annotations. We will break down how it works, why it matters, the most common validation attributes, how to create custom validation rules, and how validation interacts with controllers, Razor pages, and APIs. Everything will be explained in simple language so you can apply it to your project immediately.
What Is Model Validation?
Model validation is the process of checking whether the data sent by a user or an API client meets the rules you expect. For example, if your form asks for an email, you must ensure the email is valid. If your application requires a password of at least eight characters, you want to reject a short password. If an API endpoint receives a product with a price, you need to ensure the price is a positive number.
In ASP.NET Core, validation can happen automatically. Once ASP.NET Core sees the data mapped into a model, it checks if that model is valid. If it is not valid, the framework reports the validation errors so you can return the right message to the user.
What Are Data Annotations?
Data Annotations are simple attributes that you place above class properties (or sometimes classes). These attributes contain rules describing how the data should be validated. Examples include:
Required
StringLength
MaxLength
Range
EmailAddress
Compare
RegularExpression
By adding these annotations, ASP.NET Core automatically validates the model when it is submitted.
For example:
public class RegisterViewModel
{
[Required]
public string Username { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[MinLength(8)]
public string Password { get; set; }
}
With this code, ASP.NET Core will reject any user registration that has missing or invalid fields before your controller even runs your logic.
Why Model Validation Matters
Model validation protects your application. Here are the most important reasons:
1. Prevents Wrong Data From Entering Your System
Without validation, users may submit incomplete, incorrect, or badly formatted data. This could lead to database errors, application crashes, or corrupted data.
2. Improves User Experience
With proper validation, users get clear error messages about what is wrong. This allows them to fix mistakes easily.
3. Security
Validation is essential for preventing attacks like SQL injection, cross-site scripting, and overposting attacks. When the incoming data is validated and restricted, attackers have fewer ways to break your system.
4. Saves Developer Time
Instead of writing your own validation logic every time, you can use built-in Data Annotations. They reduce code duplication and make your system easier to maintain.
How Model Validation Works in ASP.NET Core
To understand Data Annotations better, you need to know how the ASP.NET Core pipeline handles model validation.
Step 1. User Sends Data
This could be form data from a Razor page or JSON data from a web API.
Step 2. ASP.NET Core Model Binding
The framework maps the data to your model class. For example, name, email, password, and other inputs will be mapped to the model properties.
Step 3. Validation Automatically Runs
ASP.NET Core checks all the Data Annotation attributes on the model.
Step 4. Controller Receives the Data
If the model is invalid, the ModelState object in the controller will contain errors.
You check it like this:
if (!ModelState.IsValid)
{
return View(model);
}
In APIs:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
This simple pattern allows you to safely work with valid data.
Common Data Annotation Attributes
ASP.NET Core provides many built-in attributes. Below are the most commonly used ones with simple explanations.
1. Required
Marks a property as mandatory.
[Required]
public string FirstName { get; set; }
2. StringLength
Sets minimum and maximum allowed length for a string.
[StringLength(50, MinimumLength = 3)]
public string Username { get; set; }
3. MaxLength and MinLength
These specify length limits without affecting database schema.
[MaxLength(200)]
public string Description { get; set; }
4. Range
Used to specify numeric limits.
[Range(1, 100)]
public int Quantity { get; set; }
5. EmailAddress
Validates email format.
[EmailAddress]
public string Email { get; set; }
6. Phone
Validates phone numbers.
[Phone]
public string PhoneNumber { get; set; }
7. Url
Validates website URLs.
[Url]
public string Website { get; set; }
8. Compare
Useful for confirming fields, such as password confirmation.
[Compare("Password")]
public string ConfirmPassword { get; set; }
9. RegularExpression
Powerful for custom pattern validation.
[RegularExpression(@"^[A-Za-z0-9]{5,10}$")]
public string Code { get; set; }
10. CreditCard
Validates credit card numbers.
[CreditCard]
public string CreditCardNumber { get; set; }
Custom Error Messages
Each attribute allows custom error messages. This gives users clearer feedback.
[Required(ErrorMessage = "Email is required.")]
[EmailAddress(ErrorMessage = "Enter a valid email address.")]
public string Email { get; set; }
This is important for improving user experience and creating more helpful forms.
Server-Side vs Client-Side Validation
ASP.NET Core supports both server-side and client-side validation.
Server-Side Validation
This runs on the server when data is submitted. It is always required because client-side validation can be bypassed.
You check it using:
if (!ModelState.IsValid)
{
return View(model);
}
Client-Side Validation
Browsers can show validation errors instantly without sending a request to the server. ASP.NET Core uses jQuery Validation for this by default in MVC and Razor Pages.
You must include these scripts:
<script src="~/lib/jquery/jquery.js"></script>
<script src="~/lib/jquery-validation/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
Client-side validation improves usability, but server-side validation is the true protection.
Validation in MVC Controllers
When using MVC, validation typically happens inside POST actions.
Example
[HttpPost]
public IActionResult Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Process valid registration
return RedirectToAction("Success");
}
The controller action receives the model, and ASP.NET Core has already filled it with any validation errors.
Validation in Razor Pages
Razor Pages also support validation automatically.
Example
public class RegisterModel : PageModel
{
[BindProperty]
public RegisterViewModel Input { get; set; }
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
return RedirectToPage("Success");
}
}
Razor Pages work similarly to MVC but without needing separate controllers.
Validation in ASP.NET Core Web API
For APIs, model validation works a bit differently. API methods usually return JSON responses instead of web views.
Simple example
[HttpPost]
public IActionResult CreateProduct(ProductModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok("Product created");
}
Automatic 400 Responses
In ASP.NET Core 2.1 and later, the framework can automatically return a 400 Bad Request response when the model is invalid. You can enable or disable this behavior.
Binding and Overposting Protection
Model validation also helps protect against overposting attacks. An overposting attack happens when a client sends data for properties they should not be allowed to change.
Example: A user trying to set IsAdmin = true in their profile.
To prevent this, always use a ViewModel or DTO with specific properties instead of binding entire database model classes.
Creating Custom Validation Attributes
Sometimes built-in attributes are not enough. ASP.NET Core allows you to create your own validation attributes by extending ValidationAttribute.
Example: A simple custom validator that checks if the value is not a future date.
public class NotFutureDateAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return true;
DateTime date = (DateTime)value;
return date <= DateTime.Now;
}
}
Using it:
[NotFutureDate(ErrorMessage = "Date cannot be in the future.")]
public DateTime BirthDate { get; set; }
Custom validation gives you total control over data rules.
Cross-Field Validation Using IValidatableObject
If you need to validate multiple fields together, you can use IValidatableObject.
Example: Ensuring end date is greater than start date.
public class EventModel : IValidatableObject
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
if (EndDate < StartDate)
{
yield return new ValidationResult(
"End date must be after start date.",
new[] { "EndDate" });
}
}
}
This is useful for business rules that depend on multiple properties.
Display Attributes
Data Annotations also include display attributes that improve how validation messages and form labels appear.
Example
[Display(Name = "Full Name")]
public string Name { get; set; }
This makes forms easier for users to understand.
Validation Summary and Field Errors in Views
In Razor views, you show validation messages using:
@Html.ValidationSummary()
@Html.ValidationMessageFor(m => m.Email)
Or in Razor Pages:
<partial name="_ValidationSummary" />
These helpers display error messages generated from Data Annotations.
Handling Validation Errors in APIs
When validation fails in APIs, you often return JSON with error details.
Example response
{
"Email": [
"Email is required.",
"Enter a valid email address."
]
}
You can customize error messages or create a standard API error format.
Validation Best Practices
To use Data Annotations effectively, follow these best practices.
1. Use ViewModels Instead of Entities
Never apply validation directly on Entity Framework models for user input. Use a ViewModel or DTO layer.
2. Combine Client-Side and Server-Side Validation
Client-side validation improves user experience.
Server-side validation ensures security.
3. Use Custom Attributes Only When Needed
Do not write custom validation unless the built-in attributes cannot handle your case.
4. Keep Error Messages Clear
Good error messages make your application more user friendly.
5. Unit Test Your Validation
You can write tests to make sure your custom validators behave correctly.
Real-World Example: User Registration Form
Here is a practical example combining everything.
Model
public class RegisterViewModel
{
[Required(ErrorMessage = "Username is required.")]
[StringLength(20, MinimumLength = 3)]
public string Username { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[MinLength(8)]
public string Password { get; set; }
[Compare("Password")]
public string ConfirmPassword { get; set; }
}
Controller
[HttpPost]
public IActionResult Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
return RedirectToAction("Success");
}
This is a clean and maintainable way to validate registration data.
Conclusion
Model validation is a core part of building safe, stable, and user-friendly ASP.NET Core applications. Data Annotations provide a simple but powerful way to define validation rules directly on your models. They remove the need for repetitive code, reduce bugs, and make your application easier to maintain.
With built-in validators like Required, Range, and EmailAddress, plus support for custom validation and cross-field checks, ASP.NET Core gives you all the tools you need to validate user input effectively. Whether you are building MVC apps, Razor Pages, or APIs, validation works the same way and helps you keep your system secure.
By understanding and applying the techniques in this article, you can build web applications that accept only valid, safe, and well-structured data.