(In which AJAX-enhanced CheckBoxes become more useful than RadioButtons but inheritance saves the day, and a simple RadioButton-derived control establishes the purpose of a control’s naming container)

The RadioButtonList. Very handy for inviting users to select just one item from a list although because of the screen real estate it takes up, used less and less in favour of the DropDownList. In plain HTML, the browser knows to enforce the unique selected value amongst a list of radio buttons because they all have the same value for their name attributes.

<input type="radio" name="RadioButtonList1" value="1" />
   <label for="RadioButtonList1_0">Orlando Gee</label><br />
<input type="radio" name="RadioButtonList1" value="2" />
   <label for="RadioButtonList1_1">Keith Harris</label><br />
<input type="radio" name="RadioButtonList1" value="3" />
   <label for="RadioButtonList1_2">Donna Carreras</label> 

And in ASP.NET, the RadioButtonList control lets you set the name attribute for each radio button in the list using the control’s ID property.

<asp:RadioButtonList ID="RadioButtonList1" runat="server" /> 

Alternately, you can group individual RadioButton controls together using their GroupName property.

<asp:RadioButton runat="server" ID="rbSelectUnique" GroupName="selectList" /> 

And the effect is still the same; each HTML radio button (with the same GroupName) has its name attribute set to the same value.

The Problem

The main problem with RadioButtons though is that by default you cannot span a group of them across table rows. Let’s take an example, I have a sortable, pageable GridView of customers which I like, but I want to make sure the user can select only one of those customers as we go down the list. The obvious (in my mind anyway) solution is to add a template field to the GridView containing a RadioButton for each row and make the table generated by a GridView into a big radio button list by setting their GroupName attribute the same.

<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="customerid" DataSourceID="SqlDataSource1">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:RadioButton runat="server" ID="rbSelectUnique" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="name" HeaderText="name" 
          ReadOnly="True" SortExpression="name" />
        <asp:BoundField DataField="customerid" HeaderText="customerid" 
          InsertVisible="False" ReadOnly="True" SortExpression="customerid" />
    </Columns>
</asp:GridView> 

Perfectly sensible except that because each RadioButton is dynamically added to a table row by the GridView, ASP.NET generates the nameattribute of each radio button by appending the value you give it in GroupName to the internal name it generates for it when rendering the GridView. Hence we get this code for the <asp:GridView> above.

<table cellspacing="0" rules="all" border="1" 
   id="GridView2" style="border-collapse:collapse;">
   <tr>
      <th scope="col">&nbsp;</th>
      <th scope="col">name</th>
      <th scope="col">customerid</th>
   </tr>
   <tr>
      <td>
         <input id="GridView2_ctl02_rbSelectUnique" type="radio" 
            name="GridView2$ctl02$rbSelectUnique" value="rbSelectUnique" />
      </td>
      <td>Orlando Gee</td><td>1</td>
   </tr>
   <tr>
      <td>
         <input id="GridView2_ctl03_rbSelectUnique" type="radio" 
            name="GridView2$ctl03$rbSelectUnique" value="rbSelectUnique" />
      </td><td>Keith Harris</td><td>2</td>
   </tr>
   <tr>
      <td>
         <input id="GridView2_ctl04_rbSelectUnique" type="radio" 
            name="GridView2$ctl04$rbSelectUnique" value="rbSelectUnique" />
      </td><td>Donna Carreras</td><td>3</td>
   </tr>
</table> 

As you can see, the radio button’s name value takes the form GridView2$ctlXX$rbSelectUnique, where XX changes for each row and renders the list of the radio buttons useless. You can select them all at the same time if you wish. And it turns out that this issue is by design, according to the bug report on MS Connect.

Each row in a GridView is its own naming container so the controls’ names don’t collide. However, RadioButtons do not support spanning multiple naming containers and having their groupname attribute still work correctly. We will be looking at solving the RadioButton GroupName/multiple naming container issue in future versions of the product.

