Back to Aspnetcore

Parameter Binding7

aspnetcore/fundamentals/minimal-apis/includes/parameter-binding7.md

latest20.5 KB
Original Source

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

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

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:

[!code-csharp]

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

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

Explicit Parameter Binding

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

<!-- TODO - finish Service -->

[!code-csharp]

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

[!NOTE] Binding from form values is not natively supported in .NET 6 and 7.

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

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

URIresult
/products?pageNumber=33 returned
/productsBadHttpRequestException: Required parameter "int pageNumber" was not 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-csharp]

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

The preceding nullable and default value applies to all sources:

[!code-csharp]

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

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

The following code binds the request body to a Stream:

[!code-csharp]

The following code shows the complete Program.cs file:

[!code-csharp]

  • 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

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>

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

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

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

[!code-csharp]

The following code binds to an int array:

[!code-csharp]

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

[!code-csharp]

Use an API testing tool like HttpRepl to pass the following data to the previous endpoint:

[!code-csharp]

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

[!code-csharp]

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

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

<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 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)

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 throwsdoes not mattercustom500
Failure to deserialize JSON bodydoes not matterbody400
Wrong content type (not application/json)does not 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. Service: [FromServices]
    6. 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. IFormFileCollection (HttpContext.Request.Form.Files)
    7. IFormFile (HttpContext.Request.Form.Files[paramName])
    8. Stream (HttpContext.Request.Body)
    9. 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. In app.Map("/todo/{id}", (int id) => {});, id is 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-csharp]

The preceding code:

:::moniker-end