ASP.NET MVC Application Building: Forums #6 – Ajax
In this series of blog entries, I build an entire MVC Forums application from start to finish. In this entry, I create the user interface for the Forums application by taking advantage of ASP.NET AJAX client templates. I build the entire user interface for the Forums application with a single view and perform all updates to the view with Ajax calls.
A page request is just an excuse for a bad user experience. We are conditioned to expect our web pages to freeze whenever we interact with a website. For example, each and every time we submit a form, we expect the browser to freeze and the clock to tick until the browser updates the page again.
There is no reason to inflict this pain on the users of your web applications. By taking advantage of Ajax, you can create web applications that respond instantly to user interactions. In this blog entry, I demonstrate how you can take advantage of Ajax when building ASP.NET MVC applications.
In particular, I discuss how you can take advantage of a new feature of ASP.NET AJAX arriving with the next version of this technology. I discuss how you can take advantage of ASP.NET AJAX client templates in the context of an ASP.NET MVC application. A preview version of client templates is available now and you can start experimenting with this technology in the applications that you are building today.
In this blog entry, I demonstrate how you can create a single page ASP.NET MVC application. Single page web applications are the holy grail of the Ajax developer. In a single page application, only one page must be requested by the browser. After the single page has loaded, all updates to the page are performed using client-side JavaScript. All new data displayed in the page is retrieved by performing Ajax calls against the server.
An Ajax application takes a little more work to write (yes, you do have to write some JavaScript), but it provides your users with a much better user experience.
The Single Page
The single page (the single view) in our MVC Forums application is contained in Listing 1. Notice that the header of the view contains several JavaScript includes. The view takes advantage of the following JavaScript libraries:
· MicrosoftAjax.js – The main Microsoft AJAX Library. Required for all things AJAX related in the Microsoft universe.
· MicrosoftAjaxTemplates.js – The preview version of the client template library included with the next version of Microsoft AJAX.
· MicrosoftMvcAjax.js – Extensions to the Microsoft AJAX Library for the MVC framework. This script is included with the default MVC Visual Studio project template.
· MvcAjax.js – A JavaScript Library that I wrote which contains useful helper methods for invoking controller actions.
· MvcForums.js – A JavaScript library that contains all of the user-interface logic used by the MVC Forums application.
Listing 1 – Views\Forums\Index.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcForums.Views.Forum.Index" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>MVC Forums</title> <link href="../../Content/MvcForums.css" rel="stylesheet" type="text/css" /> <script src="../../Content/MicrosoftAjax.debug.js" type="text/javascript"></script> <script src="../../Content/MicrosoftAjaxTemplates.debug.js" type="text/javascript"></script> <script src="../../Content/MicrosoftMvcAjax.debug.js" type="text/javascript"></script> <script src="../../Content/MvcAjax.js" type="text/javascript"></script> <script src="../../Content/MvcForums.js" type="text/javascript"></script> </head> <body> <div id="banner" class="panel"> <h1>MVC Forums</h1> </div> <div id="main"> <div id="threadsAndMessageContainer"> <% Html.RenderPartial("~/Partials/Threads.ascx"); %> <% Html.RenderPartial("~/Partials/Message.ascx"); %> </div> <% Html.RenderPartial("~/Partials/Post.ascx"); %> <% Html.RenderPartial("~/Partials/Reply.ascx"); %> <% Html.RenderPartial("~/Partials/LoginRegister.ascx"); %> </div> <script type="text/javascript"> Sys.Application.initialize(); </script> </body> </html>
Notice, furthermore, that the body of the view in Listing 1 consists of a series of calls to the Html.RenderPartial() method. The MVC Forums application contains several forms: a login form, a registration form, a post form, a reply form. Each of these forms is contained in a separate partial. Placing the different forms displayed by the single page Ajax application in different partials makes it easier to manage the application. For example, the partial used to post a new message, named Post.ascx, is contained in Listing 2.
Listing 2 – Partials\Post.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Post.ascx.cs" Inherits="MvcForums.Partials.Post" %> <form id="post" action="" class="panel"> <label for="post.Subject">Subject:</label> <br /> <input name="post.Subject" /> <br /><br /> <label for="post.Body">Body:</label> <br /> <textarea name="post.Body" cols="60" rows="8"></textarea> <br /><br /> <button id="post.Post">Post</button> <button id="post.Cancel">Cancel</button> </form>
There is nothing special about the partial in Listing 2. It simply consists of a form for posting a new message to the MVC Forums.
Using Client Templates
By taking advantage of client templates, you can format any array of JavaScript objects. Client templates are included with the next version of the Microsoft AJAX Library. You can download a preview of client templates from the following address:
http://www.codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=16766
The MVC Forums application uses client templates in two places. First, a client template is used to format the list of message threads (see Figure 1). The list of threads is retrieved from the server with an Ajax call. When the data is returned, the data is formatted with the help of a client template.
Figure 1 – Message threads are rendered with a client template
Second, a client template is used to format a particular thread (see Figure 2). A thread consists of a post and all of the replies to the post. The contents of a thread are also retrieved with an Ajax call from the server.
Figure 2 – A particular thread is rendered with a client template
A client template is a fragment of XHTML. The client template used for formatting the list of threads is contained in Listing 3.
Listing 3 – allThreads template
<ul id="allThreads" class="sys-template"> <li> {{Author}} — <a href="" messageid="{{Id}}">{{Subject}}</a> </li> </ul>
There are two things that you should notice about the template in Listing 3. First, notice that the opening tag for the template includes a CSS class attribute that points to the sys-template class. This class is defined in the Forums.css file. The sys-template CSS class sets the display attribute to the value None in order to hide the contents of the template.
Second, notice the expressions {{Author}} and {{Subject}}. These expressions are replaced automatically with the values of these properties. These expressions work very much like the <%# Eval() %> expressions in a server-side ASP.NET page.
The template in Listing 3 is used by a client-side AJAX DataView control. This control is instantiated with the following line of code in the MvcForums.js library:
_allThreadsView = $create(Sys.UI.DataView, {}, {}, {}, $get("allThreads"));
This line of code creates a new client-side DataView control named _allThreadsView that uses the template in Listing 3. After you create a DataView control, you can assign data to the control by using its set_data() property. You can assign any JavaScript array that you want to the DataView by calling set_data(). In the MvcForums application, the array of data is retrieved from the server through an Ajax call which I describe in the next section.
Actions are Invoked with Ajax
The MVC Forums application never performs a normal post to the server. All information is conveyed back and forth between the Index view and the server through Ajax calls. The Ajax calls invoke MVC controller actions asynchronously.
In order to invoke MVC controller actions asynchronously, I had to write some helper methods. The MvcAjax.js file contains the following two helper methods:
· MvcAjax.MvcHelpers.invokeGet(url, succeededCallback, failedCallback)
· MvcAjax.MvcHelpers.invokePost(url, form, succeededCallback, failedCallback)
The first method enables you to invoke MVC controller actions by performing an HTTP GET. The second method enables you to invoke MVC controller actions by performing an HTTP POST.
For example, the list of message threads is retrieved from the following action exposed by the Forum controller:
public JsonResult Threads() { return Json(_repository.SelectThreads()); }
This controller action returns a JSON result that represents all of the message threads. The list of threads is retrieved from the ForumRepository.
The following JavaScript code is used to display the threads:
MvcAjax.MvcHelpers.invokeGet("Forum/Threads", updateAllThreads); function updateAllThreads(data) { _allThreadsView.set_data(data); }
The invokeGet() method invokes the Forum controller Threads() action. When this action returns the JSON result, the JSON result is passed to the updateAllThreads() method which assigns the data to the _allThreadsView client-side DataView control. At this point, the data is formatted by the client template and the list of threads is displayed.
Network Traffic is Kept to a Minimum
By taking advantage of client templates, you can reduce your network traffic to the bare minimum necessary to transfer database records between the server and the client. Unlike an UpdatePanel, when you use a client template, you don’t need to ship actual HTML markup between the server and client. The only thing that gets passed between server and client is pure data.
Furthermore, because we are working in the context of an MVC application, there is no view data that needs to be passed between server and client. Because we are building a single page application, there is really no reason for view data to exist. View data enables you to preserve state across postbacks. However, we don’t ever lose any state because we never perform a postback.
Passing only essential information between the server and client improves the responsiveness of the Forums application. The entire page is not reloaded whenever you click a new message thread to view it. Instead, only the message thread itself is transferred across the wire.
Forms Appear Instantly
When you click the New Post link in the MVC Forums application, the form for posting a new message appears instantly (see Figure 3). Unlike a normal web application, the form is not retrieved from the server using a page request. Instead, the Post form is loaded when the first page is loaded. It is hidden (with display:none) until you need to post a new message.
All of the forms in the MVC Forums application work in the same way. The Login, Register, Reply, and Post forms are all shipped to the browser with the first page request. You can post and reply to messages very quickly because these forms do not need to be repeatedly requested from the server.
Figure 3 – Posting a new message
Summary
I created the user interface for the Forums application that I describe in this blog entry as an experiment to test the new client templates functionality coming with the next version of the Microsoft AJAX Library. I discovered that client templates work very well in the context of an ASP.NET MVC application.
ASP.NET MVC enables you to easily build actions that return JSON results. Client-templates enable you to easily format JSON results. ASP.NET MVC and ASP.NET AJAX client templates are the perfect combination for Ajax fanatics like me.
I recommend that you download and play with the MVC Forums application included at the end of this blog entry. I think that you will be surprised by the responsiveness of the application.