Пользовательская модель переплета для свойства - PullRequest
31 голосов
/ 02 февраля 2010

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

[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", привязайте свойство с моим настраиваемым связывателем. Возможно ли это?

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

Ответы [ 4 ]

19 голосов
/ 03 февраля 2010

переопределить BindProperty и, если свойство "PropertyB" связать собственность с моим таможенным переплетом

Это хорошее решение, хотя вместо проверки "is PropertyB" вам лучше проверить свои собственные атрибуты, которые определяют привязки уровня свойств, например

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

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

17 голосов
/ 02 октября 2012

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

My 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; }
    }
}
5 голосов
/ 01 ноября 2013

@ ответ 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);
}
1 голос
/ 22 декабря 2016

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...