简体   繁体   English

属性的自定义模型绑定器

[英]Custom model binder for a property

I have the following controller action: 我有以下控制器操作:

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

Where MyModel looks like this: MyModel如下:

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

So DefaultModelBinder should bind this without a problem. 所以DefaultModelBinder应该没有问题地绑定它。 The only thing is that I want to use special/custom binder for binding PropertyB and I also want to reuse this binder. 唯一的事情是我想使用特殊/自定义绑定器来绑定PropertyB ,我也想重用这个绑定器。 So I thought that solution would be to put a ModelBinder attribute before the PropertyB which of course doesn't work (ModelBinder attribute is not allowed on a properties). 所以我认为解决方案是在PropertyB之前放置一个ModelBinder属性,当然这不起作用(属性上不允许使用ModelBinder属性)。 I see two solutions: 我看到两个解决方案:

  1. To use action parameters on every single property instead of the whole model (which I wouldn't prefer as the model has a lot of properties) like this: 要在每个属性上使用动作参数而不是整个模型(我不喜欢,因为模型具有很多属性),如下所示:

     public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB) 
  2. To create a new type lets say MyCustomType: List<int> and register model binder for this type (this is an option) 要创建一个新类型,可以说MyCustomType: List<int>并注册此类型的模型绑定器(这是一个选项)

  3. Maybe to create a binder for MyModel, override BindProperty and if the property is "PropertyB" bind the property with my custom binder. 也许为MyModel创建一个绑定器,重写BindProperty ,如果属性为"PropertyB"则使用我的自定义绑定器绑定该属性。 Is this possible? 这可能吗?

Is there any other solution? 还有其他解决方案吗?

override BindProperty and if the property is "PropertyB" bind the property with my custom binder 覆盖BindProperty,如果属性为“PropertyB”,则将该属性与我的自定义绑定器绑定

That's a good solution, though instead of checking "is PropertyB" you better check for your own custom attributes that define property-level binders, like 这是一个很好的解决方案,但不是检查“是PropertyB”,而是更好地检查您自己定义属性级绑定器的自定义属性,例如

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

You can see an example of BindProperty override here . 您可以在此处查看BindProperty覆盖的示例。

I actually like your third solution, only, I would make it a generic solution for all ModelBinders, by putting it in a custom binder that inherits from DefaultModelBinder and is configured to be the default model binder for your MVC application. 我实际上喜欢你的第三个解决方案,我将它作为所有ModelBinder的通用解决方案,将它放在一个自定义的绑定器中,该绑定器继承自DefaultModelBinder并被配置为MVC应用程序的默认模型绑定器。

Then you would make this new DefaultModelBinder automatically bind any property that is decorated with a PropertyBinder attribute, using the type supplied in the parameter. 然后,您将使用参数中提供的类型使新的DefaultModelBinder自动绑定使用PropertyBinder属性修饰的任何属性。

I got the idea from this excellent article: http://aboutcode.net/2011/03/12/mvc-property-binder.html . 我从这篇优秀的文章中得到了这个想法: http//aboutcode.net/2011/03/12/mvc-property-binder.html

I'll also show you my take on the solution: 我还将向您展示我对解决方案的看法:

My DefaultModelBinder : 我的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();
        }
    }
}

My IPropertyBinder interface: 我的IPropertyBinder界面:

namespace MyApp.Web.Mvc
{
    interface IPropertyBinder
    {
        object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext, MemberDescriptor memberDescriptor);
    }
}

My PropertyBinderAttribute : 我的PropertyBinderAttribute

namespace MyApp.Web.Mvc
{
    public class PropertyBinderAttribute : Attribute
    {
        public PropertyBinderAttribute(Type binderType)
        {
            BinderType = binderType;
        }

        public Type BinderType { get; private set; }
    }
}

An example of a property binder: 属性绑定器的示例:

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

Example of the above property binder being used: 使用上述属性绑定器的示例:

namespace MyApp.Web.Models
{
    public class MyModel
    {
        [PropertyBinder(typeof(TimeSpanBinder))]
        public TimeSpan InspectionDate { get; set; }
    }
}

@jonathanconway's answer is great, but I would like to add a minor detail. @ jonathanconway的答案很棒,但我想补充一点细节。

It's probably better to override the GetPropertyValue method instead of BindProperty in order to give the validation mechanism of the DefaultBinder a chance to work. 最好覆盖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);
}

It has been 6 years since this question was asked, I would rather take this space to summarize the update, instead of providing a brand new solution. 问这个问题已有6年了,我宁愿利用这个空间来总结更新,而不是提供一个全新的解决方案。 At the time of writing, MVC 5 has been around for quite a while, and ASP.NET Core has just come out. 在撰写本文时,MVC 5已经存在了很长一段时间,ASP.NET Core刚刚问世。

I followed the approach examined in the post written by Vijaya Anand (btw, thanks to Vijaya): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes . 我按照Vijaya Anand撰写的文章中检查的方法(顺便说一下,感谢Vijaya): http//www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes And one thing worth noting is that, the data binding logic is placed in the custom attribute class, which is the BindProperty method of the StringArrayPropertyBindAttribute class in Vijaya Anand's example. 值得注意的是,数据绑定逻辑放在自定义属性类中,这是Vijaya Anand示例中StringArrayPropertyBindAttribute类的BindProperty方法。

However, in all the other articles on this topic that I have read (including @jonathanconway's solution), custom attribute class is only a step stone that leads the framework to find out the correct custom model binder to apply; 但是,在我读过的关于这个主题的所有其他文章中(包括@ jonathanconway的解决方案),自定义属性类只是引导框架找出要应用的正确自定义模型绑定器的一个步骤; and the binding logic is placed in that custom model binder, which is usually an IModelBinder object. 并且绑定逻辑放置在该自定义模型绑定器中,该绑定器通常是IModelBinder对象。

The 1st approach is simpler to me. 第一种方法对我来说更简单。 There may be some shortcomings of the 1st approach, that I haven't known yet, though, coz I am pretty new to MVC framework at the moment. 第一种方法可能存在一些缺点,我还不知道,但是,我现在对MVC框架很新。

In addition, I found that the ExtendedModelBinder class in Vijaya Anand's example is unnecessary in MVC 5. It seems that the DefaultModelBinder class which comes with MVC 5 is smart enough to cooperate with custom model binding attributes. 另外,我发现Vigaya Anand的示例中的ExtendedModelBinder类在MVC 5中是不必要的。似乎MVC 5附带的DefaultModelBinder类足够聪明,可以与自定义模型绑定属性配合使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM