Untitled 1
Using GridView and DetailsView in ASP.NET MVC - Part 2
In Part 1 of this two
part article, I explained how GridView and DetailsView controls can be used in
ASP.NET MVC web pages. In this part I will show how to add sorting and paging
capabilities to the GridView control without breaking the MVC design pattern.
Before going ahead with the coding let's take a look at the GridView control
that displays the employee listing.

Notice the column headings and footer of the GridView. When you use the
GridView control's inbuilt sorting and paging capabilities, it makes use of
LinkButton controls to render clickable column headings and page numbers. This
default mechanism won't fall properly in line with MVC pattern. Therefore, we
will code our view in such a way that instead of LinkButtons it will render
HyperLink controls and the hyperlinks will point to some controller.
Adding a class for storing sorting and paging settings
Let's begin. Add a new class to the web application and name it as
SortingPagingData. The SortingPagingData class will be used to store and
transfer sorting and paging related settings between the controller and the
view. The complete class is shown below:
namespace DataControlsInMVC
{
public class SortingPagingData
{
public string SortField { get; set; }
public string SortDirection { get; set; }
public int PageCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
}
}
The SortingPagingData class consists of five properties in all viz. SortField,
SortDirection, PageCount, PageNumber and PageSize. The property names are self
explanatory and indicate the kind of data they store.
Modified Controller
Now we need to modify the Employee controller to make use of the
SortingPagingData class. The Index() action method will now look like this:
public ActionResult Index(SortingPagingData data)
{...}
The Index() action method now accepts a parameter of type SortingPagingData.
Who will supply this parameter and how? This will be clear when you will modify
the index view.
The complete version of the modified Index() action method is shown below:
public ActionResult Index(SortingPagingData data)
{
DataClasses1DataContext db = new DataClasses1DataContext();
IQueryable<Employee> emplist=null;
emplist = from rows in db.Employees
select rows;
if (data.SortField != null)
{
//sorting
if (data.SortDirection == null)
{
data.SortDirection = "asc";
}
switch (data.SortField.ToLower())
{
case "id":
emplist = emplist.OrderByWithDirection
(emp => emp.Id, data.SortDirection);
break;
case "name":
emplist = emplist.OrderByWithDirection
(emp => emp.Name, data.SortDirection);
break;
default:
break;
}
}
//paging
data.PageSize = 2;
if (data.PageSize > 0)
{
data.PageCount = emplist.Count() / data.PageSize;
int start = (data.PageSize * data.PageNumber);
emplist = emplist.Skip(start).Take(data.PageSize);
}
ViewData["emplist"]=emplist;
ViewData["SortingPagingData"] = data;
return View();
}
We first fetch all the records from the Employees table using a LINQ query.
We then check the SortField property of the SortingPagingData parameter. It will
be null initially i.e. when we have not clicked on any column header. If
SortField is specified we check the SortDirection property. We need to sort the
records in ascending order if previously they were in descending order and vice
a versa. This is accomplished by a custom extension method OrderByWithDirection().
The OrderByWithDirection() extension method simply sorts a given set of data by
toggling the sorting direction. The OrderByWithDirection() method is discussed
in detail in the next section.
The controller, then defines the PageSize for the GridView control (2 in this
example). We then calculate the number pf pages the grid will have and set its
PageCount property. We finally retrieve only the records need by the current
page using LINQ Skip() and Take() methods. This way only the records that are to
be actually displayed in the grid will be passed to the view. As before the
emplist value is stored in a ViewData variable. The SortingPagingData object is
also passed to the view since the view needs it for rendering paging hyperlinks.
Creating the extension method for sorting data
To create the OrderByWithDirection() extension method add a new class to the
application and name it as OrderByExtension. The complete code of
OrderByWithDirection() extension method is as follows:
public static class OrderByExtension
{
public static IQueryable<TSource>
OrderByWithDirection<TSource, TKey>
(this IQueryable<TSource> source,
Expression<Func<TSource, TKey>>
key,string direction)
{
if (direction == "desc")
{
source=source.OrderByDescending(key);
}
else
{
source=source.OrderBy(key);
}
return source;
}
}
The OrderByWithDirection() extension method takes three parameters viz.
source, key and direction. The source parameter will be the object on which we
are calling this method. Key parameter indicates the column on which you wish to
sort the data and direction indicates the sorting direction. Based on the
direction parameter we call either OrderBy() or OrderByDescending() methods on
the emplist (source).
This completes the controller class. Now let's move to index view.
Controller actions and query string parameters
Before we start coding the index view we need to know how controller action
method and query string parameters work. We need to know this because we will be
using this knowledge while developing the index view.
Consider the following URLs...
http://localhost/mycontroller/myaction
http://localhost/mycontroller/myaction?a=100&b=200
In case of the first URL, the ASP.NET MVC framework will invoke MyAction()
action method from the MyController controller. If MyAction() method takes any
parameters, they will be passed as null. Now, consider the second URL. In this
case since we are passing some query string parameters ASP.NET MVC framework
will try to map them with the MyAction() method parameters. If the signature of
the action method under consideration is - MyAction(int a, int b) then the query
string parameters will be passed as 100 and 200 respectively. If the signature
is MyAction(MyData) where MyData is your custom class then MVC framework will
try to map the query string parameters with the properties of MyData class i.e
MyData.A will get value of query string parameter a and MyData.B will get value
of query string parameter b.
In our example the Index() action method accepts a parameter of type
SortingPagingData. The value for this parameter will be constructed by MVC
framework based on the query string parameters we pass. If we don't pass any
query string parameters SortingPagingData parameter will be null, otherwise
query string parameters will be mapped with the properties of SortingPagingData
class and we receive an instance of SortingPagingData class. So, technically we
could have created our Index() action method as -
public ActionResult Index(string sortfield,string sortdirection,
int pagecount, int pagenumber, int pagesize)
Or
public ActionResult Index(SortingPagingData data)
We opted for the second version because it is more neat and manageable than
the previous one. It also makes data transfer through ViewData object easy since
we need to pass just one variable.
Header and Footer Templates
The GridView on the index view has two columns - Id and Name. The Id and Name
column are template fields (If you added them as BoundFields then just click on
"Convert this column to template field" link in the Edit Fields dialog). We need
to design the header template of Id and Name column as shown below:
<asp:TemplateField HeaderText="Id" InsertVisible="False"
SortExpression="Id">
<HeaderTemplate>
<asp:HyperLink ID="lnkSortId" runat="server" ForeColor="White">
Id
</asp:HyperLink>
</HeaderTemplate>
<FooterTemplate>
<asp:PlaceHolder ID="PlaceHolder2" runat="server">
</asp:PlaceHolder>
</FooterTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("Id") %>'>
</asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Name" SortExpression="Name">
<HeaderTemplate>
<asp:HyperLink ID="lnkSortName" runat="server" ForeColor="White">
Name
</asp:HyperLink>
</HeaderTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("Name") %>'>
</asp:Label>
</ItemTemplate>
</asp:TemplateField>
Notice how we put a HyperLink server control in the header template of Id as
well as Name columns. Also note that the footer template of Id column contains a
PlaceHolder control. Since the number of paging links is not known at design
time we use a PlaceHolder control and later add required number of hyperlinks as
its child controls. Footer template of Name column in empty because later we are
going to merge all footer cells into the footer cell of Id column.
Now comes the important part that actually adds the sorting and paging links
to the GridView control. The task of adding sorting and paging links is done
inside RowDataBound event of the GridView. The RowDataBound event is raised when
the GridView is bound with data (emplist in our case).
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
DataControlsInMVC.SortingPagingData data =
ViewData["SortingPagingData"] as DataControlsInMVC.SortingPagingData;
string sortdir = "";
if (data != null)
{
if (data.SortDirection == "asc")
sortdir = "desc";
else
sortdir = "asc";
}
//sorting
if (e.Row.RowType == DataControlRowType.Header)
{
HyperLink lnk1 = (HyperLink)e.Row.FindControl("lnkSortId");
HyperLink lnk2 = (HyperLink)e.Row.FindControl("lnkSortName");
lnk1.NavigateUrl = "~/employee/index?sortfield=id";
if (data!=null && data.SortField == "id")
{
lnk1.NavigateUrl += "&sortdirection=" + sortdir;
}
lnk2.NavigateUrl = "~/employee/index?sortfield=name";
if (data != null && data.SortField == "name")
{
lnk2.NavigateUrl += "&sortdirection=" + sortdir;
}
}
//paging
if (e.Row.RowType == DataControlRowType.Footer)
{
e.Row.Cells.Clear();
TableCell pager = new TableCell();
pager.ColumnSpan = 4;
pager.HorizontalAlign=HorizontalAlign.Center;
for(int i=0;i<data.PageCount;i++)
{
HyperLink lnk=new HyperLink();
lnk.Text=(i+1).ToString();
lnk.NavigateUrl= string.Format("~/employee/index?
sortfield={0}&sortdirection={1}&
pagenumber={2}",data.SortField,data.SortDirection,i);
pager.Controls.Add(lnk);
pager.Controls.Add(new LiteralControl(" "));
}
e.Row.Cells.Add(pager);
}
}
The code first decides the sorting direction needed. If currently the data is
sorted in ascending order, the next time it should be sorted in descending order
and hence we toggle the "asc" and "desc" value. The RowDataBound event is raised
for every row in the GridView including header and footer. So, we check the
RowType property with each iteration. If the row is header row, we get hold of
HyperLink controls from the header template and set their NavigateUrl property.
Notice how we add query string parameters sortfield and sortdirection. This way
clicking on the hyperlink will invoke the Index() action method with required
SortingPagingData values.
Similarly, if the row is of type footer we dynamically create HyperLink
controls and add them to the PlaceHolder. Notice how we are spanning a single
table cell for entire footer. This way the paging buttons will be neatly
displayed in the center. The paging hyperlinks pass pagenumber query string
parameter in addition to sortfield and sortdirection. Note that if you are on
page N when the data was sorted on the basis of Id column and then you click on
Name sorting link, the current page number will be reset to 0. You can, of
course, adjust this behavior as per your requirement.
That's it! Your GridView is now sortable and pagable. Run the application and
see it in action. The following figure show the grid sorted by Name column and
showing data from page number 3.

Keep coding!