【问题标题】:Model Binding to Enums in ASP.NET MVC 3模型绑定到 ASP.NET MVC 3 中的枚举
【发布时间】:2011-08-28 10:46:03
【问题描述】:

我的控制器中有一个方法,它接受一个对象作为参数并返回一个JsonResult。此对象的属性之一是具有三个可能值的枚举。我假设当客户端为该属性传入一个 int 时,它会填充枚举,但事实并非如此,它默认为 0,并且枚举设置为第一个可能的选择。

有什么建议吗?

【问题讨论】:

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

标签: c# asp.net-mvc asp.net-mvc-3 enums model-binding


【解决方案1】:

绑定到模型上的钩子属性怎么样?

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

【讨论】:

    【解决方案2】:

    注意:这已在 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);
              }
          }
      }
      
    2. 然后只需将其注册到您的 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

    【讨论】:

    • 感谢您的信息。看到服务器到客户端的默认行为是 Enum -> int 非常令人失望,但客户端到服务器是字符串 -> Enum。好东西。
    • @kdawg,确实令人失望。但是,在 MVC 团队的视频演示中,我确实记得听到有人提到他们打算在未来的版本中修复枚举支持,尽管你可能不应该引用我的话。
    • 我想知道——继承 DefaultModelBinder 还是使用 EnumModelBinder 实现来实现 IModelBinder 接口更好? Phil Haack 在这里有一个例子:haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx 我还没有完全实现 IModelBinder 枚举解决方案。我提出它是因为我需要一个十进制绑定器(如 Haack 的帖子),并且将特定于类型的绑定器添加到 ModelBinders.Binders 集合(即:.Add(typeof(enum), EnumModelBinder); .Add(typeof( Decimal), DecimalModelBinder)) 而不是检查 MyModelBinder 中的每种类型
    • 哈!当然,Type 没有 IsDecimal 属性。这降低了在 MyModelBinder 方法中处理模型绑定到小数的难易程度......
    • 另外,typeof(enum) 也在爆炸。嘘,别理我。 =)
    【解决方案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);
        }
    

    我承认,这不是最直观的方式,但对我来说效果很好。如果我可以优化这个,请随时告诉我。

    【讨论】:

    • 您是否正在寻找对此代码的评论?试试Code Review。您是否想分享您遇到的问题的解决方案,而不是这个问题?关于它的博客。
    猜你喜欢
    • 1970-01-01
    • 2021-08-03
    • 2016-05-23
    • 1970-01-01
    • 2011-08-07
    • 1970-01-01
    • 1970-01-01
    • 2011-09-02
    • 1970-01-01
    相关资源
    最近更新 更多