Skip to main content

The default binding model

For attributed controller methods, the router infers where values come from:
  • For POST, PUT, and PATCH, one class-type parameter is treated as the request body
  • Parameters whose names match route placeholders are read from the route
  • Remaining scalar parameters are read from the query string

Implicit binding example

  • Body — from the JSON body
  • OrderId — from /orders/[orderId]
  • Sort — from ?sort=...

Explicit binding example

Explicit attributes:
  • [FromBody]
  • [FromRoute]
  • [FromQuery]

Important binding rules

  • Only one body parameter is allowed
  • [FromBody] is only valid on POST, PUT, and PATCH
  • [FromBody] must target a class-type parameter
  • Missing required route or query values return 400 Bad Request
  • Nullable query parameters can be omitted and bind as null
Route placeholder names and query string names are matched case-insensitively. Body field names are different: they are decoded through the active IParameterNameCodec, so the JSON payload must follow the router’s configured naming convention.

Built-in conversions beyond strings

The current binder supports more than just basic strings and body objects.
  • Enums can bind from route values, query values, and body fields
  • DateTimeOffset, DateOnly, TimeOnly, and TimeSpan can bind from route, query, and body data when the incoming value is convertible

Transport validation attributes

The current binding pipeline also validates parameters and bound body models before the controller action runs.
Available transport validation attributes include:
  • NonEmpty
  • NonDefault
  • StringLength(min, max)
  • Range(min, max)
  • EmailFormat
  • UrlFormat
Use ValidateElements = true on collection validators when each element should be checked individually. When validation fails, the router returns 400 Bad Request before the controller action runs.

Returning JSON

Non-raw controller methods should return Task<T>. The returned object is serialized to JSON automatically.

Returning errors

For attributed methods, throw an HTTP exception:
Useful helpers:
  • RestApiErrorResponse — simple { message, error_code } payload
  • ClientError — carries status code, message, localization key, and error code
  • Expected<T, E> — models success and failure explicitly before converting to HTTP
If an unexpected exception escapes the controller, the router converts it into an internal server error with code internal_server_exception.
Validation field paths use the active parameter-name codec for body properties. If you switch to snake case, validation messages follow that same JSON-facing naming.
Middleware and pipeline shows how to add cross-cutting behavior before and after your endpoints run.