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 and IFormFile) and
- Parameter does not have
BindAsync method and
- 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:
|
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;
});
Current design
With the Minimal APIs, what the
RequestDelegateFactorydoes is fall back to try check if the Parameter is a Service registered in the DI when:andHttpContext,HttpRequest,HttpResponse,ClaimsPrincipal,CancellationToken,IFormFileCollectionandIFormFile)andBindAsyncmethodandstringandTryParsemethodCurrently MVC do not have support for items 3 and 4.
The way MVC implements the
ModelBindinglogic is throughModelBindersthat will be assigned, by Provider (Eg.ComplexObjectModelBinderProvider) based on the Parameter metadata.Also, the binding
FromServicesis already implemented (ServicesModelBinderProvider/ServicesModelBinder) that will be used when theBindingSourceis set to Services.For API Controllers, the BindingSource will be inferred, when the metadata is not set yet, based on with the following logic:
BindingSource = BodyBindingSource = PathBindingSource = QueryIn addition to that, Item 2 is similar in MVC since the
SpecialorFormFilebinding source is set byBindingSourceMetadataProvider, except forHttpContext,HttpRequestandHttpResponsethat are available in theControllerBaseclassEg.:
Based on the previous logic, nothing will be inferred as Services and the only way to the
BindingSourceto be set toServicesis 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
a. Is Registered in the DI =>
BindingSource = Servicesb. No registered =>
BindingSource = Bodya. Is part of any route =>
BindingSource = PathBindingSource = QueryMy proposal is to use the
IServiceProviderIsServiceservice, same used by theRequestDelegateFactory, injected in the constructor.public InferParameterBindingInfoConvention( IModelMetadataProvider modelMetadataProvider, + IServiceProviderIsService? serviceProviderIsService = null)And update the
InferBindingSourceForParametermethod to verify if the parameter type is registered in the DI.That will cover the idea of implicit inference of
FromServicesince theServiceModelBinderwill be activated when we set theBindingSourcetoServices.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