AJAX support in the ASP.NET MVC Framework

[Edit: Note that this post is based on a pre-release, and now out of date CTP of the MVC framework. I'd suggest you use it to understand concepts, but look elsewhere now if you're after up to date facts. I've recently added a post about Virtual Earth and the MVC framework that includes the use of some AJAX - read it here]

I’ve been curious about how AJAX support might be added to the ASP.NET MVC Framework. Specifically I could see huge potential in defining portions of pages (views) as User Controls inheriting from System.Web.Mvc.ViewUserControl<T>. This allows the region of the page to be rendered either inline in another view, or on its own, and therefore means that an initial page can be rendered, yet the User Control section can be updated using AJAX calls. What enables this to work is the fact that the Controller.RenderView method in the framework can handle both ViewPage and ViewUserControl derived UI elements.

So I decided to write some code to enable me to play with this. Bear in mind that this is purely flaky experimental code – I’m sure the product group will do a far better job than this sample, but the point of this post is to demonstrate the kind of things you can achieve with the MVC framework, not to produce a product! If you are interested in AJAX support in the framework, I recommend heading over to Nikhil’s blog for a quick read.

This post is actually incidental to the real reason I was writing some code, to demonstrate a different technique – which will be revealed in a future blog post in the not-too-distant future.

 As usual the code included conveys no warranties etc, and has been written against the December 2007 CTP – and therefore will likely be superseded or simply won’t work on later releases J.

Anyway, to meet my needs I’ve done a number of things;

1. Created a simple Employee table, and used LINQ-to-SQL to get at the data easily.

2. Written a Controller with two actions – one to render a full initial page, and one to render just the portion of the page contained within the user control.

3. Created two views – one full page (that includes the user control), and one user control.

4. Created an extension method for the AjaxHelper type that renders a link to update a user control’s content.

 

Initial Setup

The code for the controller is very simple. I’ve created a simple Model entity to hold a list of Employee entities, plus a selected one;

public class EmployeeSet

{

    public List<Employee> Employees { get; set; }

    public Employee SelectedEmployee { get; set; }

}

There is then an action that renders a whole view, passing in the EmployeeSet entity;

[ControllerAction]

public void ViewPeople()

{

    DbDataContext db = new DbDataContext();

    List<Employee> all = (from e in db.Employees

                          select e).ToList<Employee>();

           

    EmployeeSet set = new EmployeeSet();

    set.Employees = all;

    // by default, show the first employee in the user control

    set.SelectedEmployee = all[0];

    RenderView("People", set);

}

 

The key to this working is of course the “People” view, for which the first version looks very simple indeed;

<asp:Content ID="Content1"

     ContentPlaceHolderID="MainContentPlaceHolder"

     runat="server">

    <table>

       <tr>

        <th>Name</th>

       </tr>

       <% foreach (Employee emp in ViewData.Employees)

          {

        %>

        <tr>

            <td>

                <%= emp.Name %>

            </td>

        </tr>

        <%

          }

        %>

    </table>

    <div id="Individual">

        <prs:PersonInfo ID="PersonInfo1"

                        runat="server"

                        ViewDataKey="SelectedEmployee" />

    </div>

</asp:Content>

 

Of course, this ASPX view inherits from ViewPage<EmployeeSet> to enable me to use strongly typed access to the model data. Perhaps the next most significant point is the use of the User Control – note that I have omitted the Register directive in this snippet, but it does need to be there. I specify the name of the property on the View Data that I wish to pass to the User Control in the ViewDataKey property. This ensures that the currently selected Employee is passed to the control.

To allow this to work, I have an ASCX User Control view named PersonInfo that derives from ViewUserControl<Employee>. The content of this view is incredibly simple in my example (UI design is not a focus of this post!);

<%@ Control Language="C#" AutoEventWireup="true"

            CodeBehind="PersonInfo.ascx.cs"

            Inherits="MvcAjax.Views.AjaxSample.PersonInfo" %>

Name: <%= ViewData.Name %><br />

Job Title: <%= ViewData.JobTitle %>

Browsing to /AjaxSample/ViewPeople will now render a list of all Employees, plus the details of a specific employee (the first one in the list) below the main list.

 

Adding AJAX

So my next task is to add AJAX that will allow the “Individual” DIV element content that contains the User Control to be replaced with the result of a call to a Controller Action of my choice. I love the lambda expression approach used by much of the MVC framework, so my ideal solution is to replace the “emp.Name” in the list of employees with code similar to that below that outputs a hyperlink to perform my update;

<%= Ajax.UpdateRegionLink<AjaxSampleController>(d => d.UpdatePerson(emp.Id),

                                          "Individual", emp.Name) %>

 

Here I am specifying the Controller I want to use, the Action I wish to call (and its parameters), the name of the DIV element I wish to update the content of, and the link text (the Employee’s name).

Implementing this boils down to an extension method, which adds functionality to the AjaxHelper class. I came up with the following;

public static string UpdateRegionLink<T>(this AjaxHelper helper,

       Expression<Action<T>> action,

       string elementId,

       string linkText) where T : Controller

{

    string linkFormat =

    "<a href=\"javascript:updateRegion('{0}', '{1}')\">{2}</a>";

    string link = helper.BuildUrlFromExpression<T>(action);

    return string.Format(linkFormat, elementId, link, linkText);

}

I then directly copied the BuildUrlFromExpression method from the MVC Toolkit implementation of the HtmlHelper class; in a real framework I assume this functionality would be shared, but this is a hack, not a real deliverable J

Basically all this function does is output HTML that looks like this;

<a href="javascript:updateRegion(

           'Individual',

           '/AjaxSample/UpdatePerson/3')">{Name}</a>

 

