- Home
- .NET tutorials
- How to use parameter binding for routes in Minimal APIs
How to use parameter binding for routes in Minimal APIs
Published: Monday 18 August 2025
When discovering Minimal APIs, I was astounded how simple it was to create a route using one of the HTTP methods.
Parameter binding
With controllers, I have written many requests over the years that includes parameters in the route. Particularly the id parameter. And it works similar in Minimal APIs. If you have a parameter in your route, you can you use it as a binding source for your strongly typed parameters.
{id} parameter in the route. This automatically binds to the int id parameter to be used in the endpoint:
// Program.cs
app.MapGet("/todo/{id}", (int id) =>
{
return id;
});
I wondered what would happen if I was to apply an id to the query string? Something like /todo/2?id=4? In the response above, it would return 2. Turns out that having a route parameter is given a higher precedence than a query string parameter. If I wanted to explictly state that the id parameter comes from the querystring, I would have to remove {id} from the route.
// Program.cs
app.MapGet("/todo", (int id) =>
{
return id;
});
But what if you want to specify the id in both the route and the query string? You can use the [FromRoute] and [FromQuery] attributes. And if the names are not a direct match, you can use the Name parameter in each of these attributes:
// Program.cs
app.MapGet("/todo/from/{id}", (
[FromRoute(Name="id")] int routeId,
[FromQuery(Name="id")] int queryId
) =>
{
return $"Route Id = {routeId}, Query Id = {queryId}";
});
Here are the other [From*] attributes that you can use:
| Name | From attribute | Notes |
|---|---|---|
| Route | [FromRoute] |
|
| Query string | [FromQuery] |
|
| Header | [FromHeader] |
You must always include this attribute if the parameter is being binded from a request header key. |
| Body | [FromBody] |
If you are using a GET, HEAD, OPTIONS or DELETE method, you must include the [FromBody] attribute |
| Form | [FromForm] |
|
Service |
[FromService] |
You don't normally need to include [FromService] when injecting from dependency injection. |
I also came across [AsParameters]. What is that I wondered? Not come across that before? Turns out you can group your parameters into a class or a record type meaning you only need to pass in one parameter into the endpoint. You can even use any of the [From*] attributes to specify where the value comes from:
// ParametersDto.cs
public record ParametersDto(
int Id,
int? Page,
string? Type,
[FromHeader(Name="api-key")] string? ApiKey
);
// Program.cs
app.MapGet("/todo/params/{id}", (
[AsParameters] ParametersDto parameters
) => {
return $"Id = {parameters.Id},\n" +
$"Page = {parameters.Page},\n" +
$"Type = {parameters.Type},\n" +
$"Api key = {parameters.ApiKey}";
});
If you forget to add the [AsParameters] attribute, it will try to read it from the request body. If you are using a GET, HEAD, OPTIONS or DELETE method, you'll get the following exception when you run the application:
InvalidOperationException: 'Body was inferred but the method does not allow inferred body parameters.
Wildcard and route constraints
I wanted a way that you could add wildcard parameters in routes so I could capture the parameter including the forward slash. All you need to do is to add a * after the opening curly brace:
// Program.cs
app.MapGet("/todo/wildcard/{*slug}", (string slug) => slug);
When you run this endpoint with the route /todo/wildcard/show/the-full-path, the slug parameter will output show/the-full-path.
: after the parameter name.
regex constraint:
// Program.cs
app.MapGet("/todo/regex-wildcard/{*slug:regex(^[a-z0-9_-]+$)}",
(string slug) => slug);
The regex constraint is just one of the route constraints that you can use in your endpoints.
Special types
When I use controllers, I often referenced the HttpContext instance. But, how would you do that with Minimal APIs?
HttpContext instance:
// Program.cs
app.MapGet("/hello-world",
(HttpContext context) => context.Response.WriteAsync("Hello World"));
You can also use these types:
| Special type | Same as... |
|---|---|
HttpContext |
|
HttpRequest |
HttpContext.Request |
HttpResponse |
HttpContext.Response |
ClaimsPrincipal |
HttpContext.User |
CancellationToken |
HttpContext.RequestAborted |
Link generator
I also wanted the ability to generate a link from an existing route. That's where the LinkGenerator special type comes in. When you create a route, you can use the WithName extension method to specify the name.
LinkGenerator type as a parameter and call the GetPathByName method.
// Program.cs
app.MapGet("/my-link", () => "My link").WithName("My link");
app.MapGet("/get-my-link", (LinkGenerator linkGenerator) =>
$"The link to My Link is {linkGenerator.GetPathByName("My link")}");
Form values
There aren't many times that I need to pass in form values. These days I tend to create a class with properties I expect from a POST body request.
IFormCollection will be of use to you. You can use the IFormCollection special type as a parameter in your route to capture form values. The IFormCollection has a list of KeyValuePair entires where you can get the key and value from each of your form values.
// Program.cs
app.MapPost("/form-values", (IFormCollection formCollection) =>
{
return formCollection.Select(s => $"key = {s.Key}, value={s.Value}").ToList();
}).DisableAntiforgery();
File uploads
So far, minimal APIs has a lot of functionality that controllers does. But can it handle file uploads?
IFormFile type. This example shows us uploading the file that is requested into the uploads directory:
// Program.cs
app.MapPost("/upload-file", async (IFormFile file) =>
{
if (!Directory.Exists("uploads"))
{
Directory.CreateDirectory("uploads");
}
using var stream = File.OpenWrite($"uploads/{file.FileName}");
await file.CopyToAsync(stream);
return Results.NoContent();
}).DisableAntiforgery();
And if you are uploading multiple files in a request, you can use the IFormFileCollection type:
// Program.cs
app.MapPost("/upload-files", async (IFormFileCollection files) =>
{
if (!Directory.Exists("uploads"))
{
Directory.CreateDirectory("uploads");
}
foreach (var file in files)
{
using var stream = File.OpenWrite($"uploads/{file.FileName}");
await file.CopyToAsync(stream);
}
return Results.NoContent();
}).DisableAntiforgery();
We've disabled antiforgery for demonstrating purposes. However, antiforgery was a breaking change introduced to Minimal APIs in .NET 8 and it's recommended to be enabled. This is because it's a security precaution for APIs that consume data from a form.
Watch the video
To show you how simple and straight forward it is to set up parameters in Minimal APIs, I recorded this video where you can see how simple it is to use:
And rather than you having to type each example out, I'm sharing the code example so you can try it out.
Final thoughts
I like the fact that it shares a lot of common behaviours with controllers when it comes to parameter binding. It feels a lot more simpler than controllers. Less boiler plate code to write and much more readable.
HttpContext instance. This was something I used a lot with controllers so it was essential that it was easy to use with minimal APIs.Related tutorials