Вопрос

У меня есть следующее действие контроллера:

[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 и я также хочу повторно использовать эту папку.Поэтому я подумал, что решением будет поместить атрибут ModelBinder перед PropertyB, что, конечно, не работает (атрибут ModelBinder не разрешен для свойств).Я вижу два решения:

  1. Чтобы использовать параметры действия для каждого отдельного свойства, а не для всей модели (что я бы не предпочел, поскольку модель имеет много свойств), например:

    public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
    
  2. Чтобы создать новый тип, скажем MyCustomType: List<int> и зарегистрировать связку модели для этого типа (это опция)

  3. Возможно, чтобы создать связующее для MyModel, переопределите BindProperty и если имущество "PropertyB" свяжите собственность с помощью моей специальной папки.Это возможно?

Есть ли другое решение?

Это было полезно?

Решение

Переопределить BINDPROPERTY и если свойство «PropertyB» связывает свойство с моим пользовательским связующим

Это хорошее решение, хотя вместо проверки «является недвижимостью», вам лучше проверить свои собственные пользовательские атрибуты, которые определяют связующие имущества, как

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

Вы можете увидеть пример переопределения BindProperty здесь.

Другие советы

Я на самом деле люблю ваше третье решение, только я бы сделал это общим решением для всех моделей, поместив его в пользовательское связующее, которое наследует от 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; }
    }
}

@ ответ 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);
}

Прошло 6 лет с тех пор, как этот вопрос был задан, я бы предпочел подвести итог обновления, а не предложить совершенно новое решение.На момент написания MVC 5 существовал уже довольно давно, а ASP.NET Core вышел только что.

Я следовал подходу, рассмотренному в посте Виджая Ананда (кстати, спасибо Виджая): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes.Стоит отметить одну вещь: логика привязки данных размещается в классе настраиваемых атрибутов, который в примере Виджая Ананда представляет собой метод BindProperty класса StringArrayPropertyBindAttribute.

Однако во всех других статьях по этой теме, которые я читал (включая решение @jonathanconway), класс пользовательского атрибута является лишь ступенькой, которая ведет платформу к поиску правильного привязки пользовательской модели для применения;и логика привязки помещается в эту пользовательскую привязку модели, которая обычно является объектом IModelBinder.

Мне первый подход проще.Однако у первого подхода могут быть некоторые недостатки, о которых я еще не знал, поскольку на данный момент я довольно новичок в среде MVC.

Кроме того, я обнаружил, что класс ExtendedModelBinder в примере Виджая Ананда не нужен в MVC 5.Кажется, что класс DefaultModelBinder, поставляемый с MVC 5, достаточно умен, чтобы взаимодействовать с пользовательскими атрибутами привязки модели.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top