プロパティのカスタム モデル バインダー
-
25-09-2019 - |
質問
次のコントローラーアクションがあります。
[HttpPost]
public ViewResult DoSomething(MyModel model)
{
// do something
return View();
}
どこ MyModel
次のようになります:
public class MyModel
{
public string PropertyA {get; set;}
public IList<int> PropertyB {get; set;}
}
したがって、DefaultModelBinder は問題なくこれをバインドするはずです。唯一のことは、バインディングに特別な/カスタムバインダーを使用したいことです PropertyB
そしてこのバインダーも再利用したいと思っています。そこで、解決策は PropertyB の前に ModelBinder 属性を置くことだと思いましたが、これはもちろん機能しません(プロパティでは ModelBinder 属性は許可されていません)。解決策は 2 つあります。
モデル全体ではなく、すべての単一プロパティでアクション パラメーターを使用するには (モデルには多くのプロパティがあるため、これは望ましくありません)、次のようにします。
public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
新しいタイプを作成するには、次のようにします。
MyCustomType: List<int>
このタイプのモデル バインダーを登録します (これはオプションです)MyModel のバインダーを作成するには、オーバーライドする必要があるかもしれません。
BindProperty
そしてそのプロパティが"PropertyB"
プロパティをカスタム バインダーにバインドします。これは可能でしょうか?
他に解決策はありますか?
解決
代わりに「PropertyBある」チェックの方が良い。のように、プロパティレベルのバインダーを定義し、独自のカスタム属性を確認するのには、bindPropertyを無効にしている場合 プロパティは、「PropertyB」であるバインド 私のカスタムバインダーを持つプロパティ
、良い解決策だこと
[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}
あなたはここでは、bindPropertyオーバーライドの例を見ることができますA>。
他のヒント
私実際にあなたの第三の溶液のような、唯一、私はDefaultModelBinder
からの継承とは、あなたのMVCアプリケーションのデフォルトのモデルバインダーになるように構成されていることをカスタムバインダにそれを置くことによって、それをすべてModelBindersのための一般的な解決策になるだろう。
次に、あなたは、この新しいDefaultModelBinder
が自動的にパラメータで指定された型を使用して、PropertyBinder
属性で飾られている任意のプロパティをバインドするだろう。
私はこの素晴らしい記事からアイデアを得た: http://aboutcode.net/2011/ 3月12日/ MVC-プロパティbinder.htmlするます。
私はまた、あなたが解決策を取る私の紹介します:
のマイDefaultModelBinder
ます:
namespace MyApp.Web.Mvc
{
public class DefaultModelBinder : System.Web.Mvc.DefaultModelBinder
{
protected override void BindProperty(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor);
if (propertyBinderAttribute != null)
{
var binder = CreateBinder(propertyBinderAttribute);
var value = binder.BindModel(controllerContext, bindingContext, propertyDescriptor);
propertyDescriptor.SetValue(bindingContext.Model, value);
}
else // revert to the default behavior.
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
IPropertyBinder CreateBinder(PropertyBinderAttribute propertyBinderAttribute)
{
return (IPropertyBinder)DependencyResolver.Current.GetService(propertyBinderAttribute.BinderType);
}
PropertyBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor)
{
return propertyDescriptor.Attributes
.OfType<PropertyBinderAttribute>()
.FirstOrDefault();
}
}
}
のマイIPropertyBinder
インターフェース:の
namespace MyApp.Web.Mvc
{
interface IPropertyBinder
{
object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext, MemberDescriptor memberDescriptor);
}
}
のマイPropertyBinderAttribute
ます:
namespace MyApp.Web.Mvc
{
public class PropertyBinderAttribute : Attribute
{
public PropertyBinderAttribute(Type binderType)
{
BinderType = binderType;
}
public Type BinderType { get; private set; }
}
}
の性バインダーの例:の
namespace MyApp.Web.Mvc.PropertyBinders
{
public class TimeSpanBinder : IPropertyBinder
{
readonly HttpContextBase _httpContext;
public TimeSpanBinder(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
MemberDescriptor memberDescriptor)
{
var timeString = _httpContext.Request.Form[memberDescriptor.Name].ToLower();
var timeParts = timeString.Replace("am", "").Replace("pm", "").Trim().Split(':');
return
new TimeSpan(
int.Parse(timeParts[0]) + (timeString.Contains("pm") ? 12 : 0),
int.Parse(timeParts[1]),
0);
}
}
}
の使用されるバインダー上記プロパティの例:の
namespace MyApp.Web.Models
{
public class MyModel
{
[PropertyBinder(typeof(TimeSpanBinder))]
public TimeSpan InspectionDate { get; set; }
}
}
@ jonathanconwayの答えは素晴らしいですが、私はマイナーなディテールを追加したいと思います。
はそれが仕事にチャンスをGetPropertyValue
の検証メカニズムを与えるために、代わりにBindProperty
のDefaultBinder
メソッドをオーバーライドすることはおそらく良いでしょう。
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
PropertyBinderAttribute propertyBinderAttribute =
TryFindPropertyBinderAttribute(propertyDescriptor);
if (propertyBinderAttribute != null)
{
propertyBinder = CreateBinder(propertyBinderAttribute);
}
return base.GetPropertyValue(
controllerContext,
bindingContext,
propertyDescriptor,
propertyBinder);
}
この質問がされてから 6 年が経過しました。真新しいソリューションを提供するのではなく、このスペースを使って最新情報を要約したいと思います。この記事の執筆時点では、MVC 5 はかなり前から存在しており、ASP.NET Core は登場したばかりです。
私は、Vijaya Anand が書いた投稿で検討されたアプローチに従いました (ちなみに、Vijaya に感謝します)。 http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes. 。注目すべき点の 1 つは、データ バインディング ロジックがカスタム属性クラスに配置されていることです。これは、Vijaya Anand の例では StringArrayPropertyBindAttribute クラスの BindProperty メソッドです。
ただし、私が読んだこのトピックに関する他のすべての記事 (@jonathanconway のソリューションを含む) では、カスタム属性クラスは、フレームワークが適用する正しいカスタム モデル バインダーを見つけるための単なるステップストーンにすぎません。バインド ロジックはカスタム モデル バインダー (通常は IModelBinder オブジェクト) に配置されます。
私にとっては 1 番目のアプローチの方が簡単です。ただし、現時点では私は MVC フレームワークに慣れていないため、1 番目のアプローチにはまだわかっていない欠点がいくつかあるかもしれません。
さらに、Vijaya Anand の例にある ExtendedModelBinder クラスは MVC 5 では不要であることがわかりました。MVC 5 に付属する DefaultModelBinder クラスは、カスタム モデル バインディング属性と連携できるほど賢いようです。