C# 属性的自定义模型绑定器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2186969/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-07 00:12:48  来源:igfitidea点击:

Custom model binder for a property

c#asp.net-mvcmodel-bindingcustom-model-binder

提问by rrejc

I have the following controller action:

我有以下控制器操作:

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

Where MyModellooks 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. The only thing is that I want to use special/custom binder for binding PropertyBand I also want to reuse this binder. 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). I see two solutions:

所以 DefaultModelBinder 应该毫无问题地绑定它。唯一的事情是我想使用特殊/自定义绑定器进行绑定PropertyB,我也想重用这个绑定器。所以我认为解决方案是在 PropertyB 之前放置一个 ModelBinder 属性,这当然不起作用(属性上不允许使用 ModelBinder 属性)。我看到两种解决方案:

  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)

  3. Maybe to create a binder for MyModel, override BindPropertyand if the property is "PropertyB"bind the property with my custom binder. Is this possible?

  1. 要在每个属性而不是整个模型上使用动作参数(我不喜欢,因为模型有很多属性),如下所示:

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

  3. 也许为 MyModel 创建一个活页夹,覆盖BindProperty,如果该属性是"PropertyB"用我的自定义活页夹绑定该属性。这可能吗?

Is there any other solution?

还有其他解决方案吗?

采纳答案by queen3

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 覆盖示例。

回答by Jonathan

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 DefaultModelBinderand is configured to be the default model binder for your MVC application.

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

Then you would make this new DefaultModelBinderautomatically bind any property that is decorated with a PropertyBinderattribute, 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 IPropertyBinderinterface:

我的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; }
    }
}

回答by Gebb

@jonathanconway's answer is great, but I would like to add a minor detail.

@jonathanconway 的回答很好,但我想添加一个小细节。

It's probably better to override the GetPropertyValuemethod instead of BindPropertyin order to give the validation mechanism of the DefaultBindera 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);
}

回答by VincentZHANG

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. At the time of writing, MVC 5 has been around for quite a while, and ASP.NET Core has just come out.

问这个问题已经6年了,我宁愿利用这个空间来总结更新,而不是提供一个全新的解决方案。在撰写本文时,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. 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 写的帖子(顺便说一句,感谢 Vijaya)中检查的方法:http: //www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes。还有一点值得注意的是,数据绑定逻辑放在自定义属性类中,也就是 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; and the binding logic is placed in that custom model binder, which is usually an IModelBinder object.

然而,在我读过的关于这个主题的所有其他文章中(包括@jonathanconway 的解决方案),自定义属性类只是引导框架找出要应用的正确自定义模型绑定器的垫脚石;并且绑定逻辑放置在该自定义模型绑定器中,该绑定器通常是一个 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.

另外,我发现 Vijaya Anand 示例中的 ExtendedModelBinder 类在 MVC 5 中是不必要的。 似乎 MVC 5 自带的 DefaultModelBinder 类足够智能,可以配合自定义模型绑定属性。