First, let me say that I really like Microsoft’s MVC development products. They’re (for me!) easy and natural to use and make for an enjoyable development experience. Isn’t that what we all want? Still, there are gaps, holes, things missing that shouldn’t be, etc. Here’s one of them: There are nice helper routines to construct list boxes and dropdown lists based on the SelectList class, but no easy way to construct a list of radio buttons. You’re stuck coding all of them by hand in your html code file. Not only is this ugly and counter-intuitive, it badly clutters the HTML file with lots of inline code. Yes, there’s a place for inline code in the HTML file – this isn’t it.
The solution, of course, is to write your own extension method. So how to do this? First problem is knowing how to create an extension method. That’s pretty easy. You define a class with static methods (yes, they must be static) that have the first parameter named “this” with a type of the class you’re trying to extend. An example of a simple class to help build image tags is:
public static MvcHtmlString Image(this HtmlHelper helper, string id, string url, string alternateText, object htmlAttributes) { // Instantiate a UrlHelper var urlHelper = new UrlHelper(helper.ViewContext.RequestContext); // Create tag builder var builder = new TagBuilder("img"); // Create valid id builder.GenerateId(id); // Add attributes builder.MergeAttribute("src", urlHelper.Content(url)); builder.MergeAttribute("alt", alternateText); builder.MergeAttributes(new RouteValueDictionary(htmlAttributes)); // Render tag return MvcHtmlString.Create(builder .ToString(TagRenderMode.SelfClosing)); }
Since the first parameter is of type HtmlHelper, this method is an extension to the HtmlHelper class. It looks and acts like any other static method available on the HtmlHelper class. To use this class, you call it like you do any other HtmlHelper method:
<%= Html.Image("CountySeal","~/Content/images/CountySeal-Full Color_90x90.jpg", "County Seal") %>
Html helper methods are required to return a string containing the constructed HTML that will be substituted inline for the method call. You can see in the Image method, that we’re building the HTML code for an image element, including the alt text. Since the parameters are required, you won’t be forgetting to put alt text on your “img” tags if you use this method.
So, how to do a radio button list? The pattern I want to emulate is the xxxxxxFor<>(property, valuelist, <other parms> ) helper methods. An example of this type of Helper is
<%: Html.DropDownListFor<WaterHeaterData, String>(model=>model.heatertype, Model.heatertypelist) %>
Since it’s important that we write our code only once, we’ll be using generics to create code that can be used for any model and property. The output of the radio button list method should be a sequence of “input” elements and “label” elements in proper HTML code (that’s the point of doing this, isn’t it?) More importantly, we want to have the selected radio button value show up in our model property when the page is posted back to the server. Here’s what our radio button list method call should look like:
<%: Html.RadioButtonListFor<WaterHeaterData, String>(model=>model.requirements, Model.requirementslist) %>
Note that first parameter. It’s not your ordinary string or int. Just a little poking around gives you this signature for that parameter:
Expression<Func<TModel, TProperty>> property
Not the easiest thing in the world to interpret. It’s a lambda expression providing a function (or ‘delegate’) on the Model that returns a type of TProperty. In the Html.DropDownListFor example just above, TProperty is a String. If you don’t know what a lambda expression is, I suggest looking them up – LINQ is one of the few things to come along in recent years that have actually changed how I write programs. Anyway, we can now start to construct our helper method to generate a list of radio buttons. Remember, we want to take a list of possible values from a SelectList, and it would be nice to be able to provide some styling to apply to the radio button and to its text label. Since we’re going to create a list of radio buttons, it would also be nice to include a separator string as well. This is the result:
public static MvcHtmlString RadioButtonListFor<TModel, TProperty>( this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> property, SelectList list, String separator = "", object inputhtmlAttributes = null, object labelhtmlAttributes = null )
We’ve got our proper generic input model and property type declarations so we can use our method with any model and any type of property. Note that the first parameter is not just an HtmlHelper, but has a type specifier on it. The second parameter is our property delegate which is followed by our SelectList. We then have our separator string for between the radio buttons and finally we include the ability to provide classes, styling or whathaveyou for both the radio button input control and the associated label.
The next problem is how to get from a delegate for our property to both the property name and property value. Remember, we want ASP.NET MVC’s processing to automatically place the selected radio button value back into our property. That means naming the radio buttons with the property name. We also want the current property value to show up as the selected radio button and that means getting access to the property value. This is the point at which some serious digging gets glossed over. Essentially, we can use the model metadata accessible through the Lambda expression to get to our name and value.
// get the model metadata and verify we found something var modelmetadata = ModelMetadata.FromLambdaExpression(property, helper.ViewData); if (null == modelmetadata) throw new NotFoundException(property.ToString() + " Not Found"); // get the property value and name from the metadata. var value = modelmetadata.Model ?? default(TProperty); var name = modelmetadata.PropertyName;
Once we have our value and SelectList figured out, we want to enable the currently selected item to be selected in the radio button list. To do that, we need to know “what’s currently selected”. Since the input value could be null, we check for that and, if it is null, we use the default “selected” item in the SelectList.
// convert the value to a String for comparison with the SelectList contents. String selected = (null != value) ? value.ToString() : (null != list.SelectedValue) ? list.SelectedValue.ToString() : String.Empty;
I’m using the generic ‘var’ type for the value since I can’t predict what type it will be. I’m also checking for null at several points along the way. Defensive programming is your friend! Once we have these two things, the rest just falls into place. Here’s the final routine
/// <summary> /// Create a radio button list from a Selectlist of options and a lambda referencing a /// model property. /// </summary> /// <typeparam name="TModel">Data entity type of your Model</typeparam> /// <typeparam name="TProperty">Data type of the property</typeparam> /// <param name="helper"></param> /// <param name="property">Lambda referencing the property of the model that will be the selected item/output of the selection</param> /// <param name="list">SelectList of properties</param> /// <param name="separator">Html to put between radio button/label pairs</param> /// <param name="inputhtmlAttributes">Styling for the button</param> /// <param name="labelhtmlAttributes">Styling for the label</param> /// <returns></returns> public static MvcHtmlString RadioButtonListFor<TModel, TProperty>( this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> property, SelectList list, String separator = "", object inputhtmlAttributes = null, object labelhtmlAttributes = null ) { // get the model metadata and verify we found something var modelmetadata = ModelMetadata.FromLambdaExpression(property, helper.ViewData); if (null == modelmetadata) throw new NotFoundException(property.ToString() + " Not Found"); // get the property value and name from the metadata. var value = modelmetadata.Model ?? default(TProperty); var name = modelmetadata.PropertyName; // convert the value to a String for comparison with the SelectList contents. String selected = (null != value) ? value.ToString() : (null != list.SelectedValue) ? list.SelectedValue.ToString() : String.Empty; // build the output html for the radiobuttons and their labels. StringBuilder outstr = new StringBuilder(); int counter = 0; foreach (SelectListItem item in list) { string id = name + "_" + counter.ToString(); counter++; // the radio button TagBuilder input = new TagBuilder("input"); input.Attributes.Add("id", id); input.Attributes.Add("name", name); input.Attributes.Add("type", "radio"); input.Attributes.Add("value", item.Value); if (selected == item.Value || selected == item.Text) input.Attributes.Add("checked", "checked"); if (null != inputhtmlAttributes) input.MergeAttributes(new RouteValueDictionary(inputhtmlAttributes)); outstr.Append(input.ToString(TagRenderMode.SelfClosing)); // the label TagBuilder label = new TagBuilder("label"); label.Attributes.Add("for", id); label.InnerHtml = item.Text; if (null != labelhtmlAttributes) label.MergeAttributes(new RouteValueDictionary(labelhtmlAttributes)); outstr.Append(label.ToString(TagRenderMode.Normal)); // add the separator outstr.Append(separator); } return MvcHtmlString.Create(outstr.ToString()); }
Hopefully, this will be useful to you! Enjoy!
Update: Changed the selected item logic to choose the selected item from the input SelectList when the input value is null.
Leave a comment