Paging, sorting and filtering a list in ASP.NET MVC 4

Contents:

  1. Introduction
  2. Creating a list
  3. The repository pattern
  4. The unit of work pattern
  5. Adding a pager to the list
  6. Allow sorting on the list
  7. Add list filters
  8. Create a base view model for lists that support sorting, filtering and paging
  9. Summary

1. Introduction Go top

Starting from the solution created in the previous post Data validation, we will build from scratch a new page that displays a list of users and gives the possibility to filter, page and sort the list. The final result will look like this:

MVC Course 3 - Final result

We will be building the examples in this post over this solution, so we will have some predefined CSS styles already in place but you can start as well from a completely new solution.

2. Creating a list Go top

DB structure
Let’s start with the necessary DB tables: [dbo].[Users] and [dbo].[UserRoles]. Below is the SQL script for creating and populating these two tables with some default data.

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[UserRoles](
	[UserRoleId] [int] NOT NULL,
	[UserRoleName] [varchar](50) NOT NULL,
 CONSTRAINT [PK_UserRoles] PRIMARY KEY CLUSTERED
(
	[UserRoleId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

INSERT INTO [dbo].[UserRoles]([UserRoleId],[UserRoleName]) VALUES (1,'Administrator')
INSERT INTO [dbo].[UserRoles]([UserRoleId],[UserRoleName]) VALUES (2,'User')
GO

CREATE TABLE [dbo].[Users](
	[UserId] [int] IDENTITY(1,1) NOT NULL,
	[FirstName] [varchar](100) NOT NULL,
	[LastName] [varchar](100) NOT NULL,
	[Email] [varchar](100) NULL,
	[Username] [varchar](50) NOT NULL,
	[Password] [varchar](50) NOT NULL,
	[UserRoleId] [int] NOT NULL,
	[Deleted] [bit] NOT NULL,
 CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
(
	[UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE UNIQUE NONCLUSTERED INDEX [IDX_Username] ON [dbo].[Users]
(
	[Username] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Users]  WITH CHECK ADD  CONSTRAINT [FK_Users_UserRoles] FOREIGN KEY([UserRoleId])
REFERENCES [dbo].[UserRoles] ([UserRoleId])
GO

ALTER TABLE [dbo].[Users] CHECK CONSTRAINT [FK_Users_UserRoles]
GO

INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('John','Dow','administrator@mailinator.com','admin','password',1,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Bela','Lugosi','bela.lugosi@mailinator.com','bela.lugosi','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('David','Copperfield','david.copperfield@mailinator.com','david.copperfield','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Jennifer','Jones','jennifer.jones@mailinator.com','jennifer.jones','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Sophia Loren','Loren','sophia.loren@mailinator.com','sophia.loren','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Woody','Allen','woody.allen@mailinator.com','woody.allen','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Stanley','Donwood','stanley.donwood@mailinator.com','stanley.donwood','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Marilyn','Monroe','marilyn.monroe@mailinator.com','marilyn.monroe','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Jamie','Foxx','jamie.foxx@mailinator.com','jamie.foxx','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('John','Ford','john.ford@mailinator.com','john.ford','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Diane','Keaton','diane.keaton@mailinator.com','diane.keaton','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Angelina','Jolie','angelina.jolie@mailinator.com','angelina.jolie','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Brad','Pitt','brad.pitt@mailinator.com','brad.pitt','password',2,0)
GO

Next we will update the edmx model to include users and user roles and then we shall add a new controller called UsersController. By default our controller will have an action method called Index. Let’s add a view for the Index method and display the list of users that aren’t deleted. First we will fetch the list from the DB inside the controller and pass it to the view.

Note: For the moment, I will concentrate mainly on the user interface and I will put all the server side logic intro the controller but lately we will refactor our application’s architecture using a view model and applying the repository and unit of work patterns.

public ActionResult Index()
{
	using (var context = new MvcDemoEntities())
	{
		var list = context.Users.Include(u => u.UserRole).Where(u => !u.Deleted).ToList();
		return View(list);
	}
}

In the view we will iterate through the collection of users and render one row for each user:

@model IEnumerable
@{
    ViewBag.Title = "Users list";
}
@if (!Model.Any())
{
    <div>No users were found.</div>
}
else
{
    <table>
        <thead>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
                <th>E-mail</th>
                <th>Role</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var user in Model) {
                <tr>
                    <td>@user.FirstName</td>
                    <td>@user.LastName</td>
                    <td>@user.Email</td>
                    <td>@user.UserRole.UserRoleName</td>
                </tr>
            }
        </tbody>
    </table>
}

If you run now the site, you will see a table with all the users in the DB.

3. The repository pattern Go top

As defined by Martin Fowler:

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.

So, the repository pattern is used to create an abstraction between the business layer and the data access layer so that the business layer doesn’t have to know anything about the underlying data access techniques be it static files, external services, ADO.NET, Entity Framework or NHibernate. The main advantages that come from this abstraction are:

  • Helps to separate the application layers providing a simplified model for obtaining/persisting data.
  • Makes it easier to implement unit testing because it provides an easy way of defining test repositories that create in-memory dummy collections and use them for testing instead of accessing the real data source.

You can find a complete explanation of the Repository Pattern on MSDN.

Below is an interesting recommendation taken from the book “Domain-Driven Design: Tackling Complexity in the Heart of Software” that I added here because I think it’s a good recommendation:

Leave transaction control to the client. Although the REPOSITORY will insert into and delete from the database, it will ordinarily not commit anything. It is tempting to commit after saving, for example, but the client presumably has the context to correctly initiate and commit units of work. Transaction management will be simpler if the REPOSITORY keeps its hands off.

After this brief introduction, let’s use the repository pattern in our example. For the moment, in order to keep things simple, we will define only a data retrieval method and later, in the next post, we will add data persistence methods as we will define the add/edit/delete user functionality.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace HelloWorld.Code.DataAccess
{
    /// <summary>
    /// Base repository class
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IRepository where T : class
    {
        /// <summary>
        /// Return a list.
        /// </summary>
        /// <param name="filters">A lambda expression used to filter the result.</param>
        /// <param name="sorting">The sort expression, example: ColumnName desc</param>
        /// <param name="includeList">The list of related entities to load.</param>
        /// <returns></returns>
        IList Search(Expression<Func<T, bool>> filters, string sorting, List includeList);
    }
}

Before building the basic repository class, we will add the System.Linq.Dynamic library using NuGet. We need it because we will create dynamic Linq queries inside our repositiries starting from the input parameters. Below is the code for the default repository class:

using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Linq.Dynamic;

namespace HelloWorld.Code.DataAccess
{
    public class Repository<T> : IRepository<T> where T : class
    {
        internal MvcDemoEntities Context;
        public Repository(MvcDemoEntities context)
        {
            Context = context;
        }

        public IList<T> Search(Expression<Func<T, bool>> filters, string sorting, List<string> includeList)
        {
            using (var context = new MvcDemoEntities())
            {
                return GetQuery(context, filters, sorting, includeList).ToList();
            }
        }

        /// <summary>
        /// Method used to build the query that will reflect the filter conditions and the sort expression.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="filters"></param>
        /// <param name="sorting"></param>
        /// <param name="includeList"></param>
        /// <returns></returns>
        protected IQueryable<T> GetQuery(ObjectContext context, Expression<Func<T, bool>> filters, string sorting, List<string> includeList)
        {
            var objectSet = context.CreateObjectSet<T>();
            if (string.IsNullOrEmpty(sorting))
                sorting = objectSet.EntitySet.ElementType.KeyMembers.First().Name; //consider the PK as the default sort column

            var objectQuery = (ObjectQuery<T>)objectSet;
            if (includeList != null)
            {
                foreach (var include in includeList)
                {
                    objectQuery = objectQuery.Include(include);
                }
            }

            var query = objectQuery.Where(filters == null ? t => 1 == 1 : filters);
            query = query.OrderBy(sorting);
            return query;
        }
    }
}

Now, we will change also the controller method to use the repository.

public ActionResult Index()
{
	using (var context = new MvcDemoEntities())
	{
		var repository = new Repository<User>(context);
		var list = repository.Search(u => u.Deleted == false, null, new List{ "UserRole"});
		return View(list);
	}
}

The down-side of this implementation is that the controller has to provide a context, meaning that it’s still tightly coupled with the DAL. You can say that we could move the creation of the context inside the repository. This is true but contradicts with the recommendation mentioned just a little before: “Leave transaction control to the client”. It’s true that for the moment we are only retrieving data, so we are not concerned about transactions, but soon we will need to add in our repository Add,Update and Delete methods. In order to remove this problem we will use the Unit of Work pattern.

4. The unit of work pattern Go top

As defined by Martin Fowler:

Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. […] A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you’re done, it figures out everything that needs to be done to alter the database as a result of your work.

And, as usual, some more references to MSDN for further reading:

Let’s modify our code to include also this pattern. The code for the UnitOfWork class is:

using System;

namespace HelloWorld.Code.DataAccess
{
    public class UnitOfWork : IDisposable
    {
        #region Repositories

        protected IRepository<User> UserRepository;
        public IRepository<User> GetUserRepository
        {
            get { return UserRepository ?? (UserRepository = new Repository<User>(Context)); }
        }

        #endregion

        #region UnitOfWork

        protected MvcDemoEntities Context;
        private bool _disposed;

        public UnitOfWork()
        {
            Context = new MvcDemoEntities();
            _disposed = false;
        }

        public void Save()
        {
            Context.SaveChanges();
        }

        public void Dispose()
        {
            if(!_disposed)
            {
                Context.Dispose();
                _disposed = true;
            }

            GC.SuppressFinalize(this);
        }

        #endregion
    }
}

The controller Index method will use the UnitOfWork class instead of crating a context and passing it to the repository constructor:

public ActionResult Index()
{
	var unitOfWork = new UnitOfWork();
	var repository = unitOfWork.GetUserRepository;
	var list = repository.Search(u => u.Deleted == false, null, new List { "UserRole" });
	return View(list);
}

5. Adding a pager to the list Go top

List with paging
In order to do this we have to:

  1. Add a new method to the repository for returning data in a paged manner.
  2. Add a pager in the View so the user can browse between the pages.

Because each of these topics it’s complex enough to be treated independently, I wrote two other posts to serve as a helping guide. First, let’s apply the instructions described in: Retrieve paged lists from DAL. Then we will integrate in our solution the pager described in: ASP.NET MVC – Pager HtmlHelper extention. In the end our controller Index method looks like this:

public ActionResult Index(int page = 1)
{
	var unitOfWork = new UnitOfWork();
	var repository = unitOfWork.GetUserRepository;
	var pagedList = repository.Search(u => !u.Deleted, null, new List { "UserRole" }, page, 5);

	return View(pagedList);
}

The view was changed to include the pager:

@using HelloWorld.Code.Util
@model HelloWorld.Code.DataAccess.Paging.IPagedList
@{
    ViewBag.Title = "Users list";
}
@if (!Model.CurrentPage.Any())
{
    <div>No users were found.</div>
}
else
{
    <table>
        <thead>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
                <th>E-mail</th>
                <th>Role</th>
            </tr>
        </thead>
        <tbody>
        @foreach (var user in Model.CurrentPage) {
            <tr>
                <td>@user.FirstName</td>
                <td>@user.LastName</td>
                <td>@user.Email</td>
                <td>@user.UserRole.UserRoleName</td>
            </tr>
        }
        </tbody>
    </table>
    @Html.PagedListPager(Url, new PagerHtmlRenderer(
                currentPageNumber: Model.CurrentPageNumber,
                pageSize: Model.PageSize,
                totalNumberOfItems: Model.TotalNumberOfItems,
                actionName: "Index",
                controllerName: "Users",
                routeValues: new RouteValueDictionary() { { "page", "" } },
                pageRouteValueName: "page"))
}

6. Allow sorting on the list Go top

We want to allow the user to sort the list by first name and last name. To enable this we first modify the controller action to receive two new parameters, the sort column name and the sort direction.

public ActionResult Index(int page = 1, string sort = "", string direction = "asc")
{
	var unitOfWork = new UnitOfWork();
	var repository = unitOfWork.GetUserRepository;
	var pagedList = repository.Search(u => !u.Deleted, string.IsNullOrEmpty(sort) ? null : string.Format("{0} {1}", sort, direction), new List { "UserRole" }, page, 5);

	ViewBag.Sort = sort;
	ViewBag.Direction = direction;

	return View(pagedList);
}

We pass the sorting information to the repository search method and also to the view: ViewBag.Sort, ViewBag.Direction. For the columns that allow sorting, the view will display links in the header. Each link will indicate the column name and the sort direction to apply (ascending for the first click and descending for the second). So, in the view, we will replace the table headers for ‘First Name’ and ‘Last Name’ with:

@Html.ActionLink("First Name", "Index", "Users",
	new RouteValueDictionary
		 {
				{ "page", Model.CurrentPageNumber },
				{ "sort", "FirstName" },
				{ "direction", ViewBag.Sort == "FirstName" ? (ViewBag.Direction == "asc" ? "desc" : "asc") : "asc" }
		 }, null)@Html.ActionLink("Last Name", "Index", "Users",
	new RouteValueDictionary
		{
			{ "page", Model.CurrentPageNumber },
			{ "sort", "LastName" },
			{ "direction", ViewBag.Sort == "LastName" ? (ViewBag.Direction == "asc" ? "desc" : "asc") : "asc" }
		}, null)

7. Add list filters Go top

In this step we will add a text box and a search button to allow the user to filter the list by name and/or surname. These controls will be placed in the view inside a form so we are able to post the data to the server when the user click the submit button.

@using (Html.BeginForm("Index", "Users", FormMethod.Post))
{
    @:Filter by name: <input type="text" name="filter" />
    <input type="submit" name="search" value="Search" />
}

In the controller we will get the value and apply the filter:

public ActionResult Index(int page = 1, string sort = "", string direction = "asc", string filter = "")
{
	var unitOfWork = new UnitOfWork();
	var repository = unitOfWork.GetUserRepository;
	var pagedList =
		repository.Search(
			u =>
			!u.Deleted &&
			(string.IsNullOrEmpty(filter) || u.FirstName.Contains(filter) || u.LastName.Contains(filter)),
			string.IsNullOrEmpty(sort) ? null : string.Format("{0} {1}", sort, direction),
			new List {"UserRole"}, page, 5);

	ViewBag.Sort = sort;
	ViewBag.Direction = direction;
	ViewBag.Filter = filter;

	return View(pagedList);
}

Now, we are able to filter the page but, if we filter the page and then we sort or change the page number the filter will be lost. To solve this we need to remember the filter value in our page number and sort column links so we will pass the filter value in the RouteValueDictionary for both sorting and paging.

8. Create a base view model for lists that support sorting, filtering and paging Go top

Displaying lists that allow sorting, filtering and paging is a common and repetitive task inside applications so, the next thing to do, is to create a basic view model that will allow us to reuse as much code as possible. The generic model should allow us to set:

  • the repository
  • the page size and the current page number
  • the sort expression

Then we will use this view model as the base class for our lists and we will define, inside the custom list classes, the filters to apply. We want the filters to be easy to define, so we will create an attribute that, when applied to a property will add filtering functionality to it. The main classes and their roles will be:

  • ListViewModel – provides basic paging, sorting and filtering,
  • ListViewFilterAttribute – the ListViewModel will get all the properties marked with the ListViewFilterAttribute and create ListViewFilter objects,
  • ListViewFilter – will be used to understand:
    • what’s the filter query string parameter name in the URL (in our example we will keep the filter values in the URL),
    • what’s the filter property name,
    • the value of the filter.

I will explain the code starting from how the base class will be used. Here is how the user list model will look like:

public class UsersList : ListViewModel<User>
{
	#region Filters

	[DisplayName("First name")]
	[ListViewFilter("fn")]
	public string FirstName { get; set; }

	[DisplayName("Last name")]
	[ListViewFilter("ln")]
	public string LastName { get; set; }

	public override Expression<Func<User, bool>> FilterCondition
	{
		get
		{
			return
				(u =>
				 (string.IsNullOrEmpty(FirstName) || u.FirstName.Contains(FirstName.Trim())) &&
				 (string.IsNullOrEmpty(LastName) || u.LastName.Contains(LastName.Trim())));
		}
		set { }
	}

	#endregion

	public UsersList()
	{       
		var unitOfWork = new UnitOfWork();
		Repository = unitOfWork.GetUserRepository;            
		IncludeList.Add("UserRole");
		PageSize = 5;
	}
}

In the constructor we set a default repository, the related entityto load (UserRole) and the default page size. Of course this values could be set from outside the class but the constructor it’s a convenient place for default values. Apart from this, we have two filters defined in the form of properties decorated with the attribute [ListViewFilter(“FilterUrlParameterName”)]. The filter sets the name of the query string parameter where to store the filter value between post-backs. Just to give a well known example, this is more or less the same idea as in a Google search. Below is the code for the attribute:

[System.AttributeUsage(System.AttributeTargets.Property)]
public class ListViewFilterAttribute : System.Attribute
{
	public string UlrParameterName { get; set; }

	public ListViewFilterAttribute(string ulrParameterName)
	{
		UlrParameterName = ulrParameterName;
	}
}

Using this attribute we will be able to populate (using reflection), a collection of ListViewFilter objects:

public class ListViewFilter
{
	public string PropertyName { get; set; }

	public string UrlFilterName { get; set; }

	public object Value { get; set; }
}

And the main class that actually supports paging, sorting and filtering is:

public class ListViewModel<T> : IListViewModel where T : class
{
	#region Properties

	/// <summary>
	/// The number of elements to display in the list. Default is 10.
	/// </summary>
	public int PageSize
	{
		get { return _pageSize < 1 ? 10 : _pageSize; }
		set { _pageSize = value; }
	}
	private int _pageSize;

	/// <summary>
	/// The current page to be displayed. Default is 1.
	/// </summary>
	public int CurrentPageNumber
	{
		get { return _currentPageNumber < 1 ? 1 : _currentPageNumber; }
		set { _currentPageNumber = value; }
	}
	private int _currentPageNumber;

	/// <summary>
	/// The column on which the sorting is applied.
	/// </summary>
	public string SortColumn { get; set; }

	/// <summary>
	/// The direction of the sorting. Default is ascending.
	/// </summary>
	public ListSortDirection SortDirection
	{
		get { return _sortingDirection; }
		set { _sortingDirection = value; }
	}
	private ListSortDirection _sortingDirection = ListSortDirection.Ascending;

	/// <summary>
	/// Get the current sort expression.
	/// </summary>
	public string SortExpression
	{
		get
		{
			return string.IsNullOrEmpty(SortColumn) ? string.Empty
					   : string.Format("{0} {1}", SortColumn, SortDirection == ListSortDirection.Ascending ? "asc" : "desc");
		}
	}

	/// <summary>
	/// Used to specify the related objects to include in the result.
	/// </summary>
	public List<string> IncludeList { get; private set; }

	/// <summary>
	/// A lambda expression used to filter the list
	/// </summary>
	/// <returns></returns>
	public virtual Expression<Func<T, bool>> FilterCondition { get; set; }

	/// <summary>
	/// The repository used to obtain the data
	/// </summary>
	public IRepository<T> Repository { get; set; } 

	#endregion

	#region Constructor

	public ListViewModel()
	{
		IncludeList = new List<string>();
	}

	#endregion

	#region Fill list & Paged list

	/// <summary>
	/// Returns the elements for the current page. The list is filtered and sorted.
	/// </summary>
	/// <param name="refresh">The refresh parameter is used to force the data retrieval in care the list was already filled.</param>
	/// <returns></returns>
	protected IPagedList<T> GetPagedList(bool refresh)
	{
		if (refresh || _pagedList == null)                           
			 _pagedList = Repository.Search(FilterCondition, SortExpression, IncludeList, CurrentPageNumber, PageSize);                
		
		return _pagedList;
	}

	public IPagedList<T> PagedList
	{
		get { return GetPagedList(false); }
		set { _pagedList = value; }
	}
	private IPagedList<T> _pagedList;

	/// <summary>
	/// Returns all the elements that match the filter condition. The list is sorted.
	/// </summary>
	/// <param name="refresh">The refresh parameter is used to force the data retrieval in care the list was already filled.</param>
	/// <returns></returns>
	protected IList<T> GetList(bool refresh)
	{
		if (refresh || _list == null)            
			_list = Repository.Search(FilterCondition, SortExpression, IncludeList);            

		return List;
	}

	public IList<T> List
	{
		get { return GetList(false); }
		set { _list = value; }
	}
	private IList<T> _list;

	#endregion

	#region Public methods

	/// <summary>
	/// Helper methods used for setting the current page number, the column to sort on and the sorting direction.
	/// </summary>
	/// <param name="currentPageNumber">The current page to be displayed.</param>
	/// <param name="sortColumn">The column on which the sorting is applied.</param>
	/// <param name="sortDirection">The direction of the sorting. Default is ascending.</param>
	/// <param name="requestParameters">Used to populate the filters.</param>
	public void SetParameters(int currentPageNumber, string sortColumn, string sortDirection, NameValueCollection requestParameters)
	{
		CurrentPageNumber = currentPageNumber;
		if (!string.IsNullOrEmpty(sortColumn))
		{
			SortColumn = sortColumn;
			if (string.Compare(sortDirection, "desc", StringComparison.InvariantCultureIgnoreCase) == 0 || string.Compare(sortDirection, "descending", StringComparison.InvariantCultureIgnoreCase) == 0)
				SortDirection = ListSortDirection.Descending;
		}

		//Populate the filter values
		if(requestParameters != null)
		{
			var filters = GetFilters();
			foreach (var filter in filters)
			{
				filter.Value = requestParameters[filter.UrlFilterName] ?? requestParameters[filter.PropertyName];
			}
			SetFilters(filters);
		}
	}

	/// <summary>
	/// Get a list of name and value for all the properties inside this class that are marked with the ListViewFilterAttribute.
	/// </summary>
	/// <returns></returns>
	public List<ListViewFilter> GetFilters()
	{
		return _filters ?? (_filters = (from p in GetType().GetProperties()
										let attr = p.GetCustomAttributes(typeof (ListViewFilterAttribute), true)
										where attr.Length == 1
										select new ListViewFilter
												   {
													   PropertyName = p.Name,
													   UrlFilterName =
														   ((ListViewFilterAttribute) (attr.First())).
														   UlrParameterName,
													   Value = p.GetValue(this, null)
												   }).ToList());
	}
	private List<ListViewFilter> _filters;
	
	/// <summary>
	/// Populate the model filter properties using the filter values.
	/// </summary>
	/// <param name="filters"></param>
	public void SetFilters(List<ListViewFilter> filters)
	{
		var type = GetType();
		foreach (var filter in filters)
		{
			var property = type.GetProperty(filter.PropertyName);
			if(property != null)
				property.SetValue(this, filter.Value, null);
		}
	}

	#endregion
}

Now we will use the UsersList in our controller:

public ActionResult Index(UsersList model, int page = 1, string sort = "", string direction = "")
{
	model.SetParameters(page, sort, direction, Request.Params);
	return View(model);
}

The view is a little modified too:

@using System.ComponentModel
@using HelloWorld.Code.Util
@model HelloWorld.Models.UsersList
@{
    ViewBag.Title = "Users list";
}
@using (Html.BeginForm("Index", "Users", FormMethod.Post))
{            
    @Html.LabelFor(model => model.FirstName) 
    @Html.TextBoxFor(model => model.FirstName)
    <br/> 
    @Html.LabelFor(model => model.LastName) 
    @Html.TextBoxFor(model => model.LastName)   
    <input type="submit" name="Search" value="Search"/>
}
@if (!Model.PagedList.CurrentPage.Any())
{
    <div>No users were found</div>
}
else
{
    <table>
        <thead>
            <tr>
                <th>@Html.ActionLink("First name", "Index", "Users", 
                                    Url.GetRouteValueDictionaryForList(
                                        Model, 
                                        sortColumn: "FirstName", 
                                        direction: string.Compare(Model.SortColumn, "FirstName", 
                                        StringComparison.CurrentCultureIgnoreCase) != 0 ? "asc" : Model.SortDirection == ListSortDirection.Ascending ? "desc" : "asc"), null)</th>
                <th>@Html.ActionLink("Last name", "Index", "Users", 
                                    Url.GetRouteValueDictionaryForList(
                                        Model, 
                                        sortColumn: "LastName", 
                                        direction: string.Compare(Model.SortColumn, "LastName", StringComparison.CurrentCultureIgnoreCase) != 0 ? "asc" : Model.SortDirection == ListSortDirection.Ascending ? "desc" : "asc"), null)</th>
                <th>E-mail</th>
                <th>Role</th>
            </tr>
        </thead>
        @foreach (var user in Model.PagedList.CurrentPage)
        {
            <tr>
                <td>@user.FirstName</td>
                <td>@user.LastName</td>
                <td>@user.Email</td>
                <td>@user.UserRole.UserRoleName</td>
            </tr>
        } 
    </table>
@Html.PagedListPager(Url, new PagerHtmlRenderer(
                                                currentPageNumber: Model.PagedList.CurrentPageNumber,
                                                pageSize: Model.PagedList.PageSize,
                                                totalNumberOfItems: Model.PagedList.TotalNumberOfItems,
                                                actionName: "Index",
                                                controllerName: "Users",
                                                routeValues: Url.GetRouteValueDictionaryForList(Model),
                                                pageRouteValueName: "page"))
    
}

Here you can notice an extension for the UrlHelper class Url.GetRouteValueDictionaryForList that was created to easily build a RouteValueDictionary that is used to create the URLs for paging and sorting so that the filters, the sort criteria and the paging is not lost when the page is refreshed.

public static RouteValueDictionary GetRouteValueDictionaryForList(this UrlHelper urlHelper, IListViewModel model, string sortColumn = "", string direction = "")
{
	var dictionary = new RouteValueDictionary
						 {
							 {"page", model.CurrentPageNumber},
							 {"sort", string.IsNullOrEmpty(sortColumn) ? model.SortColumn : sortColumn},
							 {"direction", string.IsNullOrEmpty(direction) ? (model.SortDirection == ListSortDirection.Ascending ? "asc" : "desc") : direction}                                    
						 };
	foreach (var filter in model.GetFilters())
	{
		dictionary.Add(filter.UrlFilterName, filter.Value);
	}

	return dictionary;
}

9. Summary Go top

In this post we created a list that supports paging, sorting and filtering. In the end we extracted the reusable parts in a generic class that we can use as the base for our lists.

You can download the complete source code from Codeplex.

Introduction to MVC

Contents:

  1. Basic concepts
  2. Advantages of MVC
  3. From HTTP to Routes
  4. A Hello World application
  5. Adding a master page
  6. How to create a ‘Contact us’ form

1. Basic concepts

The Microsoft ASP.NET Model-View-Controller (MVC) is a web development framework that comes as an alternative to the standard ASP.NET Web Forms model. This alternative is based on a common software architecture design pattern called MVC.

A design pattern is a general reusable solution to a commonly occurring problem and can be seen as a best practice in the field.

The main purpose of MVC is to separate an application into 3 distinct parts so that the complexity of each part is decreased. This idea is very similar to the divite et impera concept. In addition to this separation MVC defines also the interactions between these 3 components:

  • Model – contains the data and the business logic,
  • View – defines the user interface and displays the model data,
  • Controller – connects the view and the model and controls the input handling.

mvc In simple words, the application flow can be summarized like this: when an event occurs (e.g. a user requests a page), the controller is invoked and is its responsibility to understanding what kind of event occurred, what action to do (e.g. update some data in a model) and what view to render as a response to that event. Except for the very simple cases, the view needs to receive a model as an input in order to know what content to render. If this is the case, the controller is also responsible for instantiating the correct model and passing it to the view.

This separation into components is called in the literature separation of concerns and it helps to minimize the impact a modification in one component has on the other components. For example, if we need to change the way the data is displayed, it should be enough to change the view without having to modify the business logic inside the model.

2. Advantages of MVC

The main advantage that comes from using the MVC pattern is that the application is divided into components avoiding the mixing of the code into a single big monolithic application. Apart from this we have another set of advantages that come rather from the way the new Microsoft MVC framework is written than from the pure concept of MVC. After all MVC is only an architectural pattern, a way of structuring an application. Nothing more and nothing less!

As everybody already knows, with the Web Forms, Microsoft tried to wrap the HTML controls into .net objects that are able to automatically remember between post backs the values of their properties by making use of the view state. This has 2 negative impacts:

  1. the view state is ugly and unacceptably big for medium to large pages and
  2. the HTML that the .net controls render is usually hard to control and change. For example the id of the elements is unnecessarily long. This was fixed with the introduction of the static client id mode but still the name attribute of the controls keeps the old format.

With the MVC framework the view state is removed and we have complete control on the rendered HTML.

Another advantage that comes from the way in which the MVC web development framework is implemented and not from the MVC pattern itself is that the complicated Page Life Cycle set of events was removed and we now have a simplified execution pipeline. The main purpose of the Page Life Cycle was to hide the way in which the communication between the client and the server happens and automatically bind user interface events, like the click of a button, with server side methods that would handle that event, creating the impression that the web interaction is event-driven and similar to the way Windows applications work. Though this was a nice idea in the beginning, time proved that it adds a lot of complexity:

  • understanding the Page Life Cycle is not a trivial job, and
  • it tends to make the pages monolithic, for example, in order to execute a button’s click event handler first the page initialization and loading events are executed, making all those event methods tightly coupled.

So, with no .net wrapper controls, no view state and no page life cycle the MVC framework doesn’t try to hide the way HTTP anf HTML work and offers a more elegant and simple development framework. Now, since we no longer have the abstractions provided by the Web Forms it’s better to get started by making sure all the basic concepts about how the web actually works are clear.

3. From HTTP to Routes

The Hypertext Transfer Protocol (HTTP) is an application level response-request protocol that defines the set of rules used by 2 participants (a client and a server) in order to communicate. Or, a more memorable definition taken from this nice book RESTful Web Services: “HTTP is the one thing that all ‘animals’ on the programmable web have in common.”

When a page is requested, the browser sends a request to the web server. The first 2 lines of a request look like this:Fiddler
Note: For inspecting the request text I used Fiddler which it’s a very useful tool that can help you see all the HTTP(S) traffic between your computer and the Internet.

The first word in the request is the HTTP method (in the example: GET). It is followed by the Uniform Resource Identifier (URI) of the resource to be retrieved (http://wordpress.com/) and the HTTP version to be used for processing the command (HTTP/1.1). The second line (Host: wordpress.com) identifies the name of the website and is used by the servers that host multiple websites under a single IP address to understand what is the website the page belongs to.

So, a request message from a client to a server includes (within the first line of that message) the method to be applied to the resource, the identifier of the resource, and the protocol version to use.

The HTTP methods, also known as HTTP verbs, are tokens that indicate the action to be performed on a resource and are very important for us because we will use them together with the URIs to map an incoming browser request to a particular MVC controller action.

Below is a list of possible methods, as defined by the World Wide Web Consortium (W3C):

  1. OPTIONS – represents a request for information about the communication options available on the request/response chain identified by the request URI. This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval. Responses to this method are not cacheable. Simply said, you can use OPTIONS to check if a server allows a particular command and avoid wasting network bandwidth trying to send an unsupported request.
  2. GET – means: retrieve whatever information is identified by the request URI.
  3. HEAD – is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method can be used for testing hypertext links for validity, accessibility, and recent modification. HEAD is typically used to verify that a resource didn’t change since the browser cached it.
  4. POST – requests that the origin server accepts the entity enclosed in the request as a new subordinate of the resource identified by the request URI. The actual function performed by the POST method is determined by the server and is usually dependent on the request URI. POST is designed to allow a uniform method to cover the following functions:
    • Annotation of existing resources;
    • Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
    • Providing a block of data, such as the result of submitting a form, to a data-handling process;
    • Extending a database through an append operation.
  5. PUT – requests that the enclosed entity be stored under the supplied request URI. If the request URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the request URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. So, simply said, PUT allows a client to add a resource to the server at the specified URI. If the user has permissions, the server creates the file specified in the URI and copies the request body to the newly created file.
  6. DELETE – requests that the origin server deletes the resource identified by the request URI.
  7. TRACE – is used for testing or diagnostics information and allows a client to see what is being received at the other end of the request chain.
  8. CONNECT – is reserved to be used with a proxy that can dynamically switch to being a tunnel (e.g. SSL tunneling).

Notes:
– The HTTP methods are case-sensitive.
– As specified by the W3C the methods GET and HEAD MUST be supported by all general-purpose servers. All other methods are OPTIONAL; however, if they are implemented, they MUST be implemented with the semantics previously explained.

Now, after so much talking is time to move on and do something practical, and that obviously is a Hello World application.

4. A Hello World application

Let’s start with the classic ‘File’ -> ‘New’ -> ‘Project’ but this time we will create an ‘ASP.NET MVC 4 Web Application’ :D.
Create a new MVC project
The next step is to select a project template. Please choose the empty project template and then make sure that the selected view engine is Razor.
Select the project templateA view engine is a pluggable module that provides a way to insert dynamic blocks into a template and is able to render that template to HTML or whatever content type it is designed to emit. There are 2 available view engines:

  1. one is the old ASPX that uses code blocks separated by <% tags %>,
  2. the other one is Razor that uses the character @ in order to indicate the beginning of a code block and doesn’t require the explicit closing of the code block.

Razor is not a new language, and it’s actually very easy and intuitive, it helps to create a template that contains static HTML and C# code blocks that can render dynamic content. So, we will use Razor for creating the Views.

Looking at the structure of our new empty project we see that we have a dedicated folder for the controllers, one for the models and one for the views plus another folder called App_Start that contains a RouteConfig.cs file. This file is used to define the allowed URLs and the association between an URL and the controller that will process that request.
Defining the routesEach route must:

  • have an unique name, otherwise we will receive an error
  • and must define the structure of the allowed URLs. In the example, the URLs are composed by 3 parts: the controller name {controller}, the name of the method inside the controller that will handle the request {action} and an identifier {id}.

The last line defines the default values to be used in case a part of the URL is missing. For example, if the root of the web site is requested (http://site-name/) the default controller that will be called is ‘Home’ and the default action is ‘Index’. The last parameter is optional so it will be empty.

Now, let’s add a controller called ‘Home’ in order to see how the routing works. In Solution Explorer right click on the Controller folder -> Add -> Controller. Name the new controller ‘HomeController’. Note that the naming convention for controllers is: Name + Controller and using another name, for example only ‘Home’ will make the controller unreachable.
Add a new controllerLet’s replace the default implementation of the index method with the following:

        public string Index()
        {
            return "Hello world!";
        }

Now, if we run the site, we will see the text ‘Hello world!’. If we want to use also the {id} part of the URL we can change the Index action like this:

        public string Index(string id)
        {
            return string.IsNullOrEmpty(id) ? "Hello world!" : string.Format("Hello world! Your id is: {0}.", id);
        }

So calling http://site-name/home/index/Hey we will see the text: ‘Hello world! Your id is: Hey.’. Note that here we have another naming convention, that is, the name of the input parameter matches the 3rd part of our route {id}, if the name of the parameter would have been different the displayed text would be ‘Hello world!’.

Next thing to do is to add a view file for returning HTML content rather than a string. A fast way of doing this is to right click on the action name and select ‘Add View’:
Add a new view
Let’s replace the default content of the view with the following one:

@{
    ViewBag.Title = "Home";
}
<h2>Hello from the view.</h2>
@if(!string.IsNullOrEmpty(ViewBag.Id))
{
    <text>Your id is: @ViewBag.Id.</text>
}

<i>@ViewBag.Message</i>

and update the Index action method like this:

        public ActionResult Index(string id)
        {
            ViewData["Message"] = "This is a string created in the controller";
            ViewBag.Id = id;
            return View();
        }

The first thing to notice here is the ViewData which is a dictionary that we can use to pass data from the controller to the view. The ViewBag is a dynamic type created as a wrapper over the ViewData that allows us to use dynamic properties instead of strings for accessing the objects in the ViewData. As you can see in the last line of the view, we can access the value in ViewData[“Message”] by calling @ViewBag.Message.

The last line in the action method tells the rendering engine to return the view associated with this action. To find the correct view, another naming convention is applied. The implicit view should be located under the Views folder in a sub-folder with the same name as the controller (‘Home’) and the name of the file should match the name of the action method (‘Index.cshtml’). Note that it is possible to render a different view, for this we have to pass the name of the view as a parameter to the View method.

5. Adding a master page

Our next goal is to define a common layout for the entire site. To do this we have to add a file called _ViewStart.cshtml (or _ViewStart.vbhtml for VB) under the Views folder of the project.

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

_ViewStart.cshtml indicates what file to use as a master page. It is recommended that we store the common layout files under the folder ‘~/Views/Shared’ and that we prefix the name of those files with an underscore, like _Layout.cshtml so, let’s stick to the recommendations and add the folder Shared and the file _Layout.cshtml with the following content:

<!DOCTYPE html>
<html>
    <head>
       <title>MVC - @ViewBag.Title</title>
        <style type="text/css">            
            body { margin:0; padding:0; }
            #header_container { background: black; color: white; height:60px; left:0; position:fixed; width:100%; top:0; }
            #header { line-height:60px; margin:0 auto; padding-left: 50px; font-weight: bold;font-size: 18pt;}
            #container { margin:0 auto; overflow:auto; padding:80px 0; width:940px; }
            .control-label{ width: 120px;display: inline-block;}
        </style>
    </head>
    <body>       
        <div id="header_container">
            <div id="header">
                <i>=(^.^)=</i>
            </div>
        </div>              
        <div id="container">
            <div id="content">
                @RenderBody()    
            </div>
        </div>
        @RenderSection("scripts", required: false)
    </body>
</html>

The code it’s pretty straight forward:
– @RenderBody() will render the content of a view that is not within any named sections,
– @RenderSection(“scripts”, required: false) – if the view contains a section called scripts it renders the content of that section.

To define the scripts section inside Index.cshtml just add at the end of the file the following:

@section scripts{
<script type="text/javascript">
    document.getElementById("header").innerHTML += ' MVC'
</script>
}

Now, that we have such a nice layout plus some text inserted via JavaScript, let’s add a ‘Contact us’ page that will allow our visitors to praise our great design. We will save all the positive comments in the DB so we can show to the managers how great we are!

6. How to create a ‘Contact us’ form

Contact Us Form
First thing first, let’s create a DB and use Entity Framework to access it. Our DB will have these 2 tables:
The DB structure I named the new DB [MvcDemo]. Below is the script for creating the tables:

USE [MvcDemo]
GO
/****** Object:  Table [dbo].[ContactReasons]    Script Date: 03/12/2013 21:51:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[ContactReasons](
	[ContactReasonId] [int] IDENTITY(1,1) NOT NULL,
	[ContactReasonText] [nvarchar](100) NOT NULL,
 CONSTRAINT [PK_ContactReasons] PRIMARY KEY CLUSTERED 
(
	[ContactReasonId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object:  Table [dbo].[Messages]    Script Date: 03/12/2013 21:51:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Messages](
	[MessageId] [int] IDENTITY(1,1) NOT NULL,
	[Name] [nvarchar](50) NOT NULL,
	[Email] [nvarchar](50) NOT NULL,
	[ContactReasonId] [int] NULL,
	[Subject] [nvarchar](100) NOT NULL,
	[Message] [text] NOT NULL,
 CONSTRAINT [PK_Messages] PRIMARY KEY CLUSTERED 
(
	[MessageId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object:  ForeignKey [FK_Messages_ContactReasons]    Script Date: 03/12/2013 21:51:40 ******/
ALTER TABLE [dbo].[Messages]  WITH CHECK ADD  CONSTRAINT [FK_Messages_ContactReasons] FOREIGN KEY([ContactReasonId])
REFERENCES [dbo].[ContactReasons] ([ContactReasonId])
GO
ALTER TABLE [dbo].[Messages] CHECK CONSTRAINT [FK_Messages_ContactReasons]
GO

USE [MvcDemo];
SET NOCOUNT ON;
SET XACT_ABORT ON;
GO
SET IDENTITY_INSERT [dbo].[ContactReasons] ON;
BEGIN TRANSACTION;
INSERT INTO [dbo].[ContactReasons]([ContactReasonId], [ContactReasonText])
SELECT 1, N'Positive remark' UNION ALL
SELECT 2, N'Negative remark'
COMMIT;
RAISERROR (N'[dbo].[ContactReasons]: Insert Batch: 1.....Done!', 10, 1) WITH NOWAIT;
GO
SET IDENTITY_INSERT [dbo].[ContactReasons] OFF;
GO

We will use Entity Framework to connect to the DB, so let’s create a new EF file ‘MvcDemoDb.edmx’ under the folder ‘~/Code/DataAccess’. This is how the solution looks now:
Project structure after adding EFNow, let’s add a new action method in the Home controller called ContactUs and a new view for this action. This time, when creating the view, tick the ‘Create a strongly-typed view’ check-box and enter as the model class: HelloWorld.Code.DataAccess.Message.
Create a contact us viewLet’s add inside the view the code below:

@model HelloWorld.Code.DataAccess.Message

@{
    ViewBag.Title = "Contact Us";
}

<h2>Contact Us</h2>
<hr/>
@using (Html.BeginForm("ContactUs", "Home", FormMethod.Post))
{
    @Html.LabelFor(model => model.Name, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Name)
    <br/>
    @Html.LabelFor(model => model.Email, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Email)
    <br/>
    @Html.LabelFor(model => model.ContactReasonId, new { @class = "control-label" })
    @Html.DropDownListFor(model => model.ContactReasonId, new List<SelectListItem>())
    <br/>
    @Html.LabelFor(model => model.Subject, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Subject)
    <br/>
    @Html.LabelFor(model => model.Message1, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Message1)
    <br/>
    <input type="submit" name="Save" value="Save" />
}

In the above code, the syntax Html.BeginForm is used to create a HTML form element. Here Html is a property of the view and it’s an instance of the HtmlHelper class. The HtmlHelper class provides methods that can be used to generate HTML elements programatically. All the methods of the HtmlHelper class generate HTML and return the result as a string. There are a lot of extension methods for the HtmlHelper class and, in the next posts, I will show how we can create our own custom extensions. So @Html.LabelFor(model => model.Name, new { @class = “control-label” }) will create a label element for the property Name of the model. With new { @class = “control-label” } we create a dictionary object that contains the HTML attributes to set to the element. In our example the rendered HTML will be:

 
<label class="control-label" for="Name">Name</label>

Try to enter some data and press the save button. You will notice that the values entered are not remembered after the post back. This is the expected behavior since we are not using the view state any more. Let’s have a look at the request with Fiddler and see what’s sent to the server.
Inspect the post data with FiddlerSo we have a POST to /Home/ContactUs that results in the action method ContactUs being called. In MVC we have a set of attributes that we can use to restrict what HTTP verbs an action method will handle. We can use [HttpGet] and [HttpPost] to indicate that an action method will be called only for GET/POST requests. Knowing this, we can modify our controller class and add a new custom action that will handle only the POST method, which, in our case, means that the user pressed the save button:

[HttpGet]
public ActionResult ContactUs()
{
	return View(new Code.DataAccess.Message());
}

[HttpPost]
public ActionResult ContactUs(Code.DataAccess.Message message)
{
	return View(message);
}

The new ContactUs method receives a message object that is filled automatically with the data the user inserted. This happens because we used in the view the HtmlHelper method @Html.TextBoxFor that created form elements with the same name as the model properties and now the MVC framework can automatically map the HTML form elements with the model properties based on the name. When rendering the response we will pass this message object back to the View and now we can notice that the data the user entered remains filled also after a post back.

The next thing that we can improve is the text that appears in the labels. For example we want to display ‘Contact reason’ instead of ‘ContactReasonId’. There is a little trick for doing this and it is called the MetadataTypeAttribute. We are going to add in our Models folder a new file and we will extent here the class HelloWorld.Code.DataAccess.Message. We can do this because the Message class is partial which means that we can split the definition of the class in more files. Please make sure that the class name and the namespace name are identical to the one generated by the entity framework model, otherwise you will be creating a new class instead of adding new elements to the existing partial class.

using System.ComponentModel.DataAnnotations;

namespace HelloWorld.Code.DataAccess
{
    [MetadataType(typeof(MessageMetaData))]
    public partial class Message
    {
    }

    public class MessageMetaData
    {
        [Display(Name = "E-mail")]
        public string Email { get; set; }

        [Display(Name = "Contact reason")]
        public int ContactReasonId { get; set; }

        [Display(Name = "Message")]
        public int Message1 { get; set; }
    }
}

The MessageMetaData defines additional attributes for our Message class. This type of helper class is called a buddy class. The display attribute sets the UI text to associate to a property and needs to be declared in a buddy class because, in this way, we don’t have to change the file generated by the EF that will be overridden the next time we will change the EF edmx file. Note that the properties defined in the MessageMetaData class are exactly the same as the ones defined in the Message class that was generated by the entity framework, otherwise the mapping would fail. If we see now the contact us page we can notice that the label text is changed. Next, we will populate the contact reason drop-down. First we define the collection in the model:

public IEnumerable<SelectListItem> ContactReasonsList
{
	get
	{
		using (var context = new MvcDemoEntities())
		{
			return (new[]
					{
						new SelectListItem
							{
								Selected = (ContactReasonId == 0),
								Text = "Select...",
								Value = string.Empty
							}
					}).Union(context.ContactReasons.
									 Select(c => new SelectListItem
									 {
										 Selected = (c.ContactReasonId == ContactReasonId),
										 Text = c.ContactReasonText,
										 Value = c.ContactReasonId.ToString(CultureInfo.InvariantCulture)
									 }));
		}                
	}
}

and then we use it in the view:

@Html.DropDownListFor(model => model.ContactReasonId, Model.ContactReasonsList)

The code above speaks for itself BUT it has a big problem: each time the view is rendered a call to the DB is made to retrieve the contact reasons. So we need to implement a basic caching mechanism. I’ll just put the code here, if you need more details check this post where you can find some explanations.

public static class CacheManager
{
	/// <summary>
	/// Get the list of contact reasons
	/// </summary>
	/// <param name="getFromCache"></param>
	/// <returns></returns>
	public static List<ContactReason> GetContactReasons(bool getFromCache)
	{
		return (List<ContactReason>)GetFromCache("ContactReasons", getFromCache, delegate
		{
			using (var context = new MvcDemoEntities())
			{
				return context.ContactReasons.OrderBy(c => c.ContactReasonText).ToList();
			}
		});
	}

	#region Private methods

	/// <summary>
	/// Helper method for adding/retrieving a value from/to the cache.
	/// </summary>
	/// <param name="cacheKey">The key of the cached item</param>
	/// <param name="f">If the key is not found in the cache execute the f() function to retrieve the object</param>
	/// <returns></returns>
	private static object GetFromCache(string cacheKey, Func<object> f)
	{
		return GetFromCache(cacheKey, true, f);
	}

	/// <summary>
	/// Helper method for adding/retrieving a value from/to the cache.
	/// </summary>
	/// <param name="cacheKey">The key of the cached item</param>
	/// <param name="getFromCache">True if a cached vaersion cand be returned. If false, f() will be used to get the data</param>
	/// <param name="f">If the key is not found in the cache execute the f() function to retrieve the object</param>
	/// <returns></returns>
	private static object GetFromCache(string cacheKey, bool getFromCache, Func<object> f)
	{
		object cachedItem = null;
		if (getFromCache)
			cachedItem = HttpRuntime.Cache[cacheKey];

		if (cachedItem == null)
		{
			cachedItem = f();
			if (cachedItem != null)
				HttpRuntime.Cache.Insert(cacheKey, cachedItem, null, DateTime.Now.AddSeconds(WebConfigSettings.CachingTime), TimeSpan.Zero);
		}

		return cachedItem;
	}

	#endregion
}

Below the modification to be done in the model:

public IEnumerable<SelectListItem> ContactReasonsList
{
	get
	{
		return (new[]
					{
						new SelectListItem
							{
								Selected = (ContactReasonId == 0),
								Text = "Select...",
								Value = string.Empty
							}
					}).Union(CacheManager.GetContactReasons(HttpContext.Current.Request.HttpMethod == "POST").
								 Select(c => new SelectListItem
								 {
									 Selected = (c.ContactReasonId == ContactReasonId),
									 Text = c.ContactReasonText,
									 Value = c.ContactReasonId.ToString(CultureInfo.InvariantCulture)
								 }));              
	}
}

And now the final touch, implementing the save with our preferred business logic (save in the DB only the positive comments). Of course, the business logic goes in the model:

public bool Save(out string message)
{
	try
	{
		using (var context = new MvcDemoEntities())
		{
			if (ContactReasonId == 1) //A positive remark
			{
				context.Messages.AddObject(this);
				message = "Your message was saved successfully. Thank you for your appreciation!";
				context.SaveChanges();
				return true;
			}                   
			message = "We are sorry you were disappointed.";
			return false;                                        
		}
	}
	catch (Exception exp)
	{
		message = "An unexpected error occured.";
		return false;
	}
}

then we call the save method from the controller

[HttpPost]
public ActionResult ContactUs(Code.DataAccess.Message message)
{
	string saveResultMessage;
	ViewBag.SaveResult = message.Save(out saveResultMessage);
	ViewBag.SaveResultMessage = saveResultMessage;
	return View(message);
}

and display the result in the view.

@if (ViewBag.SaveResult != null &amp;&amp; ViewBag.SaveResult)
{
    <span style="color:green;">@ViewBag.SaveResultMessage</span>
}
@if (ViewBag.SaveResult != null &amp;&amp; !ViewBag.SaveResult)
{
    <span style="color:red;">@ViewBag.SaveResultMessage</span>
}

This is it, now we have a completely functional ‘Contact Us’ and we can add a link to it in our default page:

@Html.ActionLink("Contact Us", "ContactUs", "Home")

Download the source code from CodePlex