Ajax, ASP.Net MVC and Progressive Enhancement

Andy Butland | October 27th, 2010

 

Visitors to the web sites and applications we build are increasingly demanding an intuitive and responsive user interface.  As developers it’s our responsibility to provide this for them.  However we also need to bear in mind other users of our sites who may be unable to make use of the enhanced functionality – restricted browser clients, simpler devices and, not least, search engine spiders.

This balance has been part of the requirements of building web interfaces for many years and traditionally a principle of graceful degradation was employed.  The aim would be to produce the best possible interface for users that could make use of it, whilst still ensuring that the site would be at least workable and presentable for those that could not.  In some cases we might go so far as to create an alternate version that is separately built and maintained.

More recently the technique of progressive enhancement has been gaining currency and this turns the graceful degradation approach on its head.  Here the focus on creating a functional base user interface that is enhancedby the addition of CSS and JavaScriptto produce an improved experience for those that can make use of it.

Now this might sound like simply the same technique viewed from a different perspective and it is true that either method can lead to the same result.  In part it is just the change of approach which leads to a different design mindset and to a result that better supports the full user base.  In addition though there are specific steps that are taken differently in building a progressively enhanced user interface, which I’ll briefly explore in the next section.

Building a Progressively Enhanced User Interface

The basis of any web interface is of course the HMTL which should be constructed in order to produce clean, valid, standards-compliant and semantic mark-up.  It should follow the principle of separation of content from presentation and behaviour and as such contain only the necessary tags to mark-up the content and identify the necessary elements for styling.

Presentation is provided by CSS and behaviour by JavaScript; the code for which should be placed in separate files that are linked from the HTML page.  This external referencing of assets ensures that more basic clients aren’t unnecessarily swamped with code or information they are not in a position to utilise.

So in terms of CSS this means avoiding the use of inline styles, even if the presentation really does only apply in the one case.  Instead appropriate id and class attributes should be used and the presentation defined in the referenced CSS file.

So instead of:

<p style="font-weight: bold; color: navy">Lorem ipsum dolor sit amet</p>

We would use:

<p class="standfirst">Lorem ipsum dolor sit amet</p>

And in our CSS file:

p.standfirst { font-weight: bold; color: navy }

For JavaScript things are a little trickier – however we still want to avoid the use of inline code such as onclick attributes on buttons and hyperlinks and instead apply what is known as unobtrusive JavaScript

So again instead of:

<a href="#" onclick="alert(‘Hello’);">Say hello</a>

We would use:

<a class="greeting" href="greeting.html">Say hello</a>

And in the referenced JavaScript file:

function initializeGreeting() {

  var links = document.getElementsByTagName("a");

  for (var i=0; i < links.length; i++) {

    if (links[i].className == "greeting") {

      links[i].onclick = function() {

        alert("Hello");

        return false;

      };

    }

  }

}

Or more easily with the support of the jquery library:

$(document).ready(function() {

  initializeGreeting();

});



function initializeGreeting() {

  $("a.greeting").click(function() {

    alert("Hello");

    return false;

  }); 

}

The last example above illustrates the method of link hijacking – where the default behaviour of the hyperlink is modified by the JavaScript code.  The key point to note is that if scripting is not supported in the browser environment, the standard link behaviour will remain in place (in this case navigating to a second page instead of displaying an alert).

We can do the same with forms too.  Again using JavaScript we can attach a function to the form submit event and instead perform an AJAX post of the information and display of the confirmation message to the user.

Ajax Support from ASP.Net MVC

The code examples presented thus far illustrate only the client side techniques, but of course web applications – Ajax enabled or otherwise – generally require server side code to provide their functionality.  On the server can be any language or platform of course, but ideally we want to make use of a framework that supports the Ajax techniques and allows us to re-use code between the basic and enhanced user interface.

The ASP.Net MVC framework is one such platform that provides good support for Ajax enhancements and re-use of server side code.  In the following two examples, I’ll illustrate how this is the case for each of the two general patterns for building such interfaces.

Using JSON

In this first example we use the BST (browser-side template) pattern where Ajax communication with the server is via JSON based messages. It’s part of a basic feed reader application, where a user has a list of RSS feeds to which they can add new ones by entering the URL of the feed into a form.

The first step is to set up the basic interface that will function without requirements for scripting to be enabled on the client.  Starting from the view we create the mark-up for a simple list of feeds, a placeholder for a message and a form for the entry of the URL for a new feed:

<h2>My RSS Feeds</h2>

    

    <div id="message"><%= TempData["Message"]%></div>



    <ul id="feed-list">

        <% foreach (var item in Model) { %>

            <li><%= Html.Encode(item.Name) %></li>

        <% } %>

    </ul>

    

