Geeks With Blogs
The Quandary Phase This code was generated by a tool.

This is another installment of how to use the ASP.NET ListView and Repeater to generate repeating groups of controls in ASP.NET pages in scenarios which often lead developers to add unnecessary complexity to their pages by resorting to dynamically generated controls.

Part 2 demonstrated using the ListView control to maintain the values in a repeating group of textboxes, using a collection of strings as a data source,and allowing the user to add rows as required.

This works well for simple scenarios, however…what happens if each row consists of multiple values which need to be persisted in separate controls, while also allowing the user to add and remove rows?

In this case, the simplest solution is often to use a GridView. The ASP.NET GridView has excellent out-of-the-box functionality for displaying and editing data in an HTML table, and much of what it lacks can be compensated for using template columns to define your own cell content template.

However, you will frequently find that the lack of control over the formatting of the items rules the GridView out. For example, you may want to display your controls over multiple table rows, or you may simply not want to use an HTML table at all. This is where the Repeater and ListView controls become indispensible: they provide the developer with full control over the layout of the items, and can be bound to any data source which implements IEnumerable.

This sample demonstrates the binding of an ASP.NET ListView to a DataTable, preserving the values across posts-back, and crucially, allowing the user to add and remove rows (a feature which is often a stumbling block for developers, as the number of questions on this topic on ASP.NET forums is testament to).

I have put together a hypothetical user administration page as an example, for a hypothetical web-based application used by a software company. The page is intended to display a list of users to system administrators, providing them with some bogstandard CRUD functionality, and allowing them to edit the role of each user, which is used to determine what parts of this imaginary system each user has access to.

The ListView markup might look something like this:

   1: <asp:ListView ID="lvDynamicTextboxes" runat="server" 
   2:     ItemPlaceholderID="itemPlaceholder" 
   3:     onitemdatabound="lvDynamicTextboxes_ItemDataBound" 
   4:             onitemcommand="lvDynamicTextboxes_ItemCommand">
   5:     <LayoutTemplate>
   6:         <table style="width: 50%" class="permissions">
   7:             <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
   8:         </table>
   9:     </LayoutTemplate>
  10:     <ItemTemplate>
  11:         <tr class="top">
  12:             <td>
  13:                 Name:
  14:             </td>
  15:             <td colspan="2">
  16:                 <asp:TextBox ID="txtName" runat="server" Width="90%">
  17:                 </asp:TextBox>
  18:             </td>
  19:             <td>
  20:                 <asp:LinkButton ID="lnkDelete" runat="server">Delete</asp:LinkButton>
  21:             </td>   
  22:         </tr>
  23:         <tr class="bottom">
  24:             <td>
  25:                 Role:
  26:             </td>
  27:             <td>
  28:                 <asp:DropDownList ID="cboRole" runat="server">
  29:                     <asp:ListItem Value="0">--select--</asp:ListItem>
  30:                     <asp:ListItem Value="1">Developer</asp:ListItem>
  31:                     <asp:ListItem Value="2">Team Leader</asp:ListItem>
  32:                     <asp:ListItem Value="3">Manager</asp:ListItem>
  33:                 </asp:DropDownList>    
  34:             </td>
  35:             <td>
  36:                 System Administrator:
  37:             </td>
  38:             <td>
  39:                 <asp:CheckBox ID="chkSysAdmin" runat="server" />
  40:             </td>   
  41:         </tr>
  42:     </ItemTemplate>
  43: </asp:ListView>

And the output of the above markup should be a table which looks like this:



As you can see, each set of controls is split over 2 separate rows in the resulting HTML table, which instantly excludes GridView-based solutions. Each item in the grid consists of a textbox to hold the user’s name, a dropdown list to edit their role, a checkbox which specifies whether or not the user is a system administrator, and a LinkButton to delete a user.

In order to persist the values, I used a DataTable, though in a real-world application, I would probably use custom business objects for all but single-tier applications; although I do often use DataSets and DataTables in my data layer classes, I tend not to pass these beyond the data layer, as I don’t think a relational view of the data is much use to a UI in general. In order to hold the DataTable values in memory between posts-back, I used session state, rather than ViewState as in the previous article, because of the increased data size.

