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