在ASP.NET MVC 3中绑定枚举类型的模型

50

我在控制器中有一个方法,该方法接受一个对象作为参数并返回JsonResult。这个对象上的一个属性是一个枚举类型,有三种可能的值。我认为当客户端为该属性传递 int 值时,它会填充枚举类型,但实际上它没有,而是默认为 0 并将枚举设置为可选项中的第一个。

有什么建议吗?


您是否正在尝试将下拉选项绑定到它? - Stanislav Ageev
不是这样的。它是一个作为JSON传回的对象,然后在下一个AJAX请求中将JSON对象传回服务器。我正在对此JSON对象进行模型绑定。该对象上的一个属性是C#代码中的枚举。在客户端,当我将C#对象序列化为json时,我可以看到它正确地使用枚举的整数值填充了JSON对象,但是当我将其传回并进行绑定时,枚举默认为零。 - Chev
你的枚举是基于 int 类型的吗?enum MyEnum : int {MyEnumValue = 3} 此外,如果你使用字符串值(而不是整数),它也会绑定。你还可以为你的枚举创建一个自定义模型绑定器。 - Tony Basallo
这听起来像是枚举属性未设置的行为,但我怀疑这不是你的情况。 - Stanislav Ageev
1
在Firebug中,我可以看到JSON对象具有正确的int,并使用正确的int进行ajax请求。当我在服务器上调试时,枚举默认为零(好像没有设置)。 - Chev
3个回答

70

注意:此问题已经在MVC 4中得到解决。如果升级到MVC 4对您的项目是可行的,那么您只需要这样做就可以开始将枚举绑定到模型。

即便如此,如果您仍然需要在MVC 3中使用,请看以下解决方法:


问题出在MVC中的默认模型绑定程序。正确的整数值传递到了模型绑定程序,但绑定程序没有编码以将其映射到枚举的整数值。如果传递的值为包含枚举命名值的字符串,则它会正确地进行绑定。但问题是,当使用Json()方法将C#对象解析为JSON时,它将整数值作为枚举值发送,而不是命名值。

最简单和最透明的解决方法是覆盖默认模型绑定程序并编写一些自定义逻辑来修复它绑定枚举的方式。

  1. 创建一个新类,如下所示。

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);
        }
    }
}
然后只需在Global.asax文件中注册它即可。
protected override void OnApplicationStarted()
{
    base.OnApplicationStarted();

    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);

    // Register your new model binder
    ModelBinders.Binders.DefaultBinder = new EnumModelBinder();
}

就这样。现在枚举类型将正确地绑定到JSON对象上。

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


6
谢谢您提供的信息。看到服务器到客户端的默认行为是枚举->整数,但客户端到服务器是字符串->枚举,感觉非常失望。不过还是挺好的。 - kdawg
5
我在想——是从DefaultModelBinder继承还是用EnumModelBinder实现IModelBinder接口更好?Phil Haack在这里有一个例子:http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx。 我还没有完全实现IModelBinder枚举解决方案。我提出这个问题是因为我需要一个十进制绑定器(就像Haack的文章中所述),并且将特定类型的绑定器添加到ModelBinders.Binders集合似乎更加简洁(例如:.Add(typeof(enum), EnumModelBinder); .Add(typeof(Decimal), DecimalModelBinder))而不是在MyModelBinder中检查每种类型。 - kdawg
哈!当然,Type对象上没有IsDecimal属性。这就降低了使用MyModelBinder方法处理十进制数模型绑定的便利性... - kdawg
同时,typeof(enum)也出现了问题。所以,就当我没说。=) - kdawg
这个 EnumModelBinder 现在似乎破坏了 GET 请求和查询字符串的解析。 - Michael R
显示剩余2条评论

17

那绑定到模型的钩子属性呢?

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

3
好的,大家好。我查了一些方法来解决.Net框架中的这个缺陷,因为我厌倦了编写愚蠢的解决方法。根据几个线程的信息,我组合了以下解决方案。
免责声明,这不是完全自动化的解决方案,所以它并不适用于所有情况。但基于我的实现,它可以工作。也许我的方式能帮助其他人设计出适用于他们的东西。
首先,我创建了一个枚举存储库。枚举不必驻留在此处,但需要从存储库中可见。
在存储库中,我创建了一个类和一个公共静态属性来公开枚举类型列表。
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;
            }
        }
    }
}

接下来,我创建了一个模型绑定器并实现了IModelBinder接口(参考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;
        }
    }
}

为确保valueResult.AttemptedValue的转换不会失败,可以添加一些代码来帮助。

接下来,在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);
    }

我承认,这不是最直观的方式,但对我来说非常有效。如果有优化建议,请随时告诉我。


3
你是否需要对这段代码进行评审?请尝试访问Code Review。如果你想分享自己解决问题的方法,但并非此问题,请写一篇博客。 - Ry-

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接