Model Binding to Enums in ASP.NET MVC 3

J’ai une méthode dans mon contrôleur qui accepte un object comme argument et renvoie un JsonResult . L’une des propriétés de cet object est une énumération avec trois valeurs possibles. J’ai supposé que lorsque le client passait un int pour cette propriété, il remplissait l’énumération, mais ce n’est pas le cas, la valeur par défaut est 0 et l’énumération est définie sur la première de ses sélections possibles.

Aucune suggestion?

REMARQUE: Ceci a été résolu dans MVC 4. Si la mise à niveau vers MVC 4 est une option viable pour votre projet, c’est tout ce que vous devez faire pour commencer la liaison de modèle aux énumérations.

Cela dit, voici la solution de contournement pour MVC 3 si vous en avez encore besoin.


Le problème concerne le classeur de modèle par défaut dans MVC. La valeur entière correcte correspond au modèle, mais le classeur n’est pas codé pour correspondre à la valeur entière de l’énumération. Il se lie correctement si la valeur transmise est une chaîne contenant la valeur nommée de l’énumération. Le problème est que lorsque vous parsingz un object C # dans JSON en utilisant la méthode Json() , il envoie la valeur entière comme valeur enum, pas la valeur nommée.

Le correctif le plus simple et le plus transparent consiste à remplacer le modèle par défaut et à écrire une logique personnalisée pour corriger la manière dont il lie les énumérations.

  1. Créez une nouvelle classe, comme ça.

     namespace CustomModelBinders { ///  /// Override for DefaultModelBinder in order to implement fixes to its behavior. /// This model binder inherits from the default model binder. All this does is override the default one, /// check if the property is an enum, if so then use custom binding logic to correctly map the enum. If not, /// we simply invoke the base model binder (DefaultModelBinder) and let it continue binding as normal. ///  public class EnumModelBinder : DefaultModelBinder { ///  /// Fix for the default model binder's failure to decode enum types when binding to JSON. ///  protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { var propertyType = propertyDescriptor.PropertyType; if (propertyType.IsEnum) { var providerValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (null != providerValue) { var value = providerValue.RawValue; if (null != value) { var valueType = value.GetType(); if (!valueType.IsEnum) { return Enum.ToObject(propertyType, value); } } } } return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } } } 
  2. Ensuite, enregistrez-le simplement dans votre fichier Global.asax.

     protected override void OnApplicationStarted() { base.OnApplicationStarted(); AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); // Register your new model binder ModelBinders.Binders.DefaultBinder = new EnumModelBinder(); } 

C’est tout. Les énumérations seront désormais correctement liées aux objects JSON.

http://www.codetunnel.com/how-to-bind-to-enums-on-json-objects-in-aspnet-mvc-3

Qu’en est-il de la liaison à une propriété hook sur votre modèle?

 public class SomeModel { public MyEnum EnumValue { get; set; } public int BindToThisGuy { get { return (int) EnumValue; } set { EnumValue = (MyEnum)value; } } } 

Ok les gars. J’ai cherché quelques façons de faire cela parce que j’étais fatigué d’écrire un travail stupide pour contourner cette lacune dans le cadre .Net. Basé sur quelques threads, j’ai composé la solution suivante.

Déni de responsabilité, cette solution n’est pas totalement automatisée, elle ne fonctionnera donc pas pour tous. Compte tenu de ma mise en œuvre, cela fonctionne. Peut-être que ma manière aidera quelqu’un d’autre à concevoir quelque chose qui fonctionnera pour eux.

Tout d’abord, j’ai créé un référentiel enum. Les énumérations n’ont pas à résider ici, mais elles doivent être visibles depuis le référentiel.

Dans le référentiel, j’ai créé une classe et une propriété statique publique pour exposer une liste de types enum.

 namespace MyApp.Enums { public enum ATS_Tabs { TabOne = 0, TabTwo = 1, TabThree = 2, TabFour = 3, TabFive = 4 }; public class ModelEnums { public static IEnumerable Types { get { List Types = new List(); Types.Add(typeof(ATS_Tabs)); return Types; } } } } 

Ensuite, j’ai créé un modèle de classeur et implémenté l’interface IModelBinder (réf. Kdawg’s comment and link).

 namespace MyApp.CustomModelBinders { public class EnumModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); ModelState modelState = new ModelState { Value = valueResult }; object actualValue = null; try { return Enum.ToObject(Type.GetType(bindingContext.ModelType.AssemblyQualifiedName), Convert.ToInt32(valueResult.AttemptedValue)); } catch (FormatException e) { modelState.Errors.Add(e); } bindingContext.ModelState.Add(bindingContext.ModelName, modelState); return actualValue; } } } 

Il peut être utile d’append du code pour garantir que la conversion de valueResult.AttemptedValue n’échoue pas.

Ensuite, j’ai parcouru la liste des types d’énumération que j’ai créés ci-dessus et ajouté des classeurs de modèle pour eux (… dans Global.asax.cs).

  protected void Application_Start() { AreaRegistration.RegisterAllAreas(); foreach (Type type in ModelEnums.Types) { ModelBinders.Binders.Add(type, new EnumModelBinder()); } RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } 

Je l’avoue, ce n’est pas la manière la plus intuitive, mais ça marche très bien pour moi. N’hésitez pas à me faire savoir si je peux optimiser cela.