{"Post":{"Title":" Asp.net MVC Content Negotiation","Slug":"asp-net-mvc-content-negotiation","FileName":"asp-net-mvc-content-negotiation.markdown","PublishedAtCst":"2013-11-08T00:00:00","Content":"
It's long bothered me that I had a separate endpoint for Atom. After adding the archive endpoint, the absurdity really showed since is the same data, just in a different format.
\n\nContent negotiation to the rescue.
\n\nWeb API has a decent conneg system built in. Fortunately, asp.net has enough extension points that we can craft a workable solution.
\n\nIn order to get the 404 page working, I used a custom action invoker. I figured that could be used as the basis for content negotiation. I leaned on several sources to pull together the implementation.
\n\nThe 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.
\n\nThe result is that the atom feed uses the same endpoint as the archive you can see this post in json, xml, html, and partial html.
\n\nHat tip to Joey Guerra 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.
\n\nOne 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.
\n\nFor instance, I have code to do csv negotiation but it isn't being used because the current structure for posts 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.
\n\nI am halfway there as I enable atom and xml to use a razor view to shape the output. For csv though I'd rather have something like:
\n\npublic class MyController : Controller\n{\n ...\n public object Csv(PostRequest request)\n {\n var model = _mediator.Send<PostRequest, PostGetViewModel>(request);\n return new SomeCsvShape\n {\n Title = model.Post.Title,\n ...\n };\n }\n} \n
\n\nSimilarly rather than have a bunch of interfaces to support something like HAL, an action could decorate the regular model with links, etc.
\n\nIn the end, I'd like content negotiation to have
\n\nInterestingly, 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.
\n\nA side note on scope creep. I spent a fair amount of time trying to work out pdf content negotiation. After hunting around, I found Rotativa, which looked promising, but I ran into a bug. 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 little css, pdf support done. :-]
\n","Comments":[{"Name":"khalidabuhakmeh","Email":"khalidabuhakmeh@gmail.com","When":"2013-11-08T17:05:09","Message":"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.
https://restfulrouting.com/mapp...
OH YEAH I WENT THERE!!!! Booyah!
"},{"Name":"Joey Guerra","Email":"graphite@joeyguerra.com","When":"2013-11-11T20:14:07","Message":"IIIIIIEEEEEEEEE!!!!!! IE Accepts header doesn't include text/html.
https://www.gethifi.com/blog/br...
"},{"Name":"Kijana Woodard","Email":"disqus@wyldeye.com","When":"2013-11-12T03:44:21","Message":"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.
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.
"}],"Summary":"It's long bothered me that I had a separate endpoint for Atom. After adding the archive endpoint, the absurdity really showed since is the same data, just in a different format.
\n\nContent negotiation to the rescue.
\n\nWeb API has a decent conneg system built in. Fortunately, asp.net has enough extension points that we can craft a workable solution.","Description":"It's long bothered me that I had a separate endpoint for Atom. After adding the archive endpoint, the absurdity really showed since is the same data, just in a different format."},"Previous":{"Title":" Goodbye Disqus","Slug":"goodbye-disqus","FileName":"goodbye-disqus.markdown","PublishedAtCst":"2016-07-27T00:00:00","Content":"
I've decided to remove disqus comments from my blog.
\n\nI had several issues with disqus:
\n\nWhat 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.
\n\nOn 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#.
\n\nAt 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. ;-)
\n","Comments":[],"Summary":"I've decided to remove disqus comments from my blog.
\n\nI had several issues with disqus:
\n\nWhat 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.","Description":"I've decided to remove disqus comments from my blog."},"Next":{"Title":" A Tale of Scope Creep","Slug":"a-tale-of-scope-creep","FileName":"a-tale-of-scope-creep.markdown","PublishedAtCst":"2013-11-05T00:00:00","Content":"
Zach Burke mentioned to me that he wanted to add a 404 page to his new blog. Sounds like a good idea, lets do it.
\n\nI assumed I was going to configure httpErrors, but I figured I'd google a bit anyway. Turns out, there is quite a debate 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. Commit.
\n\nNext 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 scope creep: how about we have an independent archive page. Fine. Using the mediator, it was straight forward to implement. Commmit.
\n\nSo 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.
\n\nif (ControllerContext.IsChildAction)\n return PartialView(model);\nelse\n return View(model);\n
\n\nYuck. I don't like that. How can we remove this logic from our controller? I need something like FubuMVC Behaviors, but we don't have those in asp.net mvc.
\n\nAfter a quite a bit of stumbling around, using an IActionInvoker
derived from the built-in ControllerActionInvoker
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. Commit.
I used Vessel to wire the IActionInvoker
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 don't want the controller to have to muck about with setting their own ActionInvoker and I really don't want a controller base class. Yet, unless I'm ready to switch to FubuMVC or OpenRasta, I'm not going to get a nice pipeline to work with. Using Property Injection seems like a reasonable compromise.
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 container incantations to consider. I also find that it makes me think about things like request pipelines, behavior chains, and the true responsibilities of a controller.
\n\n404 page done.
\n\nSide notes.
\n\nIt turns out that setting the layout in the view overrides the partial view behavior, so I had to remove the layout declaration. That led to adding a ViewStart page. Scope creep.
\n\nConceptually, 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 think I would like that to be more specific than a ViewStart file, but I don't have bearings on an alternate solution.
\n\nIt also turns out that the Application_EndRequest
wasn't getting called when running on Azure Websites, aka production. Found an SO post that solved the problem. Commit. This scenario highlights the value of pushing to production often. The bug simply doesn't happen in dev/test. It only happens in production. Because the prod deploy was so small, it was easy to grok the issue and fix it.
The more I use git, 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 commit them individually, or make a note and come back to them later. Getting to done is vital.
\n","Comments":[],"Summary":"Zach Burke mentioned to me that he wanted to add a 404 page to his new blog. Sounds like a good idea, lets do it.
\n\nI assumed I was going to configure httpErrors, but I figured I'd google a bit anyway. Turns out, there is quite a debate 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. Commit.
\n\nNext 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 scope creep: how about we have an independent archive page. Fine. Using the mediator, it was straight forward to implement. Commmit.","Description":"Zach Burke mentioned to me that he wanted to add a 404 page to his new blog. Sounds like a good idea, lets do it."},"HasPrevious":true,"HasNext":true}