ASP.NET GridView Filtering with DropDownList

Display filtered subsets of data in the GridView control using DropDownList embedded in GridView column HeaderTemplate.


Recently, while working on one of the company’s web based projects, I needed to display filtered subsets of the data in the GridView control. Naturally, the first thing that came to mind was using FilterExpression property of SqlDataSource, and on a test page, it was working just fine, but when I set GridView property AllowPaging to “true,” the problems began. As soon as I tried to navigate pages on filtered GridView, several things went wrong: First, DropDownList did not retain the value selected earlier. Second, GridView was losing the applied filter. At this point, ViewState came to the rescue.
The final solution was easy to implement, maintain and extend, so I decided to share it with the developers’ community. 
Firstly, let’s prepare the working environment by placing two SqlDataSources and a GridView on WebForm.
In this example I am using a connection to Northwind database – Customers table.
Here is how SqlDataSource for GridView looks like:
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
            ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
            SelectCommand="SELECT [CompanyName], [ContactName], [ContactTitle],
            [Country], [Phone], [Address], [City] FROM [Customers]">
        </asp:SqlDataSource>
Then, I modified the “Country” field of the GridView to TemplateField so it gave me the ability to place DropDownList in the HeaderTemplate:
This is what my GridView looks like after the changes:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
            CellPadding="4" DataSourceID="SqlDataSource1"
            ForeColor="#333333" GridLines="None"
            AllowPaging="True" OnPageIndexChanged="GridView1_PageIndexChanged">
            <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
            <Columns>
              ...
                <asp:TemplateField HeaderText="Country" SortExpression="Country">
                <HeaderTemplate>
                Country
                <asp:DropDownList ID="ddCountry" 
                DataTextField="Country"
                AutoPostBack="true"
                OnSelectedIndexChanged="FilterDropDown_IndexChanged"
                OnPreRender="SetValue"
                DataSourceID="SqlDataSource2" runat="server"/>
                </HeaderTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("Country") %>'></asp:TextBox>
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Label ID="Label1" runat="server" Text='<%# Bind("Country") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                ...
            </Columns>
            <RowStyle BackColor="#EFF3FB" />
            <EditRowStyle BackColor="#2461BF" />
         <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
         <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
            <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
            <AlternatingRowStyle BackColor="White" />
        </asp:GridView>
Secondly, I configured the SqlDataSource to return distinct list of the countries from the Customers table and it looks like this:
<asp:SqlDataSource ID="SqlDataSource2" runat="server"
       ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
SelectCommand="SELECT 'All Countries' AS Country UNION SELECT DISTINCT [Country] as Country FROM [Customers]">
 </asp:SqlDataSource>
As you can see, so far everything is simple; the only trick I used here is to UNION “All Countries” string with the rest of the data to add “All Countries” to DropDownList items collection, so the user will be able to remove the filter when needed.
Now we should add methods FilterDropDown_IndexChanged and SetValue of the DropDownList to the code behind the page like this:
protected void FilterDropDown_IndexChanged(object sender, EventArgs e)
    {

    }
protected void SetValue(object sender, EventArgs e)
    {

    }
and we are ready to test the page.

After you run the page you will see something like this:

When you test the result after filtering you will notice that after clicking on page number links, the filtering will not persist. It is a good time to start a little coding.
To preserve selected values between page postbacks, we will need a page level variable. I chose the Hashtable.
public partial class GridFiltering : System.Web.UI.Page
{
   Hashtable filter;
...
}
This variable is being initialized on every page load from ViewState of the page.
protected void Page_Load(object sender, EventArgs e)
    {
        filter = ViewState["FilterArgs"] == null ? new Hashtable() : (Hashtable)ViewState["FilterArgs"];

    }
The keystone of the application is a ApplyGridFilter method with the following code:
protected void ApplyGridFilter()
    {
        string args = " ";
        int i = 0;
        foreach (object key in filter.Keys)
        {
            if (i == 0)
            {
                args = key.ToString() + filter[key].ToString();
            }
            else
            {
                args += " AND " + key.ToString() + filter[key].ToString();
            }
            i++;
        }
        SqlDataSource1.FilterExpression = args;
        //Filter needs to be saved between postbacks
        ViewState.Add("FilterArgs", filter);
    }
As you can see from the snipet above, everything is pretty simple: Local variable args being initialized to hold filter value. Default value of args is a space character.
Note: If you use empty string (“”) in FilterExpression in your GridView, you will not see any records. To remove the filter, FilterExpression should be set to space (“ ”)
Then we iterate trough every key in filter Hashtable (if there are any) and build filter string in the following format “Field Name =/LIKE/<> ’value’ “ if there is more than one filter provided we will append  the Field Name value pare using AND operator. After all filter values are combined in the string, we assign a value to SqlDataSource. FilterExpression and save Hashtable back to Page ViewState. The logical question is: how do they get into Hashtable in the first place?
To answer the question, let’s look at the code in DropDownList methods.
protected void FilterDropDown_IndexChanged(object sender, EventArgs e)
    {
         DropDownList dd = (DropDownList)sender;
         if (dd.SelectedItem.Text != "ALL Countries")
         {
             if(filter.ContainsKey("Country")){
                 filter["Country"] = "='" + dd.SelectedItem.Text + "'";
             }else{
                 filter.Add("Country", "='" + dd.SelectedItem.Text + "'");
             } 
         }
         else
         {
             filter.Remove("Country");
         }
        ApplyGridFilter();
        GridView1.PageIndex = 0;
    }
As you can see from the html code provided at the beginning of this article, we set AutoPostBack property of DropDownList to "true". This will trigger Page postback every time DropDownList SelectedIndex changes. We get the value of the selected item in the DropDownList by casting sender, then we add Key and Value if key does not exist or reset value of the existing key to a new value.
When the Hashtable filter contains desired value we call ApplyGridFilter method (described above), and reset GridView PageIndex property. We are almost done. The last thing to do for us is to make sure that DropDownList selected value stays intact with applied FilterExpression value. This effect is achieved using SetValue method on OnPreRender event of the DropDownList.
protected void SetValue(object sender, EventArgs e)
    {
        DropDownList ddl = (DropDownList)sender;
        if (filter != null)
        {
            if (filter.ContainsKey("Country"))
            {
                foreach (ListItem li in ddl.Items)
                {
if (li.Text == filter["Country"].ToString().Substring(2,        filter["Country"].ToString().Length - 3))
                    {
                        li.Selected = true;
                        return;
                    }
                }
            }
        }
    }

Right before the DropDownList is drawn on the page, we look at our filter Hashtable and if we find the value corresponding to this DropDownList we set its SelectedItem.
This is it. Hope you find it helpful.
By the way, I’m attaching full aspx and cs files with the code used in this article.

No comments:

Post a Comment

Flipkart