Add support for implicit inference of `FromServices` for types that appear in DI
See original GitHub issueCurrent 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:
- 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;
});
Issue Analytics
- State:
- Created 2 years ago
- Comments:9 (9 by maintainers)
Top GitHub Comments
We just approved
DisableImplicitFromServiceParameters
for the same option in SignalR. https://github.com/dotnet/aspnetcore/pull/34047 It’s not too late to change the property name in SignalR, but I think we should use the same name.Announcement Discussion: https://github.com/dotnet/aspnetcore/issues/40071