Binding Reference
Complete guide to request binding in Pragmatic.Endpoints.
Route Parameters
Section titled “Route Parameters”Route parameters are extracted from the URL path. The SG matches properties to route segments by name (case-insensitive). If your route has {id} and your class has a property named Id, the binding happens automatically — [FromRoute] is optional:
Basic Route Parameters
Section titled “Basic Route Parameters”[Endpoint(HttpVerb.Get, "/users/{id}")]public partial class GetUserEndpoint : Endpoint<UserDto>{ public Guid Id { get; set; } // auto-bound: matches {id} by name
// [FromRoute] is optional — only needed when the property name // doesn't match the route parameter name}Multiple Route Parameters
Section titled “Multiple Route Parameters”[Endpoint(HttpVerb.Get, "/customers/{customerId}/orders/{orderId}")]public partial class GetOrderEndpoint : Endpoint<OrderDto>{ [FromRoute] public Guid CustomerId { get; set; }
[FromRoute] public Guid OrderId { get; set; }}Route Constraints
Section titled “Route Constraints”Route constraints are specified in the route template:
[Endpoint(HttpVerb.Get, "/products/{id:int}")]public partial class GetProductEndpoint : Endpoint<ProductDto>{ [FromRoute] public int Id { get; set; }}
[Endpoint(HttpVerb.Get, "/files/{**path}")] // Catch-allpublic partial class GetFileEndpoint : Endpoint<FileResponse>{ [FromRoute] public string Path { get; set; } = null!;}Query Parameters
Section titled “Query Parameters”Query parameters come from the URL query string.
Required Query Parameters
Section titled “Required Query Parameters”[Endpoint(HttpVerb.Get, "/search")]public partial class SearchEndpoint : Endpoint<SearchResults>{ // Required - uses 'required' keyword [FromQuery] public required string Query { get; set; }}// GET /search?query=helloOptional Query Parameters
Section titled “Optional Query Parameters”[Endpoint(HttpVerb.Get, "/products")]public partial class ListProductsEndpoint : Endpoint<ProductList>{ // Optional - nullable type [FromQuery] public string? Category { get; set; }
// Optional - value type with nullable [FromQuery] public int? Page { get; set; }
// Optional - with default value [FromQuery] public int PageSize { get; set; } = 20;}// GET /products?category=electronics&page=2Custom Query Parameter Names
Section titled “Custom Query Parameter Names”[Endpoint(HttpVerb.Get, "/search")]public partial class SearchEndpoint : Endpoint<SearchResults>{ [FromQuery(Name = "q")] public required string Query { get; set; }
[FromQuery(Name = "max_results")] public int MaxResults { get; set; } = 10;}// GET /search?q=hello&max_results=50Array Query Parameters
Section titled “Array Query Parameters”[Endpoint(HttpVerb.Get, "/products")]public partial class FilterProductsEndpoint : Endpoint<ProductList>{ [FromQuery] public List<string>? Tags { get; set; }
[FromQuery] public int[]? Ids { get; set; }}// GET /products?tags=electronics&tags=sale&ids=1&ids=2&ids=3Header Parameters
Section titled “Header Parameters”Header parameters come from HTTP request headers.
Required Headers
Section titled “Required Headers”[Endpoint(HttpVerb.Post, "/api/data")]public partial class DataEndpoint : Endpoint<DataResponse>{ // Required - uses 'required' keyword [FromHeader(Name = "X-Api-Key")] public required string ApiKey { get; set; }}Optional Headers
Section titled “Optional Headers”[Endpoint(HttpVerb.Post, "/api/data")]public partial class DataEndpoint : Endpoint<DataResponse>{ // Optional - nullable type [FromHeader(Name = "X-Correlation-Id")] public string? CorrelationId { get; set; }
// Optional - with default [FromHeader(Name = "X-Request-Timeout")] public int RequestTimeout { get; set; } = 30;}Standard Headers
Section titled “Standard Headers”[Endpoint(HttpVerb.Put, "/resources/{id}")]public partial class UpdateResourceEndpoint : Endpoint<ResourceDto>{ [FromRoute] public Guid Id { get; set; }
// ETag for optimistic concurrency [FromHeader(Name = "If-Match")] public string? IfMatch { get; set; }
// Accept-Language for localization [FromHeader(Name = "Accept-Language")] public string? AcceptLanguage { get; set; }}Body Parameters
Section titled “Body Parameters”Properties without binding attributes become the request body.
Simple Body
Section titled “Simple Body”[Endpoint(HttpVerb.Post, "/users")]public partial class CreateUserEndpoint : Endpoint<UserDto>{ // These properties become the request body DTO public required string Name { get; set; } public required string Email { get; set; } public string? Phone { get; set; }}
// Generated body DTO:// public sealed record CreateUserEndpointBody// {// public required string Name { get; init; }// public required string Email { get; init; }// public string? Phone { get; init; }// }Complex Body Types
Section titled “Complex Body Types”[Endpoint(HttpVerb.Post, "/orders")]public partial class CreateOrderEndpoint : Endpoint<OrderDto>{ public required Guid CustomerId { get; set; } public required List<OrderItem> Items { get; set; } public Address? ShippingAddress { get; set; } public PaymentInfo? Payment { get; set; }}
public record OrderItem(Guid ProductId, int Quantity);public record Address(string Street, string City, string Country);public record PaymentInfo(string CardNumber, string Expiry);Body with Documentation
Section titled “Body with Documentation”[Endpoint(HttpVerb.Post, "/articles")]public partial class CreateArticleEndpoint : Endpoint<ArticleDto>{ /// <summary> /// The article title. Must be unique. /// </summary> public required string Title { get; set; }
/// <summary> /// The article content in Markdown format. /// </summary> public required string Content { get; set; }
/// <summary> /// Tags for categorization. Maximum 5 tags allowed. /// </summary> public List<string>? Tags { get; set; }}Mixed Bindings
Section titled “Mixed Bindings”Combine multiple binding sources in one endpoint:
[Endpoint(HttpVerb.Put, "/customers/{customerId}/orders/{orderId}")][EndpointSummary("Update Order")]public partial class UpdateOrderEndpoint : Endpoint<OrderDto>{ // Route parameters [FromRoute] public Guid CustomerId { get; set; }
[FromRoute] public Guid OrderId { get; set; }
// Required header [FromHeader(Name = "X-Idempotency-Key")] public required string IdempotencyKey { get; set; }
// Optional header [FromHeader(Name = "X-Correlation-Id")] public string? CorrelationId { get; set; }
// Query parameter [FromQuery] public bool? Notify { get; set; }
// Body properties public required List<OrderItem> Items { get; set; } public string? Notes { get; set; }}
// Request:// PUT /customers/123/orders/456?notify=true// Headers:// X-Idempotency-Key: abc123// X-Correlation-Id: corr-789// Body:// { "items": [...], "notes": "Rush order" }Parameter Ordering
Section titled “Parameter Ordering”The generator automatically orders parameters correctly:
- Route parameters - First (positional in URL)
- Required headers - Next (mandatory)
- Required body - If using body DTO
- Required query - If any required
- Dependencies - DI injected services
- HttpContext - If needed
- CancellationToken - Always last required
- Optional headers - With
= default - Optional query - With
= default
This ensures C# parameter ordering rules (required before optional) are satisfied.
Validation Integration
Section titled “Validation Integration”Combine with Pragmatic.Validation:
[Endpoint(HttpVerb.Post, "/users")]public partial class CreateUserEndpoint : Endpoint<UserDto>{ [Required] [Email] public required string Email { get; set; }
[Required] [MinLength(2)] [MaxLength(50)] public required string Name { get; set; }
[Phone] public string? Phone { get; set; }}Validation runs automatically before HandleAsync via the DomainAction pipeline.
File Uploads
Section titled “File Uploads”[Endpoint(HttpVerb.Post, "/files")]public partial class UploadFileEndpoint : Endpoint<FileInfo>{ [FromForm] public required IFormFile File { get; set; }
[FromForm] public string? Description { get; set; }}Best Practices
Section titled “Best Practices”- Use
requiredfor mandatory parameters - Clear intent and compile-time safety - Use nullable types for optional -
string?,int?instead of defaults - Name headers explicitly -
[FromHeader(Name = "X-...")] - Document body properties - XML comments become OpenAPI descriptions
- Group related endpoints - Use
[EndpointGroup]for common prefixes - Keep endpoints focused - One responsibility per endpoint class