.NET Performance Blog

July 6, 2009

ASP.NET MVC Grid – Part 4 – View Models, Unit Testing, Keyword Search

Filed under: ASP.NET MVC,General — Eric P @ 12:00 am

In this part, I decided to take a step back and do some refactoring. At the same time I would like to keep moving forward, by adding new Keyword Search functionality as well as an Auto Complete control to quickly find/edit a customer.

Click image to view full screenshot

Click image to view full screenshot

You can see demo here:
http://samples.entechsolutions.com/MvcGridSample/Part4

Code is available here:
http://samples.entechsolutions.com/Downloads/MvcGridSample/MvcGridSample_Part4.zip

Implementation

1. Use View Model (instead of ViewData) to organize Grid variables

As I mentioned in last couple of posts, ViewData was being used for too many variables. Such variables in ViewData are not strongly typed and result in verbose code which could be error-prone (for example null errors).

Instead I introduce the following class structure for Grid View Model:

class Grid
{
	Pager
		CurrentPage
		TotalPages
		RowStats
		...

	Sorter 
		SortField
		SortDirection


	SearchCriteria	- new functionality for keyword search
		Keyword


	GridAction - enumeration with items like "Sort", "GoToPage", ...
}

Using this class feels a lot cleaner then ViewData. In the code below Model is of class Grid.