<% using (Html.BeginForm("Add", "Home", FormMethod.Post, 

        new {id = "form-add-feed"})) { %>

        <label for="Url">Enter Url:</label>

        <%= Html.TextBox("Url", "", new { id = "form-add-feed-url" })%>

        <input type="submit" value="Add RSS Feed To List" />

    <% } %>

On submission, the form posts to a controller action which in turn calls methods to validate and save the details of the RSS feed to the user’s account.  It then redirects back to the Index action which re-displays the user’s feed list along with an appropriate message:

public ActionResult Index()

        {

            //Get list of feeds for user

            IList<Feed> feeds = GetFeedsForUser(User.Identity.Name);



            //Render view passing feed list as the model

            return View(feeds);

        }



        [AcceptVerbs(HttpVerbs.Post)]

        public ActionResult Add(string url)

        {

            string message;



            //Check that the user has entered a correct RSS feed

            if (IsValidRSSFeed(url))

            {

                //Create the Feed object

                Feed feed = new Feed();

                feed.Name = GetRSSFeedNameFromUrl(url);

                feed.Url = url;



                //Save the feed to the user’s account

                AddFeedForUser(feed, User.Identity.Name);

                message = "Feed added to list";

            }

            else

            {

                message = "Not a valid RSS feed";

            }



            //Set a message and redisplay the list

            TempData["Message"] = message;

            return RedirectToAction("Index");

        }

Applying our form hijacking technique, we can intercept the form post to implement the Ajax enhanced interface.  Instead of submitting the form and having the whole page re-render, we instead use jquery to submit an Ajax request to the same controller action and set up an asynchronous call-back to wait for the response.  The response comes back as a JSON structure, signalling success or otherwise of the operation, in response to which we can add the feed to the list via some simple DOM manipulation.

$(document).ready(function() {

        hookUpAddFeedFormAjaxPost();

    });



    function hookUpAddFeedFormAjaxPost() {

        $("#form-add-feed").submit(function() {



            //Get feed details

            var url = $("#form-add-feed-url").val();



            //Post to controller action for adding feed

            $.post("/Home/Add/", { url: url },

                function(data) {



                    //If successful, add feed to list

                    if (data.Success) {

                        $("#feed-list").append("<li>" + data.Name +

"</li>");

                    }



                    //Show message                    

                    $("#message").text(data.Message);



                    //Clear form

                    $("#form-add-feed-url").val("");



                }, "json");



            //Prevent default form submission

            return false;



        });

    }

The key point to note with this example is that we have posted to the same controller action, and hence are able to re-use all the business logic that may be required around this application to manage user authorisation and feed validation.  It just requires a simple amend to the controller action using the IsAjaxRequest() method, in order to return the JSON response instead of the redirect behaviour:

public ActionResult Add(string url)

        {

            bool success;

            string message, feedName = "";



            //Check that the user has entered a correct RSS feed

            if (IsValidRSSFeed(url))

            {

                //Create the feed object

                Feed feed = new Feed();

                feedName = GetRSSFeedNameFromUrl(url);

                feed.Name = feedName;

                feed.Url = url;



                //Save the feed to the user’s account

                AddFeedForUser(feed, User.Identity.Name);

                message = "Feed added to list";

                success = true;

            }

            else

            {

                message = "Not a valid RSS feed";

                success = false;

            }



            //Check for AJAX request

            if (Request.IsAjaxRequest())

            {

          //Prepare JSON response

                JsonResult json = new JsonResult();

                json.Data = new

                {

                    Success = success,

                    Message = message,

                    Name = feedName

                };

                return json;

            }

            else

            {

          //Set a message and redisplay the list (non-AJAX behaviour)

                TempData["Message"] = message;

                return RedirectToAction("Index");

            }

        }

Using Partial Views

A further example illustrates the second of the patterns known as the HTML Message (HM) pattern.  In this case rather than returning data, HTML fragments are built up server side and provided in the Ajax response.

Here we are looking at a calendar application, which uses a calendar widget to allow the user to select a date.  Clicking on the calendar renders a list of events taking place on the selected date.

Once again, the first step is to set up the version that uses straightforward links and form posts.  I’ve used a simple form with drop-down lists for the user to select a date (which could of course itself be enhanced using a nice calendar widget).  Once selected the events that match the date are displayed in the list below.

<h2>List of Events</h2>

    

<% using (Html.BeginForm("Index", "Home", FormMethod.Get, 

        new {id = "form-select"})) { %>

        <label for="Year">Year:</label>

        <%= Html.DropDownList("Year", Model.Years, 

            new { id = "form-select-year" })%>

        <label for="Month">Month:</label>

        <%= Html.DropDownList("Month", Model.Months, 

            new { id = "form-select-month" })%>

        <input type="submit" value="Show Events" />

    <% } %>    



    <ul id="event-list">

    <% foreach (var item in Model.Events) { %>   

        <li>

            <%= Html.Encode(item.Name) %>

            on <%= Html.Encode(String.Format("{0:g}", item.Start)) %>

            at <%= Html.Encode(item.Location) %>

        </li>    

    <% } %>

    </ul>

