c# - switch - Lista de enlace de modelo de Enum Flags
enum to list c# (6)
Bitmapped, ha hecho preguntas importantes y puedo sugerir la siguiente solución: debe anular el método BindProperty de su ModelBinder y la próxima necesidad de reemplazar el valor de la propiedad del modelo:
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType.IsEnum && propertyDescriptor.PropertyType.GetCustomAttributes(typeof(FlagsAttribute), false).Any())
{
var value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
if (value != null)
{
// Get type of value.
var rawValues = value.RawValue as string[];
if (rawValues != null)
{
// Create instance of result object.
var result = (Enum)Activator.CreateInstance(propertyDescriptor.PropertyType);
try
{
// Try parse enum
result = (Enum)Enum.Parse(propertyDescriptor.PropertyType, string.Join(",", rawValues));
// Override property with flags value
propertyDescriptor.SetValue(bindingContext.Model, result);
return;
}
catch
{
}
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
else
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
Tengo una grilla de Enum Flags en la que cada registro es una fila de casillas de verificación para determinar los valores de bandera de ese registro. Esta es una lista de notificaciones que el sistema ofrece y el usuario puede elegir (para cada una) cómo quiere que se entreguen:
[Flag]
public enum NotificationDeliveryType
{
InSystem = 1,
Email = 2,
Text = 4
}
Encontré este article pero está obteniendo un valor de indicador único y lo vincula al controlador de esta manera (con un concepto de días de la semana):
[HttpPost]
public ActionResult MyPostedPage(MyModel model)
{
//I moved the logic for setting this into a helper
//because this could be re-used elsewhere.
model.WeekDays = Enum<DayOfWeek>.ParseToEnumFlag(Request.Form, "WeekDays[]");
...
}
No puedo encontrar en ninguna parte que el archivador modelo MVC 3 pueda manejar banderas. ¡Gracias!
El código de Darin fue genial, pero me costó un poco usarlo con MVC4.
En la extensión HtmlHelper para crear los cuadros, seguí recibiendo errores de tiempo de ejecución que el modelo no era una enumeración (específicamente, diciendo System.Object). Repasé el código para tomar una expresión Lambda y limpié este problema usando la clase ModelMetadata:
public static IHtmlString CheckBoxesForEnumFlagsFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumModelType = metadata.ModelType;
// Check to make sure this is an enum.
if (!enumModelType.IsEnum)
{
throw new ArgumentException("This helper can only be used with enums. Type used was: " + enumModelType.FullName.ToString() + ".");
}
// Create string for Element.
var sb = new StringBuilder();
foreach (Enum item in Enum.GetValues(enumModelType))
{
if (Convert.ToInt32(item) != 0)
{
var ti = htmlHelper.ViewData.TemplateInfo;
var id = ti.GetFullHtmlFieldId(item.ToString());
var name = ti.GetFullHtmlFieldName(string.Empty);
var label = new TagBuilder("label");
label.Attributes["for"] = id;
var field = item.GetType().GetField(item.ToString());
// Add checkbox.
var checkbox = new TagBuilder("input");
checkbox.Attributes["id"] = id;
checkbox.Attributes["name"] = name;
checkbox.Attributes["type"] = "checkbox";
checkbox.Attributes["value"] = item.ToString();
var model = htmlHelper.ViewData.Model as Enum;
if (model.HasFlag(item))
{
checkbox.Attributes["checked"] = "checked";
}
sb.AppendLine(checkbox.ToString());
// Check to see if DisplayName attribute has been set for item.
var displayName = field.GetCustomAttributes(typeof(DisplayNameAttribute), true)
.FirstOrDefault() as DisplayNameAttribute;
if (displayName != null)
{
// Display name specified. Use it.
label.SetInnerText(displayName.DisplayName);
}
else
{
// Check to see if Display attribute has been set for item.
var display = field.GetCustomAttributes(typeof(DisplayAttribute), true)
.FirstOrDefault() as DisplayAttribute;
if (display != null)
{
label.SetInnerText(display.Name);
}
else
{
label.SetInnerText(item.ToString());
}
}
sb.AppendLine(label.ToString());
// Add line break.
sb.AppendLine("<br />");
}
}
return new HtmlString(sb.ToString());
}
También extendí la carpeta de modelo para que funcione con cualquier tipo de enum genérico.
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Fetch value to bind.
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value != null)
{
// Get type of value.
Type valueType = bindingContext.ModelType;
var rawValues = value.RawValue as string[];
if (rawValues != null)
{
// Create instance of result object.
var result = (Enum)Activator.CreateInstance(valueType);
try
{
// Parse.
result = (Enum)Enum.Parse(valueType, string.Join(",", rawValues));
return result;
}
catch
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
Aún necesita registrar cada tipo de enumeración en Application_Start, pero al menos esto elimina la necesidad de clases de cuaderno separadas. Puedes registrarlo usando:
ModelBinders.Binders.Add(typeof(MyEnumType), new EnumFlagsModelBinder());
Publiqué mi código en Github en https://github.com/Bitmapped/MvcEnumFlags .
En general, evito usar enumeraciones al diseñar mis modelos de vista porque no juegan con los ayudantes de ASP.NET MVC y el archivador modelo fuera de la caja. Están perfectamente bien en los modelos de tu dominio, pero para ver modelos puedes usar otros tipos. Así que dejo mi capa de mapeo que es responsable de convertir entre mis modelos de dominio y modelos de vista para preocuparse por esas conversiones.
Dicho esto, si por alguna razón decide usar enumeraciones en esta situación, puede rodar un archivador de modelo personalizado:
public class NotificationDeliveryTypeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value != null )
{
var rawValues = value.RawValue as string[];
if (rawValues != null)
{
NotificationDeliveryType result;
if (Enum.TryParse<NotificationDeliveryType>(string.Join(",", rawValues), out result))
{
return result;
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
que se registrará en Application_Start:
ModelBinders.Binders.Add(
typeof(NotificationDeliveryType),
new NotificationDeliveryTypeModelBinder()
);
Hasta aquí todo bien. Ahora lo estándar:
Ver modelo:
[Flags]
public enum NotificationDeliveryType
{
InSystem = 1,
Email = 2,
Text = 4
}
public class MyViewModel
{
public IEnumerable<NotificationDeliveryType> Notifications { get; set; }
}
Controlador:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
Notifications = new[]
{
NotificationDeliveryType.Email,
NotificationDeliveryType.InSystem | NotificationDeliveryType.Text
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
Ver ( ~/Views/Home/Index.cshtml
):
@model MyViewModel
@using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Notification</th>
</tr>
</thead>
<tbody>
@Html.EditorFor(x => x.Notifications)
</tbody>
</table>
<button type="submit">OK</button>
}
plantilla de editor personalizado para NotificationDeliveryType
( ~/Views/Shared/EditorTemplates/NotificationDeliveryType.cshtml
):
@model NotificationDeliveryType
<tr>
<td>
@foreach (NotificationDeliveryType item in Enum.GetValues(typeof(NotificationDeliveryType)))
{
<label for="@ViewData.TemplateInfo.GetFullHtmlFieldId(item.ToString())">@item</label>
<input type="checkbox" id="@ViewData.TemplateInfo.GetFullHtmlFieldId(item.ToString())" name="@(ViewData.TemplateInfo.GetFullHtmlFieldName(""))" value="@item" @Html.Raw((Model & item) == item ? "checked=/"checked/"" : "") />
}
</td>
</tr>
Es obvio que un desarrollador de software (yo en este caso) que escribe dicho código en una plantilla de editor no debería estar muy orgulloso de su trabajo. Quiero decir, mira! Incluso yo que escribí esta plantilla Razor como hace 5 minutos ya no puedo entender lo que hace.
Por lo tanto, refactorizamos este código de spaghetti en un asistente de HTML personalizado reutilizable:
public static class HtmlExtensions
{
public static IHtmlString CheckBoxesForEnumModel<TModel>(this HtmlHelper<TModel> htmlHelper)
{
if (!typeof(TModel).IsEnum)
{
throw new ArgumentException("this helper can only be used with enums");
}
var sb = new StringBuilder();
foreach (Enum item in Enum.GetValues(typeof(TModel)))
{
var ti = htmlHelper.ViewData.TemplateInfo;
var id = ti.GetFullHtmlFieldId(item.ToString());
var name = ti.GetFullHtmlFieldName(string.Empty);
var label = new TagBuilder("label");
label.Attributes["for"] = id;
label.SetInnerText(item.ToString());
sb.AppendLine(label.ToString());
var checkbox = new TagBuilder("input");
checkbox.Attributes["id"] = id;
checkbox.Attributes["name"] = name;
checkbox.Attributes["type"] = "checkbox";
checkbox.Attributes["value"] = item.ToString();
var model = htmlHelper.ViewData.Model as Enum;
if (model.HasFlag(item))
{
checkbox.Attributes["checked"] = "checked";
}
sb.AppendLine(checkbox.ToString());
}
return new HtmlString(sb.ToString());
}
}
y limpiamos el desorden en nuestra plantilla de editor:
@model NotificationDeliveryType
<tr>
<td>
@Html.CheckBoxesForEnumModel()
</td>
</tr>
que produce la tabla:
Ahora, obviamente, hubiera sido bueno si pudiéramos proporcionar etiquetas más amigables para esas casillas de verificación. Como por ejemplo:
[Flags]
public enum NotificationDeliveryType
{
[Display(Name = "in da system")]
InSystem = 1,
[Display(Name = "@")]
Email = 2,
[Display(Name = "txt")]
Text = 4
}
Todo lo que tenemos que hacer es adaptar el helper HTML que escribimos anteriormente:
var field = item.GetType().GetField(item.ToString());
var display = field
.GetCustomAttributes(typeof(DisplayAttribute), true)
.FirstOrDefault() as DisplayAttribute;
if (display != null)
{
label.SetInnerText(display.Name);
}
else
{
label.SetInnerText(item.ToString());
}
que nos da un mejor resultado:
Puedes probar el paquete MVC Enum Flags (disponible a través de nuget ). Se omite automáticamente las opciones de enum de valor cero, lo cual es un buen toque.
[Lo siguiente es de la Documentation y sus comentarios; mira allí si esto no es vinculante para ti]
Después de la instalación, agregue lo siguiente a Global.asax.cs / Application_Start:
ModelBinders.Binders.Add(typeof(MyEnumType), new EnumFlagsModelBinder());
Luego, en la vista, ponga @using MvcEnumFlags
arriba y @Html.CheckBoxesForEnumFlagsFor(model => model.MyEnumTypeProperty)
para el código actual.
Uso el enfoque descrito en MVVM Framework .
enum ActiveFlags
{
None = 0,
Active = 1,
Inactive = 2,
}
class ActiveFlagInfo : EnumInfo<ActiveFlags>
{
public ActiveFlagInfo(ActiveFlags value)
: base(value)
{
// here you can localize or set user friendly name of the enum value
if (value == ActiveFlags.Active)
this.Name = "Active";
else if (value == ActiveFlags.Inactive)
this.Name = "Inactive";
else if (value == ActiveFlags.None)
this.Name = "(not set)";
}
}
// Usage of ActiveFlagInfo class:
// you can use collection of ActiveFlagInfo for binding in your own view models
// also you can use this ActiveFlagInfo as property for your classes to wrap enum properties
IEnumerable<ActiveFlagInfo> activeFlags = ActiveFlagInfo.GetEnumInfos(e =>
e == ActiveFlags.None ? null : new ActiveFlagInfo(e));
Utilizando Darin y el código bitmapped, escribo la respuesta, pero no funcionó para mí, así que primero arreglé cosas anulables, luego aún tenía problemas con el bining, descubrí que hay algo mal con el html, así que pierdo la fe en esta respuesta , busque otro, encontré algo en un foro de mi país, que usó el mismo código que aquí, pero con un pequeño cambio, así que lo fusiono con mi código, y todo fue bien, mi proyecto usa el valor de nulo, así que no sé cómo va a funcionar en otros lugares, puede que necesite un poco de solución, pero traté de pensar sobre los nulos y el modelo siendo la enumeración misma.
public static class Extensions
{
public static IHtmlString CheckBoxesForEnumFlagsFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumModelType = metadata.ModelType;
var isEnum = enumModelType.IsEnum;
var isNullableEnum = enumModelType.IsGenericType &&
enumModelType.GetGenericTypeDefinition() == typeof (Nullable<>) &&
enumModelType.GenericTypeArguments[0].IsEnum;
// Check to make sure this is an enum.
if (!isEnum && !isNullableEnum)
{
throw new ArgumentException("This helper can only be used with enums. Type used was: " + enumModelType.FullName.ToString() + ".");
}
// Create string for Element.
var sb = new StringBuilder();
Type enumType = null;
if (isEnum)
{
enumType = enumModelType;
}
else if (isNullableEnum)
{
enumType = enumModelType.GenericTypeArguments[0];
}
foreach (Enum item in Enum.GetValues(enumType))
{
if (Convert.ToInt32(item) != 0)
{
var ti = htmlHelper.ViewData.TemplateInfo;
var id = ti.GetFullHtmlFieldId(item.ToString());
//Derive property name for checkbox name
var body = expression.Body as MemberExpression;
var propertyName = body.Member.Name;
var name = ti.GetFullHtmlFieldName(propertyName);
//Get currently select values from the ViewData model
//TEnum selectedValues = expression.Compile().Invoke(htmlHelper.ViewData.Model);
var label = new TagBuilder("label");
label.Attributes["for"] = id;
label.Attributes["style"] = "display: inline-block;";
var field = item.GetType().GetField(item.ToString());
// Add checkbox.
var checkbox = new TagBuilder("input");
checkbox.Attributes["id"] = id;
checkbox.Attributes["name"] = name;
checkbox.Attributes["type"] = "checkbox";
checkbox.Attributes["value"] = item.ToString();
var model = (metadata.Model as Enum);
//var model = htmlHelper.ViewData.Model as Enum; //Old Code
if (model != null && model.HasFlag(item))
{
checkbox.Attributes["checked"] = "checked";
}
sb.AppendLine(checkbox.ToString());
// Check to see if DisplayName attribute has been set for item.
var displayName = field.GetCustomAttributes(typeof(DisplayNameAttribute), true)
.FirstOrDefault() as DisplayNameAttribute;
if (displayName != null)
{
// Display name specified. Use it.
label.SetInnerText(displayName.DisplayName);
}
else
{
// Check to see if Display attribute has been set for item.
var display = field.GetCustomAttributes(typeof(DisplayAttribute), true)
.FirstOrDefault() as DisplayAttribute;
if (display != null)
{
label.SetInnerText(display.Name);
}
else
{
label.SetInnerText(item.ToString());
}
}
sb.AppendLine(label.ToString());
// Add line break.
sb.AppendLine("<br />");
}
}
return new HtmlString(sb.ToString());
}
}
public class FlagEnumerationModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null) throw new ArgumentNullException("bindingContext");
if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
var values = GetValue<string[]>(bindingContext, bindingContext.ModelName);
if (values.Length > 1 && (bindingContext.ModelType.IsEnum && bindingContext.ModelType.IsDefined(typeof(FlagsAttribute), false)))
{
long byteValue = 0;
foreach (var value in values.Where(v => Enum.IsDefined(bindingContext.ModelType, v)))
{
byteValue |= (int)Enum.Parse(bindingContext.ModelType, value);
}
return Enum.Parse(bindingContext.ModelType, byteValue.ToString());
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
return base.BindModel(controllerContext, bindingContext);
}
private static T GetValue<T>(ModelBindingContext bindingContext, string key)
{
if (bindingContext.ValueProvider.ContainsPrefix(key))
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(key);
if (valueResult != null)
{
bindingContext.ModelState.SetModelValue(key, valueResult);
return (T)valueResult.ConvertTo(typeof(T));
}
}
return default(T);
}
}
ModelBinders.Binders.Add(
typeof (SellTypes),
new FlagEnumerationModelBinder()
);
ModelBinders.Binders.Add(
typeof(SellTypes?),
new FlagEnumerationModelBinder()
);