Has anyone implement RadioButtonListFor<T> for ASP.NET MVC?

Here is the usage in the aspx page

    <%= Html.RadioButtonListFor(m => m.GenderRadioButtonList)%>

Here is the view model

public class HomePageViewModel
{
    public enum GenderType
    {
        Male,
        Female
    }
    public RadioButtonListViewModel<GenderType> GenderRadioButtonList { get; set; }

    public HomePageViewModel()
    {
        GenderRadioButtonList = new RadioButtonListViewModel<GenderType>
        {
            Id = "Gender",
            SelectedValue = GenderType.Male,
            ListItems = new List<RadioButtonListItem<GenderType>>
            {
                new RadioButtonListItem<GenderType>{Text = "Male", Value = GenderType.Male},
                new RadioButtonListItem<GenderType>{Text = "Female", Value = GenderType.Female}
            }
        };
    }
}

Here's the view model used for radio button lists

public class RadioButtonListViewModel<T>
{
    public string Id { get; set; }
    private T selectedValue;
    public T SelectedValue
    {
        get { return selectedValue; }
        set
        {
            selectedValue = value;
            UpdatedSelectedItems();
        }
    }

    private void UpdatedSelectedItems()
    {
        if (ListItems == null)
            return;

        ListItems.ForEach(li => li.Selected = Equals(li.Value, SelectedValue));
    }

    private List<RadioButtonListItem<T>> listItems;
    public List<RadioButtonListItem<T>> ListItems
    {
        get { return listItems; }
        set
        {
            listItems = value;
            UpdatedSelectedItems();
        }
    }
}

public class RadioButtonListItem<T>
{
    public bool Selected { get; set; }

    public string Text { get; set; }

    public T Value { get; set; }

    public override string ToString()
    {
        return Value.ToString();
    }
}

Here's the extension methods for RadioButtonListFor

public static class HtmlHelperExtensions
{
    public static string RadioButtonListFor<TModel, TRadioButtonListValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, RadioButtonListViewModel<TRadioButtonListValue>>> expression) where TModel : class
    {
        return htmlHelper.RadioButtonListFor(expression, null);
    }

    public static string RadioButtonListFor<TModel, TRadioButtonListValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, RadioButtonListViewModel<TRadioButtonListValue>>> expression, object htmlAttributes) where TModel : class
    {
        return htmlHelper.RadioButtonListFor(expression, new RouteValueDictionary(htmlAttributes));
    }

    public static string RadioButtonListFor<TModel, TRadioButtonListValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, RadioButtonListViewModel<TRadioButtonListValue>>> expression, IDictionary<string, object> htmlAttributes) where TModel : class
    {
        var inputName = GetInputName(expression);

        RadioButtonListViewModel<TRadioButtonListValue> radioButtonList = GetValue(htmlHelper, expression);

        if (radioButtonList == null)
            return String.Empty;

        if (radioButtonList.ListItems == null)
            return String.Empty;

        var divTag = new TagBuilder("div");
        divTag.MergeAttribute("id", inputName);
        divTag.MergeAttribute("class", "radio");
        foreach (var item in radioButtonList.ListItems)
        {
            var radioButtonTag = RadioButton(htmlHelper, inputName, new SelectListItem{Text=item.Text, Selected = item.Selected, Value = item.Value.ToString()}, htmlAttributes);

            divTag.InnerHtml += radioButtonTag;
        }

        return divTag + htmlHelper.ValidationMessage(inputName, "*");
    }

    public static string GetInputName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
    {
        if (expression.Body.NodeType == ExpressionType.Call)
        {
            var methodCallExpression = (MethodCallExpression)expression.Body;
            string name = GetInputName(methodCallExpression);
            return name.Substring(expression.Parameters[0].Name.Length + 1);

        }
        return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1);
    }

    private static string GetInputName(MethodCallExpression expression)
    {
        // p => p.Foo.Bar().Baz.ToString() => p.Foo OR throw...

        var methodCallExpression = expression.Object as MethodCallExpression;
        if (methodCallExpression != null)
        {
            return GetInputName(methodCallExpression);
        }
        return expression.Object.ToString();
    }

    public static string RadioButton(this HtmlHelper htmlHelper, string name, SelectListItem listItem,
                         IDictionary<string, object> htmlAttributes)
    {
        var inputIdSb = new StringBuilder();
        inputIdSb.Append(name)
            .Append("_")
            .Append(listItem.Value);

        var sb = new StringBuilder();

        var builder = new TagBuilder("input");
        if (listItem.Selected) builder.MergeAttribute("checked", "checked");
        builder.MergeAttribute("type", "radio");
        builder.MergeAttribute("value", listItem.Value);
        builder.MergeAttribute("id", inputIdSb.ToString());
        builder.MergeAttribute("name", name + ".SelectedValue");
        builder.MergeAttributes(htmlAttributes);
        sb.Append(builder.ToString(TagRenderMode.SelfClosing));
        sb.Append(RadioButtonLabel(inputIdSb.ToString(), listItem.Text, htmlAttributes));
        sb.Append("<br>");

        return sb.ToString();
    }

    public static string RadioButtonLabel(string inputId, string displayText,
                                 IDictionary<string, object> htmlAttributes)
    {
        var labelBuilder = new TagBuilder("label");
        labelBuilder.MergeAttribute("for", inputId);
        labelBuilder.MergeAttributes(htmlAttributes);
        labelBuilder.InnerHtml = displayText;

        return labelBuilder.ToString(TagRenderMode.Normal);
    }


    public static TProperty GetValue<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) where TModel : class
    {
        TModel model = htmlHelper.ViewData.Model;
        if (model == null)
        {
            return default(TProperty);
        }
        Func<TModel, TProperty> func = expression.Compile();
        return func(model);
    }
}