I love the automatic generation of the URL!

The code that is found at this URL is a very simple Action;

[ControllerAction]

public void UpdatePerson(int id)

{

    DbDataContext db = new DbDataContext ();

    Employee emp = (from e in db.Employees

                    where e.Id == id

                    select e).SingleOrDefault<Employee>();

    RenderView("PersonInfo", emp);

}

It simply loads the Employee that matches the specified Identifier, and renders the “PersonInfo” User Control only – and this is the power of splitting up the UI into these parts.

 

Doh!

So where do I put the javascript definition of the function “updateRegion” that is called by the link generated by my extension method? I need to register it as a client script block... so my first attempt was to pass Page.ClientScript into my extension method, and register the prerequisite javascript there. If that worked, I was going to think about how to get hold of it through the ViewContext... if it is possible.

But it didn’t work. I’ve not gotten to the bottom of why yet, so I may well dedicate more time investigating in the future, but basically it doesn’t look like any client scripts are emitted when using the MVC framework. I guess this is something different about the page lifecycle execution, or perhaps an override in ViewPage.

Either way, my interim solution is to put the script in a “.js” file under the “Content” folder, and import it in the master page;

<script type="text/javascript"

        src="/Content/MvcAjaxExtensions.js">

</script>

 

The Script

The script is quick and dirty, designed to get the job done and that’s about all, as quickly and simply as I could think of! As with all the code in this example, it is nowhere near production strength (</excuse-making>).

var regionId;

var req;

function updateRegionReturn()

{

    if(req.readyState == 4)

        document.getElementById(regionId).innerHTML = req.responseText;

}

function updateRegion(elementId, url)

{

    regionId = elementId;

    req = new XMLHttpRequest();

    req.onreadystatechange = updateRegionReturn;

    req.open('GET', url);

    req.send();

}

 

It simply uses the XMLHttpRequest object to make a request to the supplied URL – which of course was built by my extension method and passed into updateRegion() by the link we generate on the page.

 

It Works!

So it is a little hack, but the important thing is that the Proof of Concept works – try clicking a link on the People page, and hey presto, a web request is made, the Action is invoked, the User Control is rendered, and the mark-up is updated. I think this shows that AJAX support could feel really natural in the MVC framework – and there’s no reason why many of the standard AJAX functionality couldn’t be rolled in.

This is a very simple example, but I hope it has illustrated how the framework could evolve itself, or be built on to suit your own project.

 

MvcAjax.zip