Skip to content

Commit 1d6d9e1

Browse files
authored
Merge pull request #142 from MRCollective/flags-enum-support
Flags enum support
2 parents 2fd8d48 + 1d1683c commit 1d6d9e1

43 files changed

Lines changed: 992 additions & 208 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ _ReSharper.*
1010
*.log
1111
packages
1212
*.received.*
13+
.vs

AppStart.cs.pp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using ChameleonForms.Attributes;
12
using ChameleonForms.ModelBinders;
23
using System;
34
using System.Web.Mvc;
@@ -12,6 +13,13 @@
1213
{
1314
System.Web.Mvc.ModelBinders.Binders.Add(typeof(DateTime), new DateTimeModelBinder());
1415
System.Web.Mvc.ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeModelBinder());
16+
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredFlagsEnumAttribute), typeof(RequiredAttributeAdapter));
17+
GetType().Assembly.GetTypes().Where(t => t.IsEnum && t.GetCustomAttributes(typeof(FlagsAttribute), false).Any())
18+
.ToList().ForEach(t =>
19+
{
20+
System.Web.Mvc.ModelBinders.Binders.Add(t, new FlagsEnumModelBinder());
21+
System.Web.Mvc.ModelBinders.Binders.Add(typeof(Nullable<>).MakeGenericType(t), new FlagsEnumModelBinder());
22+
});
1523
}
1624
}
1725
}

BREAKING_CHANGES.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
ChameleonForms Breaking Changes
22
-------------------------------
33

4+
Version 3.0.0
5+
=============
6+
7+
Enums marked with the `[Flags]` attribute will now show as multiple select elements (or checkboxes when displaying as a list).
8+
9+
### Reason
10+
11+
Support has been added for flags enums; these make sense as multiple-select controls since a flags enum can support multiple values.
12+
13+
### Workaround
14+
15+
Create a enum class without the `[Flags]` attribute with the same values and bind to that instead.
16+
417
Version 2.0.0
518
=============
619

ChameleonForms.AcceptanceTests/Helpers/ObjectMother.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public static ModelBindingViewModel BasicValid
2525
RequiredEnum = SomeEnum.SomeOtherValue,
2626
RequiredNullableEnum = SomeEnum.Value1,
2727
OptionalEnum = null,
28+
RequiredFlagsEnum = FlagsEnum.Four | FlagsEnum.Two,
29+
RequiredNullableFlagsEnum = FlagsEnum.Two | FlagsEnum.Five,
30+
OptionalFlagsEnum = null,
2831
RequiredEnums = new List<SomeEnum> { SomeEnum.ValueWithDescription, SomeEnum.SomeOtherValue },
2932
RequiredNullableEnums = new List<SomeEnum?> { SomeEnum.Value1 },
3033
OptionalEnums = null,

ChameleonForms.AcceptanceTests/ModelBinding/ModelBindingTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using ChameleonForms.AcceptanceTests.Helpers;
22
using ChameleonForms.AcceptanceTests.ModelBinding.Pages;
33
using NUnit.Framework;
4-
using TestStack.Seleno.Configuration;
54

