aspnetcore/fundamentals/minimal-apis/includes/parameter-binding7.md
:::moniker range="= aspnetcore-7.0" Parameter binding is the process of converting request data into strongly typed parameters that are expressed by route handlers. A binding source determines where parameters are bound from. Binding sources can be explicit or inferred based on HTTP method and parameter type.
Supported binding sources:
Binding from form values is not natively supported in .NET 6 and 7.
The following GET route handler uses some of these parameter binding sources:
The following table shows the relationship between the parameters used in the preceding example and the associated binding sources.
| Parameter | Binding Source |
|---|---|
id | route value |
page | query string |
customHeader | header |
service | Provided by dependency injection |
The HTTP methods GET, HEAD, OPTIONS, and DELETE don't implicitly bind from body. To bind from body (as JSON) for these HTTP methods, bind explicitly with [FromBody] or read from the xref:Microsoft.AspNetCore.Http.HttpRequest.
The following example POST route handler uses a binding source of body (as JSON) for the person parameter:
The parameters in the preceding examples are all bound from request data automatically. To demonstrate the convenience that parameter binding provides, the following route handlers show how to read request data directly from the request:
Attributes can be used to explicitly declare where parameters are bound from.
<!-- TODO - finish Service -->| Parameter | Binding Source |
|---|---|
id | route value with the name id |
page | query string with the name "p" |
service | Provided by dependency injection |
contentType | header with the name "Content-Type" |
[!NOTE] Binding from form values is not natively supported in .NET 6 and 7.
Parameter binding for Minimal APIs binds parameters through dependency injection when the type is configured as a service. It's not necessary to explicitly apply the [FromServices] attribute to a parameter. In the following code, both actions return the time:
Parameters declared in route handlers are treated as required:
| URI | result |
|---|---|
/products?pageNumber=3 | 3 returned |
/products | BadHttpRequestException: Required parameter "int pageNumber" was not provided from query string. |
/products/1 | HTTP 404 error, no matching route |
To make pageNumber optional, define the type as optional or provide a default value:
| URI | result |
|---|---|
/products?pageNumber=3 | 3 returned |
/products | 1 returned |
/products2 | 1 returned |
The preceding nullable and default value applies to all sources:
The preceding code calls the method with a null product if no request body is sent.
NOTE: If invalid data is provided and the parameter is nullable, the route handler is not run.
| URI | result |
|---|---|
/products?pageNumber=3 | 3 returned |
/products | 1 returned |
/products?pageNumber=two | BadHttpRequestException: Failed to bind parameter "Nullable<int> pageNumber" from "two". |
/products/two | HTTP 404 error, no matching route |
See the Binding Failures section for more information.
The following types are bound without explicit attributes:
xref:Microsoft.AspNetCore.Http.HttpContext: The context which holds all the information about the current HTTP request or response:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
xref:Microsoft.AspNetCore.Http.HttpRequest and xref:Microsoft.AspNetCore.Http.HttpResponse: The HTTP request and HTTP response:
app.MapGet("/", (HttpRequest request, HttpResponse response) =>
response.WriteAsync($"Hello World {request.Query["name"]}"));
xref:System.Threading.CancellationToken: The cancellation token associated with the current HTTP request:
app.MapGet("/", async (CancellationToken cancellationToken) =>
await MakeLongRunningRequestAsync(cancellationToken));
xref:System.Security.Claims.ClaimsPrincipal: The user associated with the request, bound from xref:Microsoft.AspNetCore.Http.HttpContext.User%2A?displayProperty=nameWithType:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
<a name="rbs"></a>
Stream or PipeReaderThe request body can bind as a Stream or PipeReader to efficiently support scenarios where the user has to process data and:
For example, the data might be enqueued to Azure Queue storage or stored in Azure Blob storage.
The following code implements a background queue:
The following code binds the request body to a Stream:
The following code shows the complete Program.cs file:
Stream is the same object as HttpRequest.Body.Stream and PipeReader aren't usable outside of the minimal action handler as the underlying buffers will be disposed or reused.The following code uses xref:Microsoft.AspNetCore.Http.IFormFile and xref:Microsoft.AspNetCore.Http.IFormFileCollection to upload file:
:::code language="csharp" source="~/fundamentals/minimal-apis/iformFile/7.0-samples/MinimalApi/Program.cs" :::
Authenticated file upload requests are supported using an Authorization header, a client certificate, or a cookie header.
There is no built-in support for antiforgery in ASP.NET Core in .NET 7. Antiforgery is available in ASP.NET Core in .NET 8 or later. However, it can be implemented using the IAntiforgery service.
<a name="bindar"></a>
The following code demonstrates binding query strings to an array of primitive types, string arrays, and StringValues:
Binding query strings or header values to an array of complex types is supported when the type has TryParse implemented. The following code binds to a string array and returns all the items with the specified tags:
The following code shows the model and the required TryParse implementation:
The following code binds to an int array:
To test the preceding code, add the following endpoint to populate the database with Todo items:
Use an API testing tool like HttpRepl to pass the following data to the previous endpoint:
The following code binds to the header key X-Todo-Id and returns the Todo items with matching Id values:
[!NOTE] When binding a
string[]from a query string, the absence of any matching query string value will result in an empty array instead of a null value.
<a name="asparam7"></a>
xref:Microsoft.AspNetCore.Http.AsParametersAttribute enables simple parameter binding to types and not complex or recursive model binding.
Consider the following code:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/arg-lists/Program.cs" id="snippet_top":::
Consider the following GET endpoint:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/arg-lists/Program.cs" id="snippet_id" highlight="2":::
The following struct can be used to replace the preceding highlighted parameters:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/arg-lists/Models/TodoDb.cs" id="snippet" :::
The refactored GET endpoint uses the preceding struct with the AsParameters attribute:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/arg-lists/Program.cs" id="snippet_ap_id" highlight="2":::
The following code shows additional endpoints in the app:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/arg-lists/Program.cs" id="snippet_post_put_delete" :::
The following classes are used to refactor the parameter lists:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/arg-lists/Models/TodoDb.cs" id="snippet_1" :::
The following code shows the refactored endpoints using AsParameters and the preceding struct and classes:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/arg-lists/Program.cs" id="snippet_ap_post_put_delete" :::
The following record types can be used to replace the preceding parameters:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/arg-lists/Models/TodoRecord.cs" id="snippet_1" :::
Using a struct with AsParameters can be more performant than using a record type.
The complete sample code in the AspNetCore.Docs.Samples repository.
There are three ways to customize parameter binding:
TryParse method for the type.BindAsync method on a type.HttpContext.TryParse has two APIs:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
The following code displays Point: 12.3, 10.1 with the URI /map?Point=12.3,10.1:
BindAsync has the following APIs:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
The following code displays SortBy:xyz, SortDirection:Desc, CurrentPage:99 with the URI /products?SortBy=xyz&SortDir=Desc&Page=99:
<a name="bf"></a>
IBindableFromHttpContextASP.NET Core provides support for custom parameter binding in Minimal APIs using the xref:Microsoft.AspNetCore.Http.IBindableFromHttpContext%601 interface. This interface, introduced with C# 11's static abstract members, allows you to create types that can be bound from an HTTP context directly in route handler parameters.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
By implementing the xref:Microsoft.AspNetCore.Http.IBindableFromHttpContext%601 interface, you can create custom types that handle their own binding logic from the HttpContext. When a route handler includes a parameter of this type, the framework automatically calls the static BindAsync method to create the instance:
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/CustomBindingExample/Program.cs" id="snippet_IBindableFromHttpContext":::
The following is an example implementation of a custom parameter that binds from an HTTP header:
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/CustomBindingExample/CustomBoundParameters.cs":::
You can also implement validation within your custom binding logic:
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/CustomBindingExample/Program.cs" id="snippet_Validation":::
View or download the sample code (how to download)
When binding fails, the framework logs a debug message and returns various status codes to the client depending on the failure mode.
| Failure mode | Nullable Parameter Type | Binding Source | Status code |
|---|---|---|---|
{ParameterType}.TryParse returns false | yes | route/query/header | 400 |
{ParameterType}.BindAsync returns null | yes | custom | 400 |
{ParameterType}.BindAsync throws | does not matter | custom | 500 |
| Failure to deserialize JSON body | does not matter | body | 400 |
Wrong content type (not application/json) | does not matter | body | 415 |
The rules for determining a binding source from a parameter:
[FromRoute][FromQuery][FromHeader][FromBody][FromServices][AsParameters]HttpContextHttpRequest (HttpContext.Request)HttpResponse (HttpContext.Response)ClaimsPrincipal (HttpContext.User)CancellationToken (HttpContext.RequestAborted)IFormFileCollection (HttpContext.Request.Form.Files)IFormFile (HttpContext.Request.Form.Files[paramName])Stream (HttpContext.Request.Body)PipeReader (HttpContext.Request.BodyReader)BindAsync method.TryParse method.
app.Map("/todo/{id}", (int id) => {});, id is bound from the route.The body binding source uses xref:System.Text.Json?displayProperty=fullName for deserialization. It is not possible to change this default, but JSON serialization and deserialization options can be configured.
Options that apply globally for an app can be configured by invoking xref:Microsoft.Extensions.DependencyInjection.HttpJsonServiceExtensions.ConfigureHttpJsonOptions%2A. The following example includes public fields and formats JSON output.
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinJson/Program.cs" id="snippet_confighttpjsonoptions" highlight="3-6":::
Since the sample code configures both serialization and deserialization, it can read NameField and include NameField in the output JSON.
xref:Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync%2A has overloads that accept a xref:System.Text.Json.JsonSerializerOptions object. The following example includes public fields and formats JSON output.
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinJson/Program.cs" id="snippet_readfromjsonasyncwithoptions" highlight="5-8,12":::
Since the preceding code applies the customized options only to deserialization, the output JSON excludes NameField.
Read the request body directly using a xref:Microsoft.AspNetCore.Http.HttpContext or xref:Microsoft.AspNetCore.Http.HttpRequest parameter:
The preceding code:
:::moniker-end