MVC 3 example which creates 3 radio buttons with validation to ensure 1 option is selected. And if the form fails validation (e.g. on other fields) the chosen radio option is preselected when the form is reshown.

View

@Html.RadioButtonForSelectList(m => m.TestRadio, Model.TestRadioList)
@Html.ValidationMessageFor(m => m.TestRadio)

Model

public class aTest
{
    public Int32 ID { get; set; }
    public String Name { get; set; }
}

public class LogOnModel
{
    public IEnumerable<SelectListItem> TestRadioList { get; set; }

    [Required(ErrorMessage="Test Error")]
    public String TestRadio { get; set; }

    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }
}

Controller Actions

public ActionResult LogOn()
    {
        List<aTest> list = new List<aTest>();
        list.Add(new aTest() { ID = 1, Name = "Line1" });
        list.Add(new aTest() { ID = 2, Name = "Line2" });
        list.Add(new aTest() { ID = 3, Name = "Line3" });

        SelectList sl = new SelectList(list, "ID", "Name");

        var model = new LogOnModel();
        model.TestRadioList = sl;

        return View(model);
    }

    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
             ....
        }

        // If we got this far, something failed, redisplay form
        List<aTest> list = new List<aTest>();
        list.Add(new aTest() { ID = 1, Name = "Line1" });
        list.Add(new aTest() { ID = 2, Name = "Line2" });
        list.Add(new aTest() { ID = 3, Name = "Line3" });

        SelectList sl = new SelectList(list, "ID", "Name");
        model.TestRadioList = sl;

        return View(model);
    }

Here is the extension:

public static class HtmlExtensions
{
    public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        IEnumerable<SelectListItem> listOfValues)
    {
        var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        var sb = new StringBuilder();

        if (listOfValues != null)
        {
            foreach (SelectListItem item in listOfValues)
            {
                var id = string.Format(
                    "{0}_{1}",
                    metaData.PropertyName,
                    item.Value
                );

                var radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id }).ToHtmlString();
                sb.AppendFormat(
                    "<label for=\"{0}\">{1}</label> {2}",
                    id,
                    HttpUtility.HtmlEncode(item.Text),
                    radio
                );
            }
        }

        return MvcHtmlString.Create(sb.ToString());
    }
}