ASP.NET MVC4 AJAX Search Tutorial

Overview

This article is an update to my earlier one entitled ASP.NET MVC3 AJAX Search Tutorial. Since writing that first article, ASP.NET MVC4 and WebApi has been released and I have learned considerably more about Javascript. The purpose of this article is to share this newly gained knowledge.

The complete C# project is available on GitHub here.

Creating the ASP.NET MVC4 Web Application

To start the tutorial, open Visual Studio 2012 and create a new project using the ASP.NET MVC 4 Web Application template. I named the application “Id.Mvc4.Sample” but you can call it whatever you wish.

Create new ASP.NET MVC4 project

In the New ASP.NET MVC 4 Project dialog, select Intranet Application, make sure the Razor view engine is selected and then click OK.

Template selection screen

Adding the Model

The application uses the following simple model:

Application Model

This is an Entity Framework 5.0 ‘Code-First’ model that uses SQL Server Express LocalDB as its data store. A detailed example of how to build a ‘Code-First’ model is detailed in ‘Getting started with ASP.NET MVC 4 – Adding a Model‘.

The ‘seed’ data to be added to the new model is found in the class ‘LibraryInitializer’ which is located in the Models folder. To make this initializer run the first time the application is run  we have to add the following entry to the web.config file within the <entityFramework> section:

<contexts>
  <context type="Id.Mvc4.Sample.Models.LibraryEntities, Id.Mvc4.Sample">
    <databaseInitializer type="Id.Mvc4.Sample.Models.LibraryInitializer, Id.Mvc4.Sample" />
  </context>
</contexts>

Adding the View Model

In Solution Explorer, right-click the Project and add a new folder called ViewModels. Right-click this new folder and add a new class called SearchCriteria.cs.

Add ViewModels folder and SearchCriteria class

This class represents the search criteria provided by the user when searching the books catalogue. A new folder was created to separate the domain (database) model from the view model.

Build the application so that the user model will be available to the scaffolding wizard in the next step.

Creating the Default View

The next step is to add an action method and view to allow the user to search.

Delete the existing Views\Home\Index.cshtml file. You will create a new Index file to display the search fields.

In the HomeController class, replace the contents of the class with the following code:

public ActionResult Index()
{
      Models.LibraryEntities libraryDB = new Models.LibraryEntities();

      var formats = libraryDB.Formats.OrderBy(f => f.Name).ToList();
      ViewBag.FormatId = new SelectList(formats, "FormatId", "Name");

      var subjects = libraryDB.Subjects.OrderBy(s => s.Name).ToList();
      ViewBag.SubjectId = new SelectList(subjects, "SubjectId", "Name");

      return View();
}

Right-click inside the Index method and then click Add View.

Add Index View

Select the Create a strongly-typed view option. For Model class, select SearchCriteria. (If you don’t see SearchCriteria in the Model class box, you need to build the project.) Make sure that the View engine is set to Razor. Set Scaffold template to Details and then click Add.

Replace the contents of the newly created Views\Home\Index.cshtml file with the following:

@model Id.Mvc4.Sample.ViewModels.SearchCriteria
@{
    ViewBag.Title = "Home Page";
}
@section featured {
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h2>Search Books</h2>            
            </hgroup>
        </div>
    </section>
}

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { @Id = "SearchForm" }))
{
    <fieldset id="search">
        <legend>Search Criteria</legend>
        <div>
            <div class="searchOption">
                @Html.LabelFor(model => model.Title)
                @Html.EditorFor(model => model.Title)
            </div>
            <div class="searchOption">
                @Html.LabelFor(model => model.FormatId)
                @Html.DropDownList("FormatId", "All Formats")
            </div>
        </div>
        <div>
            <div class="searchOption">
                @Html.LabelFor(model => model.Author)
                @Html.EditorFor(model => model.Author)
            </div>
            <div class="searchOption">
                @Html.LabelFor(model => model.SubjectId)
                @Html.DropDownList("SubjectId", "All Subjects")
            </div>
            <div class="searchOption">
                <input type="hidden" id="StartIndex" value="1" />
                <input type="button" id="Search" title="Search" value="Search" /> <img src="@Url.Content("~/Content/themes/base/images/busy.gif")" alt="Please wait..." title="Please wait..." id="waitImage" height="22" width="22" style="display: none;" />
            </div>
        </div>
    </fieldset>
}

<div id="resultsTableHolder">

</div>

Creating the Search Controller

The next step is to add a Search Web API Controller. In Solution Explorer, right-click the ‘Controllers’ folder and add a new Web API Controller Class called SearchController.

Adding a nnew Web API Controller

Replace the body of the SearchController class with the following code:

Models.LibraryEntities libraryDB = new Models.LibraryEntities();

        public IEnumerable GetBySearch([FromUri]ViewModels.SearchCriteria criteria)
        {
            string authorCriteria = string.Empty;
            string titleCriteria = string.Empty;

            if (!string.IsNullOrWhiteSpace(criteria.Author)) authorCriteria = criteria.Author.ToLower().Trim();
            if (!string.IsNullOrWhiteSpace(criteria.Title)) titleCriteria = criteria.Title.ToLower().Trim();

            IQueryable results = libraryDB.Books;

            if (criteria.FormatId != 0)
                results = results.Where(b => b.Format.FormatId == criteria.FormatId);
            if (criteria.SubjectId != 0)
                results = results.Where(b => b.Subject.SubjectId == criteria.SubjectId);
            if (titleCriteria != string.Empty)
                results = results.Where(b => b.Title.ToLower().Contains(titleCriteria));
            if (authorCriteria != string.Empty)
            {
                results = (from book in results
                           from author in book.Authors
                           where author.Name.ToLower().Contains(authorCriteria)
                           select book).Distinct();
            }

            results = results.Include(b => b.Authors).Include(b => b.Format).Include(b => b.Subject).OrderBy(b => b.Title);

            return results.ToList();

        }

