Solving HTTP 415 Errors In .NET Core

I recently ran into a problem where my .NET API was returning an error 415. The full error gives you a hint as to what the actual issue is : “415 Unsupported Media Type”, although this can lead you down a wild goose chase of stackoverflow answers.

In short, the API is expecting a post request with a particular content-type header, but the caller (Or maybe your front end) is using a different media type. There are actually some other gotchas that are incredibly frustrating to figure out in .NET too that can blow this entire thing up without you noticing. But let’s get on to it!

Check Your Front End Caller

The first thing we need to do is understand what our API is expecting. In general, API’s these days are expecting JSON requests. In some cases, they are expecting a classic “form post”. These are not the same thing! But whichever you use, your front end caller (Whether that be a javascript library or another machine), must attach the correct content type when making a request to the API.

For example, if I have a JSON API, and I make the following call from jQuery :

 $.ajax({
  url: '/myapiendpoint',
  type: 'POST'
});

This actually won’t work! Why? Because the default content-type of an Ajax request from jQuery is actually “application/x-www-form-urlencoded”, not “application/json”. This can catch you out if you aren’t familiar with the library and it’s making calls using the default content-type.

But of course, we can go the other way where you copy and paste someone’s helpful code from stackoverflow that forces the content-type to be JSON, but you are actually using form posts :

 $.ajax({
  url: '/myapiendpoint',
  contentType: 'application/json'
  type: 'POST'
});

Don’t think that you are immune to this just because you are using a more modern library. Every HttpClient library for javascript will have some level of default Content Type (Typically application/json), and some way to override it. Often, libraries such as HttpClient in Angular, or Axios, have ways to globally override the content-type and override it per request, so it can take some time working out exactly how the front end is working.

When it comes down to it, you may need to use things like your browser dev tools to explicitly make sure that your front end library is sending the correct content-type. If it is, and you are certain that the issue doesn’t lie there, then we have to move to debugging the back end.

Checking The Consumes Attribute

If we are sure that our front end is sending data with a content-type we are expecting, then it must be something to do with our backend. The first thing I always check is if we are using the Consumes attribute. They look a bit like this :

[Consumes("application/xml")]
public class TestController : ControllerBase
{
}

Now in this example, I’ve placed the attribute on the Controller, but it can also be placed directly on an action, or even added to your application startup to apply globally, so your best bet is usually a “Ctrl + Shift + F” to find all of them.

If you are using this attribute, then make sure it matches what the front end is sending. In 99% of cases, you actually don’t need this attribute except for self documenting purposes, so if you can’t find this in use anywhere, that’s normal. Don’t go adding it if you don’t already have it and are running into this issue, because often that will just complicate matters.

In the above example, I used [Consumes(“application/xml)] as an example of what might break your API and return an error 415. If my front end has a content-type of json, and my consumes specifies I’m expecting XML, then it’s pretty clear there’s going to be a conflict of some kind we need to resolve.

Checking FromBody vs FromForm

Still not working? The next thing to check is if you are using FromBody vs FromForm correctly. Take this action for example :

public IActionResult MyAction([FromForm]object myObject)

This endpoint can only be called with non form post data. e.g. The content type must be “application/x-www-form-urlencoded”. Why? Because we are using the [FromForm] attribute.

Now if we change it to FromBody like so :

public IActionResult MyAction([FromBody]object myObject)

This can only accept “body” types of JSON, XML etc. e.g. Non form encoded content types. It’s really important to understand this difference because sometimes people change the Consumes attribute, without also changing how the content of the POST is read. This has happened numerous times for me, mostly when changing a JSON endpoint to just take form data because a particular library requires it.

ApiController Attribute

Finally, I want to talk about a particular attribute that might break an otherwise working API. In .NET Core and .NET 5+, there is an attribute you can add to any controller (Or globally) called “ApiController”. It adds certain conventions to your API, most notably it will check ModelState for you and return a nice error 400 when the ModelState is not valid.

However, I have seen API’s act very differently when it comes to modelbinding, because of this attribute. It adds some nice “conventions” for you that it will try and infer the FromBody, FromRoute, FromQuery etc for you. Generally speaking, I don’t see this breaking API’s, and for the most part, I use it everywhere. But if you are comparing two projects with the exact same controller and action setup, and one works and one doesn’t, it’s worth checking if one implements the ApiController attribute. Again, “Ctrl + Shift + F” is your friend here to find anywhere that it may be getting applied.

ENJOY THIS POST?
Join over 3.000 subscribers who are receiving our weekly post digest, a roundup of this weeks blog posts.
We hate spam. Your email address will not be sold or shared with anyone else.

Leave a Reply

Your email address will not be published. Required fields are marked *