Asp.net MVC Content Negotiation
asp-net-mvc-content-negotiation
asp-net-mvc-content-negotiation.markdown
2013-11-08T00:00:00
<p>It's long bothered me that I had a separate endpoint for Atom. After adding the <a href="/archive">archive</a> endpoint, the absurdity really showed since is the same data, just in a different format.</p>
<p>Content negotiation to the rescue.</p>
<p>Web API has a decent <a href="https://docs.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/content-negotiation">conneg system</a> built in. Fortunately, asp.net has enough extension points that we can craft a workable solution.</p>
<p>In order to get the <a href="/oops">404 page</a> working, I used a <a href="https://github.com/kijanawoodard/Blog/blob/728c10ec6608cac03644454a7a38b7376bd10d71/src/Blog.Web/Infrastructure/PartialViewActionInvoker.cs">custom action invoker</a>. I figured that could be used as the basis for content negotiation. I leaned on several sources to pull together the <a href="https://github.com/kijanawoodard/Blog/blob/0c6c3fb975deaec89035c79e9213698c7a5be5a3/src/Blog.Web/Infrastructure/ContentNegotiatingActionInvoker.cs#L14">implementation</a>.</p>
<p>The code needs a bit of work, but I'm happy with the effects. You can make a request to an endpoint with an appropriate accept header or by adding an extension to the url. </p>
<p>The result is that the <a href="/archive.atom">atom feed</a> uses the same endpoint as the archive you can see this post in <a href="/asp-net-mvc-content-negotiation.json">json</a>, <a href="/asp-net-mvc-content-negotiation.xml">xml</a>, <a href="/asp-net-mvc-content-negotiation.html">html</a>, and <a href="/asp-net-mvc-content-negotiation.phtml">partial html</a>. </p>
<p>Hat tip to <a href="https://blog.joeyguerra.com/">Joey Guerra</a> for the extensions and phtml. He's pushed these ideas for years. I haven't always been receptive to them, but I felt there was enough value to implement them here.</p>
<p>One thing I learned doing this implementation is that the content negotiation should effect what action gets called rather than being merely reactive to the action result. </p>
<p>For instance, I have code to do <a href="https://github.com/kijanawoodard/Blog/blob/0c6c3fb975deaec89035c79e9213698c7a5be5a3/src/Blog.Web/Infrastructure/ContentNegotiatingActionInvoker.cs#L182">csv negotiation</a> but it isn't being used because the <a href="https://github.com/kijanawoodard/Blog/blob/0c6c3fb975deaec89035c79e9213698c7a5be5a3/src/Blog.Web/Actions/PostGet/PostGetController.cs#L59">current structure for posts</a> isn't "flat" enough for csv. I considered some tricks using the mediator or cooking up some reflection magic to automatically flatten classes, but that seemed time consuming. Besides, what I really wanted was to get a clear opportunity in my controller to shape the output for a given mime type.</p>
<p>I am halfway there as I enable atom and xml to use a <a href="https://github.com/kijanawoodard/Blog/blob/0c6c3fb975deaec89035c79e9213698c7a5be5a3/src/Blog.Web/Actions/PostGet/Index.Atom.cshtml">razor view</a> to shape the output. For csv though I'd rather have something like:</p>
<pre><code>public class MyController : Controller
{
...
public object Csv(PostRequest request)
{
var model = _mediator.Send<PostRequest, PostGetViewModel>(request);
return new SomeCsvShape
{
Title = model.Post.Title,
...
};
}
}
</code></pre>
<p>Similarly rather than have a bunch of interfaces to support something like <a href="https://stateless.co/hal_specification.html">HAL</a>, an action could decorate the regular model with links, etc. </p>
<p>In the end, I'd like content negotiation to have</p>
<ul>
<li>some default behavior that may or may not work for your type</li>
<li>an outlet to shape the return result within a controller action</li>
<li>an outlet to shape the result with a custom view</li>
</ul>
<p>Interestingly, I had nearly run out of reasons to have a controller class, other than because c# code has to be in a class. Content negotiation gives new perspective to controller cohesion.</p>
<p>A side note on <a href="/a-tale-of-scope-creep">scope creep</a>. I spent a fair amount of time trying to work out pdf content negotiation. After hunting around, I found <a href="https://github.com/webgio/Rotativa">Rotativa</a>, which looked promising, but I ran into <a href="https://github.com/webgio/Rotativa/issues/44">a bug</a>. It could be my issue, by while I was thinking about how to code my way out of this problem, it finally dawned on me: ctrl-p in chrome, plus <a href="https://github.com/kijanawoodard/Blog/blob/13d109fbd53f7acc949553bded904306447cc144/src/Blog.Web/Content/css/site.css#L90">a little css</a>, pdf support done. :-]</p>
khalidabuhakmeh
khalidabuhakmeh@gmail.com
2013-11-08T17:05:09
<p>Restful Routing has the idea of a FormatResult. You can send in data, and based on the requests extension it will figure out what view you want to render it in. It works decently well, just gets goofy when dealing with ajax and routings "keep stuff around" default.</p><p><a href="https://restfulrouting.com/mappings/extras" rel="nofollow">https://restfulrouting.com/mapp...</a></p><p>OH YEAH I WENT THERE!!!! Booyah!</p>
Joey Guerra
graphite@joeyguerra.com
2013-11-11T20:14:07
<p>IIIIIIEEEEEEEEE!!!!!! IE Accepts header doesn't include text/html.</p><p><a href="https://www.gethifi.com/blog/browser-rest-http-accept-headers" rel="nofollow">https://www.gethifi.com/blog/br...</a></p>
Kijana Woodard
disqus@wyldeye.com
2013-11-12T03:44:21
<p>Bleh. I checked my blog and IE 10 seems ok. I don't think I care about IE 8. I'll wait for the complaints to come in.</p><p>In terms of webkit, I'm handling that by making the preference based on the order of my conneg components. It looks like I should prefer the one marked with q, but I'll leave that for another day. A day, far far far in the future.</p>
Goodbye Disqus
goodbye-disqus
goodbye-disqus.markdown
2016-07-27T00:00:00
<p>I've decided to remove disqus comments from my blog.</p>
<p>I had several issues with disqus:</p>
<ul>
<li>Long page loads [async aside]</li>
<li>Threaded comments </li>
<li>Time shifting - oldest may not be on top, so a "first!" comment might be in the middle</li>
<li>Links to other sites</li>
<li>Injected marketing cookies</li>
</ul>
<p>What really forced the decision was the nature of comments themselves. Are comments a valuable part of the blog? If so, I should own them. If not, I should turn them off.</p>
<p>On taking ownership, disqus has an export feature. It "works", but it's not ideal. I'll talk about how I dealt with that in a future post. Hint: F#.</p>
<p>At the moment, I've migrated comments to yaml and they are stored in the github repo with posts. I'm looking at options for a friendlier comment system, but in the meantime, send me a pull request. ;-)</p>
A Tale of Scope Creep
a-tale-of-scope-creep
a-tale-of-scope-creep.markdown
2013-11-05T00:00:00
<p><a href="https://www.throw-up.com/building-openresty">Zach Burke</a> mentioned to me that he wanted to add a 404 page to his new blog. Sounds like a good idea, lets do it.</p>
<p>I assumed I was going to <a href="https://stackoverflow.com/questions/3554844/asp-net-mvc-404-handling-and-iis7-httperrors">configure httpErrors</a>, but I figured I'd google a bit anyway. Turns out, there is quite a <a href="https://stackoverflow.com/a/9026907/214073">debate</a> about 404 pages with asp.net mvc. I decided I didn't really care about the nuances and I wanted to get the feature done. Good enough. <a href="https://github.com/kijanawoodard/Blog/commit/48ed632e7045522e1404f1739dcc2cd982a63697">Commit</a>.</p>
<p>Next I decided that the 404 page should display the post archive so that the user can choose a post that exists. Hmmm. Ok, a little <em>scope creep</em>: how about we have <a href="/archive">an independent archive page</a>. Fine. Using the <a href="/introducing-liaison">mediator</a>, it was straight forward to implement. <a href="https://github.com/kijanawoodard/Blog/commit/ce65020fdb81e06dab3a70365c7588407e695f1e">Commmit</a>.</p>
<p>So far so good. But the archive is rendered with the full layout on the 404 page. We don't want duplicate headers and sidebars. The easy answer is to write some code like this in the controller.</p>
<pre><code>if (ControllerContext.IsChildAction)
return PartialView(model);
else
return View(model);
</code></pre>
<p>Yuck. I don't like that. How can we remove this logic from our controller? I need something like <a href="https://lostechies.com/chadmyers/2011/06/23/cool-stuff-in-fubumvc-no-1-behaviors/">FubuMVC Behaviors</a>, but we don't have those in asp.net mvc.</p>
<p>After a quite a bit of <a href="https://github.com/kijanawoodard/Blog/blob/728c10ec6608cac03644454a7a38b7376bd10d71/src/Blog.Web/Infrastructure/PartialViewActionInvoker.cs#L8">stumbling around</a>, using an <code>IActionInvoker</code> derived from the built-in <code>ControllerActionInvoker</code> seemed to fit the bill pretty well. I'm not happy with the implementation of the class at all. It is a hack and it shows, but we're here to ship features, not build ivory towers. <a href="https://github.com/kijanawoodard/Blog/commit/728c10ec6608cac03644454a7a38b7376bd10d71">Commit</a>.</p>
<p>I used <a href="/introducing-vessel">Vessel</a> to <a href="https://github.com/kijanawoodard/Blog/commit/97dee8e93dd305436e7687892bebbcdfeba0b9de">wire</a> the <code>IActionInvoker</code> to the controller. In some sense, using Property Injection this way violates our sensibilities. My view is practical: I don't really want to do it this way, but this is what asp.net mvc gives me. I <em>don't</em> want the controller to have to muck about with setting their own ActionInvoker and I <em>really</em> don't want a controller base class. Yet, unless I'm ready to switch to <a href="https://fubumvc.github.io/">FubuMVC</a> or <a href="https://openrasta.org/">OpenRasta</a>, I'm not going to get a nice pipeline to work with. Using Property Injection seems like a reasonable compromise. </p>
<p>I've also decided to continue to leave the "pain" of manual controller setup in place. Connecting the action invoker was dead simple since there were no <a href="https://docs.structuremap.net/ConstructorAndSetterInjection.htm">container incantations</a> to consider. I also find that it makes me <em>think</em> about things like request pipelines, behavior chains, and the true responsibilities of a controller. </p>
<p><a href="/oops">404 page</a> done.</p>
<p>Side notes.</p>
<p>It turns out that setting the layout in the view overrides the partial view behavior, so I had to <a href="https://github.com/kijanawoodard/Blog/commit/728c10ec6608cac03644454a7a38b7376bd10d71#diff-8bc27b9c14ab2cf27debd6ecd280be8eL5">remove the layout declaration</a>. That led to adding a ViewStart page. <em>Scope creep</em>. </p>
<p>Conceptually, I like the views hierarchy to be composed by "something outside of themselves". If the layout is hard coded in the view, it's hard to reuse in another layout. I <em>think</em> I would like that to be more specific than a ViewStart file, but I don't have bearings on an alternate solution.</p>
<p>It also turns out that the <code>Application_EndRequest</code> wasn't getting called when running on Azure Websites, aka <em>production</em>. Found an <a href="https://stackoverflow.com/a/18938991/214073">SO post</a> that solved the problem. <a href="https://github.com/kijanawoodard/Blog/commit/c5bcffeef6bc3e10c8fcf635ca5a8bff26d69357">Commit</a>. This scenario highlights the value of pushing to production often. The bug simply doesn't happen in dev/test. It <em>only</em> happens in production. Because the prod deploy was so small, it was easy to grok the issue and fix it.</p>
<p>The more I use <a href="https://git-scm.com/">git</a>, the more I like tiny commits that address a single issue. I don't bother to create tickets for personal projects, but in my head, I try and reason about the simplest way to solve a problem and then only commit code for that problem. Other issues I see along the way, I will either code them, but <a href="https://github.com/kijanawoodard/Blog/commit/8f3ae65d841bab2bc6287f41923a269a458adf94">commit</a> them individually, or make a note and come back to them later. Getting to <em>done</em> is vital.</p>