Toggle Layout File Based On Ajax Request in ASP.NET MVC

Progressive enhancement is the addition of javascript in order to enhance the site with more modern functionality that it would not contain without it, but isn't necessary for the site to function. The problem with this is it often becomes necessary to make modifications to your site that are specific to helping along your added enhancement scripts and these modifications can often balloon into a full-scale reworking of your site and all its action methods. Seems silly for what should have been "simple" enhancement.

One such example is hijaxing links and form POSTs. Hijaxing is using javascript to override a link or a form and prevent it from executing its normal action which usually loads a new page. Then, your script reads the destination that form/link was going to and it makes a GET request to that URL via AJAX. It then drops the results on the page asynchronously. This gives a nice smooth feeling to the users; you get to use fancy loading graphics and the page never has to refresh.

The issue comes in when you use some sort of template extraction like master pages or MVC layout pages. Naturally, when you make an AJAX request, you don't want a full page of HTML sent back, you only want a partial page. So how do you do this? First you have to write some server logic that can discern between normal requests and AJAX requests. In ASP.NET MVC 3 this is easy. All you have to do is call Request.IsAjaxRequest() and that will tell you if it is an AJAX request. But now that you know it's AJAX how do we send back different files based on this?

That's the tricky part. It would be rather bothersome to have to do...

if (Request.IsAjaxRequest())
{
	return PartialView();
}
else
{
	return View();
}

...in EVERY single action method! In addition to that pain, you also have to split all your views up in an awkward way. A parent view that uses the layout page and makes a call to @{Html.RenderPartial();} and a partial containing the actual HTML for the page. That way return PartialView(); in the action method only returns the partial, whereas return View(); grabs the parent page with layout and all.

In my opinion this is a little perverse because you have to do a lot of extra work just for this one simple AJAX request. Also, I'm not fond of the idea of having parent pages that only contain a call to RenderPartial(). I decided to take some time and do some research on the situation. There had to be a better way. There was.

_ViewStart.cshtml:

@{
	Layout = Request.IsAjaxRequest() ? null : "~/Views/Shared/_Layout.cshtml";
}

I don't know why I hadn't thought of it before. It's so simple. The _ViewStart.cshtml page is where all the views look for their layout page if they don't have one specified. It is convenient because you don't have to specify your layout page in every page declaration like you used to with the web forms view engine. This is a very convenient place to toggle the layout page based on the request.

One person did argue that this was abuse of separation of concerns and that the _ViewStart.cshtml was the last place they would want to find any sort of logic. However I respectfully disagree. This little beauty is a single line of code and it's a simple IF. I don't think it's causing much harm and it is very DRY. The amount of work it saves is tremendous and after weighing the pros and cons I have decided it is currently the best way to implement this functionality with the least amount of extra work. Now when I'm hijaxing links/forms I don't have to worry about a thing on the server when I'm making the AJAX requests.

Another person suggested you do this somewhere in a BaseController that all your other controllers inherit from. I'm sure this is possible, but as far as I know there isn't an easy way to access ViewBag within _ViewStart.cshml (I tried). I did figure out how to access ViewData within _ViewStart.cshtml but it's a little hackish (this.ViewContext.ViewData <- strangely, ViewContext does not contain a property for ViewBag :/). You also have to go through the work of setting up a BaseController if you don't already have one. I suppose if you already have one then figuring out a solution that utilizes that might ease some of the concern about separation, but I really don't think modifying the _ViewStart.cshtml to check Request.IsAjaxRequest() is that big of a deal :)

NOTE: If you're running into an issue with FireFox not returning true for Request.IsAjaxRequest() then see this post.