Как добавить метаданные в динамически создаваемую MVC3 ViewModel?
Одной из ключевых особенностей проекта, над которым я работаю, является возможность для пользователя настраивать формы (как в "формах" для заполнения) на основе пула ранее существующих типов полей (например, хорошо известных типов). "имя пользователя", "дата рождения" и т. д., но также "универсальные типы", такие как "string", "DateTime" и т. д.).
Раньше у нас был статический ViewModel, который прекрасно работал для "хорошо известных" типов и выглядел так:
public class UserInputModel
{
[StringLength(200)]
public string Name { get; set; }
[Required(ErrorMessageResourceName = "BirthDateEmptyError", ErrorMessageResourceType = typeof(Resources.ErrorMessages))]
public DateTime BirthDate { get; set; }
//Here comes a lot of other properties
}
Все известные свойства были перечислены, и мы показывали или скрывали их с учетом контекста.
Но последнее требование пришло и изменило все это. Теперь пользователь сможет добавлять столько полей универсального типа, сколько он хочет. Для этого мы решили сделать эту InputModel полностью динамичной. Теперь это выглядит так:
public class UserInputModel
{
// Each ModelProperty has an "Id" and a "Value" property
public ICollection<ModelProperty> Properties { get; set; }
}
Это работает как шарм. Бритвенное представление должно только перебирать коллекцию, создавать соответствующие элементы управления для каждого свойства коллекции более чем стандартным способом:
@Html.TextBoxFor(m => m.Properties[index].Value);
... и мы получаем данные обратно в виде заполненной формы.
=> Это работает нормально, но у нас нет никакой проверки на стороне клиента. Для этого нам понадобятся метаданные... которых у нас больше нет с помощью аннотаций, поскольку мы динамически создаем модель.
Чтобы предоставить эти метаданные, я создал CustomModelMetadataProvider
что наследует от DataAnnotationsModelMetadataProvider
и зарегистрировал его как новый ModelMetadataProvider
в Global.asax. CreateMetadata()
Функция вызывается при создании ViewModel, и это для каждого из свойств моей ViewModel... Sofar так хорошо.
Где начинается проблема: чтобы добавить некоторые метаданные к текущему свойству, мне сначала нужно определить, какое свойство я сейчас просматриваю ("Имя" имеет максимальную длину 200, а "дата рождения" - нет, поэтому я не могу назначить максимальная длина каждого свойства по умолчанию). И почему-то мне так и не удалось сделать это, так как все свойства имеют одинаковое имя Value
и тот же тип контейнера ModelProperty
,
Я попытался получить доступ к контейнеру свойства через отражение, но поскольку целью ModelAccessor является сама ViewModel (из-за лямбда-выражения m => m.Properties
), следующая конструкция дает мне ViewModel в целом, а не только ModelProperty:
var container = modelAccessor.Target.GetType().GetField("container");
var containerObject = (UserInputModel)container.GetValue(modelAccessor.Target);
Я переворачиваю это снова и снова, но не могу найти способ определить, какой ModelProperty у меня в руках. Есть ли способ сделать это?
Обновление: после того, как какое-то время мы перебирали это во всех возможных направлениях, мы наконец пошли другим путем. Мы в основном используем ненавязчивый javascript, чтобы использовать возможности валидации MVC, не касаясь атрибутов и метаданных. Короче говоря, мы добавляем атрибуты HTML, такие как value-data="true"
(и все другие обязательные атрибуты) @Html.TextBoxFor()
заявления. Это прекрасно работает для всех атомарных проверок (требуется, длина строки и т. Д.).
2 ответа
Тим, ты можешь использовать то, что кажется проверкой на стороне клиента через Ajax с атрибутом Remote в твоих свойствах.
По сути, вам нужно настроить контроллер проверки, а затем записать в него несколько смартов. Но, по крайней мере, вы сможете написать несколько вспомогательных методов и хранить все это в одном месте. У вас будет ряд валидаторов, основанных на метаданных, которые вы представляете конечным пользователям, и каждый метод валидатора будет работать для определенного типа с хорошим повторным использованием.
Единственный недостаток этого подхода заключается в том, что вам нужно будет написать метод проверки для каждого типа и условия, которые вы хотите поддерживать. Похоже, вам все равно придется идти по этой дороге.
Надеюсь это поможет.
Посмотрите, поможет ли вам эта статья: Техника переноса метаданных для просмотра моделей с помощью AutoMapper.
Также используйте это для идей (поставщик метаданных пользовательской модели): изменение атрибута MetadataType viewmodel во время выполнения
На мой взгляд, для вас, пожалуй, лучший вариант проверки, но вам, безусловно, нужно выбрать лучший вариант среди вышеперечисленных.
Обновить
Попробуй использовать ModelMetadata
и переопределить ModelMetadataProvider
: Погрузитесь в MVC: ModelMetadata и ModelMetadataProvider. Таким образом, вы полностью настраиваете метаданные модели (это заменяет аннотации данных) и вы полностью контролируете происходящее, а не полагаетесь на ASP.NET MVC.
Еще одно хорошее место, чтобы взглянуть на это - создание собственного ModelMetadataProvider для обработки пользовательских атрибутов.
Надеюсь, что все это поможет вам.