web-dev-qa-db-ja.com

ASP.NET MVC 3の列挙型へのモデルバインディング

コントローラにオブジェクトを引数として受け取り、 JsonResult を返すメソッドがあります。このオブジェクトのプロパティの1つは、3つの可能な値を持つ列挙です。クライアントがそのプロパティのintを渡すと、enumが生成されると想定しましたが、デフォルトでは0になり、enumは可能な選択の最初に設定されます。

助言がありますか?

47
Chev

注:これはMVC 4で解決されました。MVC 4へのアップグレードがプロジェクトの実行可能なオプションである場合、それだけで完了です列挙型へのモデルバインドを開始します。

それでも、MVC 3が必要な場合の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-

70
Chev

モデルのフックプロパティへのバインドはどうですか?

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

わかりました。 .Netフレームワークのこの欠陥を乗り越えるための愚かな作業を書くのにうんざりしていたので、これを行ういくつかの方法を探しました。いくつかのスレッドに基づいて、次のソリューションを作成しました。

免責事項、これは完全に自動化されたソリューションではないため、すべてのユーザーに有効ではありません。私の実装を考えると、動作します。たぶん私のやり方は、他の誰かが彼らのために働く何かを設計するのを助けるでしょう。

最初に、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;
            }
        }
    }
}

次に、モデルバインダーを作成し、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
Difinity