Introduction
This article shows you how to implement MvcContrib
grid paging, filtering and preserving search URL in ASP.NET MVC3.
Background
MvcContrib
grid provides nice paging interface with column ordering. In the real world, we need to aggregate complex ViewModel
s to present grid view. We should also provide multiple searching filters and keywords to the grid view.
So, I implemented clean PagedViewModel<T>
class to make MvcContrib
Grid paging and filtering simple in ASP.NET MVC3, based on this nice article.
Using the Code
Summary
I added some code and classes to the original source to simplify implementation as follows:
PagedViewModel<T>
containing IPagenation<T>
, AddFilter
methods and sorting/paging information
- Preserving the query URL between list page and view page
Business Layer
Let's use the music store database of http://chinookdatabase.codeplex.com/ for our AlbumServie. (*I attached the mdf with basic connection string in web.config in our MVC project.)
Let's add MVCMusicStoreDB
EF4 model including Album
, Genre
and Artist
tables. We can see the basic entity diagram.
The AlbumViewModel
class is the composite ViewModel
class of Album
, Genre
and Artist
entities. The MvcContrib
Grid uses those Display*
and ScaffoldColumn
attributes in the AutoGenerateColumns()
function.
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace MvcMusicStore.Models
{
public class AlbumViewModel
{
[DisplayName("ID")]
public int AlbumId { get; set; }
[ScaffoldColumn(false)]
public int? GenreId { get; set; }
[DisplayName("Genre")]
public string Genre { get; set; }
[ScaffoldColumn(false)]
public int? ArtistId { get; set; }
[DisplayName("Artist")]
public string Artist { get; set; }
[ScaffoldColumn(false)]
public string AlbumTitle { get; set; }
[DisplayName("Price")]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal AlbumPrice { get; set; }
}
}
Let's build our main business service methods. The first step is adding some basic reading methods in AlbumService
. We don't implement CUD methods and any UoW or Repository layer for simple & quick implementation.
namespace MvcMusicStore.Models
{
public class AlbumService
{
private MvcMusicStoreEntities _context;
public AlbumService()
{
_context = new MvcMusicStoreEntities();
}
public IQueryable<AlbumViewModel> GetAlbumsView()
{
var query = from a in GetAlbums()
select new AlbumViewModel
{
AlbumId = a.AlbumId,
GenreId = a.GenreId,
Genre = a.Genre.Name,
ArtistId = a.ArtistId,
Artist = a.Artist.Name,
AlbumTitle = a.Title,
AlbumPrice = a.Price
};
return query;
}
public AlbumViewModel FindAlbumView(int albumId)
{
return GetAlbumsView().Where(a => a.AlbumId == albumId).Single();
}
public IQueryable<Album> GetAlbums()
{
return _context.Albums;
}
public IQueryable<Genre> GetGenres()
{
return _context.Genres;
}
public IQueryable<Artist> GetArtists()
{
return _context.Artists;
}
public void Save()
{
_context.SaveChanges();
}
}
}
PagedViewModel<T>
PagedViewModel<T>
is the generic container holding all data for grid presentation including search filters. There are also several AddFilter
implementations that register simple filters like search keyword and SelectListFilterViewItem
is for saving SelectList
object with adding viewdata dictionay.
To support fluent filter setting function to the PagedViewModel<T>
, we should implement AddFilter
and Setup
methods.
namespace MvcMusicStore.Models
{
public class PagedViewModel<T>
{ ...
public PagedViewModel<T> AddFilter(Expression<Func<T, bool>> predicate)
{
Query = Query.Where(predicate);
return this;
}
public PagedViewModel<T> AddFilter<TValue>
(string key, TValue value, Expression<Func<T, bool>> predicate)
{
ProcessQuery(value, predicate);
ViewData[key] = value;
return this;
}
public PagedViewModel<T> AddFilter<TValue>
(string keyField, object value, Expression<Func<T, bool>> predicate,
IQueryable<TValue> query, string textField)
{
ProcessQuery(value, predicate);
var selectList = query.ToSelectList(keyField, textField, value);
ViewData[keyField] = selectList;
return this;
}
public PagedViewModel<T> Setup()
{
if (string.IsNullOrWhiteSpace(GridSortOptions.Column))
{
GridSortOptions.Column = DefaultSortColumn;
}
PagedList = Query.OrderBy
(GridSortOptions.Column, GridSortOptions.Direction)
.AsPagination(Page ?? 1, PageSize ?? 10);
return this;
}
private void ProcessQuery<TValue>
(TValue value, Expression<Func<T, bool>> predicate)
{
if (value == null) return;
if (typeof(TValue) == typeof(string))
{
if (string.IsNullOrWhiteSpace(value as string)) return;
}
Query = Query.Where(predicate);
}
}
}
Listing Action Method in Controller
Let's make a listing method of AlbumController
with searching filters, ordering and paging in our AlbumController
. We can add fluently query filters to the filter pipeline of PagedViewModel
.
namespace MvcMusicStore.Controllers
{
public class AlbumController : Controller
{
private AlbumService _service;
public AlbumController()
{
_service = new AlbumService();
}
public ActionResult Index(string albumTitle,
int? genreId, int? artistId, GridSortOptions gridSortOptions, int? page)
{
var pagedViewModel = new PagedViewModel<AlbumViewModel>
{
ViewData = ViewData,
Query = _service.GetAlbumsView(),
GridSortOptions = gridSortOptions,
DefaultSortColumn = "AlbumId",
Page = page,
}
.AddFilter("albumTitle", albumTitle,
a => a.AlbumTitle.Contains(albumTitle))
.AddFilter("genreId", genreId,
a => a.GenreId == genreId, _service.GetGenres(), "Name")
.AddFilter("artistId", artistId,
a => a.ArtistId == artistId, _service.GetArtists(), "Name")
.Setup();
return View(pagedViewModel);
}
...
}
}
Listing Razor View Page for MvcContrib Grid
Let's make List View Page. We can easily fill two dropdownlists without any additional code from PagedViewModel<T>
. Also, we add a link using Html.ActionQueryLink
helper method to keep query string
like "/Album/Details/420?albumTitle=Ro&genreId=1
".
@using MvcMusicStore.Common
@using MvcMusicStore.Models;
@using MvcContrib.UI.Grid;
@model PagedViewModel<AlbumViewModel>
@{
ViewBag.Title = "Album List";
}
<h2>Album List</h2>
@using (Html.BeginForm("Index", "Album",
FormMethod.Get, new { id = "albumSearch" }))
{
<label>
Title @Html.TextBox("albumTitle")
Genre @Html.DropDownList("genreId", "-- Select All --")
</label>
<label>
Artist @Html.DropDownList("artistId", "-- Select All --")
<input class="button" value="Search" type="submit" />
</label>
}
@{Html.RenderPartial("Pager", Model.PagedList);}
@Html.Grid(Model.PagedList).AutoGenerateColumns().Columns(
column =) {column.For(x =) Html.ActionQueryLink(x.AlbumTitle, "Details",
new { id = x.AlbumId })).Named("AlbumTitle").InsertAt(2);
}).Sort(Model.GridSortOptions).Attributes(@class =) "grid-style")
Let's see SearchBox
with title keyword
, Genre
& Album
Dropdownlist. The contrib Grid provides Column filtering and paging UI.
We can go back to the List page with preserved query option from Details page. Let's add a snippet to save routing URL to ViewBag
by using added ToRouteDic
extension method.
namespace MvcMusicStore.Controllers
{
public class AlbumController : Controller
{ ...
public ActionResult Details(int id)
{
var viewModel = _service.FindAlbumView(id);
ViewBag.RouteDicForList = Request.QueryString.ToRouteDic();
return View(viewModel);
}
}
}
Let's restore the ViewBag
data to restore the query URL in Details
Viewpage.
@model MvcMusicStore.Models.AlbumViewModel
@{
ViewBag.Title = "Details";
}
<h2>Album Details - @Model.AlbumTitle</h2>
<p>
@Html.ActionLink("Back to List", "Index",
ViewBag.RouteDicForList as RouteValueDictionary)
</p>
Conclusion
MvcContrib
Grid is a nice web grid component for MVC3 framework. We can use great paging, filtering, ordering grid functions easily.
References