ASP.NET - ASP.NET Core Parameter Binding for Minimal APIs
ASP.NET Core Minimal APIs provide a lightweight way to build HTTP APIs with very little code. One of the most important features of Minimal APIs is parameter binding. Parameter binding is the process through which ASP.NET Core takes data received in an HTTP request and converts it into strongly typed parameters that can be used directly by a route handler. Instead of manually reading values from HttpRequest, developers can declare parameters in the endpoint definition, and ASP.NET Core determines where those values should come from. (Microsoft Learn)
1. What Is Parameter Binding?
Consider a simple endpoint:
app.MapGet("/products/{id}", (int id) =>
{
return $"Product ID: {id}";
});
If a client sends:
GET /products/25
ASP.NET Core identifies 25 as the value of the id parameter, converts it from a string representation into an int, and passes it to the route handler.
Without parameter binding, the developer would have to access the route values manually through HttpContext. Parameter binding therefore makes Minimal API code shorter, clearer, and easier to maintain.
The framework supports several major binding sources, including route values, query strings, HTTP headers, JSON request bodies, form values, dependency-injected services, and custom binding mechanisms. (Microsoft Learn)
2. Binding Values from Route Parameters
Route parameters are values included directly in the URL path.
For example:
app.MapGet("/students/{id}", (int id) =>
{
return $"Student ID: {id}";
});
A request such as:
/students/101
binds the value 101 to the id parameter.
The parameter name normally corresponds to the placeholder in the route template.
You can also make the source explicit:
app.MapGet("/students/{id}", ([FromRoute] int id) =>
{
return $"Student ID: {id}";
});
Here, [FromRoute] clearly tells ASP.NET Core that id must be obtained from the route.
3. Binding Values from Query Strings
Query strings are commonly used for filtering, searching, sorting, and pagination.
For example:
app.MapGet("/products", (string category, int page) =>
{
return $"Category: {category}, Page: {page}";
});
A request might look like:
/products?category=laptops&page=2
ASP.NET Core binds:
category = laptops
page = 2
to the corresponding parameters.
You can explicitly specify the query-string source:
app.MapGet("/products",
([FromQuery] string category, [FromQuery] int page) =>
{
return $"Category: {category}, Page: {page}";
});
This becomes especially useful when the parameter name and query-string name are different.
app.MapGet("/products",
([FromQuery(Name = "p")] int page) =>
{
return $"Page: {page}";
});
The request can then be:
/products?p=3
ASP.NET Core supports binding arrays from query strings as well. For example, a request such as:
/products?tag=computer&tag=office&tag=business
can be bound to a string array. (Microsoft Learn)
4. Binding Values from HTTP Headers
HTTP headers carry additional information about a request.
Minimal APIs can bind header values directly to parameters.
app.MapGet("/profile",
([FromHeader(Name = "X-User-Type")] string userType) =>
{
return $"User type: {userType}";
});
A client could send:
X-User-Type: Premium
The value Premium is then passed directly into the userType parameter.
This can be useful when an application expects custom headers, client identifiers, language preferences, or other request metadata.
5. Binding JSON Data from the Request Body
For POST and PUT operations, applications frequently receive structured JSON data.
Suppose there is a Product record:
public record Product(string Name, decimal Price);
A Minimal API endpoint can accept it directly:
app.MapPost("/products", (Product product) =>
{
return $"Product: {product.Name}, Price: {product.Price}";
});
A client can send:
{
"name": "Laptop",
"price": 55000
}
ASP.NET Core deserializes the JSON into a Product object and supplies it to the endpoint.
This eliminates the need to manually read the request body and deserialize the JSON.
For GET, HEAD, OPTIONS, and DELETE, JSON body binding is not performed implicitly. If body data is required for such requests, the body source needs to be explicitly specified or the request body can be read directly. (Microsoft Learn)
6. Binding Form Data
Minimal APIs can also bind values submitted through HTML forms.
For example:
app.MapPost("/register",
([FromForm] string name, [FromForm] string email) =>
{
return $"Name: {name}, Email: {email}";
});
The [FromForm] attribute tells ASP.NET Core to obtain the values from submitted form data.
Form binding can also be used with files and more complex form structures. Current ASP.NET Core documentation describes support for collections and complex types when mapping form data. (Microsoft Learn)
7. Binding Services Through Dependency Injection
Parameter binding in Minimal APIs is not restricted to values coming from the HTTP request. A parameter can also be obtained from ASP.NET Core's dependency injection container.
For example:
builder.Services.AddSingleton<TimeService>();
var app = builder.Build();
app.MapGet("/time", (TimeService service) =>
{
return service.GetCurrentTime();
});
Because TimeService has been registered as a service, ASP.NET Core can automatically provide an instance to the route handler.
The developer can also make this explicit:
app.MapGet("/time",
([FromServices] TimeService service) =>
{
return service.GetCurrentTime();
});
This is particularly useful when an endpoint needs access to application services, database contexts, repositories, configuration services, or other registered dependencies. (Microsoft Learn)
8. Optional Parameters
Parameters are generally treated as required unless they are declared optional or have a default value.
For example:
app.MapGet("/products", (int pageNumber) =>
{
return $"Page: {pageNumber}";
});
A request without pageNumber can result in a binding error.
An optional parameter can instead be declared as:
app.MapGet("/products", (int? pageNumber) =>
{
return $"Page: {pageNumber ?? 1}";
});
Now:
/products?pageNumber=3
produces page 3, while:
/products
uses page 1.
ASP.NET Core also supports default values:
app.MapGet("/products", (int pageNumber = 1) =>
{
return $"Page: {pageNumber}";
});
This makes parameter binding particularly useful for pagination and optional filtering. (Microsoft Learn)
9. Binding Special Types
Some ASP.NET Core types receive special treatment and can be supplied directly to Minimal API handlers.
Examples include:
app.MapGet("/", (HttpContext context) =>
{
return context.Request.Method;
});
Other special types include HttpRequest, HttpResponse, CancellationToken, and ClaimsPrincipal.
For example:
app.MapGet("/request", (HttpRequest request) =>
{
return request.Path.ToString();
});
This gives the developer direct access to the underlying HTTP request when automatic parameter binding is not sufficient. (Microsoft Learn)
10. Binding Complex Types
Parameter binding becomes particularly useful when an endpoint receives an object containing several related properties.
For example:
public record Customer(
string Name,
string Email,
int Age);
The endpoint can simply declare:
app.MapPost("/customers", (Customer customer) =>
{
return $"Customer: {customer.Name}";
});
The incoming JSON is converted into the Customer object.
This approach keeps the route handler focused on application logic instead of low-level HTTP processing.
11. Explicit Binding with Attributes
Although ASP.NET Core can infer many binding sources automatically, explicit attributes can make an endpoint easier to understand.
Common attributes include:
| Attribute | Source |
|---|---|
[FromRoute] |
Route values |
[FromQuery] |
Query-string values |
[FromHeader] |
HTTP headers |
[FromBody] |
Request body |
[FromForm] |
Form data |
[FromServices] |
Dependency injection |
For example:
app.MapPost("/orders/{id}",
(
[FromRoute] int id,
[FromQuery] bool priority,
[FromHeader(Name = "X-Client")] string client,
[FromBody] Order order
) =>
{
return Results.Ok();
});
This endpoint clearly communicates where each value originates. (Microsoft Learn)
12. Binding Precedence
ASP.NET Core follows defined rules when determining where a parameter should come from. Explicit binding attributes have priority. The framework also recognizes special types, custom binding methods, strings and types supporting TryParse, and registered dependency-injection services. (Microsoft Learn)
This is important because a parameter may potentially appear in more than one location. Explicitly specifying the binding source removes ambiguity.
For example:
app.MapGet("/products/{id}",
([FromRoute] int id) =>
{
return id;
});
The [FromRoute] declaration ensures that the value comes from the route rather than another possible source.
13. Binding Failures
Parameter binding also performs type conversion. If a client sends an invalid value, the framework can reject the request.
For example:
app.MapGet("/products", (int id) =>
{
return $"Product: {id}";
});
If the client sends:
/products?id=abc
abc cannot be converted into an integer. ASP.NET Core therefore cannot successfully bind the parameter and returns an HTTP error rather than passing an invalid integer to the handler. Current documentation describes different failure responses depending on whether the problem occurs during parsing, custom binding, JSON deserialization, or content-type processing. (Microsoft Learn)
14. Custom Parameter Binding
Developers can create their own binding behavior for custom types.
One approach is to provide a static TryParse method.
For example, imagine an application has a Point type:
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public static bool TryParse(
string value,
out Point? point)
{
var parts = value.Split(',');
if (parts.Length == 2 &&
double.TryParse(parts[0], out var x) &&
double.TryParse(parts[1], out var y))
{
point = new Point { X = x, Y = y };
return true;
}
point = null;
return false;
}
}
An endpoint could then accept the custom type directly:
app.MapGet("/location", (Point point) =>
{
return $"X: {point.X}, Y: {point.Y}";
});
A request such as:
/location?point=12.5,30.2
can be converted into a Point object.
ASP.NET Core also supports custom binding through BindAsync and the IBindableFromHttpContext<TSelf> interface for more advanced scenarios. (Microsoft Learn)
15. Why Parameter Binding Is Important
Parameter binding provides several important benefits.
First, it reduces repetitive HTTP-processing code. Developers do not need to repeatedly access query collections, route dictionaries, headers, or request bodies manually.
Second, it provides strong typing. If an endpoint expects an int, DateTime, or custom object, ASP.NET Core attempts to convert incoming data into that type.
Third, it makes endpoints easier to read. Consider:
app.MapGet("/students/{id}",
(int id, string search, StudentService service) =>
{
// Application logic
});
From this single declaration, a developer can understand that the endpoint expects a route identifier, a query value, and a service from dependency injection.
Finally, parameter binding helps separate HTTP concerns from business logic. The endpoint can concentrate on what it needs to accomplish rather than repeatedly implementing code for extracting and converting HTTP request data.
16. Complete Example
The following example combines several binding sources:
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ProductService>();
var app = builder.Build();
app.MapGet("/products/{id}",
(
[FromRoute] int id,
[FromQuery] string? category,
[FromHeader(Name = "X-Client")] string? client,
ProductService service
) =>
{
var product = service.GetProduct(id);
return Results.Ok(new
{
Product = product,
Category = category,
Client = client
});
});
app.Run();
public class ProductService
{
public string GetProduct(int id)
{
return $"Product {id}";
}
}
A request could look like:
GET /products/25?category=electronics
with the header:
X-Client: WebApp
ASP.NET Core can then bind:
id → Route value
category → Query string
client → HTTP header
service → Dependency injection
This demonstrates the central idea behind Minimal API parameter binding: the route handler declares what it needs, while ASP.NET Core determines how to obtain those values from the request or application services.
Conclusion
Parameter binding is one of the features that makes ASP.NET Core Minimal APIs concise and developer-friendly. It automatically converts incoming HTTP data into strongly typed values and can obtain those values from routes, query strings, headers, request bodies, forms, dependency-injected services, and custom binding mechanisms. Developers can rely on automatic inference for straightforward endpoints or use explicit binding attributes when precise control is required. (Microsoft Learn)
Understanding parameter binding is particularly important when developing REST APIs because it connects the external HTTP request with the strongly typed C# code that processes it. Once developers understand the different binding sources, optional parameters, complex types, binding failures, and custom binding mechanisms, they can build Minimal APIs that are cleaner, more maintainable, and easier to understand.