属性的自定义模型活页夹

发布于 2024-08-20 04:48:04 字数 925 浏览 5 评论 0原文

我有以下控制器操作:

[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 并为此类型注册模型绑定器(这是一个选项)

  3. 也许要为 MyModel 创建绑定器,覆盖 BindProperty 并且如果属性是 "PropertyB" 则绑定财产与我的定制活页夹。这可能吗?

还有其他解决办法吗?

I have the following controller action:

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

Where MyModel looks like this:

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 PropertyB and 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:

  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 BindProperty and if the property is "PropertyB" bind the property with my custom binder. Is this possible?

Is there any other solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

淡忘如思 2024-08-27 04:48:04

覆盖 BindProperty 并且如果
属性是“PropertyB”绑定
带有我的自定义活页夹的属性

属性级绑定的自定义属性,而不是检查“is PropertyB”,例如

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

您可以看到 BindProperty override 的示例 此处

override BindProperty and if the
property is "PropertyB" bind the
property with my custom binder

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

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

You can see an example of BindProperty override here.

明月松间行 2024-08-27 04:48:04

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

然后,您可以使用参数中提供的类型,使这个新的 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; }
    }
}

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.

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.

I got the idea from this excellent article: http://aboutcode.net/2011/03/12/mvc-property-binder.html.

I'll also show you my take on the solution:

My 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:

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

My 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; }
    }
}
夏末染殇 2024-08-27 04:48:04

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

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

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.

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);
}
一身仙ぐ女味 2024-08-27 04:48:04

这个问题提出已经有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类足够聪明,可以与自定义模型绑定属性配合。

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.

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.

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.

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.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文