When you have multiple parameters on an action method that are bound to the request body (implicitly or explicitly), you get the following fatal exception upon starting the web API:
System.InvalidOperationException: Action ‘RecipeController.Post’ 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.
This crashes the web API host process right away. If you try to send a request, you won’t get a nice error response – instead you’ll get connection errors (such as “connection refused”) because the web API isn’t running.
There are two main cases where you’d run into this problem: 1) You have multiple complex type parameters or 2) You have the [FromBody] attribute multiple times. I’ll show examples of these two cases below and how to fix them.
Case 1 – Multiple complex type parameters
By default, the framework assumes a parameter’s binding source (body, path, query string, header) based on the type of the parameter (unless you explicitly configure it with an attribute – like [FromBody]). Complex type parameters (i.e. model classes) are bound to the request body by default, so when you have multiple complex types, it results in the fatal exception.
Here’s an example. This has two complex type parameters (Recipe and Person):
[HttpPost()]
public IActionResult Post(Recipe recipe, Person person)
{
return Ok();
}
Code language: C# (cs)
How you fix this depends on where the parameter data is coming from: just the body, or a combo of the body and something else (path/header/query string).
If all parameters are in the request body
Create a new model class that matches the structure of the request body. In this case, the request body has multiple complex types, so add these types (Recipe and Person) as properties in the class:
public class RequestParams
{
public Recipe Recipe { get; set; }
public Person Person { get; set; }
}
Code language: C# (cs)
Then in the action method, put this class as the only parameter:
[HttpPost()]
public IActionResult Post(RequestParams requestParams)
{
return Ok($"Recipe name={requestParams.Recipe.Name} Person name={requestParams.Person.Name}");
}
Code language: C# (cs)
To see this work, send a request containing all of the parameters in the request body:
{
"recipe":{
"name": "pizza",
"servings": 4
},
"person":{
"name":"bob"
}
}
Code language: JSON / JSON with Comments (json)
It correctly maps the request body to the multiple complex type properties and returns the following 200 (OK) response:
Recipe name=pizza Person name=bob
Code language: plaintext (plaintext)
If the parameters in the request body and in something else (path/header/query string)
Complex types are assumed to be bound to the request body. If a complex type parameter is coming from a different source, explicitly configure it with a binding attribute ([FromQuery], [FromRoute], [FromHeader]).
For example, let’s say Recipe is part of the request body and Person is part of the query string. Use the [FromQuery] attribute on the Person parameter (and leave Recipe as is, implicitly bound to the body):
[HttpPost()]
public IActionResult Post(Recipe recipe, [FromQuery]Person person)
{
return Ok($"Recipe name={recipe.Name} Person name={person.Name}");
}
Code language: C# (cs)
Now send a request with query strings with a Person name property and put Recipe parameters in the body:
POST /Recipe?name=bob
Body:
{
"name": "pizza",
"servings": 4
}
Code language: plaintext (plaintext)
It’s able to correctly map the fields using the parameters from the different sources (body and query string) and returns the following 200 (OK) response:
Recipe name=pizza Person name=bob
Code language: plaintext (plaintext)
Case 2 – Multiple [FromBody] attributes
You can’t add the [FromBody] attribute to multiple parameters, otherwise you’ll get the fatal exception. If you run into this scenario, you’re probably trying to add it to multiple simple types, like this:
[HttpPost()]
public IActionResult Post([FromBody]string name, [FromBody]int servings)
{
return Ok();
}
Code language: C# (cs)
To fix this, create a model class and put the parameters as properties (Name and Servings):
public class Recipe
{
public string Name { get; set; }
public int Servings { get; set; }
}
Code language: C# (cs)
Then use this class as the the single parameter type:
[HttpPost()]
public IActionResult Post(Recipe recipe)
{
return Ok($"Posted {recipe.Name} ({recipe.Servings})");
}
Code language: C# (cs)
Note: Don’t need to add [FromBody] to the complex type parameter. It’s implicit.
Now send a request to see it work:
POST /Recipe
Body:
{
"name":"steak",
"servings":4
}
Code language: plaintext (plaintext)
It’s able to map from the request body and returns the following 200 (OK) response:
Posted steak (4)
Code language: plaintext (plaintext)