I have an enum property and I am trying to set its value through checkboxes. The enum is flagged and when the user selects multiple options I expect the property to have all the selected flags concatenated.
I tried adding a checkbox for each enum value and gave every checkbox the same name. During postback the first selected flag is retrieved but not concatenated with the other flags.
Could I fix this somehow without having separate property for each flag?
public class HomeModel
{
public Fruit MyFruits { get; set; }
}
[Flags] public enum Fruit
{
Love = 1,
Joy = 2,
Peace = 4,
Patience = 8,
Kindness = 16,
Goodness = 32,
Faithfulness = 64,
Gentleness = 128,
SelfControl = 256
}
@model EnumFlagTest.Models.HomeModel
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
@using (Html.BeginForm())
{
<h1>Fruits</h1>
<div><label>@EnumFlagTest.Models.Fruit.Love.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.Love)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.Love) ? "checked" : String.Empty) /></label></div>
<div><label>@EnumFlagTest.Models.Fruit.Joy.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.Joy)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.Joy) ? "checked" : String.Empty) /></label></div>
<div><label>@EnumFlagTest.Models.Fruit.Peace.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.Peace)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.Peace) ? "checked" : String.Empty) /></label></div>
<div><label>@EnumFlagTest.Models.Fruit.Patience.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.Patience)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.Patience) ? "checked" : String.Empty) /></label></div>
<div><label>@EnumFlagTest.Models.Fruit.Kindness.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.Kindness)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.Kindness) ? "checked" : String.Empty) /></label></div>
<div><label>@EnumFlagTest.Models.Fruit.Goodness.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.Goodness)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.Goodness) ? "checked" : String.Empty) /></label></div>
<div><label>@EnumFlagTest.Models.Fruit.Faithfulness.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.Faithfulness)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.Faithfulness) ? "checked" : String.Empty) /></label></div>
<div><label>@EnumFlagTest.Models.Fruit.Gentleness.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.Gentleness)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.Gentleness) ? "checked" : String.Empty) /></label></div>
<div><label>@EnumFlagTest.Models.Fruit.SelfControl.ToString() <input type="checkbox" name="MyFruits" value="@((int) EnumFlagTest.Models.Fruit.SelfControl)" @(Model.MyFruits.HasFlag(EnumFlagTest.Models.Fruit.SelfControl) ? "checked" : String.Empty) /></label></div>
<input type="submit" value="GO" />
}
</div>
</body>
</html>
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index()
{
HomeModel model = new HomeModel();
model.MyFruits = Fruit.Love | Fruit.Joy | Fruit.Peace | Fruit.Patience;
return View(model);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(HomeModel returnData)
{
return View(returnData);
}
If you check the POST body the data is sent, it is just not picked up properly. This is because MVC is not handling flags enumerations well. Someone who answered a similar question describes this:
In general I avoid using enums when designing my view models because they don't play with ASP.NET MVC's helpers and out of the box model binder. They are perfectly fine in your domain models but for view models you could use other types.
There the person answering the question also provides a full answer to how to bind a flags enumeration anyway. What you basically need to do is create your own custom model binder that can handle flags enumeration. In another post called ASP.Net MVC Flag Enumeration Model Binder I found an example and I will copy the relevant code.
Add a class called CustomModelBinder
as follows:
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(true).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, (acc, i)
=> acc | (int)Enum.Parse(propertyType, i));
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);
}
}
Then in the Global.asax.cs Application_Start
method register the custom model binder as follows:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Register custom flag enum model binder
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
}
This should work.
Source: http://itq.nl/asp-net-mvc-flag-enumeration-model-binder/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With