Skip to main content

Forms and Validation

Level: Intermediate

ℹ️ What You'll Learn
  • Form submission: <form asp-controller="Students" asp-action="Create" method="post"> routes POST to action
  • asp-for binding: <input asp-for="Name"> creates <input name="Name" id="Name" value="..."> automatically
  • Two-way model binding: Form sends name=Ravi → Controller receives Student { 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:

AttributeValidation
[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 rules
  • ModelState.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
💡 Validation Tip

Validate on server. Never trust client validation.

🤖Use AI to Learn Faster

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.

nexcoding.in