Пользовательское связующее устройство для объекта
25 rrejc [2010-02-02 22:25:00]
У меня есть следующее действие контроллера:
[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 не допускается для свойств). Я вижу два решения:
-
Чтобы использовать параметры действия для каждого отдельного свойства вместо всей модели (что я бы не хотел, поскольку модель обладает множеством свойств):
public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
-
Чтобы создать новый тип, скажем
MyCustomType: List<int>
и зарегистрируйте привязку модели для этого типа (это опция) -
Возможно создание связующего для MyModel, переопределить
BindProperty
, и если свойство"PropertyB"
связывает свойство с моим настраиваемым связующим. Возможно ли это?
Есть ли другое решение?
c# asp.net-mvc model-binding custom-model-binder
4 ответа
15 Решение queen3 [2010-02-03 13:43:00]
переопределить BindProperty, и если свойство "PropertyB" связывает свойство с моим пользовательским связующим
Это хорошее решение, хотя вместо проверки "это свойство" вы лучше проверяете свои собственные атрибуты, которые определяют связывание на уровне свойств, например
[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}
Вы можете увидеть пример BindProperty переопределить здесь.
14 Jonathan [2012-10-02 04:03:00]
Мне действительно нравится ваше третье решение, только я бы сделал это универсальным решением для всех моделей ModelBinders, поместив его в настраиваемое связующее, которое наследует от 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; }
}
}
5 Gebb [2013-11-01 16:23:00]
Ответ @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 VincentZHANG [2016-12-22 05:07:00]
Прошло шесть лет с тех пор, как был задан этот вопрос, я предпочел бы взять это пространство, чтобы подвести итоги, вместо того, чтобы предлагать совершенно новое решение. На момент написания статьи MVC 5 уже давно существует, и ASP.NET Core только что вышел.
Я следил за подходом, рассмотренным в сообщении, написанном Виджайей Анандом (кстати, благодаря Виджае): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes. И стоит отметить, что логика привязки данных помещается в пользовательский класс атрибутов, который является методом BindProperty класса StringArrayPropertyBindAttribute в примере Vijaya Anand.
Тем не менее, во всех других статьях по этой теме, которые я прочитал (включая решение @jonathanconway), пользовательский класс атрибутов - это всего лишь шаг, который заставляет фреймворк найти правильное связующее устройство для конкретной модели; и логика привязки помещается в это настраиваемое связующее устройство, которое обычно является объектом IModelBinder.
Первый подход для меня проще. Могут быть некоторые недостатки 1-го подхода, которые я еще не знал, хотя, пожалуй, я довольно новичок в области MVC на данный момент.
Кроме того, я обнаружил, что класс ExtendedModelBinder в примере Vijaya Anand не нужен в MVC 5. Кажется, класс DefaultModelBinder, который поставляется с MVC 5, достаточно умен, чтобы взаимодействовать с атрибутами привязки пользовательской модели.