The functionality in the code-behind is very similar to the code in the previous article, so I won’t go over it in any great detail here, but will note a couple of important differences. As the sample application starts each time with a new dataset, and doesn’t persist its data between runs, I needed some code to generate a new DataTable to hold the values in the ListView controls which needed to be persisted. Creating a new DataTable programmatically is very straightforward, as you can see:

   1: private DataTable CreateDataTable()
   2: {
   3:     DataTable dt = new DataTable();
   4:     DataRow row;
   6:     dt.Columns.Add("rowID", typeof(int));
   7:     dt.Columns[0].AutoIncrement = true;
   8:     dt.Columns[0].AutoIncrementSeed = dt.Columns[0].AutoIncrementStep = 1;
  10:     dt.Columns.Add("staffMemberName", typeof(string));
  11:     dt.Columns.Add("roleName", typeof(string));
  12:     dt.Columns.Add("isSysAdmin", typeof(bool));
  14:     //create a dummy row
  15:     row = dt.NewRow();
  16:     row[1] = row[2] = string.Empty;
  17:     row[3] = false;
  18:     dt.Rows.Add(row);
  20:     return dt;
  21: }
This method generates a new DataTable, with 4 columns: 3 of which will be used to persist the control values, and an autonumber ID field to act as a primary key, and behaves in the same way as a SQL Server Identity field, which means that the ID is automatically generated each time a new row is added to the DataTable. I also added an empty data row to the DataTable, so that when the ListView is initially bound, it appears with an extra row, ready for the user to input data into.

There are also a number of changes in the code which populates the control values when the ListView is data bound. As in the previous article, the key event to hook up for this is the ItemDataBound event. This event is raised as each item in the data source is bound to the ListView: it enables you to access the properties of the data source item, and the controls to which the values are being bound. You can, if you prefer, specify the values directly in your ASP.NET markup, however I prefer to keep the code and markup as separate as possible, and so I do all the binding of the controls in the code-behind. The event handler therefore looks like this:

   1: protected void lvDynamicTextboxes_ItemDataBound(object sender, ListViewItemEventArgs e)
   2: {
   4:     if (e.Item is ListViewDataItem)
   5:     {
   6:         object fieldValue;
   8:         ListViewDataItem item = (ListViewDataItem)e.Item;
   9:         int rowID = (int)DataBinder.Eval(item.DataItem, "rowID");
  11:         //set the command argument of the delete row linkbutton
  12:         LinkButton lbt = (LinkButton)e.Item.FindControl("lnkDelete");
  13:         lbt.CommandArgument = rowID.ToString();
  14:         lbt.CommandName = "DELETEITEM";
  16:         fieldValue = DataBinder.Eval(item.DataItem, "staffMemberName");
  17:         if (fieldValue != DBNull.Value && fieldValue.ToString().Length > 0)
  18:         {
  19:             TextBox txt = (TextBox)e.Item.FindControl("txtName");
  20:             txt.Text = fieldValue.ToString();
  21:         }
  23:         fieldValue = DataBinder.Eval(item.DataItem, "roleName");
  24:         if (fieldValue != DBNull.Value && fieldValue.ToString().Length > 0)
  25:         {
  26:             DropDownList cbo = (DropDownList)e.Item.FindControl("cboRole");
  27:             cbo.Items.FindByText(fieldValue.ToString()).Selected = true;
  28:         }
  30:         fieldValue = DataBinder.Eval(item.DataItem, "isSysAdmin");
  31:         if (fieldValue != DBNull.Value)
  32:         {
  33:             CheckBox chk = (CheckBox)e.Item.FindControl("chkSysAdmin");
  34:             chk.Checked = (bool)fieldValue;
  35:         }
  36:     }
  37: }

The code above first checks to ensure the current ListView item being bound is a data item (as opposed to a header/footer item). It then uses DataBinder.Eval to return the value of each of the fields in the relevant DataTable row, and binds those values to the UI controls. One other important piece of functionality which occurs here is the setting of the command argument of the Delete User LinkButton. The command argument is set to the primary key of the row in the DataTable, and this is what enables the correct row to be deleted when the LinkButton is clicked.

As you can see in the ListView markup above, I have also subscribed to the ItemCommand event. This event is raised whenever a button is clicked within the ListView. The ListView control helpfully bubbles any click events of its child controls up, by raising its own ItemCommand event. The CommandArgument value (as set in the ItemDataBound event handler) is available at this point via the ListViewCommandEventArgs instance, and we can use this to retrieve the unique ID of the data row like this:

   1: protected void lvDynamicTextboxes_ItemCommand(object sender, ListViewCommandEventArgs e)
   2: {
   3:     if (e.CommandName.Equals("DELETEITEM"))
   4:     {
   5:         int rowID = int.Parse(e.CommandArgument.ToString());
   6:         this.DeleteRow(rowID);
   7:     }
   8: }

All that is then left is to remove the specified row from the DataTable, and rebind the ListView control.

