Content Negotiation is a powerful and core part of HTTP, but vastly under-utilized in ASP.NET Web APIs.
This post if part of a 5-part series which aims to:
- Convince you why it is useful
- Grumble that it is difficult to use in ASP.NET
- Show what ASP.NET provides
- Show a better way via codecs
What is HTTP Content Negotiation?
Congrats… you just experienced Content Negotiation. Browsers use it in HTTP requests for HTML, images, videos, etc. It lets the browser and the server converge on a data format that both sides support.
- The browser sends a list of formats that it understands.
- The server compares that list with what it can return and picks the best option.
- The server returns a response, and indicates which format it chose
I recommend this MDN article on content negotiation. It should prove useful as we dig deeper. Please note the term “representations”, which we’ll discuss shortly.
Useful for APIs
ASP.NET supports a coarse-grained negotiation of XML vs JSON out of the box. This is good, but Content Negotiation offers many more benefits when fully utilized:
- Versioning, Evolution, and Backwards-Compatibility
- Client / Server deployment decoupling
- Clean and semantically meaningful endpoints
Illustrated via Donuts
Let’s go get some donuts to illustrate the point. Of these three shops… which would you rather visit?
Shop #1: Donut Dictator
Many APIs work like this shop. The server dictates what is returned. The client has no say in the matter.
The responses from these APIs often lack metadata to clarify what’s in the body. This makes it hard for clients to decode (deserialize) the content. They either peek inside to detect the flavor, or they throw bits blindly into a deserializer and hope for the best.
Shop #2: Binary Donuts
Some APIs increase the number of endpoints, with each endpoint supporting a different flavor. This is sometimes used to support multiple versions as the API evolves. While functional, this is not ideal. More on this later.
Shop #3: Donut Discussions
I prefer interactions like Shop #3. The interactions are natural, efficient, and most likely to give me what I want. The rest of this series embraces this style.
A “representation” is key to “ReST”. It is part of the name “Representational State Transfer“!
A representation is an encoding of a thing. There can be multiple encodings of a given thing. For example, a contact can be encoded as a VCARD, XML, JSON, or a JPEG image.
HTTP uses the MediaType specification (RFC 2045 and RFC 2046) to identify what is in the body of a request or response. The MediaType is an excellent mechanism to qualify and specify which representation is used.
MediaTypes can be much more specific than just “application/json”. MediaTypes support key-value parameters to further specify details. For a ReST API, these could express things like the version (vcard 3 vs vcard 4) or the domain (contact vs order vs payment). Here are a few example media types for a contact resource:
application/json; domain=alner.contact; version=2 application/xml; domain=alner.contact; version=2 text/vcard; version=3 avro/binary; domain=alner.contact; version=1
I have successfully used these service-specific media types across many services. The pattern is robust and adds much value. The ability to express the version of a representation is a highly useful tool in a versioning strategy. Expressing the domain of a representation is key to properly decoding responses. Without such information, the system needs to embed this metadata inside the representation, or act on faith that the data matches expectations. Neither is ideal.
What’s Next (Part 2)
In part 2, we’ll explore how ASP.NET implements content negotiation. We’ll show how to customize the behavior and get full control.
This post is part of a series: