Skip to content

Instantly share code, notes, and snippets.

@yishaigalatzer
Last active August 29, 2015 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yishaigalatzer/4f6ef19f758c1cde8422 to your computer and use it in GitHub Desktop.
Save yishaigalatzer/4f6ef19f758c1cde8422 to your computer and use it in GitHub Desktop.
Conneg blog
<h3>Intro</h3>
<p>In this blog, I intend to provide a simplified how-things-work and how-to-change-the-behavior. It is not intended as a deep dive into content negotiation.</p>
<p>Since Web API 1 controller code can return an object of an arbitrary type and the framework will send it as JSON or XML to the client. The process of picking the output format is called “content negotiation”. The basic rules can be described simply as:</p>
<p>1. The framework will attempt to return the format that the client asked for using the <strong>Accept </strong>header.</p>
<p>2. In absence of a specific format requested or inferred, the default format is JSON. </p>
<p>3. If the format the client asked for is <strong>not available</strong> the framework will return the default format JSON. (Example: accept header was application/DoesNotExistFormat)</p>
<p>MVC 6 combined Web API and MVC into a single framework. Although content negotiation was revamped, the basic rules have not changed. There are some enhancements to the API, factoring of the code and a few behavior improvements for edge cases have been added.</p>
<p>A common question about content negotiation is: Why is my API returning XML by default. Nine out of ten times the reason is that the developer is using a browser to test the API, using Chrome or FireFox. The Chrome browser is asking for XML in the accept header and the server follows the above rules.</p>
<p><u>Here is a simple GET request from Chrome to localhost:</u></p>
<p>GET / HTTP/1.1
<br />Host: localhost:5001
<br />Proxy-Connection: keep-alive
<br /><strong>Accept: </strong>text/html,application/xhtml+xml,<strong>application/xml</strong>;q=0.9,image/webp,*/*;q=0.8
<br />User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36
<br />Accept-Encoding: gzip, deflate, sdch
<br />Accept-Language: en-US,en;q=0.8,he;q=0.6</p>
<p>Note that the accept header asks for several formats including <strong>application/xml</strong>, but does not ask for application/json. Hence the server will just return the only format it can match which is XML.</p>
<p><u>In contrast if we made the same request with IE it will simply not ask for XML:</u></p>
<p>GET / HTTP/1.1
<br /><strong>Accept: text/html, application/xhtml+xml, */*</strong>
<br />Accept-Language: en-US,en;q=0.7,he;q=0.3
<br />User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
<br />Accept-Encoding: gzip, deflate
<br />Host: localhost:5001
<br />DNT: 1
<br />Proxy-Connection: Keep-Alive</p>
<p>Since the browser did not ask for any format that the framework recognizes, it will fallback to the default rule and return JSON data.</p>
<p>Note: q factors are assigned to specific accept headers, this is something content negotiation will support. This blog doesn’t cover these scenarios.</p>
<h3>What do I do about it</h3>
<p>There are a few ways to look at it, the simplest one is to <strong>use the right tool for the right job</strong>. The browser’s job is to render <strong>HTML</strong>, so why use it to test your APIs? There are much better tools for that, like Fiddler, or to test your javascript call to the API rather than hitting it directly from the browser.</p>
<p>For me this is where I stop, the API is behaving as expected and will support any client following the HTTP spec.</p>
<h3>Thanks for your advice, but I still want to just return JSON!</h3>
<p>Many developers just care about returning JSON, while others still want to be able to content negotiate the result for some actions.</p>
<p>Here are some ways to implement the behavior.</p>
<h4>Return a JSON result explicitly</h4>
<ul>
<li>Pros – The code is simple to read, and there is no magic involved. It’s easy to test, and you can mix in other action results. </li>
<li>Cons – The code is explicit, and there is no notion of what is going to get returned for any introspection APIs such as “ApiDescriptionProvider”. ApiDescriptionProvider is the replacement to ApiExplorer.
<br /></li>
</ul>
<div id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:740fd584-3797-4b5d-8dd4-30b2aa9bf3bd" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt">
<div style="background: #fff; max-height: 300px; overflow: auto">
<ol style="background: #ffffff; margin: 0; padding: 0 0 0 5px;">
<li><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">IActionResult</span><span style="background:#ffffff;color:#000000"> GetMeData()</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">{</span></li>
<li>    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">var</span><span style="background:#ffffff;color:#000000"> data = GetDataFromSource();</span></li>
<li style="background: #f3f3f3">&nbsp;</li>
<li>    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">if</span><span style="background:#ffffff;color:#000000"> (data == </span><span style="background:#ffffff;color:#0000ff">null</span><span style="background:#ffffff;color:#000000">)</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000">{</span></li>
<li>        <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">return</span><span style="background:#ffffff;color:#000000"> HttpNotFound();</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000">}</span></li>
<li>&nbsp;</li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">return</span><span style="background:#ffffff;color:#000000"> Json(data);</span></li>
<li><span style="background:#ffffff;color:#000000">}</span></li>
</ol>
</div>
</div>
</div>
<h4>Remove the XML output formatter from the system.</h4>
<ul>
<li>Pros – A single change in options. </li>
<li>Cons – This is a global approach that removes XML from content negotiation. If one of the clients requires XML it is not longer available by default.
<br />
<br />
<div id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:65e5e377-fd8e-4cf2-b56a-312d24729960" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt">
<div style="background: #fff; max-height: 300px; overflow: auto">
<ol style="background: #ffffff; margin: 0; padding: 0 0 0 5px;">
<li><span style="background:#ffffff;color:#000000">services.Configure&lt;</span><span style="background:#ffffff;color:#2b91af">MvcOptions</span><span style="background:#ffffff;color:#000000">&gt;(options =&gt;</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000">options</span></li>
<li>    <span style="background:#ffffff;color:#000000">.OutputFormatters</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000">.RemoveAll(</span></li>
<li>        <span style="background:#ffffff;color:#000000">formatter =&gt; formatter.Instance </span><span style="background:#ffffff;color:#0000ff">is</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">XmlDataContractSerializerOutputFormatter</span><span style="background:#ffffff;color:#000000">)</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">);</span></li>
</ol>
</div>
</div>
</div>
</li>
</ul>
<h4>Use the <strong>[Produces(“application/json”)]</strong> attribute</h4>
<ul>
<li>Pros – Keeps code in the action simple, and can be applied locally or globally in various ways. </li>
<li>Cons – Can’t mix a string with Produces. </li>
</ul>
<p><strong>By applying the attribute to an action</strong></p>
<div id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:978cfebe-032c-4ce4-a55f-1e7dee09d1ef" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt">
<div style="background: #fff; max-height: 300px; overflow: auto">
<ol style="background: #ffffff; margin: 0; padding: 0 0 0 5px;">
<li><span style="background:#ffffff;color:#000000">[</span><span style="background:#ffffff;color:#2b91af">Produces</span><span style="background:#ffffff;color:#000000">(</span><span style="background:#ffffff;color:#a31515">&quot;application/json&quot;</span><span style="background:#ffffff;color:#000000">)]</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">List</span><span style="background:#ffffff;color:#000000">&lt;</span><span style="background:#ffffff;color:#2b91af">Data</span><span style="background:#ffffff;color:#000000">&gt; GetMeData()</span></li>
<li><span style="background:#ffffff;color:#000000">{</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">return</span><span style="background:#ffffff;color:#000000"> GetDataFromSource();</span></li>
<li><span style="background:#ffffff;color:#000000">}</span></li>
</ol>
</div>
</div>
</div>
<p><strong>By adding the filter globally to startup.cs</strong></p>
<div id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:83abfbf0-0e3d-45f1-8c82-8f12e77653f9" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt">
<div style="background: #fff; overflow: auto">
<ol style="background: #ffffff; margin: 0; padding: 0 0 0 5px;">
<li><span style="background:#ffffff;color:#000000">services.Configure&lt;</span><span style="background:#ffffff;color:#2b91af">MvcOptions</span><span style="background:#ffffff;color:#000000">&gt;(options =&gt;</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000">options.Filters.Add(</span><span style="background:#ffffff;color:#0000ff">new</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">ProducesAttribute</span><span style="background:#ffffff;color:#000000">(</span><span style="background:#ffffff;color:#a31515">&quot;application/json&quot;</span><span style="background:#ffffff;color:#000000">))</span></li>
<li><span style="background:#ffffff;color:#000000">);</span></li>
</ol>
</div>
</div>
</div>
<p><strong>By applying it to a base class</strong></p>
<div id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:83f7e85c-c1be-4796-9360-f71166784129" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt">
<div style="background: #fff; max-height: 500px; overflow: auto">
<ol style="background: #ffffff; margin: 0; padding: 0 0 0 5px;">
<li><span style="background:#ffffff;color:#000000">[</span><span style="background:#ffffff;color:#2b91af">Produces</span><span style="background:#ffffff;color:#000000">(</span><span style="background:#ffffff;color:#a31515">&quot;application/json&quot;</span><span style="background:#ffffff;color:#000000">)]</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#0000ff">class</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">JsonController</span><span style="background:#ffffff;color:#000000"> : </span><span style="background:#ffffff;color:#2b91af">Controller</span><span style="background:#ffffff;color:#000000"> { }</span></li>
<li><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#0000ff">class</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">HomeController</span><span style="background:#ffffff;color:#000000"> : </span><span style="background:#ffffff;color:#2b91af">JsonController</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">{</span></li>
<li>    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">List</span><span style="background:#ffffff;color:#000000">&lt;</span><span style="background:#ffffff;color:#2b91af">Data</span><span style="background:#ffffff;color:#000000">&gt; GetMeData()</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000">{</span></li>
<li>        <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">return</span><span style="background:#ffffff;color:#000000"> GetDataFromSource();</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000">}</span></li>
<li><span style="background:#ffffff;color:#000000">}</span></li>
</ol>
</div>
</div>
</div>
<h3>What else changed in content negotiation in MVC 6</h3>
<p>1. If the controller returns a string (regardless of declared return type), expect to get a text/plain content type.</p>
<div id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:9fbf9661-3407-4792-8223-242e5c6ac94e" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt">
<div style="background: #fff; max-height: 300px; overflow: auto">
<ol style="background: #ffffff; margin: 0; padding: 0 0 0 5px;">
<li><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#0000ff">object</span><span style="background:#ffffff;color:#000000"> GetData()</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">{</span></li>
<li>    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">return</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#a31515">&quot;The Data&quot;</span><span style="background:#ffffff;color:#000000">;</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">}</span></li>
<li>&nbsp;</li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#0000ff">string</span><span style="background:#ffffff;color:#000000"> GetString()</span></li>
<li><span style="background:#ffffff;color:#000000">{</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">return</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#a31515">&quot;The Data&quot;</span><span style="background:#ffffff;color:#000000">;</span></li>
<li><span style="background:#ffffff;color:#000000">}</span></li>
</ol>
</div>
</div>
</div>
<br />
<p>2. If the controller return null or the return type is void, expect a status code of 204 (NoContent) rather than 200. And the body will be empty.</p>
<div id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:8fcfd6c4-4f85-4a47-91e7-7505949cfc1d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt">
<div style="background: #fff; max-height: 500px; overflow: auto">
<ol style="background: #ffffff; margin: 0; padding: 0 0 0 5px;">
<li><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">Task</span><span style="background:#ffffff;color:#000000"> DoSomethingAsync()</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">{</span></li>
<li>    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#008000">// Do something.</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">}</span></li>
<li>&nbsp;</li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#0000ff">void</span><span style="background:#ffffff;color:#000000"> DoSomething()</span></li>
<li><span style="background:#ffffff;color:#000000">{</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#008000">// Do something.</span></li>
<li><span style="background:#ffffff;color:#000000">}</span></li>
<li style="background: #f3f3f3">&nbsp;</li>
<li><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#0000ff">string</span><span style="background:#ffffff;color:#000000"> GetString()</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">{</span></li>
<li>    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">return</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#0000ff">null</span><span style="background:#ffffff;color:#000000">;</span></li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000">}</span></li>
<li>&nbsp;</li>
<li style="background: #f3f3f3"><span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">public</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#2b91af">List</span><span style="background:#ffffff;color:#000000">&lt;</span><span style="background:#ffffff;color:#2b91af">Data</span><span style="background:#ffffff;color:#000000">&gt; GetData()</span></li>
<li><span style="background:#ffffff;color:#000000">{</span></li>
<li style="background: #f3f3f3">    <span style="background:#ffffff;color:#000000"></span><span style="background:#ffffff;color:#0000ff">return</span><span style="background:#ffffff;color:#000000"> </span><span style="background:#ffffff;color:#0000ff">null</span><span style="background:#ffffff;color:#000000">;</span></li>
<li><span style="background:#ffffff;color:#000000">}</span></li>
</ol>
</div>
</div>
</div>
<p>Both the behaviors above are controlled by the <a href="https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs">HttpNoContentOutputFormatter</a> and the <a href="https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Core/Formatters/TextPlainFormatter.cs">TextPlainFormatter</a>. Both these behaviors can be overridden by removing the formatters from the <strong>Options.OutputFormatter</strong> collection, similarly to how the XML formatter was removed in the example above.</p>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment