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.

Related articles

  • Digital Workspace
  • Cloud Native
  • Implementation and Adoption
  • Platform Engineering
  • Hybrid Cloud
  • Private: ITTS (IT Transformation Services)
  • Private: Managed Security Operations
  • Managed Cloud Platform
  • Private: Backup & Disaster Recovery
Visit our knowledge hub
Visit our knowledge hub
ITQ

Let's talk!

Knowledge is key for our existence. This knowledge we use for disruptive innovation and changing organizations. Are you ready for change?

"*" indicates required fields

First name*
Last name*
  • United States+1
  • United Kingdom+44
  • Afghanistan (‫افغانستان‬‎)+93
  • Albania (Shqipëri)+355
  • Algeria (‫الجزائر‬‎)+213
  • American Samoa+1
  • Andorra+376
  • Angola+244
  • Anguilla+1
  • Antigua and Barbuda+1
  • Argentina+54
  • Armenia (Հայաստան)+374
  • Aruba+297
  • Ascension Island+247
  • Australia+61
  • Austria (Österreich)+43
  • Azerbaijan (Azərbaycan)+994
  • Bahamas+1
  • Bahrain (‫البحرين‬‎)+973
  • Bangladesh (বাংলাদেশ)+880
  • Barbados+1
  • Belarus (Беларусь)+375
  • Belgium (België)+32
  • Belize+501
  • Benin (Bénin)+229
  • Bermuda+1
  • Bhutan (འབྲུག)+975
  • Bolivia+591
  • Bosnia and Herzegovina (Босна и Херцеговина)+387
  • Botswana+267
  • Brazil (Brasil)+55
  • British Indian Ocean Territory+246
  • British Virgin Islands+1
  • Brunei+673
  • Bulgaria (България)+359
  • Burkina Faso+226
  • Burundi (Uburundi)+257
  • Cambodia (កម្ពុជា)+855
  • Cameroon (Cameroun)+237
  • Canada+1
  • Cape Verde (Kabu Verdi)+238
  • Caribbean Netherlands+599
  • Cayman Islands+1
  • Central African Republic (République centrafricaine)+236
  • Chad (Tchad)+235
  • Chile+56
  • China (中国)+86
  • Christmas Island+61
  • Cocos (Keeling) Islands+61
  • Colombia+57
  • Comoros (‫جزر القمر‬‎)+269
  • Congo (DRC) (Jamhuri ya Kidemokrasia ya Kongo)+243
  • Congo (Republic) (Congo-Brazzaville)+242
  • Cook Islands+682
  • Costa Rica+506
  • Côte d’Ivoire+225
  • Croatia (Hrvatska)+385
  • Cuba+53
  • Curaçao+599
  • Cyprus (Κύπρος)+357
  • Czech Republic (Česká republika)+420
  • Denmark (Danmark)+45
  • Djibouti+253
  • Dominica+1
  • Dominican Republic (República Dominicana)+1
  • Ecuador+593
  • Egypt (‫مصر‬‎)+20
  • El Salvador+503
  • Equatorial Guinea (Guinea Ecuatorial)+240
  • Eritrea+291
  • Estonia (Eesti)+372
  • Eswatini+268
  • Ethiopia+251
  • Falkland Islands (Islas Malvinas)+500
  • Faroe Islands (Føroyar)+298
  • Fiji+679
  • Finland (Suomi)+358
  • France+33
  • French Guiana (Guyane française)+594
  • French Polynesia (Polynésie française)+689
  • Gabon+241
  • Gambia+220
  • Georgia (საქართველო)+995
  • Germany (Deutschland)+49
  • Ghana (Gaana)+233
  • Gibraltar+350
  • Greece (Ελλάδα)+30
  • Greenland (Kalaallit Nunaat)+299
  • Grenada+1
  • Guadeloupe+590
  • Guam+1
  • Guatemala+502
  • Guernsey+44
  • Guinea (Guinée)+224
  • Guinea-Bissau (Guiné Bissau)+245
  • Guyana+592
  • Haiti+509
  • Honduras+504
  • Hong Kong (香港)+852
  • Hungary (Magyarország)+36
  • Iceland (Ísland)+354
  • India (भारत)+91
  • Indonesia+62
  • Iran (‫ایران‬‎)+98
  • Iraq (‫العراق‬‎)+964
  • Ireland+353
  • Isle of Man+44
  • Israel (‫ישראל‬‎)+972
  • Italy (Italia)+39
  • Jamaica+1
  • Japan (日本)+81
  • Jersey+44
  • Jordan (‫الأردن‬‎)+962
  • Kazakhstan (Казахстан)+7
  • Kenya+254
  • Kiribati+686
  • Kosovo+383
  • Kuwait (‫الكويت‬‎)+965
  • Kyrgyzstan (Кыргызстан)+996
  • Laos (ລາວ)+856
  • Latvia (Latvija)+371
  • Lebanon (‫لبنان‬‎)+961
  • Lesotho+266
  • Liberia+231
  • Libya (‫ليبيا‬‎)+218
  • Liechtenstein+423
  • Lithuania (Lietuva)+370
  • Luxembourg+352
  • Macau (澳門)+853
  • North Macedonia (Македонија)+389
  • Madagascar (Madagasikara)+261
  • Malawi+265
  • Malaysia+60
  • Maldives+960
  • Mali+223
  • Malta+356
  • Marshall Islands+692
  • Martinique+596
  • Mauritania (‫موريتانيا‬‎)+222
  • Mauritius (Moris)+230
  • Mayotte+262
  • Mexico (México)+52
  • Micronesia+691
  • Moldova (Republica Moldova)+373
  • Monaco+377
  • Mongolia (Монгол)+976
  • Montenegro (Crna Gora)+382
  • Montserrat+1
  • Morocco (‫المغرب‬‎)+212
  • Mozambique (Moçambique)+258
  • Myanmar (Burma) (မြန်မာ)+95
  • Namibia (Namibië)+264
  • Nauru+674
  • Nepal (नेपाल)+977
  • Netherlands (Nederland)+31
  • New Caledonia (Nouvelle-Calédonie)+687
  • New Zealand+64
  • Nicaragua+505
  • Niger (Nijar)+227
  • Nigeria+234
  • Niue+683
  • Norfolk Island+672
  • North Korea (조선 민주주의 인민 공화국)+850
  • Northern Mariana Islands+1
  • Norway (Norge)+47
  • Oman (‫عُمان‬‎)+968
  • Pakistan (‫پاکستان‬‎)+92
  • Palau+680
  • Palestine (‫فلسطين‬‎)+970
  • Panama (Panamá)+507
  • Papua New Guinea+675
  • Paraguay+595
  • Peru (Perú)+51
  • Philippines+63
  • Poland (Polska)+48
  • Portugal+351
  • Puerto Rico+1
  • Qatar (‫قطر‬‎)+974
  • Réunion (La Réunion)+262
  • Romania (România)+40
  • Russia (Россия)+7
  • Rwanda+250
  • Saint Barthélemy+590
  • Saint Helena+290
  • Saint Kitts and Nevis+1
  • Saint Lucia+1
  • Saint Martin (Saint-Martin (partie française))+590
  • Saint Pierre and Miquelon (Saint-Pierre-et-Miquelon)+508
  • Saint Vincent and the Grenadines+1
  • Samoa+685
  • San Marino+378
  • São Tomé and Príncipe (São Tomé e Príncipe)+239
  • Saudi Arabia (‫المملكة العربية السعودية‬‎)+966
  • Senegal (Sénégal)+221
  • Serbia (Србија)+381
  • Seychelles+248
  • Sierra Leone+232
  • Singapore+65
  • Sint Maarten+1
  • Slovakia (Slovensko)+421
  • Slovenia (Slovenija)+386
  • Solomon Islands+677
  • Somalia (Soomaaliya)+252
  • South Africa+27
  • South Korea (대한민국)+82
  • South Sudan (‫جنوب السودان‬‎)+211
  • Spain (España)+34
  • Sri Lanka (ශ්‍රී ලංකාව)+94
  • Sudan (‫السودان‬‎)+249
  • Suriname+597
  • Svalbard and Jan Mayen+47
  • Sweden (Sverige)+46
  • Switzerland (Schweiz)+41
  • Syria (‫سوريا‬‎)+963
  • Taiwan (台灣)+886
  • Tajikistan+992
  • Tanzania+255
  • Thailand (ไทย)+66
  • Timor-Leste+670
  • Togo+228
  • Tokelau+690
  • Tonga+676
  • Trinidad and Tobago+1
  • Tunisia (‫تونس‬‎)+216
  • Turkey (Türkiye)+90
  • Turkmenistan+993
  • Turks and Caicos Islands+1
  • Tuvalu+688
  • U.S. Virgin Islands+1
  • Uganda+256
  • Ukraine (Україна)+380
  • United Arab Emirates (‫الإمارات العربية المتحدة‬‎)+971
  • United Kingdom+44
  • United States+1
  • Uruguay+598
  • Uzbekistan (Oʻzbekiston)+998
  • Vanuatu+678
  • Vatican City (Città del Vaticano)+39
  • Venezuela+58
  • Vietnam (Việt Nam)+84
  • Wallis and Futuna (Wallis-et-Futuna)+681
  • Western Sahara (‫الصحراء الغربية‬‎)+212
  • Yemen (‫اليمن‬‎)+967
  • Zambia+260
  • Zimbabwe+263
  • Åland Islands+358
Hidden