65
namespace ChameleonForms.AcceptanceTests.ModelBinding
76
{

ChameleonForms.AcceptanceTests/ModelBinding/Pages/Fields/MultipleFields.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,17 @@ public object Get(IModelFieldType fieldType)
2525
{
2626
var values = _elements
2727
.Select(e => FieldFactory.Create(new[] {e}).Get(new ModelFieldType(fieldType.BaseType, fieldType.Format)))
28-
.Where(e => e != null);
28+
.Where(e => e != null)
29+
.ToArray();
2930

30-
if (fieldType.HasMultipleValues)
31+
if (fieldType.IsFlagsEnum)
32+
{
33+
if (values.Length == 1)
34+
return values.First();
35+
return fieldType.GetValueFromStrings(values.Select(v => v.ToString()));
36+
}
37+
38+
if (fieldType.IsEnumerable)
3139
return fieldType.Cast(values);
3240

3341
return values.FirstOrDefault() ?? fieldType.DefaultValue;

ChameleonForms.AcceptanceTests/ModelBinding/Pages/ModelFieldType.cs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ internal interface IModelFieldType
1616
bool HasMultipleValues { get; }
1717
bool IsBoolean { get; }
1818
Type BaseType { get; }
19+
bool IsFlagsEnum { get; }
20+
bool IsEnumerable { get; }
1921
}
2022

2123
internal class ModelFieldType : IModelFieldType
@@ -34,7 +36,7 @@ public ModelFieldType(Type fieldType, string format)
3436
public object GetValueFromString(string stringValue)
3537
{
3638
var value = GetUnderlyingValueFromString(stringValue);
37-
if (!HasMultipleValues)
39+
if (!IsEnumerable)
3840
return value;
3941

4042
return Cast(new[] {value});
@@ -48,6 +50,19 @@ public object GetValueFromStrings(IEnumerable<string> stringValues)
4850
if (!HasMultipleValues)
4951
return GetUnderlyingValueFromString(string.Join(",", stringValues.Where(s => !string.IsNullOrEmpty(s))));
5052

53+
if (IsFlagsEnum)
54+
{
55+
var value = Enum.Parse(UnderlyingType, stringValues
56+
.Where(v => !string.IsNullOrEmpty(v))
57+
.Select(v => Convert.ToInt64(Enum.Parse(UnderlyingType, v)))
58+
.Aggregate(0L, (x, y) => x | y).ToString());
59+
60+
if (Convert.ToInt64(value) == 0L && Nullable.GetUnderlyingType(BaseType) != null)
61+
return null;
62+
63+
return value;
64+
}
65+
5166
return Cast(stringValues.Where(s => !string.IsNullOrEmpty(s)).Select(GetUnderlyingValueFromString));
5267
}
5368

@@ -94,11 +109,25 @@ public Type UnderlyingType
94109
}
95110

96111
public bool HasMultipleValues
112+
{
113+
get { return IsEnumerable || IsFlagsEnum; }
114+
}
115+
116+
public bool IsFlagsEnum
117+
{
118+
get
119+
{
120+
return UnderlyingType.IsEnum
121+
&& UnderlyingType.GetCustomAttributes(typeof(FlagsAttribute), false).Any();
122+
}
123+
}
124+
125+
public bool IsEnumerable
97126
{
98127
get
99128
{
100129
return _fieldType.IsGenericType &&
101-
typeof (IEnumerable<>).IsAssignableFrom(_fieldType.GetGenericTypeDefinition());
130+
typeof(IEnumerable<>).IsAssignableFrom(_fieldType.GetGenericTypeDefinition());
102131
}
103132
}
104133

@@ -111,7 +140,10 @@ public Type BaseType
111140
{
112141
get
113142
{
114-
return HasMultipleValues ? _fieldType.GetGenericArguments()[0] : _fieldType;
143+
if (_fieldType.IsGenericType &&
144+
typeof(IEnumerable<>).IsAssignableFrom(_fieldType.GetGenericTypeDefinition()))
145+
return _fieldType.GetGenericArguments()[0];
146+
return _fieldType;
115147
}
116148
}
117149
}

ChameleonForms.AcceptanceTests/ModelBinding/Pages/ModelFieldValue.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,36 @@ public ModelFieldValue(object value, string format)
2424
_format = format;
2525
}
2626

27-
public bool HasMultipleValues { get { return _value as IEnumerable != null && _value.GetType() != typeof(string); } }
27+
public bool HasMultipleValues
28+
{
29+
get
30+
{
31+
if (_value == null)
32+
return false;
33+
34+
var underlyingType = Nullable.GetUnderlyingType(_value.GetType()) ?? _value.GetType();
35+
if (underlyingType.IsEnum && underlyingType.GetCustomAttributes(typeof(FlagsAttribute), false).Any())
36+
return true;
37+
38+
return _value is IEnumerable
39+
&& _value.GetType() != typeof(string);
40+
}
41+
}
2842

