Working With Nested Grid Views in ASP.NET
While working with relational data, you would like to show the data in such a way that it should make sense to the user and have good user experience. In this article I will go over how to use the Nested Grid Views with Expand and collapsed functionality to show the data.
Objective
While working with relational data, you would like to show the data in such a way that it should make sense to the user and have good user experience. In this article I will go over how to use the Nested Grid Views with Expand and collapsed functionality to show the data.
There are numerous scenarios where we want to show data in Nested Grid Views like Parent & Child. Parent Grid View shows the main information and Child show the relevant information of Parent. Let take an example of showing the Order and Order Details Information.
I have taken the data from North wind database, but I will use that in XML for the simplicity of this article. Below is the data for Order and Order Details Order Table
Order ID Customer ID Employee ID Order Date Required Date Shipped Date Ship Via Freight Ship Country
10248 VINET 5 7/4/96 8/1/96 7/16/96 3 32.38 France
10249 TOMSP 6 7/5/96 8/16/96 7/10/96 1 11.61 Germany
10250 HANAR 4 7/8/96 8/5/96 7/12/96 2 65.83 Brazil
10251 VICTE 3 7/8/96 8/5/96 7/15/96 1 41.34 France
10252 SUPRD 4 7/9/96 8/6/96 7/11/96 2 51.3 BelgiumOrder Details Table
Order ID ProductID UnitPrice Quantity Discount
10248 11 14 12 0
10248 42 9.8 10 0
10248 72 34.8 5 0
10249 14 18.6 9 0
10249 51 42.4 40 0
10250 41 7.7 10 0
10250 51 42.4 35 0.15
10250 65 16.8 15 0.15
Order Id is the relational Key for Parent and Child. We would like to show the Order and once you click the Order Id you will see the details for that Order.
Let's put that together in XML Structure with relationship. I have nested the Order Details data inside the Order data. XML Data (OrderDetailsData.xml)
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfOrderData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<OrderData>
<Order ID>10248</Order ID>
<Customer ID>VINET</Customer ID>
<EmployeeID>5</EmployeeID>
<Order Date>7/4/96</Order Date>
<Freight>32.38</Freight>
<Ship Country>France</Ship Country>
<OrderDetailsData>
<OrderDetails>
<Order ID>10248</Order ID>
<ProductID>11</ProductID>
<UnitPrice>14</UnitPrice>
<Quantity>12</Quantity>
<Discount>0</Discount>
</OrderDetails>
<OrderDetails>
<Order ID>10248</Order ID>
<ProductID>42</ProductID>
<UnitPrice>9.8</UnitPrice>
<Quantity>10</Quantity>
<Discount>0</Discount>
</OrderDetails>
<OrderDetails>
<Order ID>10248</Order ID>
<ProductID>72</ProductID>
<UnitPrice>34.8</UnitPrice>
<Quantity>5</Quantity>
<Discount>0</Discount>
</OrderDetails>
</OrderDetailsData>
</OrderData>
<OrderData>
<Order ID>10249</Order ID>
<Customer ID>TOMSP</Customer ID>
<EmployeeID>6</EmployeeID>
<Order Date>7/5/96</Order Date>
<Freight>11.61</Freight>
<Ship Country>Germany</Ship Country>
<OrderDetailsData>
<OrderDetails>
<Order ID>10249</Order ID>
<ProductID>14</ProductID>
<UnitPrice>18.6</UnitPrice>
<Quantity>9</Quantity>
<Discount>0</Discount>
</OrderDetails>
<OrderDetails>
<Order ID>10249</Order ID>
<ProductID>51</ProductID>
<UnitPrice>42.4</UnitPrice>
<Quantity>40</Quantity>
<Discount>0</Discount>
</OrderDetails>
</OrderDetailsData>
</OrderData>
</ArrayOfOrderData>Final Application Look
Figure 1
Figure 2
Let's start joining the pieces to accomplish this. I have shown the final look and feel of our application in Figure 1 and Figure 2. Data Model
XML has the two pieces of data i.e. Order and OrderDetails which we will be showing in the two Grid Views. So I took a path to create two data models for each object. I have created OrderModel and OrderDetailsModel class to old those values for us.OrderModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace OrderDetailsXMLGridView.BAL
{
public class OrderModel
{
public string OrderID { get; set; }
public string CustomerID { get; set; }
public string EmployeeID { get; set; }
public string OrderDate { get; set; }
public string Freight { get; set; }
public string ShipCountry { get; set; }
public OrderDetailsModel OrderDetail { get; set; }
}
public class OrderDetailsModel {
public string OrderID { get; set; }
public string ProductID { get; set; }
public string UnitPrice { get; set; }
public string Quantity { get; set; }
public string Discount { get; set; }
}
}
Data models are self-explanatory as they work as Data value objects. Implementation
Now we have our data store ready and data models ready, let's have the implementation to load the data from XML to values objects as mentioned above. I am using LINQ here to load and query the XML but if you want to use traditional way of getting data from XML feel free to use that. OrderDetailsImpl.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Linq;
namespace OrderDetailsXMLGridView.BAL
{
public class OrderDetailImpl
{
private string xml;
public OrderDetailImpl()
{
xml = HttpContext.Current.Server.MapPath("~/App_data/OrderDetailsData.xml");
}
public List
{
List
XElement ele = XElement.Load(xml);
orders = (from order in ele.Descendants("OrderData")
select new OrderModel()
{
OrderID = order.Element("OrderID").Value,
CustomerID = order.Element("CustomerID").Value,
EmployeeID = order.Element("EmployeeID").Value,
OrderDate = order.Element("OrderDate").Value,
Freight = order.Element("Freight").Value,
ShipCountry = order.Element("ShipCountry").Value
}).ToList
return orders;
}
public List
{
List
XElement ele = XElement.Load(xml);
orderdetails = (from order in ele.Descendants("OrderData").Elements("OrderDetailsData").Elements("OrderDetails")
where order.Element("OrderID").Value == orderId
select new OrderDetailsModel()
{
OrderID = order.Element("OrderID").Value,
ProductID = order.Element("ProductID").Value,
UnitPrice = order.Element("UnitPrice").Value,
Quantity = order.Element("Quantity").Value,
Discount = order.Element("Discount").Value,
}).ToList
return orderdetails;
}
}
}
I have two methods as shown above to load and query the data from XML. getOrderInfo() is to load the data from XML to OrderModel object for Order which is our parent data as shown in figure 1.
getOrderDetails(string orderId) is to load the data from XML to OrderDetailsModel object for Order details which is our child data as shown in figure 2.
Both the methods return the IEnumerable data (List) which we can bind with our grid views. Now we have our objects ready to retrieve the data from XML, let's look into the UI with nested grid view. Default.aspx Design
Before looking into the design and source of our aspx, let's take a look into the ObjectDataSource Data control. I have decided to use the ObjectDataSource as our objects methods are ready and we can go ahead and tie them together instead of binding our gridview by yourself. Design
Figure 3
In figure 3 you will notice the Grid View and ObjectDataSource control. I have bound the Grid View using the ObjectDataSource using the Smart tag of Grid View control. When you choose the new data source from Smart tag it will prompt you to choose the Data source type as show below.
Figure 4
When you choose and configure the ObjectDataSource it will open the Wizard to choose the business object which will return you the data in form on Object value (OrderModel) in our case. It will prompt you to choose the Data Methods type from that object, we have getOrderInfo() methods which is our SELECT data method as show in below figures.
Your parent Grid View is bounded with the Data source and it will show the Order Data from the XML. Nested Grid View
We need to place the second grid view as Itemtemplate of the first gird view. You can do either from design or you can write in the source of aspx page. Let me put Default.aspx source code here and go over piece by piece. Default.aspx Source
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="OrderDetailsXMLGridView._Default" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
<script language="javascript" type="text/javascript">
function switchViews(obj, row) {
var div = document.getElementById(obj);
var img = document.getElementById('img' + obj);
if (div.style.display == "none") {
div.style.display = "inline";
if (row == 'alt') {
img.src = "Images/expand_button_white_alt_down.jpg";
}
else {
img.src = "Images/Expand_Button_white_Down.jpg";
}
img.alt = "Close to view other Details";
}
else {
div.style.display = "none";
if (row == 'alt') {
img.src = "Images/Expand_button_white_alt.jpg";
}
else {
img.src = "Images/Expand_button_white.jpg";
}
img.alt = "Expand to show Order Details";
}
}
</script>
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<h2>
Welcome to Nested Grid View Tutorial
</h2>
<p>
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1"
AutoGenerateColumns="False" CellPadding="4" ForeColor="#333333"
GridLines="None" Width="753px" DataKeyNames="OrderID"
OnRowDataBound="GridView1_RowDataBound">
<AlternatingRowStyle BackColor="White" ForeColor="#284775" />
<Columns>
<asp:TemplateField HeaderText="Order Details">
<ItemTemplate>
<a href="javascript:switchViews('div<%# Eval("OrderID") %>', 'one');">
<img id="imgdiv<%# Eval("OrderID") %>" alt="Click to show/hide Details" border="0"
src="Images/expand_button_white.jpg" />
</a>
</ItemTemplate>
<AlternatingItemTemplate>
<a href="javascript:switchViews('div<%# Eval("OrderID") %>', 'alt');">
<img id="imgdiv<%# Eval("OrderID") %>" alt="Click to show/hide Details" border="0"
src="Images/expand_button_white_alt.jpg" />
</a>
</AlternatingItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="OrderID" HeaderText="Order ID"
SortExpression="OrderID">
</asp:BoundField>
<asp:BoundField DataField="CustomerID" HeaderText="Customer ID"
SortExpression="CustomerID" />
<asp:BoundField DataField="EmployeeID" HeaderText="Employee ID"
SortExpression="EmployeeID" />
<asp:BoundField DataField="OrderDate" HeaderText="Order Date"
SortExpression="OrderDate" />
<asp:BoundField DataField="Freight" HeaderText="Freight"
SortExpression="Freight" />
<asp:BoundField DataField="ShipCountry" HeaderText="Ship Country"
SortExpression="ShipCountry" />
<asp:TemplateField>
<ItemTemplate>
<tr>
<td colspan="100%">
<div id="div<%# Eval("OrderID") %>" style="display: none; position: relative; left: 25px;">
<asp:GridView ID="GridView2" runat="server" Width="80%" AutoGenerateColumns="false"
EmptyDataText="No Order Details.">
<EditRowStyle BackColor="#999999" />
<FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
<RowStyle BackColor="#F7F6F3" ForeColor="#333333" HorizontalAlign="Center" />
<SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
<SortedAscendingCellStyle BackColor="#E9E7E2" />
<SortedAscendingHeaderStyle BackColor="#506C8C" />
<SortedDescendingCellStyle BackColor="#FFFDF8" />
<SortedDescendingHeaderStyle BackColor="#6F8DAE" />
<Columns>
<asp:BoundField ShowHeader="true" HeaderText="Order ID" DataField="OrderID" />
<asp:BoundField ShowHeader="true" HeaderText="Product ID" DataField="ProductID" />
<asp:BoundField ShowHeader="true" HeaderText="Unit Price" DataField="UnitPrice" />
<asp:BoundField ShowHeader="true" HeaderText="Quantity" DataField="Quantity" />
<asp:BoundField ShowHeader="true" HeaderText="Discount" DataField="Discount" />
</Columns>
</asp:GridView>
</div>
</td>
</tr>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<EditRowStyle BackColor="#999999" />
<FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
<RowStyle BackColor="#F7F6F3" ForeColor="#333333" HorizontalAlign="Center" />
<SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
<SortedAscendingCellStyle BackColor="#E9E7E2" />
<SortedAscendingHeaderStyle BackColor="#506C8C" />
<SortedDescendingCellStyle BackColor="#FFFDF8" />
<SortedDescendingHeaderStyle BackColor="#6F8DAE" />
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="getOrderInfo"
TypeName="OrderDetailsXMLGridView.BAL.OrderDetailImpl">
</asp:ObjectDataSource>
</p>
</asp:Content>
Parent and Child Grid has the relationship key i.e. OrderID. If you look into the source you will see the child grid is wrapped with Div tag which has the Unique Id based on the OrderID Key from the ObjectDataSource. We will be hiding and showing the nested grid data with the help of the Div tag.
I am expanding and collapsing the Div using the Java Script. Java script is just using the Div tag style and setting its property to hide or show.
We have our layout ready to show the parent and child data with Expanding and Collapsing features. But still the nested grid is not bound with any DataSource then how it will fill up with the child data (Order Details).
When we click on the GridView it calls the GridView1_RowDataBound event which we will be using to bind the child grid with OrderDetails data. Default.aspx.cs Code behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using OrderDetailsXMLGridView.BAL;
namespace OrderDetailsXMLGridView
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
OrderDetailImpl impl = new OrderDetailImpl();
impl.getOrderInfo();
}
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
GridView gv = (GridView)e.Row.FindControl("GridView2");
OrderDetailImpl objData = new OrderDetailImpl();
ObjectDataSource objDataSrc;
objDataSrc = new ObjectDataSource();
objDataSrc.TypeName = objData.GetType().FullName;
OrderModel rowView = (OrderModel)e.Row.DataItem;
string dataItem = rowView.OrderID;
objDataSrc.SelectMethod = "getOrderDetails";
objDataSrc.SelectParameters.Add("OrderId", dataItem);
objDataSrc.DataBind();
gv.DataSource = objDataSrc;
gv.DataBind();
}
}
}
}
On page load we are loading the XML data into our Models so that ObjectDataSource Datasource will have data to show on the grid. RowDataBound event is called when we click on the first column which is ItemTemplate and it is bound with OrderID column.
When the event is called it pass the OrderId value as event argument which we will pass to our second methods as shown above to get the child data from XML i.e. OrderDetails.
Dear Kapil
Thanks for such a wonderful code.
Please guide me how can we put a delete function in the same code.
Thanks in Advance.