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 IEnumerables 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 ChecBoxListForEnum 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<TValue>();

		// 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().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 this line:

builder.InnerHtml = item.ToString();

by this line in your html helper method:

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.

Click here to download the sample project.

Author ITQ

More posts by ITQ
28 March 2014

Join the discussion 2 Comments

  • russ says:

    Hi,

    I cant get the code to compile. Cant even get the right references added.
    Any chance you have the original code still?

    thanks
    Russ

Leave a Reply