<div id="gridHeader">
			<table>
				<tr>
					<td id="rowStats">
						<%= Model.Pager.RowStats %>
					</td>
					
					<td id="pagerNav">			
						<% if (Model.Pager.IsNavVisible) { %>
							<table>
								<tr>
									<td>
										<% if (Model.Pager.IsFirstPage) { %>
											<span class="disabled">&lt;&lt;</span>
										<% } else { %>
											<a href="#" onclick="goToPage(<%= Model.Pager.FirstPage %>)">&lt;&lt;</a>  
										<% } %>
									</td>
...

2. Write unit tests for pager. When creating a Pager class, I decided to follow TDD methodology and wrote unit tests first. Through this approach I discovered a lot of errors before actually integrating Pager into UI.

Here is a couple of unit tests… That check cases when Pager navigation should be visible and not visible.

namespace MvcGridSample.Tests.ViewModels.Shared
{
	[TestClass]
	public class PagerTest
	{

	       [TestMethod]
		public void Nav_Not_Visible_If_One_Page()
		{
			var pager = CreateAndInitPager(1, 10, 5);
			Assert.IsFalse(pager.IsNavVisible);
		}


		[TestMethod]
		public void Nav_Visible_If_More_Then_One_Page()
		{
			var pager = CreateAndInitPager(1, 10, 15);
			Assert.IsTrue(pager.IsNavVisible);

		}
               ...

3. Add Keyword search. This functionality allows user to search data by keyword which is matched against First Name, Last Name, Email or Phone. It can be expanded to search through other fields too (for ex. Customer Address – if one would exist).

The keyword search criteria was added to LINQ query using the following code:

private IQueryable<Customer> AddQuerySearchCriteria(IQueryable<Customer> query, SearchCriteria searchCriteria)
{
	if (!String.IsNullOrEmpty(searchCriteria.Keyword))
	{
		string keyword = searchCriteria.Keyword.ToLower();
		query = query.Where(customer => customer.FirstName.ToLower().Contains(keyword)
		                                || customer.LastName.ToLower().Contains(keyword)
						|| (customer.Email != null && customer.Email.Contains(keyword))
						|| (customer.Phone != null &&  customer.Phone.Contains(keyword)));
	}

	return query;
}

Note that I had to ensure that Email and Phone were not null, before checking them against keyword. Otherwise there were some “object reference is null” exceptions.

4. Add Auto Complete on Keyword. This is for a common use case when you would like to quickly find a record and perform certain operation on it. To try it out – start typing name of existing customer in keyword box. After first couple of letters you will see auto-complete drop down with a list of matching customers (by first name and last name).

If you select the customer in the list and click enter or click on it with a mouse – you will be taken to Edit Customer page.

To implement this functionality I used a JQuery auto-complete plugin from here:
http://docs.jquery.com/Plugins/Autocomplete

For this to work I created a method on controller that would return list of customer names in JSON format:

public ActionResult GetKeywordAutoCompleteData(string q, int limit)
{
	string keyword = q;

	IQueryable<Customer> query = _customerService.GetQueryable();
	query = query.Where(customer => customer.FirstName.ToLower().StartsWith(keyword)
	                                || customer.LastName.ToLower().StartsWith(keyword))
					.Take(10)
					.OrderBy(customer => customer.FirstName)
					.ThenBy(customer => customer.LastName);

	var list = query.Select(customer => new { 
												customer.ID, 
												Name = customer.FirstName + " " + customer.LastName});

	return Json(list);
}

Then call this method from client side using JQuery auto-complete plugin functionality:

jQuery(document).ready(function() {

	$("#SearchCriteria_Keyword").autocomplete(
		"/Customer/GetKeywordAutoCompleteData",
        {
        	dataType: 'json',
        	parse: function(data) {
        		var rows = new Array();

        		for (var i = 0; i < data.length; i++) {
        			rows&#91;i&#93; = { data: data&#91;i&#93;, value: data&#91;i&#93;.Name, result: data&#91;i&#93;.Name };
        		}
        		return rows;
        	},
        	formatItem: function(row, i, n) {
        		return row.Name;
        	},
        	width: 260,
        	selectFirst: false
        }
    );
    ....
&#91;/sourcecode&#93;


<h3>Observations</h3>
<ul>

<li>
         When HtmlHelper functions generate HTML controls - control IDs replace character  "." with "_".  For ex, if I want to use the following control in Jquery:


     <%= Html.Hidden("Sorter.SortDirection")%>

I need to call it like this:

     $("#Sorter_SortDirection").val(someVal);
  • Coming from Web Forms world, it seemed natural to me to create /App_Code directory to put all the utility classes.
    It has been working fine till now, but all of a sudden I started receiving compilation error:

    Compiler Error Message: CS0433: The type ‘MvcGridSample.StringFormatter’ exists in both ‘c:\Windows\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\752a9979\7f1c3889\assembly\dl3\da179bc1\70581e2e_8ff9c901\MvcGridSample.DLL’ and ‘c:\Windows\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\752a9979\7f1c3889\App_Code.nzexrw9-.dll’

    The reason for this issue is explained on the following page:
    http://weblogs.asp.net/meligy/archive/2008/08/03/converting-vs-2008-website-to-web-application.aspx

    During the conversion, VS 2008 renames your “App_Code” to “Old_App_Code”. This new name sounds ugly, but DO NOT RENAME IT BACK. In the “web application” model, all code will be in one assembly. In runtime, the web server does not know what web project type you are using. It does take all code in “App_Code” folder and create a new assembly for it. This way, if you have code in folder named “App_Code”, you’ll end up with RUNTIME compilation errors that the same types exist in two assemblies, the one created by VS, and the one created by IIS / ASP.NET Development Server.

    So to fix the issue I renamed the folder to “App_Code_”

  • Coming up for part 5…

    In part 5, I will implement grid persistance. Currently, if I am on page 3 of the grid sorted by phone number and I click on “Edit” next to one of the customers – when I save the customer and go back to grid – the grid returns to its default state of Page 1 and sorted by ID.

    Grid persistence – will remember the state of the grid when user goes to another page, so that when user will come back to the page with the grid, it will be in the state that it was left in.

    I will also add more JQuery functionality to format alternative rows, change row color on hover and provide “status line” notifications of user actions like created new customer, deleted customer, etc….

    Advertisements

    2 Comments »

    1. ASP.NET MVC Grid – Part 4 – View Models, Unit Testing, Keyword Search « RAD for N-Tier web apps in .NET…

      9efish.感谢你的文章 – Trackback from 9eFish…

      Trackback by 9eFish — July 8, 2009 @ 9:55 pm | Reply

    2. the post is really nice and its working with my mvc

      how about converting the list of customers to a list of customers from a database using linq to sql.

      i am new to mvc and trying to create a maintenance form.

      your response would be a great help

      thanks

      Comment by rowenadev09 — October 15, 2009 @ 8:58 am | Reply


    RSS feed for comments on this post. TrackBack URI

    Leave a Reply

    Please log in using one of these methods to post your comment:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s

    Blog at WordPress.com.

    %d bloggers like this: