- 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.
Now that I knew a bit about routing, I wanted to discover more. I started to look at parameter binding and this is what I found:
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.
Here there is an {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
.
I also wanted to add a constraint to the route parameter. Constraints can be added with a :
after the parameter name.
Here's how you would restrict the slug to certain characters using the 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?
Well they have a number of special types that you can pass in as parameters. Here is how you would use the 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.
Then when you want to reference that link in another route, you pass in the 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.
But if you are submitting a HTML form, then the 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?
Well it turns out it can thanks to the 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.
I also like the fact that you can easily inject the HttpContext
instance. This was something I used a lot with controllers so it was essential that it was easy to use with minimal APIs.
Latest tutorials

