属性的自定义模型绑定器
我有以下控制器操作:
[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属性)。 我看到两个解决方案:
-
要在每个属性上使用动作参数而不是整个模型(我不喜欢,因为模型有很多属性),如下所示:
public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
-
要创建一个新类型,可以说
MyCustomType: List
并注册此类型的模型绑定器(这是一个选项) -
也许为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类足够聪明,可以与自定义模型绑定属性配合使用。