web-dev-qa-db-fra.com

Liaison de modèle aux énumérations dans ASP.NET MVC 3

J'ai une méthode dans mon contrôleur qui accepte un objet comme argument et renvoie un JsonResult . L'une des propriétés de cet objet est une énumération avec trois valeurs possibles. J'ai supposé que lorsque le client transmettait 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?

47
Chev

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

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


Le problème est lié au classeur de modèle par défaut dans MVC. La valeur entière correcte arrive dans le classeur de 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 avec cela est que lorsque vous analysez un objet C # dans JSON à l'aide de la méthode Json(), il envoie la valeur entière comme valeur d'énumération, pas la valeur nommée.

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

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

    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. 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 ça. Les énumérations seront désormais correctement liées aux objets JSON.

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

70
Chev

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

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

Ok les gars. J'ai cherché quelques façons de le faire parce que j'étais fatigué d'écrire un travail stupide pour surmonter cette lacune dans le cadre .Net. Sur la base de quelques discussions, j'ai composé la solution suivante.

Avertissement, ce n'est pas une solution totalement automatisée, donc cela ne fonctionnera 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 lui.

Tout d'abord, j'ai créé un référentiel d'énumération. Les énumérations n'ont pas à résider ici, mais elles devraient ê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'énumération.

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 (réf. Commentaire et 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 peut être utile d'ajouter 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 liants 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);
    }

J'avoue, ce n'est pas la manière la plus intuitive, mais cela fonctionne très bien pour moi. N'hésitez pas à me faire savoir si je peux optimiser cela.

3
Difinity