A while ago I ran into a problem trying to model bind a flag enumeration property where of course multiple values can be selected, so what you would like to have is a checkbox list to choose from. Apparently there is no such thing in ASP.Net MVC like a html helper method CheckBoxListFor available to use. But there is a nuget package you can download called MvcCheckBoxList, this plugin is based on an IEnumerable
from which it creates a checkbox list to let the user choose from. Unfortunatly no good for binding a flag enumeration property, so I’ve created my own custom html helper and flag enumeration model binder.
To create a checkbox list from an enumeration with a html helper we have to create a new static class with a static method that returns a MvcHtmlString
. In my case I’ve created a method CheckBoxListForEnum
that can also be sorted alphabetically for user friendlyness:
public static class HtmlHelpers
{
public static MvcHtmlString CheckBoxListForEnum<TModel, TValue>( this HtmlHelper html,
Expression<Func<TModel, TValue>> expression, object htmlAttributes = null, bool sortAlphabetically = true )
{
var fieldName = ExpressionHelper.GetExpressionText( expression );
var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName( fieldName );
var fieldId = TagBuilder.CreateSanitizedId( fullBindingName );
var metadata = ModelMetadata.FromLambdaExpression( expression, html.ViewData );
var value = metadata.Model;
// Get all enum values
IEnumerable values = Enum.GetValues( typeof( TValue ) ).Cast();
// Sort them alphabetically by enum name
if ( sortAlphabetically )
values = values.OrderBy( i => i.ToString() );
// Create checkbox list
var sb = new StringBuilder();
foreach ( var item in values )
{
TagBuilder builder = new TagBuilder( "input" );
long targetValue = Convert.ToInt64( item );
long flagValue = Convert.ToInt64( value );
if ( ( targetValue & flagValue ) == targetValue )
builder.MergeAttribute( "checked", "checked" );
builder.MergeAttribute( "type", "checkbox" );
builder.MergeAttribute( "value", item.ToString() );
builder.MergeAttribute( "name", fieldId );
// Add optional html attributes
if ( htmlAttributes != null )
builder.MergeAttributes( new RouteValueDictionary( htmlAttributes ) );
builder.InnerHtml = item.ToString();
sb.Append( builder.ToString( TagRenderMode.Normal ) );
// Seperate checkboxes by new line
sb.Append( "<br />" );
}
return new MvcHtmlString( sb.ToString() );
}
}
This method enables you to do awesome stuff like this:
@Html.CheckBoxListForEnum( m => m.EnumProperty )
@Html.CheckBoxListForEnum( m => m.EnumProperty, new { @disabled = true } )
@Html.CheckBoxListForEnum( m => m.EnumProperty, new { @class = "checkbox" }, sortAlphabetically = false )
which neatly creates a checkbox list based on the enum of the property from your viewmodel.
Now that our checkbox list is created we also have correctly bind the selected values to our viewmodel when posting the data to the server. To do that we have to create a custom model binder that extends the mvc DefaultModelBinder class and override the GetPropertyValue method:
public class CustomModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue( ControllerContext controllerContext,
ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder )
{
var propertyType = propertyDescriptor.PropertyType;
// Check if the property type is an enum with the flag attribute
if ( propertyType.IsEnum && propertyType.GetCustomAttributes<FlagsAttribute>().Any() )
{
var providerValue = bindingContext.ValueProvider.GetValue( bindingContext.ModelName );
if ( providerValue != null )
{
var value = providerValue.RawValue;
if ( value != null )
{
// In case it is a checkbox list/dropdownlist/radio button list
if ( value is string[] )
{
// Create flag value from posted values
var flagValue = ( ( string[] )value ).Aggregate( 0, ( current, v ) => current | ( int )Enum.Parse( propertyType, v ) );
return Enum.ToObject( propertyType, flagValue );
}
// In case it is a single value
if ( value.GetType().IsEnum )
{
return Enum.ToObject( propertyType, value );
}
}
}
}
return base.GetPropertyValue( controllerContext, bindingContext, propertyDescriptor, propertyBinder );
}
}
Our method filters all flag enumeration typed properties and returns a flag value created from the selected checkbox values. The only thing left to do is tell our application to use our modelbinder instead of the default modelbinder, this is done in Global.asax
:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register( GlobalConfiguration.Configuration );
FilterConfig.RegisterGlobalFilters( GlobalFilters.Filters );
RouteConfig.RegisterRoutes( RouteTable.Routes );
BundleConfig.RegisterBundles( BundleTable.Bundles );
// Register custom flag enum model binder
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
}
Now I hear you thinking that those enumaration string values aren’t really user friendly. A way to solve this is to create a strongly typed resource file, put all your enumeration in there and replace builder.InnerHtml = item.ToString();
by this line: builder.InnerHtml = !string.IsNullOrEmpty( StrongResource.ResourceManager.GetString( item.ToString() ) ) ? StrongResource.ResourceManager.GetString( item.ToString() ) : item.ToString();
This renders the checkbox list with strings from the strongly typed resource instead of those ToString values.
Check out the full source code at GitHub.