Model Binding and Validation
Level: Intermediate
ℹ️ What You'll Learn
- Model binding definition: Automatically convert HTTP request data (JSON body, URL, query) into C# objects
[FromBody]binding: POST/PUT requests send JSON → automatically converts to Student object[FromRoute]binding: URL path parameters automatically converted to action method parameters[FromQuery]binding: Query string ?name=Ravi → converts to parameterstring name- Data annotation validators:
[Required],[StringLength(100)],[Range(1, 12)],[EmailAddress],[RegularExpression] [Required]: Field mandatory, returns 400 if missing[StringLength(max)]: Max character length,[StringLength(100, MinimumLength=3)]requires 3-100 chars[Range(min, max)]: Numeric range,[Range(1, 12)]for class number 1-12[RegularExpression(pattern)]: Regex validation (roll number pattern SMS-2024-001)[EmailAddress]: Email format validationModelState.IsValid: Check if all validation passed, return 400 BadRequest if not- Validation error response: 400 Bad Request with field errors listing which constraints failed
- Custom validation: Implement
IValidatableObjectfor complex validation (e.g., endDate > startDate) - School Management validation: Student name required, roll number format SMS-YYYY-NNN, class 1-12, email valid
- Fluent Validation: Alternative library with cleaner syntax than attributes for complex rules
- Error display: Validation messages shown to user (what field is wrong, what constraint failed)
Model Binding
Automatic conversion of request data to C# objects:
[HttpPost]
public async Task<ActionResult<Student>> CreateStudent([FromBody] Student student)
{
// student automatically populated from request body
// { "name": "Ravi", "className": "10-A" }
}
Binding sources:
[FromBody]— Request body (JSON)[FromRoute]— URL path parameters[FromQuery]— Query string (?name=value)[FromHeader]— HTTP headers[FromForm]— Form data
Student Model with Validation
File: Models/Student.cs
public class Student
{
[Required]
public int Id { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
[Required]
[RegularExpression(@"^SMS-\d{4}-\d{3}$")]
public string RollNumber { get; set; }
[Required]
[Range(1, 12)]
public int ClassNumber { get; set; }
[EnumDataType(typeof(StudentStatus))]
public StudentStatus Status { get; set; }
[EmailAddress]
public string Email { get; set; }
[Range(typeof(DateTime), "1990-01-01", "2020-12-31")]
public DateTime DateOfBirth { get; set; }
}
public enum StudentStatus { Active, Inactive, Graduated }
Validation Attributes
| Attribute | Validation |
|---|---|
[Required] | Field mandatory |
[StringLength(max)] | Max string length |
[Range(min, max)] | Numeric range |
[EmailAddress] | Valid email |
[RegularExpression(pattern)] | Regex pattern |
[Compare(property)] | Compare two fields |
[EnumDataType(enum)] | Valid enum value |
Automatic Validation
[HttpPost]
public async Task<ActionResult<Student>> CreateStudent([FromBody] Student student)
{
if (!ModelState.IsValid)
return BadRequest(ModelState); // 400 with errors
await _service.CreateStudentAsync(student);
return CreatedAtAction(nameof(GetStudent), new { id = student.Id }, student);
}
[ApiController] automatically returns 400 for invalid models.
Validation Response
Invalid request:
{
"errors": {
"Name": ["The Name field is required."],
"ClassNumber": ["The field ClassNumber must be between 1 and 12."]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400
}
Fluent Validation (Optional)
Advanced validation:
using FluentValidation;
public class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(x => x.Name).NotEmpty().MinimumLength(3);
RuleFor(x => x.RollNumber).Matches(@"^SMS-\d{4}-\d{3}$");
RuleFor(x => x.ClassNumber).InclusiveBetween(1, 12);
RuleFor(x => x.DateOfBirth).LessThan(DateTime.Now.AddYears(-5));
}
}
// Register in Program.cs
builder.Services.AddValidatorsFromAssemblyContaining<StudentValidator>();
Query Parameter Validation
[HttpGet]
public async Task<ActionResult<List<Student>>> GetStudents(
[FromQuery] int? classNumber, // Optional
[FromQuery] string status) // Optional
{
// classNumber validated as int
// status validated as string
}
// Request: GET /api/students?classNumber=10&status=Active
Key Takeaways
- Model binding = automatic data conversion
- Validation = prevent invalid data
- Attributes = simple validation
- FluentValidation = complex rules
💡 Validation Tip
Validate on API boundary. Never trust client data.
🤖Use AI to Learn Faster
Use ChatGPT, Claude, or Copilot to go deeper on Model Binding and Validation. Try these prompts:
"What's the difference between [FromBody] and [FromQuery]?""When should I use FluentValidation?""How do I return validation errors?""Quiz me on validation"
💡 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
Have questions on your tech stack, ongoing projects, or need one-to-one training?