When the first page is selected and on posting the form, the controller action renders the list of events.  Using the nullable parameters (indicated by the question mark before the parameter type) we can detect the situation when the user makes a choice and when a default selection of the current month should be made.

In addition to the list of events, this particular view requires some additional information, namely the lists of years and months for the user to select.  In order to provide this to the view we are preparing a custom view model.  This is a design decision – it’s perfectly possible to use the ViewData dictionary to pass the associated information in addition to the model but the view model approach has the advantage of allowing everything to be accessed in the view in the same strongly typed manner.

Finally as you’ll see, for the purposes of this example we are just hard-coding a list of events – in a real application of course this would likely be prepared by accessing the list from a database.

public ActionResult Index(int? year, int? month)

{

    //Get year and month parameter - default to current year and month

    int selectedYear = year.HasValue ? year.Value : DateTime.Now.Year;

    int selectedMonth = month.HasValue ? month.Value : DateTime.Now.Month;



    //Get list of events

    IList<Event> events = GetEvents(selectedYear, selectedMonth);



    //Set up year and month selection lists

IList<KeyValuePair<string, string>> years = 

    new List<KeyValuePair<string, string>>();

    for (int i = DateTime.Now.Year; i <= DateTime.Now.Year + 3; i++)

    {

        years.Add(new KeyValuePair<string, string>(i.ToString(),

            i.ToString()));

    }

IList<KeyValuePair<string, string>> months = 

    new List<KeyValuePair<string, string>>();

    for (int i = 1; i <= 12; i++)

    {

        months.Add(new KeyValuePair<string, string>( i.ToString(), CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(i)));

    }



    //Create view model and pass to view

    return View(new EventListViewModel

    {

        Events = events,

        Years = new SelectList(years, "key", "value", selectedYear),

        Months = new SelectList(months, "key", "value", selectedMonth),

    });

}



private IList<Event> GetEvents(int year, int month)

{

    return new List<Event>() { 

        new Event {

            Name = "Garden Party", 

            Location = "The Park",

            Start = DateTime.Parse("1-JUL-2010 12:00")

        },

        new Event {

            Name = "Barbeque", 

            Location = "My House",

            Start = DateTime.Parse("2-JUL-2010 14:00")

        },

    };

}

To provide the enhanced user interface, once again we hijack the form post to submit an Ajax request to the controller action.

$(document).ready(function() {

        hookUpSelectDateFormAjaxPost();

    });



    function hookUpSelectDateFormAjaxPost() {

        $("#form-select").submit(function() {



            //Get year and month details

            var year = $("#form-select-year").val();

            var month = $("#form-select-month").val();



            //Post to controller action for adding feed

            $.get("/Home/Index/?year=" + year + "&month=" + month, null, 

                function(data, textStatus) {

                    $("#event-list").hide().html(data).show("slow");

            });        



            //Prevent default form submission

            return false;



        });

}

The IsAjaxRequest() method this time allows us to return a partial view rather than a full page view.

//Create view model and pass to view or partial view

    EventListViewModel vm = new EventListViewModel

    {

        Events = events,

        Years = new SelectList(years, "key", "value", selectedYear),

        Months = new SelectList(months, "key", "value", selectedMonth),

    };

    if (Request.IsAjaxRequest())

    {

        return View("EventsList", vm);

    }

    else

    {

        return View(vm);

}

The partial view renders just the HTML mark-up and information for the list of events.

<% foreach (var item in Model.Events) { %>   

        <li>

            <%= Html.Encode(item.Name) %>

            on <%= Html.Encode(String.Format("{0:g}", item.Start)) %>

            at <%= Html.Encode(item.Location) %>

        </li>    

    <% } %>

Finally we can refactor our view code such that the original view now incorporates the partial view, and hence we can avoid duplicating code in this layer too.

<ul id="event-list">

        <%= Html.RenderPartial("EventList",Model) %>

    </ul>

Summary

In building the best user interface we can for our users, adopting a development method of progressive enhancement allows us to meet this goal without sacrificing accessibility for users and clients that are unable to support the necessary technologies.  With the use of appropriate server side technologies we are able to do this on both the client and server side in a clean manner, promoting separation of concerns and the reusability of code.

 

About the Author

Andy Butland leads the technical development team at London based digital agency Zone, where he works on a range of online solutions for a variety of clients ranging from blue-chips to start-ups. 

He has a decade of experience building web sites and applications primarily on the Microsoft stack, beginning with classic ASP, moving through ASP.Net and in the last couple of years focussing on the ASP.Net MVC framework. 

Outside of work, and when not spending time looking after his recently arrived daughter Eva, Andy enjoys playing bass in a soul/blues band.

Find Andy on: