I have implemented something similar in MVC 1.0. See if this will be helpful for you:
public static string RadioButtonList2(this HtmlHelper _helper, string _name, IEnumerable<SelectListItem> _items, string _selectedValue, string _seperator)
{
return RadioButtonList2(_helper, _name, _items, _selectedValue, _seperator, null);
}
public static string RadioButtonList2(this HtmlHelper _helper, string _name, IEnumerable<SelectListItem> _items, string _selectedValue, string _seperator, IDictionary<string, object> _htmlAttributes)
{
StringBuilder _outputScript = new StringBuilder();
foreach (var item in _items)
{
var optionField = new TagBuilder("input");
optionField.MergeAttribute("name", _name);
optionField.MergeAttribute("id", _name);
optionField.MergeAttribute("class", _name);
optionField.MergeAttribute("value", item.Value);
optionField.MergeAttribute("type", "radio");
// Check to see if it's checked
if (item.Value == _selectedValue)
optionField.MergeAttribute("checked", "checked");
if (_htmlAttributes != null)
optionField.MergeAttributes(_htmlAttributes);
_outputScript.Append(optionField.ToString(TagRenderMode.SelfClosing));
_outputScript.Append("<label style=\"display:inline;\">");
_outputScript.Append(item.Text);
_outputScript.Append("</label>" + _seperator);
}
return _outputScript.ToString();
}
In the controller, you can return the result as follows:
ViewData["GenderList"] = new SelectList(new[] { new { Value = "M", Text = "Male" }, new { Value = "F", Text = "Female" }, new { Value = "A", Text = "All" } }, "Value", "Text");
or
ViewData["GenderList"] = new SelectList(_resultFromSomeLinqQuery, "GenderID", "GenderName");
And use it in the View as follows:
<%= Html.RadioButtonList2("Sex", ViewData["GenderList"] as SelectList, ViewData["SelectedSex"].ToString(), " ")%>
You can also replace the with <BR /> to display them in seperate lines.
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);
}
}
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 :)
Here's a slighly 'slimmer' answer in good ol' VB.
Works for me, but it's not a complete solution.
<Extension()> _
Public Function RadioButtonListFor(Of TModel, TProperty)(ByVal htmlHelper As System.Web.Mvc.HtmlHelper(Of TModel), ByVal expression As System.Linq.Expressions.Expression(Of System.Func(Of TModel, TProperty)), ByVal selectList As System.Collections.Generic.IEnumerable(Of System.Web.Mvc.SelectListItem), ByVal htmlAttributes As Object) As System.Web.Mvc.MvcHtmlString
'Return htmlHelper.DropDownListFor(expression, selectList, htmlAttributes)
If selectList Is Nothing OrElse selectList.Count = 0 Then Return MvcHtmlString.Empty
Dim divTag = New TagBuilder("div")
divTag.MergeAttributes(New RouteValueDictionary(htmlAttributes))
Dim name = CType(expression.Body, System.Linq.Expressions.MemberExpression).Member.Name
Dim value = expression.Compile()(htmlHelper.ViewData.Model)
Dim sb As New StringBuilder()
For Each item In selectList
sb.AppendFormat("<input id=""{0}_{1}"" type=""radio"" name=""{0}"" value=""{1}"" {2} />", name, item.Value, If(item.Value = value.ToString, """checked""", ""))
sb.AppendFormat("<label for=""{0}_{1}"">{2}</label>", name, item.Value, item.Text)
Next
divTag.InnerHtml = sb.ToString
Return MvcHtmlString.Create(divTag.ToString)
End Function
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.
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());
}
}
Based in Jon post, a small improve to generate the radio button list as ul with HTMLAttributtes
public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
IDictionary<string, object> radioHtmlAttributes = null,
string ulClass = null)
{
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (radioHtmlAttributes == null)
radioHtmlAttributes = new RouteValueDictionary();
TagBuilder ulTag = new TagBuilder("ul");
if (!String.IsNullOrEmpty(ulClass))
ulTag.MergeAttribute("class", ulClass);
if (listOfValues != null)
{
// Create a radio button for each item in the list
foreach (SelectListItem item in listOfValues)
{
// Generate an id to be given to the radio button field
var id = string.Format("{0}_{1}", metaData.PropertyName, item.Value);
if (!radioHtmlAttributes.ContainsKey("id"))
radioHtmlAttributes.Add("id", id);
else
radioHtmlAttributes["id"] = id;
// Create and populate a radio button using the existing html helpers
var label = htmlHelper.Label(id, HttpUtility.HtmlEncode(item.Text));
var radio = htmlHelper.RadioButtonFor(expression, item.Value, radioHtmlAttributes).ToHtmlString();
// Create the html string that will be returned to the client
// e.g. <input data-val="true" data-val-required="You must select an option" id="TestRadio_1" name="TestRadio" type="radio" value="1" /><label for="TestRadio_1">Line1</label>
ulTag.InnerHtml += string.Format("<li>{0}{1}</li>", radio, label);
}
}
return MvcHtmlString.Create(ulTag.ToString(TagRenderMode.Normal));
}
public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
object radioHtmlAttributes = null,
string ulClass = null)
{
return RadioButtonListFor<TModel, TProperty>(htmlHelper, expression, listOfValues, new RouteValueDictionary(radioHtmlAttributes), ulClass);
}