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.
In the New ASP.NET MVC 4 Project dialog, select Intranet Application, make sure the Razor view engine is selected and then click OK.
Adding the Model
The application uses the following simple 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.
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.
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.
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.




