2010-02-02 51 views
23

自定义模型粘结剂,我有以下的控制器操作:为属性

[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属性在属性上是不允许的)。我看到两个解决方案:

  1. 要在每一个属性,而不是整个模型用行动参数(这我不喜欢的模型有很多的属性)是这样的:

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

  3. 也许创建为MyModel粘合剂,覆盖BindProperty如果属性为"PropertyB"将该属性与我的自定义联编程序绑定。这可能吗?

有没有其他解决方案?

回答

14

覆盖BindProperty如果 属性为“PropertyB”的 财产与我的定制绑定

这是一个很好的解决方案绑定的,虽然不是检查,“是PropertyB”你最好先检查一下你自己定义属性级别的粘合剂定制属性,如

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

你可以看到BindProperty覆盖here的例子。

13

我实际上很喜欢你的第三种解决方案,我将它作为所有ModelBinder的通用解决方案,方法是将它放在一个定制绑定器中,该绑定器从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<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; } 
    } 
} 
+1

我想这种方法可以通过重写'GetPropertyValue'来改进。有关详细信息,请参阅[我的回答](http://stackoverflow.com/a/19727630/129073)。 – Gebb 2013-11-01 13:26:18

+0

是否可以为Web API提供类似的PropertyBinder实现? – mehul9595 2014-05-23 06:53:47

+2

'CreateBinder'不适用于我这个构造函数,所以我放弃了'HttpContext'并使用了'var value = bindingContext.ValueProvider.GetValue(memberDescriptor.Name);'来获取属性值。 – Brad 2016-11-29 20:47:01

5

@ jonathanconway的回答是巨大的,但是我想添加一个小细节。

为了给DefaultBinder的验证机制提供工作机会,最好重写GetPropertyValue而不是BindProperty

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); 
} 
+1

根据您的需要使用,这可能适用于您,它可能不适用。请记住,GetPropertyValue仅在入站对象中的项目(在本例中为Request form/query string)与该属性匹配时触发。如果你想要一个能够搜索不同名称的元素的自定义模型联编程序,也许填充一个字典,你会不走运。例如,映射“field1 = test&field2 = ing&field3 = now”将映射到一个字典,其中“1”是关键,“测试”是价值。乔纳森康威的解决方案确实允许这样做。也许是一个混合,允许两者都更好。 – 2015-02-06 17:34:33

1

自问这个问题已经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的示例中的ExtendedModelBinder类在MVC 5中是不必要的。看起来MVC 5附带的DefaultModelBinder类足够聪明,可以与自定义模型绑定属性配合使用。