ASP.NET MVC Tip #45 – Use Client View Data

In this tip, I explore one approach to building Ajax applications with ASP.NET MVC. I show how you can use view data when building Ajax applications with ASP.NET MVC in the same way as you would use view data when building server-side application. I demonstrate how to create a custom HTML Helper that renders client view data.

One of the primary benefits of building an ASP.NET MVC application is that it enables you to build web applications that support a sharp separation of concerns. This sharp separation of concerns enables you to build applications that are highly testable and highly adaptable to future change.

Communication between the different parts of an ASP.NET MVC application is extremely constrained. An MVC view only talks to an MVC controller and never directly to an MVC model class. The only way that a view is allowed to talk to a controller is through view data. These constraints enable the sharp separation of concern.

If you are not careful, you can break this clear separation of concerns when building Ajax applications. In an Ajax application, the browser talks directly to the server. An Ajax request is made against a controller action and the controller action returns a JSON result.

In this tip, I demonstrate how you can use view data to pass data from a controller action to a view in an Ajax application in the very same that you use view data in a non-Ajax application. In this tip, I demonstrate how you can create a custom HTML Helper that renders client view data.

Creating the Client View Data Helper

The code for the client view data HTML Helper is contained in Listing 1. The ClientViewDataHelper class defines an extension method, named ClientViewData(), that extends the AjaxHelper helper class (The AjaxHelper class is exposed as the View.Ajax property in a server-side view).

Listing 1 – MvcAjax\ClientViewDataHelper.cs

using System.Web.Mvc;

namespace MvcAjax
{
    public static class ClientViewDataHelper
    {

        public static string ClientViewData(this AjaxHelper helper)
        {
            var clientViewData = new ClientViewData();
            var context = helper.ViewContext;
            var result = clientViewData.Serialize(context.Controller.ViewData);
            result = "<script type='text/javascript'> var viewData=" + result + "; </script>";
            return result;
        }

    }
}

The ClientViewData() helper renders the current view’s view data into a JavaScript object. The Helper makes the server-side view data available to client-side JavaScript code.

The ClientViewData() HTML Helper calls the ClientViewData.Serialize() method to serialize the server-side view data into the client-side object. The code for the ClientViewData class is contained in Listing 2.

Listing 2 – MvcAjax\ClientViewData.cs

using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace MvcAjax
{
    public class ClientViewData
    {

        public string Serialize(ViewDataDictionary viewData)
        {
            var ser = new JavaScriptSerializer();
            ser.RegisterConverters(new JavaScriptConverter[] { new ViewDataConverter(), new ModelStateConverter() });
            return ser.Serialize(viewData);
        }


    }
}

The ClientViewData class takes advantage of the JavaScriptSerializer class included in the .NET framework. Notice that the JavaScriptSerializer is configured to use two custom JavaScript convertors named ViewDataConverter and ModelStateConverter. These special converters are needed to properly convert the ViewDataDictionary and ModelStateDictionary objects into JavaScript objects (the converters are included in the code download at the end of this blog entry).

A Walkthrough of Client View Data

To demonstrate how you can use the ClientViewData() helper in an ASP.NET MVC Ajax application, let’s do a walkthrough of creating a basic Movie database application. Our application will consist of a single page that contains a dropdown list of movie categories and an HTML table that displays movies that match the selected category (see Figure 1).

Figure 1 – The Movie Database Application

clip_image002

Here’s the cool part. Both the list of movie categories and the list of movies are stored in client view data. When the page is first rendered to the browser, the categories and movies are included in a JavaScript object rendered by the ClientViewData() helper. When you select a new movie category, the client view data is updated with an Ajax call. All interaction between the view (both client-side and server-side) happens through view data. A sharp separation of concerns is maintained.

The view is contained in Listing 3. There’s a lot happening in this view.

Listing 3 – Views\Home\Index.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="Tip45.Views.Home.Index" %>
<%@ Import Namespace="MvcAjax" %>
<!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>Index</title>
    
    <style type="text/css">
    
    #movieContainer
    {
        display:none;
    }
    
    td, th
    {
        padding: 5px;
    }
    
    </style>

    <script type="text/javascript" src="../../Content/MicrosoftAjax.debug.js"></script>
    <script type="text/javascript" src="../../Content/MicrosoftAjaxTemplates.debug.js"></script>
    <script type="text/javascript" src="../../Content/jquery-1.2.6.min.js"></script>   
    <script type="text/javascript" src="../../Content/MvcAjax.js"></script> 
    <script type="text/javascript">

        var categoryView;
        var moviesView;

        $(pageReady);

        function pageReady()
        {
            categoryView = $create(Sys.UI.DataView, {}, {}, {}, $get("categories"));
            movieView = $create(Sys.UI.DataView, {}, {}, {}, $get("movies"));
        
            showCategories();
            $("#categories").change(function() { selectCategory($(this).val()); });
        }

        function selectCategory(categoryId)
        {
            if (categoryId)
            {
                $("#movieContainer").hide();
                updateViewData("/Home/Movies/" + categoryId, "movies", showMovies);
            }
        }

        function showCategories()
        {
            categoryView.set_data(viewData.categories);
            $("#categories").prepend( $("<option>Select Category</option>") )[0].selectedIndex = 0;
        }

        function showMovies()
        {
            movieView.set_data(viewData.movies);
            $("#movieContainer").fadeIn("normal");            
        }    
    
    </script>

</head>
<body>
    <div>
    

    <%= Ajax.ClientViewData() %>

    <label for="categories">Category:</label>
    <select id="categories">
    <option value="{{Id}}">{{Name}}</option>
    </select>

    <br /><br />

    <table id="movieContainer">
    <thead>
        <tr>
            <th>Title</th>
            <th>Director</th>
        </tr>
    </thead>
    <tbody id="movies">  
        <tr>
            <td>{{Title}}</td>
            <td>{{Director}}</td>        
        </tr>
    </tbody>
    </table>

    
    </div>
</body>
</html>

The first thing that you should notice is that the ClientViewData() helper method is called in the body of the page. The ClientViewData() helper renders the code in Listing 4.

Listing 4 – Rendered Client View Data

<script type='text/javascript'> var viewData={"categories":[{"Id":1,"Name":"Adventure","Position":1},{"Id":2,"Name":"Animation","Position":2},{"Id":3,"Name":"Drama","Position":2},{"Id":4,"Name":"Horror","Position":-2}],"movies":[{"Id":1,"CategoryId":3,"Title":"Titanic","Director":"James Cameron","DateReleased":"\/Date(866876400000)\/"},{"Id":2,"CategoryId":1,"Title":"Star Wars II","Director":"George Lucas","DateReleased":"\/Date(233996400000)\/"},{"Id":3,"CategoryId":1,"Title":"Jurassic Park","Director":"Steven Spielberg","DateReleased":"\/Date(740300400000)\/"},{"Id":4,"CategoryId":4,"Title":"Jaws","Director":"Steven Spielberg","DateReleased":"\/Date(170665200000)\/"},{"Id":5,"CategoryId":4,"Title":"Ghost","Director":"Jerry Zucker","DateReleased":"\/Date(645346800000)\/"},{"Id":7,"CategoryId":3,"Title":"Forrest Gump","Director":"Robert Zemeckis","DateReleased":"\/Date(771922800000)\/"},{"Id":8,"CategoryId":2,"Title":"Ice Age","Director":"Chris Wedge","DateReleased":"\/Date(1025074800000)\/"},{"Id":9,"CategoryId":2,"Title":"Shrek","Director":"Andrew Adamson","DateReleased":"\/Date(993452400000)\/"},{"Id":10,"CategoryId":1,"Title":"Independence Day","Director":"Roland Emmerich","DateReleased":"\/Date(835254000000)\/"},{"Id":22,"CategoryId":4,"Title":"The Ring","Director":"Gore Verbinski","DateReleased":"\/Date(1057388400000)\/"}],"model":null,"modelState":{"isValid":true}}; </script>

The code in Listing 4 is hard to read, but if you look closely then you’ll notice that the code defines a JavaScript object named viewData. The viewData object has two properties named categories and movies that represent the movie categories and movies.

The view takes advantage of both ASP.NET AJAX and jQuery to render the list of movie categories and movies. When the view is first loaded in the browser, the following code executes:

var categoryView;
var moviesView;

$(pageReady);

function pageReady()
{
   categoryView = $create(Sys.UI.DataView, {}, {}, {}, $get("categories"));
   movieView = $create(Sys.UI.DataView, {}, {}, {}, $get("movies"));
        
   showCategories();
   $("#categories").change(function() { selectCategory($(this).val()); });
}

