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

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());    }}


Okay, I'm aware this isn't a direct answer to your question, but this may be a better way to doing most inputs anyway (and it was fun to make). I have only just completed this and ran a small amount of testing against it, so I can't vouch for this being perfect in every situation.

I got this idea from Jimmy Bogard's post here. Take a look because there's a heap of really cool ideas there.

What I have done is created an "InputFor" helper which tries its best to work out what input you're asking for and outputs it accordingly. This will do radio buttons, but will default to a drop down if there is more than two, you should be able to change this functionality quite easily.

The below code allows you to make calls such as <%= Html.InputFor(m => m.Gender) %> or <%Html.InputFor(m => m.Gender, Model.GenderList)%>. There is a cool little bit at the end which allows you to do coding by convention, but we'll get to that later.

public static MvcHtmlString InputFor<TModel>(this HtmlHelper<TModel> helper, Expression<Func<TModel, object>> field, Dictionary<string, string> listing) where TModel : class        {            string property_name = GetInputName(field);            PropertyDescriptor descriptor = TypeDescriptor.GetProperties(helper.ViewData.Model).Find(property_name, true);            string property_type = descriptor.PropertyType.Name;            var func = field.Compile();            var value = func(helper.ViewData.Model);            //Add hidden element if required            if (descriptor.Attributes.Contains(new HiddenInputAttribute()))            {                return helper.Hidden(property_name, value);            }            if (property_type == "DateTime" || property_type == "Date")            {                return helper.TextBox(property_name, value, new { @class = "date_picker" });            }            if (listing != null)            {                if (listing.Count <= 2)                {                    //This is a good length for a radio button                    string output = "";                    foreach (KeyValuePair<string, string> pair in listing)                    {                        TagBuilder label = new TagBuilder("label");                        label.MergeAttribute("for", property_name);                        label.SetInnerText(pair.Value);                        output += helper.RadioButton(property_name, pair.Key, (value == pair.Key)).ToHtmlString();                        output += label.ToString();                    }                    return MvcHtmlString.Create(output);                }                else                {                    //too big for a radio button, lets make a drop down                    return helper.DropDownList(property_name, new SelectList(listing, "Key", "Value"), value);                }            }            else            {                if (property_type == "Boolean")                {                    listing = new Dictionary<string, string>();                    listing.Add("true", "Yes");                    listing.Add("false", "No");                    SelectList select_values = new SelectList(listing, "Key", "Value", ((bool)value ? "Yes" : "No"));                    return helper.DropDownList(property_name, select_values);                }                return helper.TextBox(property_name, value);            }        }

Coding by Convention

The below code allows this to be done with convention over configuration in mind. An example of this is if you have a model object which contains the property you want to list (Gender) and a dictionary with the same name but appended with "List" (GenderList) then it will use this list by default.

e.g. <%= Html.InputFor(m => m.Gender) %> can make a full drop down list/radio button group, but these default values can be overridden by making a call like <%= Html.InputFor(m => m.Gender, alternate_list) %>

public static MvcHtmlString InputFor<TModel>(this HtmlHelper<TModel> helper, Expression<Func<TModel, object>> field) where TModel : class{    string property_name = GetInputName(field) + "List";    PropertyDescriptor list_descriptor = TypeDescriptor.GetProperties(helper.ViewData.Model).Find(property_name, true);    Dictionary<string, string> listing = null;    if (list_descriptor != null)    {        //Found a match for PropertyNameList, try to pull it out so we can use it        PropertyInfo temp = helper.ViewData.Model.GetType().GetProperty(property_name);        listing = (Dictionary<string, string>)temp.GetValue(helper.ViewData.Model, null);    }    return InputFor(helper, field, listing);}

Now a slight disclaimer:

  • This isn't the fastest code in the world (due to the reflection and other things), in my situation this isn't really relevant as it's all user driven, if you're planning on doing something crazy stupid.
  • This code is in its infancy, I will be testing this more thoroughly and adding to it over the next few days, open to any suggestions to improve the code.

I hope this code is useful to someone, I know I'll be using it over the next couple of weeks to try and cut down time. Cutting this down to just do the radio button should be a trivial task, good luck :)

Jay