-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
Current design
With the Minimal APIs, what the RequestDelegateFactory
does is fall back to try check if the Parameter is a Service registered in the DI when:
- No Metadata attributes set
and
- No special types (
HttpContext
,HttpRequest
,HttpResponse
,ClaimsPrincipal
,CancellationToken
,IFormFileCollection
andIFormFile
)and
- Parameter does not have
BindAsync
methodand
- Parameter is not
string
and
- Parameter does not have
TryParse
method
Currently MVC do not have support for items 3 and 4.
The way MVC implements the ModelBinding
logic is through ModelBinders
that will be assigned, by Provider (Eg. ComplexObjectModelBinderProvider
) based on the Parameter metadata.
Also, the binding FromServices
is already implemented (ServicesModelBinderProvider
/ServicesModelBinder
) that will be used when the BindingSource
is set to Services.
For API Controllers, the BindingSource will be inferred, when the metadata is not set yet, based on with the following logic:
- Complex Type =>
BindingSource = Body
- Is part of any route =>
BindingSource = Path
- Default =>
BindingSource = Query
In addition to that, Item 2 is similar in MVC since the Special
or FormFile
binding source is set by BindingSourceMetadataProvider
, except for HttpContext
, HttpRequest
and HttpResponse
that are available in the ControllerBase
class
Eg.:
modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(CancellationToken), BindingSource.Special));
modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFile), BindingSource.FormFile));
modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormCollection), BindingSource.FormFile));
modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFileCollection), BindingSource.FormFile));
modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IEnumerable<IFormFile>), BindingSource.FormFile));
Based on the previous logic, nothing will be inferred as Services and the only way to the BindingSource
to be set to Services
is using the [FromServices]
attribute.
Proposed Change
Update the Infer mechanism to do the following:
aspnetcore/src/Mvc/Mvc.Core/src/ApplicationModels/InferParameterBindingInfoConvention.cs
Line 94 in 8bd8f58
internal BindingSource InferBindingSourceForParameter(ParameterModel parameter) |
- Complex Type:
a. Is Registered in the DI =>BindingSource = Services
b. No registered =>BindingSource = Body
- Simple Types:
a. Is part of any route =>BindingSource = Path
- Default =>
BindingSource = Query
My proposal is to use the IServiceProviderIsService
service, same used by the RequestDelegateFactory
, injected in the constructor.
public InferParameterBindingInfoConvention(
IModelMetadataProvider modelMetadataProvider,
+ IServiceProviderIsService? serviceProviderIsService = null)
And update the InferBindingSourceForParameter
method to verify if the parameter type is registered in the DI.
if (_serviceProviderIsService.IsService(parameter.ParameterType))
{
return BindingSource.Services;
}
That will cover the idea of implicit inference of FromService
since the ServiceModelBinder
will be activated when we set the BindingSource
to Services
.
Also, I prefer this to be the new default behavior, so my suggestion is to include allowing users to opt-out:
namespace Microsoft.AspNetCore.Mvc;
public class ApiBehaviorOptions
{
+ public bool SuppressInferBindingFromServicesForParameters { get; set; }
}
Usage Examples
services.Configure<ApiBehaviorOptions>(options => {
options.SuppressInferBindingFromServicesForParameters = true;
});