I would like to post data to my API using AJAX but I'm having issues. I'm using Fiddler to test my API and I'm able to post JSON correctly but when posting a name/value urlencoded string I get a 400 Bad Request with the response body being '{"":["The input was not valid."]}'.
My debug window displays: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor:Information: Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.SerializableError'.
The JSON being posted is:
{
"Name": "Test"
}
The form data being posted is:
Name=Test
This is the Controller and Action:
[Route("api/[Controller]")]
[ApiController]
public class AccountsController : Controller
{
[HttpPost]
public IActionResult CreateAccount(Account account)
{
//code
}
}
This is the Account class:
public class Account
{
public string Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Website { get; set; }
}
It seems obvious that there is an issue during model binding but the form data seems valid (I've also generated form data using AJAX and get a 400 as well).
In his post Model binding JSON POSTs in ASP.NET Core from 2016, Andrew Lock explains that in order to bind a JSON POST in ASP.NET Core, the [FromBody]
attribute must be specified on the argument, like so:
[HttpPost]
public IActionResult CreateAccount([FromBody] Account account)
{
// ...
}
With the ASP.NET Core 2.1 introduction of [ApiController]
, this is no longer required. Of importance here is that this attribute effectively infers the presence of the [FromBody]
attribute when the type being bound is "complex" (which it is in your example). In other words, it's as though you have written the code as I demonstrated above.
In his post, Andrew also states the following:
In some cases you may need to be able to bind both types of data to an action. In that case, you're a little bit stuck, as it won't be possible to have the same end point receive two different sets of data.
Here, when referring to both types of data, Andrew is referring to both a JSON post and a form-based POST. He continues on to explain how to actually achieve the required result. Modifying his example for your purposes, you'd need to do something like the following:
// Form.
[HttpPost("FromForm")]
public IActionResult CreateAccountFromForm([FromForm] Account account)) =>
DoSomething(account);
// JSON.
[HttpPost("FromBody")]
public IActionResult CreateAccountFromBody(Account account) =>
DoSomething(account);
private IActionResult DoSomething(Account account) {
// ...
}
In Andrew's example, the [FromBody]
is explicit and the [FromForm]
is implicit, but given the affect that [ApiController]
has on the defaults, the modified example above flips that around.
Make sure your request type is set to "application/json". I reproduced your code, and the method was not being called using Postman until I set the request type to application/json.
Edit:
When I added the following to the headers in fiddler I was able to get fiddler to call my method as well:
Content-Type: application/json
If you want to get form data for the headr Content-Type:application/x-www-form-urlencoded) to your api controller then you have to put [FromForm] attribute in the action method like
// POST: api/Create
[HttpPost]
public IActionResult CreateAccount([FromForm] Account account)
{
}
If you want to get form data for the header Content-Type:application/json to your api controller then you have to put [FromBody]/No attribute in the action method like
// POST: api/Create
[HttpPost]
public IActionResult CreateAccount([FromBody] Account account)
{
}
Or
// POST: api/Create
[HttpPost]
public IActionResult CreateAccount(Account account)
{
}