Finally, you might have noticed a couple of CSS classes applied to the HTML table inside the ListView markup above. This was used to delimit each of the pair of HTML table rows, to mark each one as a separate item, by setting the appropriate cell borders. The CSS to do this looks like this:

   1: <style type="text/css">
   2:     table.permissions
   3:     {
   4:         border: solid 1px black;
   5:         border-collapse:collapse;
   6:     }
   8:     table.permissions td
   9:     {
  10:         border-top: none;
  11:     }
  13:     table tr.bottom td
  14:     {
  15:         border-bottom: solid 1px black;
  16:     }
  17: </style>

The issue of how to code with dynamic controls is still one of the most popular questions on ASP.NET forums, and it’s unfortunate that many of the responses to these questions simply point the poster at one or more articles on dynamic control usage, rather than questioning why the poster feels they need to use them in the first place. I find in most cases, this is simply because they are not aware of the flexibility offered by the built-in ASP.NET controls, and not aware of the potential time-savers they can be. None of the code for the three articles in this series is at all complex. Let’s face it, when writing an application, there isn’t time to waste writing reams of logic just to populate an HTML table with contents, and a bit of time spent up-front investigating good databinding techniques can save you a lot of time further down the line.

The full source code can be downloaded using the link at the top of the article.

Posted on Sunday, April 19, 2009 6:54 PM | Back to top

Comments on this post: ASP.NET: Alternatives To Dynamic Controls- Part 3

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
I feel strongly about it and love learning more on this topic. If possible, as you gain expertise, would you mind updating your blog with more information? It is extremely helpful and beneficial to your readers Nice article.
Left by nouvelles des casinos en ligne on Mar 11, 2010 8:13 PM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
Where is the method CreateDataTable() going to be called from?
Left by Dan on Apr 12, 2010 3:39 PM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
I have seen your article as well as your codes but the problem with code is it can't hold values between postback or after delete or add so it seems useless with dynamic controls if you cant hold values.
Left by Navin jha on May 04, 2010 7:47 PM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
@Navin: sure it can. It makes use of the ASP.NET server-side session to hold the dataset in memory between postbacks, so it is able to maintain its state.
Left by adampooler on May 04, 2010 8:04 PM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
Hi Adam,

Do you have the source code for this in VB.NET please? Also, How do i go about retrieving data from the controls?

Any help would be much appreciated.

Left by Arvind on Oct 04, 2010 2:18 PM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
Nice article. It’s used for me a lot.

Left by Malli on Oct 20, 2010 10:34 PM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
thank you for sharing.
Left by ugg fluff flip flop on Nov 17, 2010 6:35 PM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
I found this is an informative and interesting post so i think so it is very useful and knowledgeable. I would like to thank you for the efforts you have made in writing this article. I am hoping the same best work from you in the future as well. In fact your creative writing ability has inspired me. Really the article is spreading its wings rapidly.
Indonesian Teak Garden Furniture.
Left by Deddy Agoes on Dec 07, 2010 5:15 AM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
it really helped me a lot. I want to actually display data from database and may contain more values so would display all values and then add a new value and save it in the database. can you help me out on this.
Left by yogesh on Jan 25, 2011 5:58 AM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
This is very helping info for me as beginner in the web development. The complexity of ListView is not give me opportunity to progress cheap custom written paper
Left by billy6 on Jan 30, 2011 1:46 AM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
Nice article. It’s used for me a lot.
This is very helping info for me as beginner in the web development.

long time ago
Left by apple on Feb 14, 2011 8:02 PM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
Thanks, great code. But Navin is right, the delete function does not preserve user input. To reproduce: add five rows, enter some data, delete a record => everything is gone. I fixed this by setting a deletion flag in a hidden field, then update the data source, then perform the delete action.
Left by Lefa on Feb 21, 2011 3:11 AM

# re: ASP.NET: Alternatives To Dynamic Controls- Part 3
Requesting Gravatar...
Thank you for your efforts and their description. Will move in the direction that you specify and develop its first blog.
Good luck to all!
Left by buying research papers on Mar 02, 2011 12:22 AM

# 先生
Requesting Gravatar...
organizing interior pockets, a padded laptop compartment,
Left by mulberry bags on Mar 02, 2011 8:22 PM

# 先生
Requesting Gravatar...
It does help me a lot knowing that you have shared this information here freely. I love the way the people here interact and shared their opinions too. I would love to track your future posts pertaining to the said topic we are able to read.
Left by Cheap Oakley sunglasses on Mar 17, 2011 8:55 PM

Comments have been closed on this topic.
Copyright © Adam Pooler | Powered by: