Skip to content

Commit a65f1b0

Browse files
committed
Add Parameter Binding documentation
1 parent d78bc7c commit a65f1b0

File tree

3 files changed

+152
-2
lines changed

3 files changed

+152
-2
lines changed

src/.vuepress/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ module.exports = {
9696
{
9797
title: "Guide",
9898
collapsable: true,
99-
children: ["", "routing" ],
99+
children: ["", "routing", "parameter-binding" ],
100100
},
101101
],
102102
},

src/guide/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ Level
55

66
Beginner 🍃
77

8-
Intermidate 🍃🍃
8+
Intermediate 🍃🍃
99

1010
Advance 🍃🍃🍃

src/guide/parameter-binding.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Parameter Binding
2+
3+
**Level: Intermediate** 🍃🍃
4+
5+
Starting in .NET 6, route handlers (the Delegate parameters in [EndpointRouteBuilderExtensions](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.builder.endpointroutebuilderextensions) methods) can accept more than just an `HttpContext` as a parameter. Route handlers can now accept any number of parameters of different types bound from different sources. This guide describes the conventions that determine how each parameter is populated.
6+
7+
## Conventions
8+
9+
```cs
10+
app.MapPut("/todos/{id}", async (TodoDb db, TodoItem updateTodo, int id) =>
11+
{
12+
// ...
13+
});
14+
```
15+
16+
The route handler above accepts three parameters:
17+
18+
1. `TodoDb db` which comes from the service provider.
19+
2. `TodoItem updateTodo` which is read as JSON from the request body.
20+
3. `int id` which is read from the `{id}` segment of the route.
21+
22+
These were all determined by convention, but could be specified explicitly with attributes as follows:
23+
24+
```cs
25+
using Microsoft.AspNetCore.Mvc;
26+
27+
// ...
28+
29+
app.MapPut("/todos/{id}", async (
30+
[FromService] TodoDb db,
31+
[FromBody] TodoItem updateTodo,
32+
[FromRoute(Name = "id")] int nameDoesNotMatter) =>
33+
{
34+
// ...
35+
});
36+
```
37+
38+
Parameters sources are determined using the following rules applied in order:
39+
40+
1. [Parameter attributes](#attributes) take precedence over conventions if they are present.
41+
2. Any [well-known types](#well-known-types) are bound from the the [HttpContext](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.httpcontext) or one of its properties.
42+
3. `string` parameters are bound from `HttpContext.RouteValues[{ParameterName}]` or `HttpContext.Query[{ParameterName}]` depending on whether `{ParameterName}` is part of the route pattern.
43+
4. Types with public static [BindAsync](#bindasync) methods are bound using `BindAsync`.
44+
5. Types with public static [TryParse](#tryparse) methods are bound by calling `TryParse` with `HttpContext.RouteValues[{ParameterName}]` or `HttpContext.Query[{ParameterName}]` depending on whether `{ParameterName}` is part of the route pattern. This includes most built-in numeric types, enums, `DateTime`, `TimeSpan` and more.
45+
6. Types registered as a service are bound from request services.
46+
7. Any remaining types are bound from the request body as JSON.
47+
48+
## Attributes
49+
50+
### `[FromRoute]`
51+
52+
[FromRouteAttribute](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.fromrouteattribute) implements `Microsoft.AspNetCore.Http.Metadata.IFromRouteMetadata`. Any attribute implementing this interface is equivalent. This will bind the parameter from `HttpRequest.RouteValues[{ParameterName}]`. If the parameter is not a string, the parameter type's [TryParse](#tryparse) method will be called to convert the string to the parameter type.
53+
54+
If the `Name` property is provided (e.g. `[FromRoute(Name = "id")]`), the name specified using the property is used instead of the parameter name.
55+
56+
### `[FromQuery]`
57+
58+
[FromQueryAttribute](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.fromqueryattribute) implements `Microsoft.AspNetCore.Http.Metadata.IFromQueryMetadata`. Any attribute implementing this interface is equivalent. This will bind the parameter from `HttpRequest.Query[{ParameterName}]`. If the parameter is not a string, the parameter type's [TryParse](#tryparse) method will be called to convert the string to the parameter type.
59+
60+
If the `Name` property is provided (e.g. `[FromQuery(Name = "page")]`), the name specified using the property is used instead of the parameter name.
61+
62+
### `[FromHeader]`
63+
64+
[FromHeaderAttribute](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.fromheaderattribute) implements `Microsoft.AspNetCore.Http.Metadata.IFromHeaderMetadata`. Any attribute implementing this interface is equivalent. This will bind the parameter from `HttpRequest.Headers[{ParameterName}]`. If the parameter is not a string, the parameter type's [TryParse](#tryparse) method will be called to convert the string to the parameter type.
65+
66+
If the `Name` property is provided (e.g. `[FromHeader(Name = "X-My-Custom-Header")]`), the name specified using the property is used instead of the parameter name.
67+
68+
### `[FromServices]`
69+
70+
[FromServicesAttribute](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.fromservicesattribute) implements `Microsoft.AspNetCore.Http.Metadata.IFromServiceMetadata`. Any attribute implementing this interface is equivalent. This will bind the parameter from request services as described in the [Services section](#services) of this doc.
71+
72+
### `[FromBody]`
73+
74+
[FromBodyAttribute](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.frombodyattribute) implements `Microsoft.AspNetCore.Http.Metadata.IFromBodyMetadata`. Any attribute implementing this interface is equivalent. This will bind the parameter from the request body as JSON as described in the [JSON Request Body section](#json-request-body) of this doc.
75+
76+
If the `EmptyBodyBehavior` property is set to `EmptyBodyBehavior.Allow` (e.g. `[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]`), the parameter will be set to `null` or its `default` value if the request body is empty. This corresponds to the `IFromBodyMetadata.AllowEmpty` being true.
77+
78+
## Well-Known Types
79+
80+
- HttpContext
81+
- HttpRequest (`HttpContext.Request`)
82+
- HttpResponse (`HttpContext.Response`)
83+
- ClaimsPrincipal (`HttpContext.User`)
84+
- CancellationToken (`HttpContext.RequestAborted`)
85+
86+
## BindAsync
87+
88+
If the parameter type, one of its parent/ancestor types or any of its implemented interfaces define a public static `BindAsync` method with one of the following signatures, the parameter will be bound using `BindAsync` assuming no parameter attribute specified another source.
89+
90+
```cs
91+
public static ValueTask<{ParameterType}> BindAsync(HttpContext context, ParameterInfo parameter)
92+
{
93+
// ...
94+
}
95+
96+
// Or
97+
98+
public static ValueTask<{ParameterType}> BindAsync(HttpContext context)
99+
{
100+
// ...
101+
}
102+
```
103+
104+
The return value can be either `ValueTask<{ParameterType}>` or `ValueTask<{ParameterType}?>` for both overloads. Whether returning a `null` value is allowed is determined by the nullability of parameter type. If the parameter type is non-nullable, the route handler will not be called and a bad request will be logged.
105+
106+
In the case where both overloads are defined anywhere in the parameter type's hierarchy, the `BindAsync` method with the `ParameterInfo` argument will be called.
107+
108+
If there is more than one `BindAsync` method with the same signature, the method from the most derived type will be called. `BindAsync` methods on interfaces are chosen last. A parameter type implementing more than one interface defining matching `BindAsync` methods is an error.
109+
110+
## TryParse
111+
112+
If the parameter type, one of its parent/ancestor types or any of its implemented interfaces define a public static `TryParse` method with one of the following signatures, the parameter will be bound using `TryParse` using the `string` from the source specified in the [Conventions section](#conventions) of this document.
113+
114+
```cs
115+
public static bool TryParse(string? value, IFormatProvider formatProvider, out {ParameterType} result)
116+
{
117+
// ...
118+
}
119+
120+
// Or
121+
122+
public static bool TryParse(string? value, out {ParameterType} result)
123+
{
124+
// ...
125+
}
126+
```
127+
128+
The out parameter can be either `out {ParameterType}` or `out {ParameterType}?` for both overloads. Whether providing a `null` value is allowed is determined by the nullability of parameter type. If the parameter type is non-nullable, the route handler will not be called and a bad request will be logged.
129+
130+
In the case where both overloads are defined anywhere in the parameter type's hierarchy, the `TryParse` method with the `IFormatProvider` argument will be called.
131+
132+
If there is more than one `TryParse` method with the same signature, the method from the most derived type will be called. `TryParse` methods on interfaces are chosen last. A parameter type implementing more than one interface defining matching `TryParse` methods is an error.
133+
134+
## Services
135+
136+
Service parameters are resolved from `HttpContext.RequestServices.GetService(typeof({ParameterType}))`.
137+
138+
Whether or not a given parameter type is a service is determined using `IServiceProviderIsService` unless the parameter is explicitly attributed with `[FromServices]`. Given the `[FromServices]` attribute, the parameter type is assumed to exist.
139+
140+
For non-nullable parameters, the parameter type must be resolvable as a service for the route handler to be called. If the service does not exist, an exception will be thrown when the endpoint is hit. For Nullable
141+
142+
`IServiceProviderIsService` is a new interface introduced in .NET 6 that automatically implemented by the default service provider and some third-party containers. If `IServiceProviderIsService` itself is not available as a service, the `[FromServices]` attribute must be used to resolve parameters from services.
143+
144+
## JSON Request Body
145+
146+
JSON request bodies are read using the [ReadFromJsonAsync](https://docs.microsoft.com/dotnet/api/system.net.http.json.httpcontentjsonextensions.readfromjsonasync#System_Net_Http_Json_HttpContentJsonExtensions_ReadFromJsonAsync__1_System_Net_Http_HttpContent_System_Text_Json_JsonSerializerOptions_System_Threading_CancellationToken_) extension method. This can be configured like all other calls to `ReadFromJsonAsync` using the [options pattern](https://docs.microsoft.com/aspnet/core/fundamentals/configuration/#configure-options-with-a-delegate-1) to configure [JsonOptions](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.json.jsonoptions).
147+
148+
For non-nullable parameters, empty request bodies are disallowed by default. If a request matching the route pattern has an empty body, the route handler will not be called and a bad request will be logged.
149+
150+
Empty request bodies are always allowed when the parameter is nullable even if `EmptyBodyBehavior.Disallow` is set via the `[FromBody]` attribute.

0 commit comments

Comments
 (0)