.NET Core / Web API71 min total · 19 parts
Building REST APIs with ASP.NET Core: Routing, Middleware, and Dependency Injection
Part 7 of 19 · ~1 min
Model Validation with Data Annotations
public class CreateReservationRequest
{
[Required]
public DateTime StartUtc { get; set; }
[Range(15, 120)]
public int DurationMinutes { get; set; }
[StringLength(280)]
public string? Notes { get; set; }
}
Because ReservationsController carries [ApiController], a POST with DurationMinutes: 500 or a missing StartUtc never reaches Create's body — Bench hands back a 400 with a structured description of exactly which field failed, automatically, before a line of your code runs. Drop [ApiController] and the identical validation still runs and populates ModelState — it just stops checking itself, and you have to:
[HttpPost]
public IActionResult CreateManual(CreateReservationRequest request)
{
if (!ModelState.IsValid) return BadRequest(ModelState); // needed only WITHOUT [ApiController]
// ...
}
Data annotations handle each field on its own, but they have a blind spot Dana ran into almost immediately: nothing on CreateReservationRequest can express "this reservation has to end before the makerspace closes at 9 PM," because that depends on both StartUtc and DurationMinutes at once, and a [Range] on one property has no way to see the other. [Range(15, 120)] alone will happily accept a 120-minute laser-cutter session that starts at 8:15 PM. Reaching IValidatableObject on the model handles a case or two; past that, most real projects — Bench included, once this list grew past "one cross-field rule" — bring in a dedicated library, and FluentValidation is the usual pick.