
Skill
migrating-mvc-validation
migrate MVC validation to ASP.NET Core
Description
Migrates ASP.NET Framework validation to ASP.NET Core including data annotations, custom ValidationAttribute classes, ModelState handling, client-side unobtrusive validation, and FluentValidation integration. Use when upgrading projects that use DataAnnotations, IValidatableObject, RemoteAttribute, custom validation attributes with service dependencies, jquery.validate.unobtrusive, or FluentValidation. Also triggers for ModelState.IsValid migration, ApiController automatic 400 responses, ValidationProblemDetails, and client-side validation script setup in Razor views.
SKILL.md
ASP.NET MVC Validation Migration
Overview
Migrate validation logic from ASP.NET Framework to ASP.NET Core. Most DataAnnotations attributes transfer directly, but custom validators with service dependencies, client-side validation infrastructure, [Remote] attribute configuration, and [ApiController] automatic model validation require targeted changes.
Workflow
Track progress across these steps:
Migration Progress:
- [ ] Step 1: Audit validation usage
- [ ] Step 2: Migrate data annotations and custom attributes
- [ ] Step 3: Update ModelState handling for API controllers
- [ ] Step 4: Migrate client-side validation scripts
- [ ] Step 5: Migrate Remote validation
- [ ] Step 6: Migrate FluentValidation integration
- [ ] Step 7: Verify validation behavior
Step 1: Audit Validation Usage
Search the project for validation patterns to determine which steps apply:
| Signal | Search pattern | Indicates |
|---|---|---|
| Data annotations | using System.ComponentModel.DataAnnotations | Step 2 |
| Custom validators | : ValidationAttribute | Step 2 |
| IValidatableObject | : IValidatableObject | Step 2 (compatible) |
| ModelState in APIs | ModelState.IsValid in ApiController classes | Step 3 |
| Client-side validation | jquery.validate, jquery.validate.unobtrusive | Step 4 |
| Remote validation | [Remote( | Step 5 |
| FluentValidation | AbstractValidator<, FluentValidation | Step 6 |
Skip steps that have no matching signals.
Step 2: Migrate Data Annotations and Custom Attributes
Standard data annotations ([Required], [StringLength], [Range], [RegularExpression], [Compare], [EmailAddress]) work identically in ASP.NET Core. No changes needed.
IValidatableObject is fully compatible. No changes needed.
Custom ValidationAttribute subclasses that override IsValid(object, ValidationContext) are compatible, but service resolution changed:
Before (ASP.NET Framework — service locator via DependencyResolver):
public class UniqueEmailAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var userService = DependencyResolver.Current.GetService<IUserService>();
if (userService.EmailExists((string)value))
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
After (ASP.NET Core — services via ValidationContext.GetService):
public class UniqueEmailAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var userService = (IUserService)validationContext.GetService(typeof(IUserService));
if (userService.EmailExists((string)value))
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
The key change: replace DependencyResolver.Current.GetService<T>() with validationContext.GetService(typeof(T)). This works because ASP.NET Core populates ValidationContext.Items and the service provider from the DI container automatically.
For attributes with complex service dependencies, consider moving the validation logic into a service-layer class or FluentValidation validator instead, to avoid the service locator pattern.
Step 3: Update ModelState Handling for API Controllers
MVC controllers (Controller base class): ModelState.IsValid checks work the same way. No changes needed.
API controllers with [ApiController]: ASP.NET Core automatically returns a 400 response with ValidationProblemDetails when model state is invalid, before the action method executes. This means manual ModelState.IsValid checks are redundant.
Before (ASP.NET Framework Web API):
[HttpPost]
public IHttpActionResult Create(ProductModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// ... create logic
}
After (ASP.NET Core with [ApiController]):
[HttpPost]
public IActionResult Create(ProductModel model)
{
// ModelState.IsValid check is automatic — invalid requests never reach here
// ... create logic
}
The automatic response returns ValidationProblemDetails (RFC 7807) instead of the legacy ModelState dictionary format. If existing API clients depend on the old error shape, customize the response factory:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problems = new ValidationProblemDetails(context.ModelState);
return new BadRequestObjectResult(problems);
};
});
To disable automatic validation entirely for a specific controller (rare), remove the [ApiController] attribute or configure SuppressModelStateInvalidFilter:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Step 4: Migrate Client-Side Validation Scripts
ASP.NET Core uses the same jquery.validate.unobtrusive library for client-side validation, but script loading setup changed.
Add the validation partial. Create or update Views/Shared/_ValidationScriptsPartial.cshtml:
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
Include the partial in views that need client-side validation. Add at the bottom of each form view or in a Scripts section:
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
The _Layout.cshtml must render this section:
@await RenderSectionAsync("Scripts", required: false)
Install client libraries. Use LibMan (the ASP.NET Core default) instead of NuGet or Bower for client-side libraries:
libman install jquery-validation -p cdnjs -d wwwroot/lib/jquery-validation
libman install jquery-validation-unobtrusive -p cdnjs -d wwwroot/lib/jquery-validation-unobtrusive
Or add entries to libman.json if it already exists.
Custom client-side validation adapters that used $.validator.unobtrusive.adapters.add() still work, but the registration call must execute after the unobtrusive script loads. Place custom adapter scripts after the _ValidationScriptsPartial include.
Step 5: Migrate Remote Validation
The [Remote] attribute exists in ASP.NET Core under Microsoft.AspNetCore.Mvc but requires different configuration.
Before (ASP.NET Framework):
[Remote("IsEmailAvailable", "Users")]
public string Email { get; set; }
After (ASP.NET Core):
[Remote(action: "IsEmailAvailable", controller: "Users")]
public string Email { get; set; }
The attribute syntax is the same, but ensure the validation endpoint:
- Returns
JsonResult— returnJson(true)for valid,Json("Error message")for invalid. - Accepts
GETrequests (the default for remote validation AJAX calls). - Is accessible without authentication if the form is on a public page.
Example validation action:
[AcceptVerbs("GET", "POST")]
public IActionResult IsEmailAvailable(string email)
{
if (_userService.EmailExists(email))
{
return Json($"Email {email} is already in use.");
}
return Json(true);
}
Remote validation depends on jquery.validate.unobtrusive — ensure Step 4 is complete.
Step 6: Migrate FluentValidation Integration
If the project uses FluentValidation, the registration mechanism changed.
Before (ASP.NET Framework with FluentValidation.Mvc):
// Global.asax.cs
FluentValidationModelValidatorProvider.Configure();
After (ASP.NET Core with FluentValidation.AspNetCore):
// Program.cs
builder.Services.AddControllersWithViews()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<ProductValidator>());
Package change: Replace FluentValidation.Mvc or FluentValidation.WebApi with FluentValidation.AspNetCore in the project file.
Note:
AddFluentValidation()onIMvcBuilderwas deprecated in FluentValidation 11+. For newer versions, use manual registration:builder.Services.AddValidatorsFromAssemblyContaining<ProductValidator>(); builder.Services.AddFluentValidationAutoValidation(); builder.Services.AddFluentValidationClientsideAdapters(); // optional: client-side support
Individual AbstractValidator<T> implementations require no changes — the validator classes themselves are framework-agnostic.
Step 7: Verify Validation Behavior
After migration, verify:
- Build succeeds with no validation-related compilation errors.
- Server-side validation rejects invalid input and returns appropriate error messages.
- Client-side validation (if used) shows errors before form submission.
- API validation (if applicable) returns the expected error response format.
- Remote validation endpoints respond correctly to AJAX calls.
Troubleshooting
ValidationContext.GetService returns null. The service is not registered in DI. Register it in Program.cs before it can be resolved during validation.
Client-side validation not firing. Ensure jquery, jquery.validate, and jquery.validate.unobtrusive load in that order, and that _ValidationScriptsPartial is included in the view's Scripts section.
API returns ValidationProblemDetails instead of the expected error format. The [ApiController] attribute enables automatic model validation with the RFC 7807 response shape. Customize via InvalidModelStateResponseFactory or suppress with SuppressModelStateInvalidFilter (see Step 3).
FluentValidation validators not executing. Confirm the FluentValidation.AspNetCore package is installed and validators are registered via AddValidatorsFromAssemblyContaining<T>() or RegisterValidatorsFromAssemblyContaining<T>().
Success Criteria
- Custom
ValidationAttributeclasses useValidationContext.GetServiceinstead ofDependencyResolver - API controllers leverage
[ApiController]automatic validation or explicitly opt out - Client-side validation scripts load via
_ValidationScriptsPartialand LibMan [Remote]validation endpoints returnJsonResultand accept GET requests- FluentValidation uses
FluentValidation.AspNetCorepackage with correct registration - No references to
DependencyResolver,FluentValidation.Mvc, orFluentValidation.WebApiremain - Project builds without errors
More skills from the upgrade-agent-plugins repository
View all 19 skillsconverting-to-cpm
migrate .NET projects to Central Package Management
Jul 18.NETMicrosoftMigrationcreating-winforms-custom-controls
create custom WinForms controls
Jul 3.NETWindowsWinUImanaging-winforms-high-dpi-layout
design high-DPI responsive WinForms layouts
Jul 18DesignDesktopUI ComponentsWindowsmanaging-winforms-mvvm
implement MVVM pattern in WinForms
Jul 18.NETWindowsWinUImigrating-aspnet-framework-to-core
migrate ASP.NET Framework to Core
Jul 3.NETASP.NET CoreBackendMigrationmigrating-documentdb-to-cosmos
migrate DocumentDB to Cosmos DB
Jul 3AzureCosmos DBDatabaseMigration
More from Microsoft
View publisherplaywright-trace
inspect Playwright trace files
playwright
Apr 6DebuggingPlaywrightTestingrushstack-best-practices
manage Rush monorepos with best practices
rushstack
Apr 6EngineeringLocal DevelopmentMicrosoftProject Management +1azure-ai-agents-persistent-dotnet
build AI agents with Azure .NET SDK
skills
Jul 3.NETAgentsAzureLLMazure-ai-anomalydetector-java
build anomaly detection applications with Java
skills
May 13AnalyticsAzureData AnalysisJava +2azure-ai-contentsafety-java
build content moderation applications with Azure AI
skills
Jul 7AI InfrastructureAzureJavaSecurityazure-ai-contentsafety-py
detect harmful content with Azure AI Content Safety
skills
Jul 18AzureComplianceLLMMicrosoft +2