Online courses in ASP.NET MVC, ASP.NET Core, and Design Patterns conducted by Bipin Joshi. Read more...
Learn ASP.NET MVC, ASP.NET Core, and Design Patterns through our online training programs. Courses conducted by Bipin Joshi on weekends. Read more details here.

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("&nbsp;"));
        }
        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!




Bipin Joshi is a software consultant, trainer, author and a yogi having 21+ years of experience in software development. He conducts online courses in ASP.NET MVC / Core, jQuery, AngularJS, and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced Yoga way of life he also teaches Ajapa Meditation to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 07 Feb 2011



Tags : ASP.NET Data Access Web Forms MVC