Closed
Description
How to determine each parameter's source
The numbered and lettered rules for determining the source of a parameter should be applied in the order. The bullet points are unordered.
- If an attribute that implements one of the "IFrom..." interfaces in
Microsoft.AspNetCore.Http.Metadata
is applied to the parameter, populate the parameter from the source specified by the attribute. Only one of these attributes can be specified per parameter, and these attributes must implement no more than one of these interfaces.IFromRouteMetadata
- Reads from
HttpRequest.RouteValues[name]
wherename
is specified byIFromRouteMetadata.Name
or the parameter name ifIFromRouteMetadata.Name
is null.
- Reads from
IFromBodyMetadata
- Reads from
HttpRequest.Body(Reader)
as JSON usingHttpRequest.ReadFromJsonAsync<{ParameterType}>()
. - Can only be used once per parameter list (there's only one body)
- Cannot be used in the same parameter list as
IFromFormMetadata
- Will cause requests with an empty body to be rejected without calling the action unless
IFromBodyMetadata.AllowEmpty
returns true. (The default interface implementation returns false.)
- Reads from
IFromFormMetadata
- Reads from
HttpRequest.Form[name]
wherename
is specified byIFromFormMetadata.Name
or the parameter name ifIFromFormMetadata.Name
is null. - Can be used multiple times in a single parameter list (presumably for different names)
- Cannot be used in the same parameter list as
IFromBodyMetadata
(there's only one body)
- Reads from
IFromHeaderMetadata
- Reads from
HttpRequest.Headers[name]
wherename
is specified byIFromHeaderMetadata.Name
or the parameter name ifIFromHeaderMetadata.Name
is null.
- Reads from
IFromQueryMetadata
- Reads from
HttpRequest.Query[name]
wherename
is specified byIFromQueryMetadata.Name
or the parameter name ifIFromQueryMetadata.Name
is null.
- Reads from
IFromServiceMetadata
- Normally resolves via
HttpRequest.RequestServices.GetRequiredService<{ParameterType}>()
. - If the parameter is a nullable reference type (i.e. has the
System.Runtime.CompilerServices.NullableAttribute
), resolve viaHttpRequest.RequestServices.GetService<{ParameterType}>()
. - If the parameter is optional (i.e. has a default value), resolve via
HttpRequest.RequestServices.GetService<{ParameterType}>() ?? parameter.DefaultValue
.
- Normally resolves via
- If the parameter is a scalar value type that lives in the System namespace, first check
HttpRequest.RouteValues
contains an entry for the parameter name. If it does, useHttpRequest.RouteValues[{ParameterName}]
. Otherwise, useHttpRequest.Query[{ParameterName}]
.- TODO: Come up with actual list of scalar value types that live in the System namespace.
- Question: Do we want to allow types to opt-in to this? Today MVC uses
TypeConverter
which is hard to implement. - Alternatives:
- Something convention-based like the presence of a
bool TryParse(string, out T)
method - The explicit implementation of an interface with that method to make it more opt in
- Something convention-based like the presence of a
- If
HttpRequest.RequestServices.GetService<{ParameterType}>()
is not null, use that value.- TODO: Figure out how to make this work well with compile-time source generation. This might need to be determined at runtime no matter what.
- Otherwise, treat the parameter as if it had an attribute implementing
IFromBodyMetadata
whereIFromBodyMetadata.AllowEmpty
is false.
How we will convert to the parameter type.
- For parameters bound from the body (has an
IFromBodyMetadata
attribute or is treated by such by convention), we will continue to assume JSON and initialize the parameter withHttpRequest.ReadFromJsonAsync<{ParameterType}>()
.- We need to figure out if we want make this configurable or force people to read they body themselves if they want configurability. If we decide on configurability, we need to decide on how much.
- For all other parameters bound from the request (i.e. not a service or from the body) we need to convert from a string.
- Since there is a finite list of "scalar value who lives in the System namespace" we'll bind to by convention, we'll use their TryParse methods or equivalent until we decide on something different for those.
- Parameters explicitly bound by attribute to a string-based source also need to be converted much like those read from the
RouteValues
orQuery
by convention. I doubt we also want to limit these parameter types to a finite list. For now, we'll continue doing what we do today to convert from string as described below.
How we convert to the parameter type today in the "MapAction" APIs
Today, all inputs are converted to their respective parameter types as follows:
- For parameters bound from the body, we already call
HttpRequest.ReadFromJsonAsync<{ParameterType}>()
. - All other parameters start as
string
s from the various sources listed above.- If
{ParameterType}
isstring
, we're done! - If there's a
{ParameterType}.Parse({ValueString})
method, use that.- NOTE: Going forward, prefer/require
TryParse
instead.
- NOTE: Going forward, prefer/require
- Lastly, try
Convert.ChangeType({ValueString}, typeof{ParameterType}, CultureInfo.InvariantCulture)
- NOTE: Going forward, we should stop using
CultureInfo.InvariantCulture
- NOTE: Going forward, we should stop using
- If
Original issue:
- Complex type -> FromBody (See https://source.dot.net/#Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs,606)
- Name appears as a route value -> FromRoute
- Everything else -> FromQuery
From: #29878 (comment)
New inference rules unique to MapAction:
[FromServices]
should work implicitly for types that can be resolved from RequestServices.