属性的自定义模型活页夹
-
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 属性)。我看到两个解决方案:
要在每个属性而不是整个模型上使用操作参数(我不喜欢这样做,因为模型有很多属性),如下所示:
public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
要创建一个新类型,可以说
MyCustomType: List<int>
并为此类型注册模型绑定器(这是一个选项)也许要为 MyModel 创建活页夹,覆盖
BindProperty
如果该财产是"PropertyB"
使用我的自定义活页夹绑定该属性。这可能吗?
还有其他解决办法吗?
解决方案
Override bindproperty,如果属性为“属性”,则用我的自定义粘合剂将属性绑定
这是一个很好的解决方案,不过您最好检查自己定义属性级绑定的自定义属性,而不是检查“is PropertyB”,例如
[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}
您可以查看 BindProperty 覆盖的示例 这里.
其他提示
我实际上喜欢你的第三个解决方案,只是,我会将其放入继承自的自定义绑定器中,使其成为所有 ModelBinder 的通用解决方案 DefaultModelBinder
并配置为 MVC 应用程序的默认模型绑定器。
然后你会把这个变成新的 DefaultModelBinder
自动绑定任何用 a 修饰的属性 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<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. 。还有一点值得注意的是,数据绑定逻辑放在自定义属性类中,即Vijaya Anand示例中的StringArrayPropertyBindAttribute类的BindProperty方法。
然而,在我读过的关于这个主题的所有其他文章(包括@jonathanconway的解决方案)中,自定义属性类只是引导框架找出要应用的正确自定义模型绑定器的垫脚石;绑定逻辑放置在自定义模型绑定器中,通常是 IModelBinder 对象。
第一种方法对我来说更简单。第一种方法可能有一些缺点,但我还不知道,因为我目前对 MVC 框架还很陌生。
另外,我发现Vijaya Anand示例中的ExtendModelBinder类在MVC 5中是不必要的。看来 MVC 5 附带的 DefaultModelBinder 类足够智能,可以与自定义模型绑定属性配合。