Or, in English, asp.net generates a unique NamingContainer for each cell in a table generated by a GridView and then uses that as a basis for all control IDs within the cell. It seems to be the only way to keep track of which events occurred where when the page posts back. That was written in April 2005 and it hasn’t been fixed in VS2008, so it’s time to look at workarounds.

The Solution

If you’re using AJAX already on the page, or are prepared to use it on the page, one way to get a radio button list of sorts into your GridView is to use checkboxes and a MutuallyExclusiveCheckBoxExtender in each TemplateField.

<ItemTemplate>
    <asp:CheckBox runat="server" ID="chkSelect" />
    <cc1:MutuallyExclusiveCheckBoxExtender ID="mecbe1" runat="server" 
        Key="chkSelectGroup" TargetControlID="chkSelect" />
</ItemTemplate> 

Now this works fine, but if you’re trying to keep your pages lean, the additional script for each MECBE added to the page might not be so good. From a UI point of view, you might also object to having checkboxes work like radio buttons and so might your users.

Fortunately, a quick check in Visual Studio’s Object Browser reveals that the RadioButton derives from a CheckBox, so you can easily swap out one for the other. And behold your RadioButtonList is back, spanning table rows.

<ItemTemplate>
    <asp:RadioButton runat="server" ID="rbSelect" />
    <cc1:MutuallyExclusiveCheckBoxExtender ID="mecbe1" runat="server" 
        Key="rbSelectGroup" TargetControlID="rbSelect" />
</ItemTemplate> 

Not The Solution, But Good To Know

If you’re not willing to use AJAX, the issue is always with the naming container clashing with the radio button list’s name property. Perhaps an obvious solution is to use your own RadioButton Control which overrides the name attribute for the button when it is rendered. Something like this.

using System;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace TryOuts
{
    public class MyRadioButton : RadioButton
    {
        public string GroupName2
        {
            get  {
                String s = (String)ViewState["GroupName2"];
                return ((s == null) ? "" : s); }
            set { ViewState["GroupName2"] = value; }
        }

        public bool Checked
        {
            get { return base.Checked; }
            set { base.Checked = value; }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            HtmlTextWriter hw = new HtmlTextWriter(sw);

            base.Render(hw);

            if (String.IsNullOrEmpty(GroupName2))
            {
                writer.Write(sb.ToString());
            }
            else
            {
                string html = Regex.Replace(
                   sb.ToString(), @"name=""[\w\$]+""", 
                   String.Format("name=\"{0}\"", GroupName2));
                writer.Write(html);
            }
        }
    }
}

Now this approach works to a degree. Replacing a standard RadioButton with this control does indeed override the control’s Name property and the RadioButtons all work within the table as required.

<cc2:MyRadioButton runat="server" ID="mrbTest" GroupName2="test" />

However, do anything simple such as select a row and post back the page (by clicking a button) and you’ll see that you can’t determine which radio button has been clicked.

foreach (GridViewRow row in GridView.Rows)
{
   RadioButton rb = (RadioButton)row.FindControl("mrbTest");
   if (rb.Checked)
   {
      lblSelected.Text = row.Cells[1].Text;
   }
}

And that’s because the NamingContainers for the cells containing the RadioButtons in the table generated in the GridView can no longer identify the RadioButtons in the cells in the table. Because our custom control has overridden the name attribute for each RadioButton to “test”, ASP.NET can’t find any controls called GridView2$ctlXX$mrbTest which is what it expects them to be called and why the naming container exists in the first place. Without the naming container producing these unique names in the way it does, we can’t do useful things like iterate over the rows in a GridView and why AJAX is the way forward when it comes to this particular problem.

Now strictly speaking, it should be possible to write a HttpModule that rewrites a RadioButton’s name attribute as it is sent to the browser so it works as part of a RadioButtonList and then rewrites the name attribute back as the page is posted back so the naming container appears to still be intact. But think about it, that’s a lot of effort for a small thing. Is the AJAX-free convenience worth the hassle?