In this article I will explain with an example, how to implement a multilevel nested GridView i.e. GridView with inner GridView and the inner GridView with another child GridView, thus creating a multilevel hierarchy of GridView in ASP.Net using C# and VB.Net.
This article illustrates how to implement Three (3) Levels of GridView.
Note: For more details on how to use Nested GridView, please refer my article Collapsible Nested GridView with Paging using ASP.Net.
 
 

Database

This article uses Microsoft’s Northwind Database. You can download it from here.
 
 

HTML Markup

The HTML Markup consists of:
GridView – For displaying parent records.
Inside a GridView there another GridView which is used to display child records.
Note: The CustomerID and OrderID fields are stored in the DataKeys property of the GridView as that will be required to populate the Child or Nested GridView of Orders as well as Products.
 
The following HTML Markup consists of:
Customers GridView: The Customers GridView is the Main GridView and it consists of a ImageButton and Panel inside a TemplateField Column.
The Panel consists of another GridView i.e. Orders GridView which in turn consists of Products GridView.
The Customers and Orders GridView consists of ImageButton to hide and show the respective Child GridViews.
<asp:GridView ID="gvCustomers" runat="server" AutoGenerateColumns="false" CssClass="Grid"
    DataKeyNames="CustomerID">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:ImageButton ID="imgOrdersShow" runat="server" OnClick="OnShowHideOrdersGrid"
                    ImageUrl="~/images/plus.png" CommandArgument="Show" />
                <asp:Panel ID="pnlOrders" runat="server" Visible="false" Style="position:relative">
                    <asp:GridView ID="gvOrders" runat="server" AutoGenerateColumns="false" PageSize="5"
                        AllowPaging="true" OnPageIndexChanging="OnOrdersGrid_PageIndexChanging" CssClass="ChildGrid"
                        DataKeyNames="OrderId">
                        <Columns>
                            <asp:TemplateField>
                                <ItemTemplate>
                                    <asp:ImageButton ID="imgProductsShow" runat="server" OnClick="OnShowHideProductsGrid"
                                        ImageUrl="~/images/plus.png" CommandArgument="Show" />
                                    <asp:Panel ID="pnlProducts" runat="server" Visible="false" Style="position:relative">
                                        <asp:GridView ID="gvProducts" runat="server" AutoGenerateColumns="false" PageSize="2"
                                            AllowPaging="true" OnPageIndexChanging="OnProductsGrid_PageIndexChanging" CssClass="Nested_ChildGrid">
                                            <Columns>
                                                <asp:BoundField ItemStyle-Width="150px" DataField="ProductId" HeaderText="Product Id" />
                                                <asp:BoundField ItemStyle-Width="150px" DataField="ProductName" HeaderText="Product Name" />
                                            </Columns>
                                        </asp:GridView>
                                    </asp:Panel>
                                </ItemTemplate>
                            </asp:TemplateField>
                            <asp:BoundField ItemStyle-Width="150px" DataField="OrderId" HeaderText="Order Id" />
                            <asp:BoundField ItemStyle-Width="150px" DataField="OrderDate" HeaderText="Date" />
                        </Columns>
                    </asp:GridView>
                </asp:Panel>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField ItemStyle-Width="150px" DataField="ContactName" HeaderText="Contact Name" />
        <asp:BoundField ItemStyle-Width="150px" DataField="City" HeaderText="City" />
    </Columns>
</asp:GridView>
 
 

Namespaces

You will need to import the following namespaces.
C#
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
 
VB.Net
Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration
 
 

Binding the Customers records to the Level 1 GridView

Inside the Page_Load event, GetData method is called and the records of Customers table are fetched and displayed in GridView.
Note: GetData is a generic function and the same function discussed above is used here.
 
C#
protected void Page_Load(object sender, EventArgs e)
{
    if (!this.IsPostBack)
    {
         gvCustomers.DataSource = GetData("select top 10 * from Customers");
        gvCustomers.DataBind();
    }
}
 
private static DataTable GetData(string query)
{
    string constr = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
    using (SqlConnection con = new SqlConnection(constr))
    {
        using (SqlCommand cmd = new SqlCommand())
        {
            cmd.CommandText = query;
            using (SqlDataAdapter sda = new SqlDataAdapter())
            {
                cmd.Connection = con;
                sda.SelectCommand = cmd;
                using (DataSet ds = new DataSet())
                {
                    DataTable dt = new DataTable();
                    sda.Fill(dt);
                    return dt;
                }
            }
        }
    }
}
 
VB.Net
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
    If Not Me.IsPostBack Then
         gvCustomers.DataSource = GetData("select top 10 * from Customers")
        gvCustomers.DataBind()
    End If
End Sub
 
Private Shared Function GetData(query As String) As DataTable
    Dim constr As String ConfigurationManager.ConnectionStrings("constr").ConnectionString
    Using con As New SqlConnection(constr)
        Using cmd As New SqlCommand()
            cmd.CommandText = query
            Using sda As New SqlDataAdapter()
                cmd.Connection = con
                sda.SelectCommand = cmd
                Using ds As New DataSet()
                    Dim dt As New DataTable()
                    sda.Fill(dt)
                    Return dt
                End Using
            End Using
        End Using
    End Using
End Function
 
 

Displaying the Child (Level 2) GridView with the Orders for each Customer in the Parent (Level 1) GridView

On the Click event of the Expand ImageButton of the Parent GridView, first the Child GridView in the corresponding GridView Row is referenced and then populated with the records from the Orders table of the Northwind Database based on CustomerId stored in the DataKey property for the row.
The CommandArgument property of the ImageButton is used to determine whether the operation is of Expand or Collapse.
The Paging for the Child GridView is done using the OnOrdersGrid_PageIndexChanging event.
C#
protected void OnShowHideOrdersGrid(object sender, EventArgs e)
{
    ImageButton imgShowHide = (sender as ImageButton);
    GridViewRow row = (imgShowHide.NamingContainer as GridViewRow);
    if (imgShowHide.CommandArgument == "Show")
    {
        row.FindControl("pnlOrders").Visible = true;
        imgShowHide.CommandArgument "Hide";
        imgShowHide.ImageUrl "~/images/minus.png";
        string customerId gvCustomers.DataKeys[row.RowIndex].Value.ToString();
        GridView gvOrders row.FindControl("gvOrders") as GridView;
        this.BindOrders(customerId,gvOrders);
    }
    else
    {
        row.FindControl("pnlOrders").Visible = false;
        imgShowHide.CommandArgument "Show";
        imgShowHide.ImageUrl "~/images/plus.png";
    }
}
 
private void BindOrders(string customerId, GridView gvOrders)
{
    gvOrders.ToolTip customerId;
    gvOrders.DataSource GetData(string.Format("select * from Orders where CustomerId='{0}'", customerId));
    gvOrders.DataBind();
}
 
protected void OnOrdersGrid_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    GridView gvOrders = (sender as GridView);
    gvOrders.PageIndex e.NewPageIndex;
    this.BindOrders(gvOrders.ToolTip, gvOrders);
}
 
VB.Net
Protected Sub OnShowHideOrdersGrid(sender As Object, e As EventArgs)
    Dim imgShowHide As ImageButton = TryCast(sender, ImageButton)
    Dim row As GridViewRow = TryCast(imgShowHide.NamingContainer, GridViewRow)
    If imgShowHide.CommandArgument "Show" Then
        row.FindControl("pnlOrders").Visible = True
        imgShowHide.CommandArgument "Hide"
        imgShowHide.ImageUrl "~/images/minus.png"
        Dim customerId As String gvCustomers.DataKeys(row.RowIndex).Value.ToString()
        Dim gvOrders As GridView =  TryCast(row.FindControl("gvOrders"), GridView)
        Me.BindOrders(customerId,gvOrders)
    Else
        row.FindControl("pnlOrders").Visible = False
        imgShowHide.CommandArgument "Show"
        imgShowHide.ImageUrl "~/images/plus.png"
    End If
End Sub
 
Private Sub BindOrders(customerId As String, gvOrders As GridView)
    gvOrders.ToolTip customerId
    gvOrders.DataSource GetData(String.Format("select * from Orders where CustomerId='{0}'", customerId))
    gvOrders.DataBind()
End Sub
 
Protected Sub OnOrdersGrid_PageIndexChanging(sender As Object, e As GridViewPageEventArgs)
    Dim gvOrders As GridView = TryCast(sender, GridView)
    gvOrders.PageIndex e.NewPageIndex
    Me.BindOrders(gvOrders.ToolTip, gvOrders)
End Sub
 
 

Displaying the Child (Level 3) GridView with the Products for each Order placed by a Customer in the Products Grid (Level 3) GridView

On the Click event of the Expand ImageButton of the Parent GridView, first the Child GridView in the corresponding GridView Row is referenced and then populated with the records from the Products table of the Northwind Database based on OrderID stored in the DataKey property for the row.
The CommandArgument property of the ImageButton is used to determine whether the operation is of Expand or Collapse.
The Paging for the Child GridView is done using the OnProductsGrid_PageIndexChanging event.
C#
protected void OnShowHideProductsGrid(object sender, EventArgs e)
{
    ImageButton imgShowHide = (sender as ImageButton);
    GridViewRow row = (imgShowHide.NamingContainer as GridViewRow);
    if (imgShowHide.CommandArgument == "Show")
    {
        row.FindControl("pnlProducts").Visible = true;
        imgShowHide.CommandArgument "Hide";
        imgShowHide.ImageUrl "~/images/minus.png";
        int orderId Convert.ToInt32((row.NamingContainer as GridView).DataKeys[row.RowIndex].Value);
        GridView gvProducts row.FindControl("gvProducts") as GridView;
        this.BindProducts(orderId, gvProducts);
    }
    else
    {
        row.FindControl("pnlProducts").Visible = false;
        imgShowHide.CommandArgument "Show";
        imgShowHide.ImageUrl "~/images/plus.png";
    }
}
 
private void BindProducts(int orderId, GridView gvProducts)
{
    gvProducts.ToolTip orderId.ToString();
    gvProducts.DataSource GetData(string.Format("SELECT ProductId, ProductName FROM Products WHEREProductId IN (SELECT ProductId FROM [Order Details] WHEREOrderId = '{0}')", orderId));
    gvProducts.DataBind();
}
 
protected void OnProductsGrid_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    GridView gvProducts = (sender as GridView);
    gvProducts.PageIndex e.NewPageIndex;
    this.BindProducts(int.Parse(gvProducts.ToolTip), gvProducts);
}
 
VB.Net
Protected Sub OnShowHideProductsGrid(sender As Object, e As EventArgs)
    Dim imgShowHide As ImageButton = TryCast(sender, ImageButton)
    Dim row As GridViewRow = TryCast(imgShowHide.NamingContainer, GridViewRow)
    If imgShowHide.CommandArgument "Show" Then
        row.FindControl("pnlProducts").Visible = True
        imgShowHide.CommandArgument "Hide"
        imgShowHide.ImageUrl "~/images/minus.png"
        Dim orderId As Integer Convert.ToInt32(TryCast(row.NamingContainer, GridView).DataKeys(row.RowIndex).Value)
        Dim gvProducts As GridView = TryCast(row.FindControl("gvProducts"), GridView)
        Me.BindProducts(orderId,gvProducts)
    Else
        row.FindControl("pnlProducts").Visible = False
        imgShowHide.CommandArgument "Show"
        imgShowHide.ImageUrl "~/images/plus.png"
    End If
End Sub
 
Private Sub BindProducts(orderId As Integer, gvProducts As GridView)
    gvProducts.ToolTip orderId.ToString()
    gvProducts.DataSource GetData(String.Format("SELECT ProductId, ProductName FROM Products WHEREProductId IN (SELECT ProductId FROM [Order Details] WHEREOrderId = '{0}')", orderId))
    gvProducts.DataBind()
End Sub
 
Protected Sub OnProductsGrid_PageIndexChanging(sender As Object, e As GridViewPageEventArgs)
    Dim gvProducts As GridView = TryCast(sender, GridView)
    gvProducts.PageIndex e.NewPageIndex
    Me.BindProducts(Integer.Parse(gvProducts.ToolTip), gvProducts)
End Sub
 
 

Client Side

Inside the HTML Markup, the following jQuery JS file is inherited.
1. jquery.min.js
 
Inside the jQuery document ready event handler, a loop is executed for all the Orders and Products ImageButton and the corresponding Child GridViews appended next to the Button.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script type="text/javascript">
    $(function () {
        $("[id*= imgOrdersShow]").each(function () {
            if ($(this)[0].src.indexOf("minus") != -1) {
                $(this).closest("tr").after("<tr><td></td><td colspan = '999'>" + $(this).next().html() + "");
                $(this).next().remove();
            }
        });
        $("[id*= imgProductsShow]").each(function () {
            if ($(this)[0].src.indexOf("minus") != -1) {
                $(this).closest("tr").after("<tr><td></td><td colspan = '999'>" + $(this).next().html() + "");
                $(this).next().remove();
            }
        });
    });
</script>
 
 

CSS Style for displaying GridViews

The GridView’s have been assigned with the following CSS.
<style type="text/css">
    body {font-family: Arial; font-size: 10pt; }
    .Grid th {background-color: #6C6C6C; color: White; font-size: 10pt; line-height: 200%; }
    .Grid td {background-color: #eee; color: black; font-size :10pt; line-height: 200%; }
    .ChildGrid th {background-color: #6C6C6C; color: #fff; font-size: 10pt; line-height: 200%; }
    .ChildGrid td {background-color: #fff; color: black; font-size: 10pt; line-height: 200%; }
</style>
 
 

Screenshot

Multilevel (N Level) Nested GridView (GridView inside GridView) in ASP.Net with Paging
 
 

Demo

 
 

Downloads