Content negotiation is one of those quality-of-life improvements you can add to your REST API to make it more user-friendly and flexible. And when we design an API, isn’t that what we want to achieve in the first place?

There are many things to keep in mind when designing a REST API and we’ve written recently about it in our Top REST API best practices article. Content negotiation is an HTTP feature that has been around for a while, but for one reason or another, it is, maybe, a bit underused.

In short, content negotiation lets you choose or rather “negotiate” the content you want in to get in response to the REST API request. If you want to learn how content negotiation works behind the scenes, you can download our Complete Guide to HTTP Book for free and look it up in the advanced features section.

You can download the source code from our Content Negotiation repository.

Today, we are going through the content negotiation implementation in ASP.NET Core.

We are going to talk about:

So let’s get down to it.

What Do You Get out of the Box?

By default, ASP.NET Core Web API returns a JSON formatted result.

Let’s make a default Web API project and remove the default WeatherForecastController. Instead, we are going to make our own controller (with blackjack and hookers), BlogController with only one method:

Things to note about this simple example:

  • We are using two classes: Blog and BlogPosts to create an object to return as a response object (you can find these classes in the Models folder in our source code)
  • We are utilizing the IActionResult interface provided by ASP.NET Core as a generic return type for different types of responses our methods might have
  • The object creation logic is in the controller. You should not implement your controllers like this; this is just for the sake of simplicity
  • We are returning the result with the Ok helper method which returns the object and the status code 200 OK

How to Use Postman to Test Your API

Postman is a nice little tool you can use to test your APIs easily. Now, let’s try calling the method using Postman and see what we get as a response.

content negotiation json

You can clearly see that the default result when calling GET on /api/blog returns our JSON result. Those of you with sharp eyes might have even noticed that we used the Accept header to try forcing the server to return other media types like plain text and XML.

But that doesn’t work. Why?

Because we need to configure server formatters to format a response the way we want it.

Let’s see how to do that.

Changing the Default Configuration of Our Project

A server does not explicitly specify where it formats a response to JSON. But we can override it by changing configuration options through the AddControllers method options. By default, it looks like this:

We can add the following options to enable the server to format the XML response when the client tries negotiating for it.

First things first, we must tell a server to respect the Accept header. After that, we just add the AddXmlDataContractSerializerFormatters method to support XML formatters.

Now that we have our server configured let’s test the content negotiation once more.

Testing Content Negotiation

Let’s see what happens now if we fire the same request through Postman.

content negotiation xml

There is our XML response.

That was easy, wasn’t it?

Now by changing the Accept header from text/xml to text/json, we can get differently formatted responses which is awesome, wouldn’t you agree?

Ok, that was nice and easy.

But what if despite all this flexibility a client requests a media type that a server doesn’t know how to format?

Restricting Media Types in Content Negotiation

Currently, it will default to a JSON type.

But we can restrict this behavior by adding one line to the configuration.

We added the ReturnHttpNotAcceptable = true option, which tells the server that if the client tries to negotiate for the media type the server doesn’t support, it should return the 406 Not Acceptable status code.

This will make your application more restrictive and force the API consumer to request only the types the server supports. The 406 status code is created for this purpose. You can find more details about that in our Complete Guide to HTTP book, or if you want to go even deeper you can check out the RFC2616.

Now, let’s try fetching the text/css media type using Postman to see what happens.

content negotiation request not acceptable

And as expected, there is no response body, and all we get is a nice 406 Not Acceptable status code.

So far so good.

More About Formatters

Let’s imagine you are making a public REST API and it needs to support content negotiation for a type that is not “in the box”. Rare as it might occur, you need to have a mechanism to do this.

So, how can you do that?

ASP.NET Core supports the creation of custom formatters. Their purpose is to give you the flexibility to create your own formatter for any media types you need to support.

We can make the custom formatter using the following method:

  • Create an output formatter class that inherits the TextOutputFormatter class
  • Create an input formatter class that inherits the TextInputformatter class
  • Add input and output classes to InputFormatters and OutputFormatters collections the same way as we did for the XML formatter

Now let’s have some fun and implement a custom CSV formatter for our example.

Implementing a Custom Formatter for Content Negotiation

Since we are only interested in formatting responses in this article, we need to implement only an output formatter. We would need an input formatter only if a request body contained a corresponding type.

The idea is to format a response to return the list of blogs and their corresponding list of blog posts in a CSV format.

Let’s add a CsvOutputFormatter class to our project.

There are a few things to note here:

  • In the constructor, we define which media type this formatter should parse as well as encodings
  • The CanWriteType method is overridden, and it indicates whether or not the Blog type can be written by this serializer.
  • The WriteResponseBodyAsync method that constructs the response
  • And finally, we have the FormatCsv method that formats a response the way we want it.

The class is pretty straightforward to implement, and the main thing that you should focus on is the FormatCsv method logic.

Now, we just need to add the newly made formatter to the list of OutputFormatters in the AddMvcOptions.

Now let’s run this and see if it actually works. This time we will put the text/csv as the value for the Accept header.

content negotiation csv

Well, what do you know, it works!

Since we only have one blog and one blog post in our example, there is only one line in the response.

You can play around with source code to see what happens when you add more blogs and blog posts.

There is a great page about custom formatters in ASP.NET Core if you want to learn more about them. You can also check out the implementation of the input and output formatters for the vcard content type if you need more examples.

Consuming API Programmatically

Up until now, we have used Postman to play around with the example. But, I feel you need to try out to consume some REST APIs using content negotiation we described here by making some requests programmatically instead of using the third party tool.

For that purpose, we have laid out a few great ways to consume RESTful API. You can find some of the best tools that .NET provides to consume any REST API. Be sure to check it out and try consuming some APIs.


In this blog post, we went through a concrete implementation of the content negotiation mechanism in an ASP.NET Core project. We have learned about formatters and how to make a custom one, and how to set them up in your project configuration as well.

We have also learned how to restrict an application only to certain content types, and not accept any others.

You should be able both to design and consume REST APIs using content negotiation now. It really is a great mechanism, and we have great tools to implement it in our projects, easily. So, there are no excuses!

If you want to play around with the source code, you can find it here: Download source code from GitHub.

Thanks for reading and please leave a comment in the comment section.