The other day I came across
this
post in Marteen Balliauw's blog where he demonstrates and
interesting way to make the same ASP.NET MVC view render correctly
from both a regular request and an Ajax request.
In Marteen's post he uses a custom ActionFilterAttribute to
detect the Ajax call and replace the standard master page with an
unadorned Empty master page.
I liked the idea that you can add that behavior to any action simply by
bolting that attribute to it.
What I'm going to illustrate here is a variation of that idea that can be used
when you have your own base class to all your controllers, which is something
I always do.
What I'm going to do is override the Controller.View()
method to detect the Ajax calls and replace the master page right there.
public abstract class ApplicationController: Controller
{
protected override ViewResult View(
string viewName, string masterName, object model)
{
if(IsAjaxRequest)
return base.View(viewName, "Empty", model);
return base.View(viewName, masterName, model);
}
protected virtual bool IsAjaxRequest
{
//Both Prototype.js and jQuery send
// the X-Requested-With header in Ajax calls
get
{
var request = ControllerContext.HttpContext.Request;
return (request.Headers["X-Requested-With"] == "XMLHttpRequest");
}
}
}
With that in place I don't need to change anything in my action code to
start supporting the Ajax calls (as long as the controller inherits from
the above base class)
public class SampleController : ApplicationController
{
public ActionResult Index()
{
return View();
}
public ActionResult Details(int id)
{
var user = new UserInfo {Name = "user" + id, Age = 30};
return View(user);
}
}
My Index action calls the Details action
using both a regular request and an Ajax call, for the sake of the example.
<%@ Page Title="" Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
AutoEventWireup="true"
CodeBehind="Index.aspx.cs"
Inherits="MvcBeta1.Views.Sample.Index" %>
<asp:Content ID="Content1"
ContentPlaceHolderID="MainContent" runat="server">
<h2>Sample View Index</h2>
<%= Html.ActionLink("Go to Details", "Details", new { id = 123 })%>
<hr />
<input type="button" id="loadDetails" value="Load with Ajax" />
<div id="detailsDiv" style="background-color:#aaa;">
[details should load here]
</div>
<script>
$(function() {
$('#loadDetails').click(function() {
$('#detailsDiv').
load('<%= Url.Action("Details", new {id = 123}) %>');
});
});
</script>
</asp:Content>
The Details view is trivial. It's just a table.
<%@ Page Title="" Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
AutoEventWireup="true"
CodeBehind="Details.aspx.cs"
Inherits="MvcBeta1.Views.Sample.Details" %>
<asp:Content ID="Content1"
ContentPlaceHolderID="MainContent" runat="server">
<h2>Details for <%= ViewData.Model.Name %></h2>
<table border="1">
<tr><th>Col 1</th><th>Col 2</th><th>Col 3</th></tr>
<% for(int i=1; i<= 5; i++) { %>
<tr>
<td>cell (<%= i %>, 1)</td>
<td>cell (<%= i %>, 2)</td>
<td>cell (<%= i %>, 3)</td>
</tr>
<%} %>
</table>
</asp:Content>
When at the Index page we can click on the "Go to Details" link and see the
full rendering of the Details view.
If we had instead clicked the "Load with Ajax" button we would
cause a simpler version of that page to be inserted in the
detailsDiv element (note that the tabs and the blue
background that surrounds the table did not come in the
rendered content.)
Of course, ASPX files are not the indicated place for placing content that can be used in
partial updates, ASCX files would be a better choice for that. That said, sometimes it
can be really convenient to have this ability.
Posted
12-05-2008 8:25 PM
by
sergiopereira