Introduction
Using nested ASP.NET Repeaters, you can easily create professional report style web pages, that include standard ASP.NET controls. Other articles on CodeProject show how you can do this with a UserControl. This example, just uses the standard Repeater Control, and in particular the ItemDataBound
method to create a sophisticated interface.
Background
I needed to create a report, but the users need to be able to update it via the same interface, so SQL Reporting Services, and Crystal Reports were unsuitable. I had to do a fair amount of research to get round some problem; like DataBinding syntax, techniques to locate the nested control, so I decided to share the results. Thanks to people who posted answers to posts in the excellent MSDN Forum.
Setup
If you wish to run the sample, carry out the following steps:
- The connection string needs to be modified to connect to your database instance.
- I have included an SQL script that creates the report stored procedure. This needs to be run against the Northwind database.
Data
The stored procedure returns two resultsets from the Northwind database.
- Product categories
- Products and total sales
A DataSet
is populated in the standard way.
Code
<asp:Repeater id="rptProducts" runat="server"
OnItemDataBound="rptProducts_ItemDataBound">
For the child repeater, you need to specify the OnItemDataBound
method to call in the ASPX file. If you just wire the event in the normal way, it does not get called.
1: protected void rptProducts_ItemDataBound(object sender,
System.Web.UI.WebControls.RepeaterItemEventArgs e)
2: {
3: if(e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
4: {
5: string product =
((DataRowView)e.Item.DataItem).Row.ItemArray[2].ToString();
6: ImageButton i =
(ImageButton)e.Item.Controls[0].FindControl("imgDelete");
7: i.CommandName = "Delete";
8: i.CommandArgument = product;
9: string script = string.Format(
"javascript:alert('Delete Product: {0}');", product);
10: i.Attributes.Add("onclick", script);
11: }
12: }
When rptProducts_ItemDataBound
method is called, we can locate product data by casting the RepeaterItem
into a DataRowView
. [5] Here, we retrieve the product name, which we use to pass into the CommandArgument
of the ImageButton
[6-8]. This CommandArgument
can be pulled out in the ImageButton_Command
event, and then used to delete or modify the relevant product in the database. This method [1] must be protected
, and not private
, otherwise it will be inaccessible.
1: private void rptCategories_ItemDataBound(object sender,
System.Web.UI.WebControls.RepeaterItemEventArgs e)
2: {
3: if(e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
4: {
5:
6: string category =
((DataRowView)e.Item.DataItem).Row.ItemArray[0].ToString();
7: Repeater rep = (Repeater)e.Item.FindControl("rptProducts") ;
8:
9: DataView dv = getChildRows(category) ;
10: rep.DataSource = dv ;
11: rep.DataBind() ;
12:
13: SalesTotal = this.calculateSalesTotals(category) ;
14:
15: Repeater r = (Repeater)e.Item.Parent ;
16: Label lblSalesTotals =
(Label)r.Controls[e.Item.ItemIndex+ 1].FindControl(
"lblSalesTotals");
17: lblSalesTotals.Text = SalesTotal.ToString("C") ;
18: }
19: }
The parent repeater ItemDataBound
method is responsible for filtering the category
data. This is the key piece of code on the page, that performs the nesting. FindControl
can return a reference to the child Repeater
[7]. A DataView
control can then be passed to the child Repeater
as the DataSource
[9-11]. Finally, we calculate the Sales Totals for the category, for the Group Footer [15-17].
1: private DataView getChildRows(string Filter)
2: {
3: string whereClause =
string.Format("CategoryName = '{0}'", Filter) ;
4: DataView dv = new DataView(ds.Tables["Products"],
whereClause, "",
DataViewRowState.CurrentRows) ;
5: return dv ;
6: }
A DataView
object is returned with the products that belong to a specified category. The DataView
constructor used takes the DataTable
, an SQL like where clause, a sort expression, and DataViewRowState
[4]. No DataRelations are needed.
Debugging
I found it really helpful to set breakpoints on the ItemDataBound
methods and take advantage of the Locals, Watch and Command windows in Visual Studio .NET to locate control/objects. This helps you to locate the controls and objects that you want to manipulate.