The jQuery command $(pageReady) causes the pageReady() function to execute after the page DOM is fully loaded. The pageReady() function creates two ASP.NET AJAX client-side controls. Two ASP.NET AJAX DataView controls that represent the dropdown list of categories and HTML table of movies are created.

Next, the showCategories() method is called. This method adds the categories from view data to the dropdown list. The showCategories() function looks like this:

function showCategories()
{
    categoryView.set_data(viewData.categories);
    $("#categories").prepend( $("<option>Select Category</option>") )[0].selectedIndex = 0;
}

This simple function assigns the categories from view data to the categoryView ASP.NET AJAX control with the help of the set_data() method. Next, the function adds a default dropdown list option labeled Select Category.

When you select a new category, the selectCategory() function executes:

function selectCategory(categoryId)
{
    if (categoryId)
    {
        $("#movieContainer").hide();
        updateViewData("/Home/Movies/" + categoryId, "movies", showMovies);
    }
}

This function starts by hiding the HTML table that contains the movies. Next, it calls a method named updateViewData() to invoke a server-side ASP.NET MVC action and get the list of matching movies. The matching movies are assigned to the client view data viewData.movies property. Finally, the updateViewData() method calls the showMovies() method to show the movies represented by the client viewData.movies property in the HTML table.

Here is the code for showMovies():

function showMovies()
{
    movieView.set_data(viewData.movies);
    $("#movieContainer").fadeIn("normal");            
}    

Just like the showCategories() method, the showMovies() method retrieves the data from client view data and assigns the data to the DataView. The showMovies() method then fades in the HTML table of movies by taking advantage of a jQuery animation effect.

The final piece of this application is the controller that returns the view and returns the movie data. The controller is contained in Listing 4.

Listing 4 – Controllers\HomeController.cs

using System.Web.Mvc;
using Tip45.Models;

namespace Tip45.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        private IMovieRepository _repository;

        public HomeController()
        {
            _repository = new MovieRepository();
        }

        public ViewResult Index()
        {

            ViewData["categories"] = _repository.ListCategories();          
            ViewData["movies"] = _repository.ListMovies();

            return View("Index");
        }

        public ActionResult Movies(int id)
        {
            return Json(_repository.ListMovies(id));
        }


    }
}

The Home controller is straightforward. It exposes two actions named Index() and Movies(). The Index() action returns the view. Notice that the categories and movies are added to server-side view data before the view is returned. The ClientViewData() helper in the Index view serializes the server-side view data to the client-side viewData JavaScript object.

The Movies() action returns a JsonResult that represents a list of matching movies. The Movies() action is invoked by the client-side code in the selectCategory() method discussed previously.

A Moment of Reflection

The advantage of the approach discussed in this tip is that it enables you to preserve the same clean separation of concerns when building an ASP.NET MVC application as when building a server-side ASP.NET MVC application. By taking advantage of client view data, you can require that all communication between the client view and the server happen through view data.

Another advantage of this approach is that the initial data for the categories dropdown list is available when the page first loads. A separate Ajax request does not need to be performed to retrieve the categories from the server because the categories are already present in the client view data.

One potential disadvantage of the approach discussed in this blog entry is it does not support graceful degradation. If someone attempts to use the Movie database application with JavaScript disabled, then nothing works. The initial dropdown of categories will never be displayed.

If you are building an ASP.NET application for internal use in your company then this drawback, most likely, won’t matter to you. You can require everyone in your company to use a browser that supports JavaScript.

If you are building a public facing website then this JavaScript requirement might be more of a concern. However, it is important to understand that everything in the code used in this tip is compatible with recent browsers (Internet Explorer, Firefox, Chrome). The change of the millennium was 8 years ago now, it is time to start expecting users to have modern browsers.

If you are building a highly interactive web application, then requiring users to use a browser that supports JavaScript seems reasonable. To put things in perspective, it would be crazy to make it a requirement that a desktop application continue to work with C# turned off.

Summary

In this tip, I demonstrated how you can create an HTML Helper that renders client view data. By taking advantage of client view data, you can maintain the same sharp separation of concerns in an ASP.NET MVC Ajax application as you maintain in an ASP.NET MVC server-side application.

I built this application by taking advantage of ASP.NET AJAX client templates used in combination with jQuery. This combination enables you to easily build pure Ajax applications.

I want to emphasize that the approach to building an ASP.NET MVC Ajax application described in this blog entry is only one approach. In future tips, I plan on exploring several approaches to the same problem.

Download the Code

7 Comments

Comments have been disabled for this content.