Blog Archives

C# Nested Repeaters ASP .Net

Nested repeaters are a very common topic in ASP .Net so I thought it would be a good subject to write on, especially considering the number of solutions that exist when you Google “nested repeaters” that offer plug-and-play answers, but don’t actually explain why one approach may be more optimal than another.

The Solution

XML/HTML for your repeaters:

<asp:Repeater runat="server" ID="rptRobotFactories" EnableViewState="false" OnItemDataBound="rptRobotFactories_ItemDataBound">
                        <ItemTemplate>
                            <div id="header">
                                <%# (((System.Data.DataRowView)Container.DataItem)["FACTORYNAME"]) %>
                            </div>
                            <asp:Repeater runat="server" ID="rptRobots" EnableViewState="false">
                                <HeaderTemplate>
                                    <ul>
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <li>
                                        <%# Eval("ROBOTNAME") %>
                                    </li>
                                </ItemTemplate>
                                <FooterTemplate>
                                    </ul></FooterTemplate>
                            </asp:Repeater>
                        </ItemTemplate>
                    </asp:Repeater>

Code-behind to load data into rptRobotFactories:

//don't forget these guys. save the practice on your typing, finger joints will thank you later =)
using System.Data;
using System.Data.SqlClient;

//a few more variables than necessary were used for code readability. feel free to condense.
//call this function on page load. don't forget your if not postback check
    private void LoadFactories()
    {
        //don't forget to wrap in your exception handling!

          SqlConnection conn = new SqlConnection(
            ConfigurationManager.ConnectionStrings["RobotDBCon"].ConnectionString);

        SqlDataAdapter da = new SqlDataAdapter();
        SqlCommand cmd = new SqlCommand("SP_GET_ROBOTS_IN_FACTORIES", conn);

        cmd.CommandType = CommandType.StoredProcedure;
        //generally you'd probably have some parameters here

        da.SelectCommand = cmd;

        DataTable dtRobots = new DataTable("robots"); //first we get the robots
        
        da.Fill(dtRobots); //notice there's only one SP call and one datatable filled

//this might seem strange using a string array for columns, but the alternative is not as clean IMO
        string[] cols = new string[1]; //if you're uncomfortable using string as primary key add another column and use ID
        cols[0] = "FACTORYNAME";

//these next two lines are the magic makers
        DataTable dtRobotFactories = dtRobots.DefaultView.ToTable("robotfactories", true, cols).Clone();

        dtRobots.DefaultView.ToTable("robotfactories", true, cols).AsEnumerable().Distinct().CopyToDataTable(dtRobotFactories, LoadOption.OverwriteChanges);

        DataSet ds = new DataSet();
        ds.Tables.Add(dtRobotFactories);
        ds.Tables.Add(dtRobots);
       ds.Relations.Add("robotsinfactories",
ds.Tables["robotfactories"].Columns["FACTORYNAME"],
ds.Tables["robots"].Columns["FACTORYNAME"],false);
        ds.Relations[0].Nested = true;

        rptSegments.DataSource = ds.Tables["robotfactories"];
        rptSegments.DataBind();

        conn.Close();
    }

Last piece of code is our itemdatabound event of the parent repeater which loads the child data. Alternatively you can put this inline as the datasource property with a one liner but this approach may allow you more flexibility later:

    protected void rptRobotFactories_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if ((e.Item.ItemType == ListItemType.Item) || (e.Item.ItemType == ListItemType.AlternatingItem))
        {
            Repeater rptRobots = (Repeater)e.Item.FindControl("rptRobots");
            rptRobots.DataSource = ((DataRowView)e.Item.DataItem).CreateChildView("robotsinfactories");
            rptRobots.DataBind();
        }
    }

And that’s it! Your result should look something like:

FACTORY1
 *ROBOTA1
 *ROBOTA2
FACTORY2
 *ROBOTB1
 *ROBOTB2

What makes this code different?

The use of data relations is disputable. Rightfully arguable, depending on the scope of your project and the data being returned and formatted, this may be overkill.

Multiple data relations and tables can use excess resources when simple string variables could be pulled from the dataset and the same achieved with only one repeater or listview.

I do not contest this point but keep in a mind, a simple re-usable design pattern can be rapidly applied to many different sets of data, whereas a customized variable or array based solution will likely be specific to your initial implementation, and not easily re-adapted in another page or separate project.

Ultimately, code patterns will improve your code readability and productivity as a developer.

See my article on how to create secure relational databases in sql for more information on why the result should come back in a single stored procedure rather than making multiple calls.

Advertisement