Content negotiation is the process of selecting the best resource for a response when multiple resource representations are available. 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 to get in response to the REST API request.
Let’s dive in.
How Does Content Negotiation Work?
Content negotiation happens when a client specifies the media type it wants as a response to the request Accept
header. By default, ASP.NET Core Web API returns a JSON formatted result and it will ignore the browser Accept header.
ASP.NET Core supports the following media types by default:
application/json
text/json
text/plain
To try this out let’s create a default Web API project and simple model for a BlogPost
:
And one class that lists all the blog posts called Blog
:
We are going to create a controller called BlogController
with only one method Get()
that ads one blog post and one blog and returns them as a result:
Content negotiation is implemented by ObjectResult
and the Ok()
method inherits from OkObjectResult
that inherits from ObjectResult
. That means our controller method is able to return the content negotiated response.
We are returning the result with the Ok()
helper method which returns the object and the status code 200 OK
all the time.
Return the Default JSON Response
If we run our application right now, we’ll get a JSON response by default when run in a browser:
For the sake of testing the responses properly we’re going to use Postman:
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 with application/xml
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.
Return XML Response
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 can be found in the Program
class and 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 AddXmlSerializerFormatters()
method to support XML formatters.
Now that we have our server configured let’s test the content negotiation once more:
Let’s see what happens now if we fire the same request through Postman:
There is our XML response, the response is no longer a default one. By changing the Accept header we can get differently formatted responses.
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, the response will default to JSON if the media type is not recognized.
But we can restrict this behavior by adding one line to the configuration:
We’ve 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 HTTP Reference article, 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:
And as expected, there is no response body, and all we get is a nice 406 Not Acceptable
status code.
Custom Formatters in ASP.NET Core
Let’s imagine we are making a public REST API and it needs to support content negotiation for a type that is not “in the box”.
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
andOutputFormatters
collections the same way as we did for the XML formatter
Let’s implement a custom CSV output formatter for our example.
Implementing a Custom Formatter in ASP.NET Core
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 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 CsvOutputFormatter
to the list of OutputFormatters
in the AddMvcOptions()
method:
Now let’s run this and see if it actually works. This time we will put the application/csv
as the value for the Accept
header in a Postman request:
It works, our only entry is formatted as a CSV response.
There is a great page about custom formatters in ASP.NET Core if you want to learn more about them.
Conclusion
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.