Modello Bind List di Enum Flags

Ho una griglia di Enum Flags in cui ogni record è una fila di checkbox per determinare i valori dei flag di quel record. Questa è una lista di notifiche che il sistema offre e l’utente può scegliere (per ognuna) come vogliono che vengano consegnate:

[Flag] public enum NotificationDeliveryType { InSystem = 1, Email = 2, Text = 4 } 

Ho trovato questo articolo ma sta recuperando un singolo valore di flag e lo sta legando nel controller in questo modo (con un concetto di giorni della settimana):

 [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.ParseToEnumFlag(Request.Form, "WeekDays[]"); ... } 

Non riesco a trovare da nessuna parte che il raccoglitore modello MVC 3 possa gestire le bandiere. Grazie!

In generale, evito di usare le enumerazioni durante la progettazione dei miei modelli di visualizzazione perché non giocano con gli helper di ASP.NET MVC e con il raccoglitore di modelli fuori dalla scatola. Stanno perfettamente bene nei tuoi modelli di dominio ma per i modelli di visualizzazione potresti usare altri tipi. Così lascio il mio livello di mapping che è responsabile di convertire avanti e indietro tra i miei modelli di dominio e visualizzare i modelli per preoccuparsi di tali conversioni.

Detto questo, se per qualche motivo decidi di utilizzare le enumerazioni in questa situazione, puoi lanciare un modello personalizzato:

 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(string.Join(",", rawValues), out result)) { return result; } } } return base.BindModel(controllerContext, bindingContext); } } 

che sarà registrato in Application_Start:

 ModelBinders.Binders.Add( typeof(NotificationDeliveryType), new NotificationDeliveryTypeModelBinder() ); 

Fin qui tutto bene. Ora le cose standard:

Visualizza modello:

 [Flags] public enum NotificationDeliveryType { InSystem = 1, Email = 2, Text = 4 } public class MyViewModel { public IEnumerable Notifications { get; set; } } 

controller:

 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); } } 

Visualizza ( ~/Views/Home/Index.cshtml ):

 @model MyViewModel @using (Html.BeginForm()) {  @Html.EditorFor(x => x.Notifications) 
Notification
}

modello di editor personalizzato per NotificationDeliveryType ( ~/Views/Shared/EditorTemplates/NotificationDeliveryType.cshtml ):

 @model NotificationDeliveryType   @foreach (NotificationDeliveryType item in Enum.GetValues(typeof(NotificationDeliveryType))) {   }   

È ovvio che uno sviluppatore di software (io in questo caso) che scrive questo codice in un modello di editor non dovrebbe essere molto orgoglioso del suo lavoro. Voglio dire, guarda! Anche io che ho scritto questo modello Razor come 5 minuti fa non riesco più a capire cosa fa.

Quindi noi refactoring questo codice spaghetti in un helper HTML riutilizzabile personalizzato:

 public static class HtmlExtensions { public static IHtmlString CheckBoxesForEnumModel(this HtmlHelper 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()); } } 

e puliamo il casino nel nostro modello di editor:

 @model NotificationDeliveryType   @Html.CheckBoxesForEnumModel()   

che produce la tabella:

inserisci la descrizione dell'immagine qui

Ora ovviamente sarebbe stato bello poter fornire etichette più amichevoli per quelle checkbox. Ad esempio:

 [Flags] public enum NotificationDeliveryType { [Display(Name = "in da system")] InSystem = 1, [Display(Name = "@")] Email = 2, [Display(Name = "txt")] Text = 4 } 

Tutto ciò che dobbiamo fare è adattare l’helper HTML che abbiamo scritto in precedenza:

 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()); } 

che ci dà un risultato migliore:

inserisci la descrizione dell'immagine qui

Il codice di Darin è stato ottimo, ma ho riscontrato qualche problema nell’utilizzarlo con MVC4.

Nell’estensione HtmlHelper per creare le caselle, continuavo a ricevere errori di run-time che il modello non era un enum (in particolare, dicendo System.Object). Ho rielaborato il codice per prendere un’espressione Lambda e ripulire questo problema utilizzando la class ModelMetadata:

 public static IHtmlString CheckBoxesForEnumFlagsFor(this HtmlHelper htmlHelper, Expression> 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("
"); } } return new HtmlString(sb.ToString()); }

Ho anche esteso il raccoglitore del modello in modo che funzioni con qualsiasi tipo di enumerazione generica.

 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); } 

È ancora necessario registrare ogni tipo di enumerazione in Application_Start ma almeno questo elimina la necessità di classi di binder separate. Puoi registrarlo usando:

 ModelBinders.Binders.Add(typeof(MyEnumType), new EnumFlagsModelBinder()); 

Ho pubblicato il mio codice su Github all’indirizzo https://github.com/Bitmapped/MvcEnumFlags .

Puoi provare il pacchetto MVC Enum Flags (disponibile tramite nuget ). Salta automaticamente le scelte enum a valore zero, che è un bel touch.

[Quanto segue è tratto dalla Documentazione e dai suoi commenti; vedere lì se questo non è vincolante per te]

Dopo l’installazione, aggiungere quanto segue a Global.asax.cs \ Application_Start:

ModelBinders.Binders.Add(typeof(MyEnumType), new EnumFlagsModelBinder());

Quindi, nella vista, metti @using MvcEnumFlags in alto e @Html.CheckBoxesForEnumFlagsFor(model => model.MyEnumTypeProperty) per il codice effettivo.

Uso l’approccio descritto in MVVM Framework .

  enum ActiveFlags { None = 0, Active = 1, Inactive = 2, } class ActiveFlagInfo : EnumInfo { 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 classs to wrap enum properties IEnumerable activeFlags = ActiveFlagInfo.GetEnumInfos(e => e == ActiveFlags.None ? null : new ActiveFlagInfo(e)); 

Bitmap, hai posto domande importanti e posso suggerire la seguente soluzione: dovresti sovrascrivere il metodo BindProperty del tuo ModelBinder e successivamente devi sovrascrivere il valore della proprietà del modello:

 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); } 

Usando il codice Darin e bitmap, scrivo la risposta, ma non ha funzionato per me, quindi prima ho risolto il problema con il nullable, poi ho ancora problemi con il bing, ho scoperto che c’è qualcosa di sbagliato nell’html, quindi mi rallegro di questa risposta , cerca un altro, che ho trovato qualcosa in un forum del mio paese, che ha usato lo stesso codice di qui, ma con un piccolo cambiamento, quindi lo unisco al mio codice, e tutto è andato bene, il mio progetto usa nullable, quindi non so come funzionerà in altri posti, potrebbe essere necessario un piccolo aggiustamento, ma ho cercato di pensare a nullable e al modello essendo l’enum stesso.

 public static class Extensions { public static IHtmlString CheckBoxesForEnumFlagsFor(this HtmlHelper htmlHelper, Expression> 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("
"); } } 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(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(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() );