I've seen parameter validation code inside controller actions on an HTTP GET request. However, the model binder in ASP.NET Core will populate the ModelState data structure with information about all input parameters on any type of request.
In other words, model binding isn't just for POST requests with form values or JSON.
Take the following class, for example.
public class SearchParameters { [Required] [Range(minimum:1, maximum:int.MaxValue)] public int? Page { get; set; } [Required] [Range(minimum:10, maximum:100)] public int? PageSize { get; set; } [Required] public string Term { get; set; } }
We'll use the class in the following controller.
[Route("api/[controller]")] public class SearchController : Controller { [HttpGet] public IActionResult Get(SearchParameters parameters) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var model = // build the model ... return new OkObjectResult(model); } }
Let's say a client sends a GET request to /api/search?pageSize=5000. We don't need to write validation code for the input in the action, all we need to do is check model state. For a request to /api/search?pageSize=5000, the above action code will return a 400 (bad request) error.
{ "Page":["The Page field is required."], "PageSize":["The field PageSize must be between 10 and 100."], "Term":["The Term field is required."] }
For the Required validation to activate for Page and PageSize, we need to make these int type properties nullable. Otherwise, the runtime assigns a default value 0 and the Range validation fails, which might be confusing to clients.
Give your input model a default constructor to provide default values and you won't need nullable properties or the Required attributes. Of course, this approach only works if you can provide sensible default values for the inputs.
public class SearchParameters { public SearchParameters() { Page = 1; PageSize = 10; Term = String.Empty; } [Range(minimum:1, maximum:int.MaxValue)] public int? Page { get; set; } [Range(minimum:10, maximum:100)] public int? PageSize { get; set; } public string Term { get; set; } }