What is important to note is the [FromUri] attribute before the parameter definition in the GetBySearch method. By default the Web API expects complex types to be passed as part of the message body, but as this is a GET request we have to tell ASP.NET to look for the data to be passed as part of the Uri.

Creating the Handlebars template

In this tutorial we are using Handlebars to display the data returned from the call to the Web API method we have just created. While this is not a tutorial on Handlebars I will describe some of the basics.

Add the following to the bottom of the Index.cshtml template that you created earlier:

<script id="resultsTable" type="text/html">
    <table>
        <tr>
            <th>
                Title
            </th>
            <th>
                Authors
            </th>
            <th>
                Price
            </th>
            <th>
                Isbn
            </th>
            <th>
                Publication Date
            </th>
            <th>
                Format
            </th>
            <th>
                Subject
            </th>
        </tr>
    {{#each book}}
        <tr>
            <td>
                {{Title}}
            </td>
            <td>
                <ul>
                {{#each Authors}}
                    <li>{{Name}}</li>
                {{/each}}       
                </ul>
            </td>
            <td>
                {{Price}}
            </td>
            <td>
                {{Isbn}}
            </td>
            <td>
                {{PublicationDate}}
            </td>
            <td>
                {{Format.Name}}
            </td>
            <td>
                {{Subject.Name}}
            </td>
        </tr>
    {{/each}}
    </table>
</script>

The Handlebars expressions are contained within {{ }} and the template is defined within a script tag. The {{#each book}} expression is very similar to the C# for…each expression and the contained html is generated for every book ‘object’ that is present in the JSON data that the template will be applied to. The other expressions within the {{ }} tags will display the data for those properties on the book ‘object’.

Bringing it all together

We now have the search page, the Web API method to get the search results data, and a template to display that data. To bring all this together we need to use some jQuery code.

Add the following to the bottom of the Index.cshtml template that you created earlier:

@section Scripts {

    <script src="~/Scripts/handlebars.js"></script>

    <script type="text/javascript">

        var searchServer = (function() {

            var searchApiUrl = '@Url.Content("~/api/search/?")';

            $(document).ajaxError(function(event, xhr) {
                alert(xhr.status + ":" + xhr.statusText);
            });

            var search = function(searchCriteria) {
                return $.ajax(searchApiUrl + searchCriteria);
            };

            return {
                search: search
            };

        }());

        (function() {

            var templates = {};
            var results = null;

            var searchForm = null;
            var resultsDisplayHolder = null;

            var compileTemplates = function() {
                templates.resultsTable = Handlebars.compile($("#resultsTable").html());
            };

            var showResults = function(data) {
                results = data;
                var output = templates.resultsTable({ book: data });
                resultsDisplayHolder.html(output);
            };

            $(function() {

                compileTemplates();

                $(document).ajaxStart(function() {
                    $("#waitImage").show();
                }).ajaxStop(function() {
                    $("#waitImage").hide();
                });

                searchForm = $("#SearchForm");
                resultsDisplayHolder = $("#resultsTableHolder");

                $("#Search").click(function () {
                    var searchCriteria = searchForm.serialize();
                    searchServer.search(searchCriteria).done(showResults);
                    return false;
                });
            });

        }());

    </script>
}

The first thing we need to do is ‘compile’ the Handlebars template so that we can use it. This is done using the Handlebars.compile method.

var compileTemplates = function() {
     templates.resultsTable = Handlebars.compile($("#resultsTable").html());
};

To call the web API method we attach an event to the Search button that uses the jQuery.ajax method.

$("#Search").click(function () {
     var searchCriteria = searchForm.serialize();
     searchServer.search(searchCriteria).done(showResults);
     return false;
});

When we receive the data we then need to pass the data to the template and then display the resulting HTML:

var showResults = function(data) {
     results = data;
     var output = templates.resultsTable({ book: data });
     resultsDisplayHolder.html(output);
};

You now have a simple but functional AJAX enabled search page.

References

to var or not to var

I have recently read a lot of posts about the var keyword in C#. It’s use (or not) seems to have the C# community pretty divided. After researching the issue, I found ‘Uses and misuses of implicit typing‘ by Eric Lippert. I advise you to thoroughly read the article, but in summary his advice is:

  • Use var when you have to; when you are using anonymous types.
  • Use var when the type of the declaration is obvious from the initializer, especially if it is an object creation. This eliminates redundancy.
  • Consider using var if the code emphasizes the semantic “business purpose” of the variable and downplays the “mechanical” details of its storage.
  • Use explicit types if doing so is necessary for the code to be correctly understood and maintained.
  • Use descriptive variable names regardless of whether you use “var”. Variable names should represent the semantics of the variable, not details of its storage; “decimalRate” is bad; “interestRate” is good.

I think this is solid advice and I have adopted it for my own coding. If you want to see what some of the ‘against’ arguments are (which I think you should) then I would take a look at ‘Misuse of the var keyword in C#‘ by Brad Smith. Some of his arguments against it’s use are actually supporting Eric Lippert’s advice of when not to use the var keyword. The immediate disagreement I have is with the claim that it encourages Hungarian notation. Nothing should encourage this, and people who will use it will always find a reason to use it.