Wednesday, March 25, 2009

ASP.NET MVC Html.RadioButtonList Blues

Recently I upgraded our ASP.NET MVC project from Preview 5 to RC2. At first I thought the Html.RadioButtonList extension was removed completely, but then realized that it was no longer in the main MVC assembly, but was moved to the Futures project (although I don't know why).

The Preview 5 version of the Html.RadioButtonList rendered the following output...

However, once I got my code to compile I went to run my application only to find that it rendered two RadioButtons with no labels! Where did the labels go?

I pulled down the source code for the 1.0 release (just to make sure it wasn't fixed in the RTM as opposed to the RC2) and dove into the extension code. Nowhere did I see the labels being applied in the extension.

//C:\...\MVC-RTM\MVC\src\MvcFutures\Mvc\RadioExtensions.cs
private static string[] RadioButtonListInternal(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, bool usedViewData, IDictionary<string, object> htmlAttributes) {
   if (String.IsNullOrEmpty(name)) {
      throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
   }
   if (selectList == null) {
      throw new ArgumentNullException("selectList");
   }

   // If we haven't already used ViewData to get the entire list of items then we need to
   // use the ViewData-supplied value before using the parameter-supplied value.
   if (!usedViewData) {
      object defaultValue = htmlHelper.ViewData.Eval(name);

      if (defaultValue != null) {
         IEnumerable defaultValues = new[] { defaultValue };
         IEnumerable<string> values = from object value in defaultValues select Convert.ToString(value, CultureInfo.CurrentCulture);
         HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
         List<SelectListItem> newSelectList = new List<SelectListItem>();

         foreach (SelectListItem item in selectList) {
            item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
            newSelectList.Add(item);
         }

         selectList = newSelectList;
      }
   }

   IEnumerable<string> radioButtons = selectList.Select<SelectListItem, string>(item => htmlHelper.RadioButton(name, item.Value, item.Selected, htmlAttributes));

   return radioButtons.ToArray();
}

Next I looked at the Unit Tests for the extesion. I was please to see tests for the RadioButtonList, but was shocked to see that the asserts were verifying html that didn't have any labels applied!

//C:\...\MVC-RTM\MVC\test\MvcFuturesTest\Mvc\Test\RadioExtensionsTest.cs
[TestMethod]
public void RadioButtonListItemSelected() {
   // Arrange
   HtmlHelper htmlHelper = TestHelper.GetHtmlHelper(new ViewDataDictionary());

   // Act
   string[] html = htmlHelper.RadioButtonList("FooList", GetRadioButtonListData(true));

   // Assert
   Assert.AreEqual(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0]);
   Assert.AreEqual(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1]);
   Assert.AreEqual(@"<input checked=""checked"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2]);
}

private static SelectList GetRadioButtonListData(bool selectBaz) {
   List<RadioItem> list = new List<RadioItem>();
   list.Add(new RadioItem { Text = "text-foo", Value = "foo" });
   list.Add(new RadioItem { Text = "text-bar", Value = "bar" });
   list.Add(new RadioItem { Text = "text-baz", Value = "baz" });
   return new SelectList(list, "value", "TEXT", selectBaz ? "baz" : "something-else");
}

private class RadioItem {
   public string Text {
      get;
      set;
   }

   public string Value {
      get;
      set;
   }
}  

Where does that leave us? I guess using the Futures Html.RadioButtonList isn't all that helpful after all. So, instead I decided to loop through my DataSource and create individual Html.RadioButton and Label combinations.

<!-- After using and looking at the code for the Html.RadioButtonList in the ASP.NET MVC 1.0 RTM codebase, I'm not sure how it is supposed to be useful. It only outputs the actual input radio button and doesn't render any corresponding labels. To get around this I ended up writing a foreach creating individual Html.RadioButton and labels -->
<% 
var radioButtonList = new SelectList(new List<ListItem> {
   new ListItem { Text = "Current", Value="false", Selected=true }, 
   new ListItem { Text = "Other", Value="true"}}, "Value", "Text", "false");
var htmlAttributes = new Dictionary<string, object> { 
   { "class", "radioButtonList" },
   { "onclick", "if(eval(this.value)) { $('#tblDate').show('slow'); } else { $('#tblDate').hide('slow'); }" }
};
foreach (var radiobutton in radioButtonList) { %>
   <%=Html.RadioButton("rblDate", radiobutton.Value, radiobutton.Selected, htmlAttributes)%>
   <label><%=radiobutton.Text%></label>
<% } %>   

1 comment:

  1. Thanks for sharing, I had the same problem and your solution looks good.

    ReplyDelete