Skip to content

Add support for implicit inference of FromServices for types that appear in DI #39667

@brunolins16

Description

@brunolins16

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:

  1. No Metadata attributes set and
  2. No special types (HttpContext, HttpRequest, HttpResponse, ClaimsPrincipal, CancellationToken, IFormFileCollection and IFormFile) and
  3. Parameter does not have BindAsync method and
  4. Parameter is not string and
  5. 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:

  1. Complex Type => BindingSource = Body
  2. Is part of any route => BindingSource = Path
  3. 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)

  1. Complex Type:
    a. Is Registered in the DI =>BindingSource = Services
    b. No registered => BindingSource = Body
  2. Simple Types:
    a. Is part of any route => BindingSource = Path
  3. 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;            
});

Metadata

Metadata

Assignees

Labels

DocsThis issue tracks updating documentationapi-approvedAPI was approved in API review, it can be implementedbreaking-changeThis issue / pr will introduce a breaking change, when resolved / merged.feature-model-bindingold-area-web-frameworks-do-not-use*DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions