Mvc: Action with complex model and CancellationToken results in ambiguous binding source inference

Created on 26 Mar 2018  路  18Comments  路  Source: aspnet/Mvc

In an API controller, if you have an action with both a complex model and a CancellationToken, MVC fails to infer the binding source:

Unable to unambiguously infer binding sources for parameters on 'System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.ActionResult`1[Models.UserModel]] Register(Controllers.RegisterUserModel, System.Threading.CancellationToken)'. More than one parameter may be inferred to bound from body.

It's easy enough to get around it by just setting SuppressInferBindingSourcesForParameters and slapping an explicit [FromBody] attribute on the model parameter, but this seems like a bug/oversight?

bug duplicate

Most helpful comment

You're missing .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); in your Startup:

            var mvcBuilder = services.AddMvcCore(config =>
            {
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

All 18 comments

Also, BTW, it would be nice if you used TypeNameHelper for those kinds of error messages 馃槈

There's already a BindingSourceMetadataProvider defined for CancellationToken (and others), marking it as BindingSource.Special:

https://github.com/aspnet/Mvc/blob/fa629cd5e112d89db7cef7374694eb08dbc6bf9c/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs#L87

Shouldn't these be considered before trying to infer another binding source? Might be a stupid question, but I'm not super familiar with the model binding parts of the codebase 馃槤

Hmm. Looking at the ApiBehaviorApplicationModelProvider, I would've expected that the existing binding source would be reflected here:

https://github.com/aspnet/Mvc/blob/fa629cd5e112d89db7cef7374694eb08dbc6bf9c/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs#L154-L158

But I guess there might be some ordering issues here?

Assigning to myself for tracking purposes only.

Might want to merge this with #7512. Cancellationtoken and existing Binding sources are listed there too.

Ah, I didn't realize that this had been filed before. Yeah, this will fix the first part of #7512, but there are other issues listed there that might need to be addressed as well.

In which version is this supposed to be fixed?
In 2.1-preview2-final with my repro from: https://github.com/aspnet/Mvc/issues/7512
I get for the Notworking example below (see repro)

[HttpPost]
[Route("NotWorking")]
public Task<IActionResult> Notworking(HelloWorld helloWorld, CancellationToken cancellationToken = default)

{
    IActionResult okResult = new OkResult();
    return Task.FromResult(okResult);
}

the following error message:

Unable to unambiguously infer binding sources for parameters on 'System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] Notworking(BodyBindingRepro.HelloWorld, System.Threading.CancellationToken)'. More than one parameter may be inferred to bound from body.

If I upgrade to the latest nugets from myget (2.1.0-rc1-30678) I get the following exception:

System.InvalidOperationException: Action 'BodyBindingRepro.TestController.Notworking (BodyBindingRepro)' has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:
HelloWorld helloWorld
CancellationToken cancellationToken
   at Microsoft.AspNetCore.Mvc.Internal.ApiBehaviorApplicationModelProvider.InferParameterBindingSources(ActionModel actionModel)
   at Microsoft.AspNetCore.Mvc.Internal.ApiBehaviorApplicationModelProvider.OnProvidersExecuting(ApplicationModelProviderContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionDescriptorProvider.BuildModel()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionDescriptorProvider.GetDescriptors()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionDescriptorProvider.OnProvidersExecuting(ActionDescriptorProviderContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ActionDescriptorCollectionProvider.UpdateCollection()
   at Microsoft.AspNetCore.Mvc.Internal.ActionDescriptorCollectionProvider.get_ActionDescriptors()
   at Microsoft.AspNetCore.Mvc.Internal.AttributeRoute.GetTreeRouter()
   at Microsoft.AspNetCore.Mvc.Internal.AttributeRoute.RouteAsync(RouteContext context)
   at Microsoft.AspNetCore.Routing.RouteCollection.<RouteAsync>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware.<Invoke>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<ProcessRequests>d__186`1.MoveNext()

I thought we all agreed that CancellationToken is ignored?

@Tornhoof I'm not seeing it fail with the rc1 bits. Could you

a) Look at the obj\project.assets.json of the project and verify it's using Microsoft.AspNetCore.Mvc 2.1.0-rc1-30678? It'd be good to rule out restore issues.
b) zip up a repro and post it here if that's correct?

The assets look ok to me, it contains "Microsoft.AspNetCore/2.1.0-rc1-30678"
I zipped it up, the archive contains an output.txt which is the output log window, it shows loading those rc dlls and the exception from above:
BodyBindingRepro.zip
I removed the ref directory from the output, on my system it contains all those rc1 dlls, everything else is included.

What I did was:

  • Take the repro from the other bug in VS 15.7 Preview @6
  • Add a nuget.config with a myget path
  • Upgrade Nuget packages for prerelease
  • Compile (after restoring the packages)
  • Start in IIS Express
  • Look at Output Window

To make sure it's not some sdk thing, I also installed the latest 2.1 rc sdk from dotnet/versions (which contains the version above: Output of dotnet --info


.NET Core SDK (gem盲脽 "global.json"):
Version: 2.1.300-rc1-008671
Commit: 04066cb5d8

Laufzeitumgebung:
OS Name: Windows
OS Version: 10.0.17134
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\2.1.300-rc1-008671\

Host (useful for support):
Version: 2.1.0-rc1-26426-04
Commit: b50a96ee38

.NET Core SDKs installed:
1.0.2 [C:\Program Files\dotnet\sdk]
1.0.3 [C:\Program Files\dotnet\sdk]
1.0.4 [C:\Program Files\dotnet\sdk]
1.1.0 [C:\Program Files\dotnet\sdk]
2.0.0 [C:\Program Files\dotnet\sdk]
2.0.2 [C:\Program Files\dotnet\sdk]
2.0.3 [C:\Program Files\dotnet\sdk]
2.1.104 [C:\Program Files\dotnet\sdk]
2.1.200-preview-007576 [C:\Program Files\dotnet\sdk]
2.1.200-preview-007589 [C:\Program Files\dotnet\sdk]
2.1.300-preview2-008530 [C:\Program Files\dotnet\sdk]
2.1.300-rc1-008671 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.1.0-preview2-final [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.0-rc1-30678 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.0-preview2-final [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.0-rc1-30678 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 1.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 1.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 1.1.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 1.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.0-preview2-26406-04 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.0-preview3-26416-01 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.0-rc1-26426-04 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
https://aka.ms/dotnet-download

You're missing .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); in your Startup:

            var mvcBuilder = services.AddMvcCore(config =>
            {
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

Thank you very much. It works now.
Still I find it weird that the new [ApiController] feature needs that one to work properly, but I understand it's a change to the 2.0 behaviour. Sorry for the trouble.

It's a bit unfortunate, but the right fix for the underlying issue affects all controllers, including non-ApiController ones. It's a change in behavior and hence guarded behind a compat switch.

Hello, I need help regarding Microsoft.AspNetCore.Mvc.Internal.ApiBehaviorApplicationModelProvider.InferParameterBindingSources(ActionModel actionModel).

Currently, I have this situation:

image

What am I doing wrong?

What am I doing wrong?

Did you annotate your controller with the [ApiController] Attribute?

Yes, please take a look:
image

Your method has multiple complex types (atleast from the type names) as parameters, as the error message tells you, you can only have on in the body.
You need to decide which one and pass everything else in as [FromQuery] or remove them.
If it is a new API and not an existing one, I highly recommend rethinking the behaviour of passing multiple complex into it.

Hi, it looks like you are posting on a closed issue/PR/commit!

We're very likely to lose track of your bug/feedback/question unless you:

  1. Open a new issue
  2. Explain very clearly what you need help with
  3. If you think you have found a bug, include detailed repro steps so that we can investigate the problem

Thanks!

Was this page helpful?
0 / 5 - 0 ratings