38

我有以下控制器操作:

[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"用我的自定义活页夹绑定该属性。这可能吗?

还有其他解决方案吗?

4

4 回答 4

23

覆盖 BindProperty,如果属性是“PropertyB”,则将属性与我的自定义活页夹绑定

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

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

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

于 2010-02-03T10:43:22.233 回答
19

我实际上喜欢你的第三个解决方案,只是,我会将它放在一个自定义绑定器中,它继承自DefaultModelBinder并配置为 MVC 应用程序的默认模型绑定器,从而使其成为所有 ModelBinders 的通用解决方案。

然后,您将使用参数中提供的类型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; }
    }
}
于 2012-10-02T01:03:13.380 回答
6

@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);
}
于 2013-11-01T13:23:57.773 回答
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 类足够智能,可以与自定义模型绑定属性配合使用。

于 2016-12-22T02:07:10.703 回答