47 votes

Liaison de modèle aux Enums dans ASP.NET MVC 3

J'ai une méthode dans mon contrôleur qui accepte un objet comme argument et retourne un JsonResult . L'une des propriétés de cet objet est un enum avec trois valeurs possibles. J'ai supposé que lorsque le client passait un int pour cette propriété, cela remplirait l'enum, mais ce n'est pas le cas, la valeur par défaut est 0 et l'enum est défini sur la première des sélections possibles.

Des suggestions ?

70voto

Alex Ford Points 15277

REMARQUE : ce problème 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 à lier les modèles aux enums.

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 est transmise au liant du modèle, mais le liant 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 analysez un objet C# en JSON à l'aide de la fonction Json() il envoie la valeur entière comme valeur de l'enum, et non la valeur nommée.

La solution la plus simple et la plus transparente consiste à remplacer le liant de modèle par défaut et à écrire une logique personnalisée pour corriger la façon dont il lie les enums.

  1. Créez une nouvelle classe, comme ceci.

    namespace CustomModelBinders
    {
        /// <summary>
        /// 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.
        /// </summary>
        public class EnumModelBinder : DefaultModelBinder
        {
            /// <summary>
            /// Fix for the default model binder's failure to decode enum types when binding to JSON.
            /// </summary>
            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. Il suffit ensuite de l'enregistrer 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 fait. Les Enums seront désormais correctement liés aux objets JSON.

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

15voto

RPM1984 Points 39648

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

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

3voto

Difinity Points 109

Ok les gars. J'ai cherché plusieurs façons de le faire parce que j'étais fatigué d'écrire des astuces stupides pour contourner cette déficience du cadre .Net. Sur la base de quelques fils de discussion, j'ai composé la solution suivante.

Attention, il ne s'agit pas d'une solution totalement automatisée, et elle ne fonctionnera donc pas pour tous. Compte tenu de ma mise en œuvre, cela fonctionne. Peut-être que ma méthode aidera quelqu'un d'autre à concevoir quelque chose qui fonctionnera pour lui.

D'abord, j'ai créé un référentiel d'enum. Les enums n'ont pas besoin de résider ici, mais ils 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 d'enum.

namespace MyApp.Enums
{
    public enum ATS_Tabs { TabOne = 0, TabTwo = 1, TabThree = 2, TabFour = 3, TabFive = 4 };

    public class ModelEnums
    {
        public static IEnumerable<Type> Types
        {
            get
            {
                List<Type> Types = new List<Type>();
                Types.Add(typeof(ATS_Tabs));
                return Types;
            }
        }
    }
}

Ensuite, j'ai créé un classeur de modèles et implémenté l'interface IModelBinder (voir le commentaire et le lien de kdawg).

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 pourrait être utile d'ajouter du code pour s'assurer que la conversion de valueResult.AttemptedValue n'échoue pas.

Ensuite, j'ai parcouru en boucle la liste des types d'enum que j'ai créée ci-dessus et j'ai ajouté des classeurs de modèles 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);
    }

J'admets que ce n'est pas la méthode la plus intuitive, mais elle fonctionne très bien pour moi. N'hésitez pas à me faire savoir si je peux l'optimiser.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X