Forms and Validation
Level: Intermediate
- Form submission:
<form asp-controller="Students" asp-action="Create" method="post">routes POST to action asp-forbinding:<input asp-for="Name">creates<input name="Name" id="Name" value="...">automatically- Two-way model binding: Form sends
name=Ravi→ Controller receivesStudent { Name = "Ravi" }automatically - Anti-forgery token:
@Html.AntiForgeryToken()prevents CSRF attacks (required in POST forms) - Data Annotations:
[Required](mandatory),[StringLength(100)](max chars),[Range(1, 12)](number range),[EmailAddress](email format) - Validation attributes on model:
public class Student { [Required] public string Name { get; set; } } - Server-side validation:
if (!ModelState.IsValid) { return View(student); }checks all annotations before processing ModelState.IsValid: True if all validations passed, false if any failed- Displaying errors:
<span asp-validation-for="Name" class="text-danger"></span>shows validation message below field - Client-side validation: jQuery Unobtrusive Validation (JavaScript) validates before submit (instant feedback)
- Custom validation:
IValidatableObject.Validate()for complex rules (example: endDate > startDate) - School Management validation: Student name required, roll number format SMS-YYYY-NNN, class 1-12, email unique
- Validation error display: List all errors with field name + message (user sees what's wrong)
- Common mistakes: Returning 200 with error message (use 400), client validation only (server validation required!)
HTML Forms
Form submits to action.
<form asp-controller="Students" asp-action="Create" method="post">
<div class="form-group">
<label asp-for="Name"></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="RollNumber"></label>
<input asp-for="RollNumber" class="form-control" />
<span asp-validation-for="RollNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ClassName"></label>
<select asp-for="ClassName" asp-items="Html.GetEnumSelectList<string>()">
<option value="">Select Class</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Create</button>
</form>
asp-for binds to model property. Auto-generates name and id attributes.
Model Binding
Form data automatically maps to model.
[HttpPost]
public async Task<IActionResult> Create(Student student)
{
// student.Name, student.RollNumber, etc. populated from form
// No manual parsing needed
}
Binding sources:
- Form data (
asp-for) - URL route (
{id}) - Query string (
?className=10-A)
Validation Attributes
Decorate model with validation rules.
public class Student
{
[Required]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
[Required]
[RegularExpression(@"^SMS-\d{4}-\d{3}$", ErrorMessage = "Invalid roll number")]
public string RollNumber { get; set; }
[Required]
[Range(1, 12)]
public int ClassNumber { get; set; }
[EmailAddress]
public string Email { get; set; }
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
[Compare("ConfirmPassword")]
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
Common attributes:
| Attribute | Validation |
|---|---|
[Required] | Field mandatory |
[StringLength(max)] | Max length |
[MinLength(n)] | Min length |
[Range(min, max)] | Number range |
[EmailAddress] | Valid email |
[Url] | Valid URL |
[RegularExpression(pattern)] | Regex match |
[Compare(property)] | Compare two fields |
[CreditCard] | Valid credit card |
[Phone] | Valid phone |
Server-Side Validation
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Student student)
{
if (!ModelState.IsValid)
{
// Validation failed. Show form with errors.
return View(student);
}
// Validation passed
await _service.CreateStudentAsync(student);
return RedirectToAction(nameof(Index));
}
ModelState.IsValid checks all validation attributes.
Validation Messages
Display errors in view.
<!-- All errors -->
@Html.ValidationSummary()
<!-- Field errors -->
<span asp-validation-for="Name" class="text-danger"></span>
<span asp-validation-for="RollNumber" class="text-danger"></span>
<!-- With custom CSS -->
<span asp-validation-for="Name" class="invalid-feedback"></span>
Client-Side Validation
Validate before posting. Requires jQuery Unobtrusive Validation.
<script src="~/lib/jquery-validation/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
Attributes auto-generate data-val-* attributes:
<input asp-for="Name"
data-val="true"
data-val-required="Name is required"
data-val-minlength="Minimum 3 characters"
data-val-minlength-min="3" />
JavaScript validates before submit.
Custom Validation
public class Student
{
public string Name { get; set; }
[CustomValidation(typeof(StudentValidator), nameof(StudentValidator.ValidateRollNumber))]
public string RollNumber { get; set; }
}
public class StudentValidator
{
public static ValidationResult ValidateRollNumber(string rollNumber, ValidationContext context)
{
if (string.IsNullOrEmpty(rollNumber))
return ValidationResult.Success;
if (!rollNumber.StartsWith("SMS-"))
return new ValidationResult("Roll number must start with SMS-");
return ValidationResult.Success;
}
}
Or implement IValidatableObject:
public class Student : IValidatableObject
{
public string Name { get; set; }
public string RollNumber { get; set; }
public string ClassName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
var errors = new List<ValidationResult>();
if (string.IsNullOrEmpty(Name))
errors.Add(new ValidationResult("Name required", new[] { nameof(Name) }));
if (!RollNumber.StartsWith("SMS-"))
errors.Add(new ValidationResult("Invalid roll number", new[] { nameof(RollNumber) }));
// Cross-field validation
if (ClassName == "12-A" && Name == "Test")
errors.Add(new ValidationResult("Test name not allowed in 12-A"));
return errors;
}
}
Fluent Validation (Optional)
Advanced validation with cleaner API.
dotnet add package FluentValidation
dotnet add package FluentValidation.AspNetCore
public class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name required")
.MinimumLength(3).WithMessage("Minimum 3 characters");
RuleFor(x => x.RollNumber)
.NotEmpty()
.Matches(@"^SMS-\d{4}-\d{3}$").WithMessage("Invalid roll number");
RuleFor(x => x.ClassNumber)
.InclusiveBetween(1, 12).WithMessage("Class must be 1-12");
// Cross-field
RuleFor(x => x.Password)
.Equal(x => x.ConfirmPassword)
.WithMessage("Passwords don't match");
}
}
// Register in Program.cs
builder.Services.AddValidatorsFromAssemblyContaining<StudentValidator>();
Validation Response
Invalid form:
<div class="alert alert-danger">
@Html.ValidationSummary()
</div>
<!-- Form re-displayed with error messages -->
<input asp-for="Name" class="form-control is-invalid" />
<span asp-validation-for="Name" class="invalid-feedback">@Model["Name"]</span>
CSS classes for styling:
.is-invalid= error input.invalid-feedback= error message.alert-danger= error summary
Key Takeaways
asp-for= bind form to model[Required], [StringLength]= validation rulesModelState.IsValid= all validations passed- Client-side = JavaScript before submit
- Server-side = always required for security
- Custom validation = IValidatableObject or
[CustomValidation] - Fluent Validation = alternative, cleaner API
Validate on server. Never trust client validation.
Use ChatGPT, Claude, or Copilot to go deeper on Forms and Validation. Try these prompts:
"What's the difference between client and server validation?""How do I create custom validation?""When should I use Fluent Validation?""How does model binding work?""Quiz me on forms"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.