Why is the ASP.NET Core FromBody not working or returning null?

Published: Wednesday 21 April 2021

There can be issues when using the FromBody attribute in an ASP.NET Core Web API.

Where do we troubleshoot when the ASP.NET Core FromBody is not working?

C# coding challenges

C# coding challenges

Our online code editor allows you to compile the answer.

We have made a JSON request to an API endpoint. However, what do we do when the parameter with FromBody is returning null? Or, we are being greeted with an 415 Unsupported Media Type error.

We are going to have a look at the FromBody and FromQuery attributes in ASP.NET Core.

As well as that, we will demonstrate how they behave in an ASP.NET Core Web API, and when to use them.

Setting up our request

For our demonstration, we are going to setup an ASP.NET Core Web API application using C#.

The first thing we will do is to create a model.

This model will consist of a customer record, which contains a first name and surname property.

The whole point of this model is that we will pass it in as a parameter inside a controller's action.

We can then see if the properties within the model are being bound successfully by the request.

// Customer.cs
public class Customer
{
	public string Firstname { get; set; }

	public string Surname { get; set; }
}

Setting up and testing endpoints using different methods

Next, we are going to set up a web controller.

This web controller will point to a route of /web.

Within this controller, we are going to set up two actions.

The first action will be a HTTP GET method that will pass in our Customer model as a parameter. We will return the Customer model as a response to see if the request has managed to populate our Customer model instance.

The other action will be a HTTP POST method that will work in the same way as our HTTP GET method. We pass in the Customer model instance as a request, and return it as a response.

// WebController.cs
[Route("web")]
public class WebController
{
	[HttpGet]
	public Customer Get(Customer customer)
	{
		return customer;
	}

	[HttpPost]
	public Customer Post(Customer customer)
	{
		return customer;
	}
}

Now that we've done that, we are going to run some tests in Postman. We are running our ASP.NET Core Web API on https://localhost:7000.

These are the tests we are running, and the responses returned.

URL Request method Request body Response code Response
https://localhost:7000/web GET (querystring) firstname=David&
surname=Grace
200 {
"firstname": "David"
"surname": "Grace"
}
https://localhost:7000/web POST
(form-data)
firstname=David
surname=Grace
200 {
"firstname": "David"
"surname": "Grace"
}
https://localhost:7000/web POST
(x-www-form-url-encoded)
firstname=David
surname=Grace
200 {
"firstname": "David"
"surname": "Grace"
}
https://localhost:7000/web POST
(application/json)
{
"FirstName": "David"
"Surname": "Grace"
}
200 {
"firstname": null,
"surname": null
}

The application/json request isn't working quite right?

The good news is that all our requests are returning a 200 response!

However, when we do a POST request using JSON, our properties within our Customer model are not being set. They are still at their default value of null.

In-order to fix that, we can prepend the [FromBody] attribute to our Customer model instance in our parameter. We do that for the action that is performing our HTTP POST request.

// WebController.cs
public class WebController
{
	...

	[HttpPost]
	public Customer Post([FromBody] Customer customer)
	{
		return customer;
	}
}

Now, that's run our three HTTP POST tests again with the [FromBody] attribute to see how they respond.

These are the tests we are running, and the responses returned.

URL Request method Request body Response code Response
https://localhost:7000/web POST
(form-data)
firstname=david
surname=grace
415 (unsupported media type)
https://localhost:7000/web POST
(x-www-form-url-encoded)
firstname=david
surname=grace
415 (unsupported media type)
https://localhost:7000/web POST
(application/json)
{
"FirstName": "David"
"Surname": "Grace"
}
200 {
"firstname": "David",
"surname": "Grace"
}

When we run a POST request using either form-data or x-www-form-url-encoded content type, it now throws a 415 unsupported media type error.

However, when we throw in a JSON request, not only does it return a 200 response, but it also populates our Customer model type with the properties we sent in the request.

This is not how it's behaving?

When setting up a controller, there might be a case that the controller is not behaving in the same way when doing the above tests.

So why might this be the case?

Well, there is one reason why this is happening. There is an [ApiController] attribute attached to the controller.

When an ASP.NET Core Web API is set up in Visual Studio, it creates a default WeatherForecast controller. Within this controller, it contains the [ApiController] attribute.

The [ApiController] makes the controller behave in a different way when binding parameters from the request.

To demonstrate how this works, we are going to set up a new controller like the WebController we set up earlier. However, this time, we are going to add the [ApiController] attribute to the controller.

The other change we will make is pointing the route to /webapi.

// WebApiController.cs
[ApiController]
[Route("webapi")]
public class WebApiController : Controller
{
	[HttpGet]
	public Customer Get(Customer customer)
	{
		return customer;
	}

	[HttpPost]
	public Customer Post(Customer customer)
	{
		return customer;
	}
}

Now, that's go ahead and run the same tests again to see what results we get.

URL Request method Request body Response code Response
https://localhost:7000/webapi GET (querystring) firstname=David&
surname=Grace
415 (unsupported media type)
https://localhost:7000/webapi POST
(form-data)
firstname=David
surname=Grace
415 (unsupported media type)
https://localhost:7000/webapi POST
(x-www-form-url-encoded)
firstname=David
surname=Grace
415 (unsupported media type)
https://localhost:7000/webapi POST
(application/json)
{
"FirstName": "David"
"Surname": "Grace"
}
200 {
"firstname": "David",
"surname": "Grace"
}

Fixing the HTTP GET request

Interestingly, the HTTP GET request using a querystring is throwing a 415 error, alongside the form-data and x-www-form-url-encoded content types in the HTTP POST request.

However, the application/json content type request is returning a 200 response, and is binding our request to our model.

When using any REST API, a HTTP POST request will typically expect an application/json content type as it's request. As a result, we are not going to try and attempt to fix the 415 errors with the other HTTP POST content types.

But how do we fix the HTTP GET request? Now we could make the request using the application/json type. But what if we want to use the querystring?

Well, for that, we can prepend the [FromQuery] attribute to the parameter like so.

// WebApiController.cs
public class WebApiController : Controller
{
	[HttpGet]
	public Customer Get([FromQuery] Customer customer)
	{
		return customer;
	}

	...
}

Now that's rerun our HTTP GET request again and see what the result is.

URL Request method Request body Response code Response
https://localhost:7000/webapi GET (querystring) firstname=David&
surname=Grace
200 {
"FirstName": "David",
"Surname": "Grace"
}

The [FromQuery] attribute has now fixed our HTTP GET request.

If you wish to try it out for yourselves, you can download the code example.

In addition, check out our video where we implement these tests and demonstrate the results.

What to do if building an API controller?

When building an API controller, the best bet is to include the [ApiController] attribute for the controller.

By creating a new controller in Visual Studio 2019, we can select the API controller template, and it will automatically add the [ApiController] attribute for the controller.

This means that the [FromBody] attribute will not be required on a POST request using JSON.

However, we need to remember to include the [FromQuery] attribute if we are performing a HTTP GET request through the querystring.

Makes sense, but it does take some getting used to when getting started!