2943
public IEnumerable<string> Values
3044
{
3145
get
3246
{
3347
if (!HasMultipleValues)
3448
throw new InvalidOperationException("Field does not have multiple values!");
49+
if (_value == null)
50+
return new string[] {};
51+
var underlyingType = Nullable.GetUnderlyingType(_value.GetType()) ?? _value.GetType();
52+
if (underlyingType.IsEnum)
53+
return Enum.GetValues(underlyingType)
54+
.Cast<object>()
55+
.Where(e => (Convert.ToInt32(e) & Convert.ToInt32(_value)) != 0)
56+
.Select(e => e.ToString());
3557
return (_value as IEnumerable).Cast<object>()
3658
.Select(o => new ModelFieldValue(o, _format))
3759
.Select(v => v.Value);

ChameleonForms.Example/Controllers/ExampleFormsController.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ public IFieldConfiguration ModifyConfig(IFieldConfiguration config)
173173
public SomeEnum? RequiredNullableEnum { get; set; }
174174
public SomeEnum? OptionalEnum { get; set; }
175175

176+
[RequiredFlagsEnum]
177+
public FlagsEnum RequiredFlagsEnum { get; set; }
178+
[RequiredFlagsEnum]
179+
public FlagsEnum? RequiredNullableFlagsEnum { get; set; }
180+
public FlagsEnum? OptionalFlagsEnum { get; set; }
181+
176182
[Required]
177183
public IEnumerable<SomeEnum> RequiredEnums { get; set; }
178184
[Required]
@@ -246,6 +252,8 @@ public ViewModelExample()
246252

247253
public string NestedField { get; set; }
248254

255+
public FlagsEnum FlagsEnums { get; set; }
256+
249257
public SomeEnum SomeEnum { get; set; }
250258

251259
public List<SomeEnum> SomeEnums { get; set; }
@@ -287,6 +295,16 @@ public class ListItem
287295
public string Name { get; set; }
288296
}
289297

298+
[Flags]
299+
public enum FlagsEnum
300+
{
301+
One = 1,
302+
Two = 2,
303+
Three = 4,
304+
Four = 8,
305+
Five = 16
306+
}
307+
290308
public enum SomeEnum
291309
{
292310
Value1,

ChameleonForms.Example/Global.asax.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
2+
using System.Linq;
23
using System.Web.Mvc;
34
using System.Web.Routing;
5+
using ChameleonForms.Attributes;
6+
using ChameleonForms.Example.Controllers;
47
using ChameleonForms.Example.Controllers.Filters;
58
using ChameleonForms.ModelBinders;
69

@@ -14,6 +17,13 @@ protected void Application_Start()
1417
HumanizedLabels.Register();
1518
System.Web.Mvc.ModelBinders.Binders.Add(typeof(DateTime), new DateTimeModelBinder());
1619
System.Web.Mvc.ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeModelBinder());
20+
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredFlagsEnumAttribute), typeof(RequiredAttributeAdapter));
21+
typeof(ExampleFormsController).Assembly.GetTypes().Where(t => t.IsEnum && t.GetCustomAttributes(typeof(FlagsAttribute), false).Any())
22+
.ToList().ForEach(t =>
23+
{
24+
System.Web.Mvc.ModelBinders.Binders.Add(t, new FlagsEnumModelBinder());
25+
System.Web.Mvc.ModelBinders.Binders.Add(typeof(Nullable<>).MakeGenericType(t), new FlagsEnumModelBinder());
26+
});
1727
GlobalFilters.Filters.Add(new FormTemplateFilter());
1828
}
1929
}

0 commit comments

Comments
 (0)