Back to Aspnetcore

Parameter Binding8 10

aspnetcore/fundamentals/minimal-apis/includes/parameter-binding8-10.md

latest26.8 KB
Original Source

:::moniker range=">= aspnetcore-8.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:

  • Route values
  • Query string
  • Header
  • Body (as JSON)
  • Form values
  • Services provided by dependency injection
  • Custom

The following GET route handler uses some of these parameter binding sources:

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_pbg" highlight="8-11":::

The following table shows the relationship between the parameters used in the preceding example and the associated binding sources.

ParameterBinding Source
idroute value
pagequery string
customHeaderheader
serviceProvided 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:

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_pbp" highlight="5":::

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:

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Snippets/Program.cs" id="snippet_ManualRequestBinding" highlight="3-5,12":::

Explicit Parameter Binding

Attributes can be used to explicitly declare where parameters are bound from.

<!-- TODO - finish Service -->

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_epb":::

ParameterBinding Source
idroute value with the name id
pagequery string with the name "p"
serviceProvided by dependency injection
contentTypeheader with the name "Content-Type"

Explicit binding from form values

The [FromForm] attribute binds form values:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/FormBinding/Program.cs" id="snippet_post_put_delete" highlight="1-2":::

An alternative is to use the [AsParameters] attribute with a custom type that has properties annotated with [FromForm]. For example, the following code binds from form values to properties of the NewTodoRequest record struct:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/FormBinding/Program.cs" id="snippet_post_put_delete_as_parameters" highlight="1":::

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/FormBinding/Program.cs" id="snippet_argumentlist_record":::

For more information, see the section on AsParameters later in this article.

The complete sample code is in the AspNetCore.Docs.Samples repository.

Secure binding from IFormFile and IFormFileCollection

Complex form binding is supported using xref:Microsoft.AspNetCore.Http.IFormFile and xref:Microsoft.AspNetCore.Http.IFormFileCollection using the [FromForm]:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/IFormFile/Program.cs" id="snippet_1" highlight="20-28":::

Parameters bound to the request with [FromForm] include an antiforgery token. The antiforgery token is validated when the request is processed. For more information, see Antiforgery with Minimal APIs.

For more information, see Form binding in Minimal APIs.

The complete sample code is in the AspNetCore.Docs.Samples repository.

Parameter binding with dependency injection

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:

:::code language="csharp" source="~/release-notes/aspnetcore-7/samples/ApiController/Program.cs" id="snippet_min" highlight="8-9":::

Optional parameters

Parameters declared in route handlers are treated as required:

  • If a request matches the route, the route handler only runs if all required parameters are provided in the request.
  • Failure to provide all required parameters results in an error.

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_op1" highlight="4":::

URIresult
/products?pageNumber=33 returned
/productsBadHttpRequestException: Required parameter "int pageNumber" wasn't provided from query string.
/products/1HTTP 404 error, no matching route

To make pageNumber optional, define the type as optional or provide a default value:

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_op2" highlight="4,6-8":::

URIresult
/products?pageNumber=33 returned
/products1 returned
/products21 returned

The preceding nullable and default value applies to all sources:

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_op3" highlight="4":::

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.

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_op4" highlight="4":::

URIresult
/products?pageNumber=33 returned
/products1 returned
/products?pageNumber=twoBadHttpRequestException: Failed to bind parameter "Nullable<int> pageNumber" from "two".
/products/twoHTTP 404 error, no matching route

See the Binding Failures section for more information.

Special types

The following types are bound without explicit attributes:

<a name="rbs"></a>

Bind the request body as a Stream or PipeReader

The request body can bind as a Stream or PipeReader to efficiently support scenarios where the user has to process data and:

  • Store the data to blob storage or enqueue the data to a queue provider.
  • Process the stored data with a worker process or cloud function.

For example, the data might be enqueued to Azure Queue storage or stored in Azure Blob storage.

The following code implements a background queue:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindStreamPipeReader/7.0-samples/PipeStreamToBackgroundQueue/BackgroundQueueService.cs" :::

The following code binds the request body to a Stream:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindStreamPipeReader/7.0-samples/PipeStreamToBackgroundQueue/Program.cs" id="snippet_1":::

The following code shows the complete Program.cs file:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindStreamPipeReader/7.0-samples/PipeStreamToBackgroundQueue/Program.cs" id="snippet":::

  • When reading data, the Stream is the same object as HttpRequest.Body.
  • The request body isn't buffered by default. After the body is read, it's not rewindable. The stream can't be read multiple times.
  • The Stream and PipeReader aren't usable outside of the minimal action handler as the underlying buffers will be disposed or reused.

File uploads using IFormFile and IFormFileCollection

File uploads using IFormFile and IFormFileCollection in Minimal APIs require multipart/form-data encoding. The parameter name in the route handler must match the form field name in the request. Minimal APIs don't support binding the entire request body directly to an IFormFile parameter without form encoding.

If you need to bind the entire request body, for example, when working with JSON, binary data, or other content types, see:

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.

<a name="bind8"></a>

Binding to forms with IFormCollection, IFormFile, and IFormFileCollection

Binding from form-based parameters using xref:Microsoft.AspNetCore.Http.IFormCollection, xref:Microsoft.AspNetCore.Http.IFormFile, and xref:Microsoft.AspNetCore.Http.IFormFileCollection is supported. OpenAPI metadata is inferred for form parameters to support integration with Swagger UI.

The following code uploads files using inferred binding from the IFormFile type:

:::code language="csharp" source="~/fundamentals/minimal-apis/parameter-binding/samples8/Iform/Program.cs" highlight="18-23,44-50":::

