Skip to content

Improve error messages for implicit and explicit FromBody attribute in Minimal API #35086 #35188

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 17, 2021

Conversation

rafikiassumani-msft
Copy link
Contributor

@rafikiassumani-msft rafikiassumani-msft commented Aug 9, 2021

Improve error messages for implicit and explicit FromBody attribute in Minimal API #35086

PR Description

This a draft PR to start the conversation on a possible solution for error messages when :

  1. The Minimal MapPost/Put/Patch/etc.. has two body parameters either explicitly or implicitly. This would address a case where one of the body parameter may be a service that the user forgot to register with the DI container and binding fails to resolve.

Example:

  app.MapPost("/car", ([FromBody] SampleDTO sampleDTO, IUserRepo userRepo) => $"{sampleDTO.Name}");

In the example above, the code changes will keep track of all the request parameters and provide an error message telling the user which parameters we could successfully infer which ones fail. It also adds the parameter source (From Query String, Route, Services, etc.)

  Unhandled exception. System.InvalidOperationException: Failure to infer one or more parameters. Below is the list of parameters we found:

Parameter          Source

sampleDTO  Body Attribute
userRepo We failed to infer this parameter. Did you forget to inject it as a Service?

   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateArguments(ParameterInfo[] parameters, FactoryContext factoryContext) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 209
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateTargetableRequestDelegate(MethodInfo methodInfo, RequestDelegateFactoryOptions options, Expression targetExpression) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 170
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(Delegate action, RequestDelegateFactoryOptions options) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 89
   at Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.Map(IEndpointRouteBuilder endpoints, RoutePattern pattern, Delegate action) in C:\Users\rassumani\bench\aspnetcore\src\Http\Routing\src\Builder\MinimalActionEndpointRouteBuilderExtensions.cs:line 173
   at Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.MapMethods(IEndpointRouteBuilder endpoints, String pattern, IEnumerable`1 httpMethods, Delegate action) in C:\Users\rassumani\bench\aspnetcore\src\Http\Routing\src\Builder\MinimalActionEndpointRouteBuilderExtensions.cs:line 109
   at Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.MapPost(IEndpointRouteBuilder endpoints, String pattern, Delegate action) in C:\Users\rassumani\bench\aspnetcore\src\Http\Routing\src\Builder\MinimalActionEndpointRouteBuilderExtensions.cs:line 54
  1. There is No content type and the content length is zero, however, there is still an implicit FromBody parameter that we need to bind like in the case below:
 app.MapGet("/test/{id}", (int id, SampleDTO sampleDTO) => $"id is {id}  - {sampleDTO.Name}");

We will read the body httpContext.Request.ReadFromJsonAsync(bodyType) and throw an exception in CreateContentTypeError method.

Exception:

  System.InvalidOperationException: We failed to infer or bind 'SampleDTO' parameter. Was this a Service parameter that you forgot to register ?
   at Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync(HttpRequest request, Type type, JsonSerializerOptions options, CancellationToken cancellationToken) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\HttpRequestJsonExtensions.cs:line 129
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass42_0.<<HandleRequestBodyAndCompileRequestDelegate>b__0>d.MoveNext() in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 517
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) in C:\Users\rassumani\bench\aspnetcore\src\Http\Routing\src\EndpointMiddleware.cs:line 79
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context) in C:\Users\rassumani\bench\aspnetcore\src\Middleware\Diagnostics\src\DeveloperExceptionPage\DeveloperExceptionPageMiddleware.cs:line 94

Fixes #35086

@ghost ghost added the area-runtime label Aug 9, 2021
@rafikiassumani-msft rafikiassumani-msft added feature-minimal-actions Controller-like actions for endpoint routing old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels and removed area-runtime labels Aug 9, 2021
@halter73
Copy link
Member

halter73 commented Aug 9, 2021

I think we should split this into two PRs. One for throwing for multiple from-body parameters during Create() and another for throwing for not finding a body when one is expected during the request.

@rafikiassumani-msft rafikiassumani-msft marked this pull request as draft August 9, 2021 19:22
@rafikiassumani-msft rafikiassumani-msft changed the title Draft: Improve error messages for implicit and explicit FromBody attribute in Minimal API #35086 Improve error messages for implicit and explicit FromBody attribute in Minimal API #35086 Aug 9, 2021
@rafikiassumani-msft rafikiassumani-msft marked this pull request as ready for review August 16, 2021 18:26
if (parameterName is not null && factoryContext.TrackedParameters.ContainsKey(parameterName))
{
factoryContext.TrackedParameters.Remove(parameterName);
factoryContext.TrackedParameters.Add(parameter.Name!, "We failed to infer this parameter. Did you forget to inject it as a Service in the DI container?");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
factoryContext.TrackedParameters.Add(parameter.Name!, "We failed to infer this parameter. Did you forget to inject it as a Service in the DI container?");
factoryContext.TrackedParameters.Add(parameter.Name!, "UNKNOWN");

I think the following is a bit hard to read:

  Unhandled exception. System.InvalidOperationException: Failure to infer one or more parameters. Below is the list of parameters we found:

Parameter          Source

sampleDTO  Body Attribute
userRepo We failed to infer this parameter. Did you forget to inject it as a Service?

   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateArguments(ParameterInfo[] parameters, FactoryContext factoryContext) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 209
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateTargetableRequestDelegate(MethodInfo methodInfo, RequestDelegateFactoryOptions options, Expression targetExpression) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 170
   ...

This would be a bit better:

  Unhandled exception. System.InvalidOperationException: Failure to infer one or more parameters. Below is the list of parameters we found:

Parameter |            Source
----------+------------------
sampleDTO |  Body (Attribute)
userRepo  |            UNKOWN


Did you forget to inject the "UNKNOWN" parameters as a Service?


   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateArguments(ParameterInfo[] parameters, FactoryContext factoryContext) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 209
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateTargetableRequestDelegate(MethodInfo methodInfo, RequestDelegateFactoryOptions options, Expression targetExpression) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 170
   ...

Adding the | and - would require more than just the suggested fix of course but I think it will help. Can you show what this looks like on the developer exception page?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do we get the same "Failure to infer one or more parameters." error when all the parameters have explicit attributes? If there actually are multiple [FromBody] attributes we should probably just continue saying that's not allowed instead of saying we couldn't infer the source.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a logger formatter or console formatter in aspnetcore that I can use ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am hoping an analyzer will prevent the double explicit [FromBody] right away.

Copy link
Contributor Author

@rafikiassumani-msft rafikiassumani-msft Aug 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New formatting. Not perfect, visually readable.

Unhandled exception. System.InvalidOperationException: Failure to infer one or more parameters.
Below is the list of parameters that we found:

Parameter           |Source
---------------------------------------------------------------------------------
id                  | Route (Inferred)
dto                 | Body (Attribute)
dto1                | UNKNOWN
repo                | Service (Attribute)
repo1               | UNKNOWN


Did you forget to inject the "UNKNOWN" parameters as a Service?


   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateArguments(ParameterInfo[] parameters, FactoryContext factoryContext) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 196
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateTargetableRequestDelegate(MethodInfo methodInfo, RequestDelegateFactoryOptions options, Expression targetExpression) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 165
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(Delegate action, RequestDelegateFactoryOptions options) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 84

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Did you mean to register the "UNKNOWN" parameters as a Service?"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unhandled exception. System.InvalidOperationException: Failure to infer one or more parameters.
Below is the list of parameters that we found:

Parameter           |Source
---------------------------------------------------------------------------------
id                  | Route (Inferred)
dto                 | Body (Attribute)
dto1                | UNKNOWN
repo                | Service (Attribute)
repo1               | UNKNOWN


Did you mean to register the "UNKNOWN" parameters as a Service?


   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateArguments(ParameterInfo[] parameters, FactoryContext factoryContext) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 196
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.CreateTargetableRequestDelegate(MethodInfo methodInfo, RequestDelegateFactoryOptions options, Expression targetExpression) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 165
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(Delegate action, RequestDelegateFactoryOptions options) in C:\Users\rassumani\bench\aspnetcore\src\Http\Http.Extensions\src\RequestDelegateFactory.cs:line 84


@@ -203,6 +217,7 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext

if (parameterCustomAttributes.OfType<IFromRouteMetadata>().FirstOrDefault() is { } routeAttribute)
{
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteAttribue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call to add a separate class for all the constants we're introducing here.

@@ -189,6 +190,13 @@ private static Expression[] CreateArguments(ParameterInfo[]? parameters, Factory
args[i] = CreateArgument(parameters[i], factoryContext);
}

if(factoryContext.HasMultipleBodyParameters)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nit:

Suggested change
if(factoryContext.HasMultipleBodyParameters)
if (factoryContext.HasMultipleBodyParameters)

@@ -254,6 +267,7 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
// when RDF.Create is manually invoked.
if (factoryContext.RouteParameters is { } routeParams)
{
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteParameter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't correct anymore. It's either route or query or either.

@rafikiassumani-msft rafikiassumani-msft merged commit cddec67 into dotnet:main Aug 17, 2021
@ghost ghost added this to the 6.0-rc1 milestone Aug 17, 2021
@amcasey amcasey added the area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc label Jun 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-minimal-actions Controller-like actions for endpoint routing old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Part1: Improve error messages for implicit and explicit FromBody attribute in Minimal API
5 participants