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
For example, if I have a JSON API, and I make the following call from jQuery :
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 :
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 :
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.
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.