Warning: When implementing forms, the app must prevent Cross-Site Request Forgery (XSRF/CSRF) attacks. In the preceding code, the xref:Microsoft.AspNetCore.Antiforgery.IAntiforgery service is used to prevent XSRF attacks by generating and validation an antiforgery token:

:::code language="csharp" source="~/fundamentals/minimal-apis/parameter-binding/samples8/Iform/Program.cs" highlight="25,45":::

For more information on XSRF attacks, see Antiforgery with Minimal APIs

For more information, see Form binding in Minimal APIs;

<a name="bindcc"></a>

Bind to collections and complex types from forms

Binding is supported for:

  • Collections, for example List and Dictionary
  • Complex types, for example, Todo or Project

The following code shows:

  • A minimal endpoint that binds a multi-part form input to a complex object.
  • How to use the antiforgery services to support the generation and validation of antiforgery tokens.

:::code language="csharp" source="~/fundamentals/minimal-apis/parameter-binding/samples8/ComplexBinding/Program.cs":::

In the preceding code:

  • The target parameter must be annotated with the [FromForm] attribute to disambiguate from parameters that should be read from the JSON body.
  • Binding from complex or collection types is not supported for Minimal APIs that are compiled with the Request Delegate Generator.
  • The markup shows an additional hidden input with a name of isCompleted and a value of false. If the isCompleted checkbox is checked when the form is submitted, both values true and false are submitted as values. If the checkbox is unchecked, only the hidden input value false is submitted. The ASP.NET Core model-binding process reads only the first value when binding to a bool value, which results in true for checked checkboxes and false for unchecked checkboxes.

An example of the form data submitted to the preceding endpoint looks as follows:

txt
__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false

<a name="bindar"></a>

Bind arrays and string values from headers and query strings

The following code demonstrates binding query strings to an array of primitive types, string arrays, and StringValues:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindingArrays/7.0-samples/todo/Program.cs" id="snippet_bqs2pa":::

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:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindingArrays/7.0-samples/todo/Program.cs" id="snippet_bind_str_array":::

The following code shows the model and the required TryParse implementation:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindingArrays/7.0-samples/todo/Program.cs" id="snippet_model":::

The following code binds to an int array:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindingArrays/7.0-samples/todo/Program.cs" id="snippet_iaray":::

To test the preceding code, add the following endpoint to populate the database with Todo items:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindingArrays/7.0-samples/todo/Program.cs" id="snippet_batch":::

Use a tool like HttpRepl to pass the following data to the previous endpoint:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindingArrays/7.0-samples/todo/Program.cs" id="batch_post_payload":::

The following code binds to the header key X-Todo-Id and returns the Todo items with matching Id values:

:::code language="csharp" source="~/fundamentals/minimal-apis/bindingArrays/7.0-samples/todo/Program.cs" id="snippet_getHeader":::

[!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>

Parameter binding for argument lists with [AsParameters]

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.

Custom Binding

There are three ways to customize parameter binding:

  1. For route, query, and header binding sources, bind custom types by adding a static TryParse method for the type.
  2. Control the binding process by implementing a BindAsync method on a type.
  3. For advanced scenarios, implement the xref:Microsoft.AspNetCore.Http.IBindableFromHttpContext%601 interface to provide custom binding logic directly from the HttpContext.

TryParse

TryParse has two APIs:

csharp
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:

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_cb":::

BindAsync

BindAsync has the following APIs:

csharp
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:

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_ba":::

<a name="bf"></a>

Custom parameter binding with IBindableFromHttpContext

ASP.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.

csharp
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, 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/10.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/10.0-samples/CustomBindingExample/CustomBoundParameters.cs":::

You can also implement validation within your custom binding logic:

:::code language="csharp" source="~/fundamentals/minimal-apis/10.0-samples/CustomBindingExample/Program.cs" id="snippet_Validation":::

View or download the sample code (how to download)

Binding failures

When binding fails, the framework logs a debug message and returns various status codes to the client depending on the failure mode.

Failure modeNullable Parameter TypeBinding SourceStatus code
{ParameterType}.TryParse returns falseyesroute/query/header400
{ParameterType}.BindAsync returns nullyescustom400
{ParameterType}.BindAsync throwsdoesn't mattercustom500
Failure to deserialize JSON bodydoesn't matterbody400
Wrong content type (not application/json)doesn't matterbody415

Binding Precedence

The rules for determining a binding source from a parameter:

  1. Explicit attribute defined on parameter (From* attributes) in the following order:
    1. Route values: [FromRoute]
    2. Query string: [FromQuery]
    3. Header: [FromHeader]
    4. Body: [FromBody]
    5. Form: [FromForm]
    6. Service: [FromServices]
    7. Parameter values: [AsParameters]
  2. Special types
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormCollection (HttpContext.Request.Form)
    7. IFormFileCollection (HttpContext.Request.Form.Files)
    8. IFormFile (HttpContext.Request.Form.Files[paramName])
    9. Stream (HttpContext.Request.Body)
    10. PipeReader (HttpContext.Request.BodyReader)
  3. Parameter type has a valid static BindAsync method.
  4. Parameter type is a string or has a valid static TryParse method.
    1. If the parameter name exists in the route template for example, app.Map("/todo/{id}", (int id) => {});, then it's bound from the route.
    2. Bound from the query string.
  5. If the parameter type is a service provided by dependency injection, it uses that service as the source.
  6. The parameter is from the body.

Configure JSON deserialization options for body binding

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.

Configure JSON deserialization options globally

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.

Configure JSON deserialization options for an endpoint

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

Read the request body directly using a xref:Microsoft.AspNetCore.Http.HttpContext or xref:Microsoft.AspNetCore.Http.HttpRequest parameter:

:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs" id="snippet_fileupload":::

The preceding code:

:::moniker-end