属性的自定义模型绑定器

我有以下控制器操作:

[HttpPost] public ViewResult DoSomething(MyModel model) { // do something return View(); } 

MyModel如下:

 public class MyModel { public string PropertyA {get; set;} public IList PropertyB {get; set;} } 

所以DefaultModelBinder应该没有问题地绑定它。 唯一的事情是我想使用特殊/自定义绑定器来绑定PropertyB ,我也想重用这个绑定器。 所以我认为解决方案是在PropertyB之前放置一个ModelBinder属性,这当然不起作用(属性上不允许使用ModelBinder属性)。 我看到两个解决方案:

  1. 要在每个属性上使用动作参数而不是整个模型(我不喜欢,因为模型有很多属性),如下所示:

     public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB) 
  2. 要创建一个新类型,可以说MyCustomType: List并注册此类型的模型绑定器(这是一个选项)

  3. 也许为MyModel创建一个绑定器,重写BindProperty ,如果属性为"PropertyB"则使用我的自定义绑定器绑定该属性。 这可能吗?

还有其他解决方案吗?

覆盖BindProperty,如果属性为“PropertyB”,则将该属性与我的自定义绑定器绑定

这是一个很好的解决方案,但不是检查“是PropertyB”,而是最好检查自己定义属性级绑定器的自定义属性,例如

 [PropertyBinder(typeof(PropertyBBinder))] public IList PropertyB {get; set;} 

您可以在此处查看BindProperty覆盖的示例。

我实际上喜欢你的第三个解决方案,我将它作为所有ModelBinder的通用解决方案,将它放在一个自定义的绑定器中,该绑定器inheritance自DefaultModelBinder并被配置为MVC应用程序的默认模型绑定器。

然后,您将使用参数中提供的类型使此新DefaultModelBinder自动绑定使用PropertyBinder属性修饰的任何属性。

我从这篇优秀的文章中得到了这个想法: http : //aboutcode.net/2011/03/12/mvc-property-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() .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的validation机制提供工作机会。

 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 。 值得注意的是,数据绑定逻辑放在自定义属性类中,这是Vijaya Anand示例中StringArrayPropertyBindAttribute类的BindProperty方法。

但是,在我读过的关于这个主题的所有其他文章中(包括@ jonathanconway的解决方案),自定义属性类只是一个步骤,它引导框架找出要应用的正确的自定义模型绑定器; 并且绑定逻辑放置在该自定义模型绑定器中,该绑定器通常是IModelBinder对象。

第一种方法对我来说更简单。 第一种方法可能存在一些缺点,我还不知道,但是,我现在对MVC框架很新。

另外,我发现Vigaya Anand的示例中的ExtendedModelBinder类在MVC 5中是不必要的。似乎MVC 5附带的DefaultModelBinder类足够聪明,可以与自定义模型绑定属性配合使用。