Повторное использование атрибутов проверки в пользовательских ViewModels - PullRequest
11 голосов
/ 13 января 2010

Когда я начал использовать xVal для проверки на стороне клиента, я реализовывал только методы действий, которые использовали объекты модели предметной области в качестве модели представления или встроенные экземпляры этих объектов в модели представления.

Этот подход прекрасно работает большую часть времени, но бывают случаи, когда представление должно отображать и публиковать только подмножество свойств модели (например, когда пользователь хочет обновить свой пароль, но не остальную часть его данные профиля).

Один (некрасивый) обходной путь - иметь скрытое поле ввода в форме для каждого свойства, которое иначе не присутствует в форме.

Очевидно, что в этом случае рекомендуется создать собственную модель представления, которая содержит только свойства, относящиеся к представлению, и заполнить модель представления с помощью Automapper . Это намного чище, так как я передаю только данные, относящиеся к представлению, но это далеко не идеально, поскольку мне приходится повторять те же атрибуты проверки, которые уже присутствуют в объекте модели домена.

В идеале я хотел бы указать объект модели домена в качестве мета-класса с помощью атрибута MetaData (это также часто называют «классом собеседника»), но это не работает, поскольку xVal создает, когда класс метаданных имеет свойства, которых нет в модели представления.

Есть ли элегантный способ обойти это? Я думал о взломе исходного кода xVal, но, возможно, есть еще один способ, который я упустил из виду.

Спасибо

Адриан

Редактировать: С появлением ASP.NET MVC 2 эта проблема больше не только связана с атрибутами проверки, но также относится к атрибутам редактора и отображения.

Ответы [ 5 ]

7 голосов
/ 01 ноября 2010

Это наиболее существенная причина, по которой ваши входные экраны не должны быть тесно связаны с вашей моделью. Этот вопрос фактически появляется здесь на тэге MVC около 3-4 раз в месяц. Я бы обманул, если бы мог найти предыдущий вопрос, и некоторые обсуждения комментариев здесь интересны. ;)

Проблема, с которой вы столкнулись, заключается в том, что вы пытаетесь объединить два разных контекста проверки модели в единую модель, которая дает сбой при большом количестве сценариев. Лучший пример - регистрация нового пользователя, а затем администратор редактирует поле пользователя позже. Вам необходимо подтвердить пароль на объекте пользователя во время регистрации, но вы не будете показывать поле пароля администратору, редактирующему данные пользователя.

Выбор для их обхода все неоптимальный. Я работал над этой проблемой для трех проектов, и реализация следующих решений никогда не была чистой и обычно разочаровывающей. Я постараюсь быть практичным и забуду все обсуждения DDD / db / model / hotnessofthemonth, которые есть у всех остальных.

1) Модели с несколькими представлениями Наличие почти одинаковых view-моделей нарушает принцип DRY, но я чувствую, что затраты на этот подход действительно низкие. Обычно нарушение режима «СУХОЙ» увеличивает расходы на техническое обслуживание, но, по-моему, затраты на это самые низкие и не слишком велики. Говоря гипотетически, вы не меняете то, как максимальное количество символов в поле LastName может быть очень часто.

2) Динамические метаданные В MVC 2 есть хуки для предоставления собственных метаданных для модели. При таком подходе вы можете использовать все, что используете для предоставления метаданных, исключая определенные поля на основе текущего HTTPRequest и, следовательно, Action и Controller. Я использовал этот метод для построения системы разрешений, управляемой базой данных, которая идет в БД и сообщает подклассу DataAnnotationsMetadataProvider для исключения значений на основе свойств, хранящихся в базе данных.

Этот метод отлично работает, но единственная проблема - проверка с помощью UpdateModel(). Чтобы решить эту проблему, мы создали метод SmartUpdateModel(), который также отправляется в базу данных и автоматически генерирует массив exclude string [], чтобы любые недопустимые поля не проверялись. Мы, конечно, кешировали это по соображениям производительности, так что это неплохо.

Просто хочу повторить, что мы использовали [ValidationAttributes] в наших моделях, а затем заменили их новыми правилами во время выполнения. Конечным результатом было то, что поле [Required] User.LastName не было проверено, если у пользователя не было разрешения на доступ к нему.

3) Сумасшедший интерфейс Dynamic Proxy Thing Последний метод, который я пытался использовать, - это использовать интерфейсы для ViewModels. В результате я получил объект User, унаследованный от таких интерфейсов, как IAdminEdit и IUserRegistration. IAdminEdit и IUserRegistration будут содержать атрибуты DataAnnotation, которые выполняют всю контекстно-зависимую проверку, например, свойство Password с интерфейсами.

Это потребовало некоторых хакерских действий и было более академическим упражнением, чем что-либо еще. Проблема с 2 и 3 заключается в том, что UpdateModel и поставщик DataAnnotationsAttribute необходимо настроить для ознакомления с этим методом.

Моим самым большим камнем преткновения было то, что я никогда не хотел отправлять весь пользовательский объект в представление, поэтому я использовал динамические прокси для создания экземпляров времени выполнения IAdminEdit

Теперь я понимаю, что это очень специфический вопрос xVal, но все пути к динамической проверке, подобные этой, ведут к настройке внутренних поставщиков метаданных MVC. Поскольку все содержимое метаданных является новым, на данном этапе нет ничего такого простого или простого. Работа, которую вам нужно сделать, чтобы настроить поведение валидации MVC, не сложна, но требует некоторых глубоких знаний о том, как работают все внутренние компоненты.

4 голосов
/ 17 января 2010

Мы переместили наши атрибуты проверки в слой ViewModel. В нашем случае это обеспечило более четкое разделение проблем в любом случае, так как тогда мы смогли спроектировать нашу модель предметной области таким образом, чтобы она не могла попасть в недопустимое состояние. Например, Дата может потребоваться для объекта BillingTransaction. Так что мы не хотим делать это Nullable. Но в нашей ViewModel нам может понадобиться предоставить Nullable, чтобы мы могли уловить ситуацию, когда пользователь не ввел значение.

В других случаях у вас может быть валидация, специфичная для каждой страницы / формы, и вы захотите валидировать на основе команды, которую пытается выполнить пользователь, вместо того, чтобы задавать кучу вещей и спрашивать модель домена, «Вы действительны для попытки сделать XYZ», где при выполнении «ABC» эти значения действительны.

3 голосов
/ 23 января 2010

Если ViewModels гипотетически навязывают вам, тогда я рекомендую, чтобы они только выполняли независимые от домена требования. Это включает в себя такие вещи, как «имя пользователя обязательно» и «электронная почта отформатирована правильно».

Если вы дублируете валидацию из моделей домена в моделях представления, то вы тесно связали домен с пользовательским интерфейсом. При изменении проверки домена («может применяться только 2 купона в неделю» становится «может применяться только 1 купон в неделю»), пользовательский интерфейс должен быть обновлен. Вообще говоря, это было бы ужасно и пагубно для ловкости.

Если вы перенесете валидацию из моделей доменов в пользовательский интерфейс, вы по сути распотрошили свой домен и возложили ответственность за валидацию на пользовательский интерфейс. Второй пользовательский интерфейс должен будет дублировать всю проверку, и вы соедините два отдельных пользовательских интерфейса вместе. Теперь, если клиент хочет специальный интерфейс для администрирования инвентаря со своего iPhone, проект iPhone должен повторить все проверки, которые также можно найти в пользовательском интерфейсе веб-сайта. Это было бы еще ужаснее, чем дублирование проверки, описанное выше.

Если вы не можете предсказать будущее и исключить эти возможности, проверьте только независимые от домена требования.

2 голосов
/ 18 февраля 2010

Я не знаю, как это будет играть для проверки на стороне клиента, но если частичная проверка является вашей проблемой, вы можете изменить DataAnnotationsValidationRunner, обсуждаемый здесь, чтобы включить в IEnumerable<string> список имен свойств следующим образом: 1003 *

public static class DataAnnotationsValidationRunner
{
     public static IEnumerable<ErrorInfo> GetErrors(object instance, IEnumerable<string> fieldsToValidate)
     {
           return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>().Where(p => fieldsToValidate.Contains(p.Name))
                  from attribute in prop.Attributes.OfType<ValidationAttribute>()
                  where !attribute.IsValid(prop.GetValue(instance))
                  select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
     }
}
0 голосов
/ 23 января 2010

Я рискну понизить число голосов и констатирую, что ViewModels (в ASP.NET MVC) не принесет никакой пользы, особенно если учесть затраты на их создание и обслуживание.Если идея состоит в том, чтобы отделить от домена, это неоправданно.Пользовательский интерфейс, отделенный от домена, не является пользовательским интерфейсом для этого домена.Интерфейс пользователя должен зависеть от домена, поэтому вы либо захотите, чтобы ваши представления / действия были связаны с моделью домена, либо ваша логика управления ViewModel была связана с моделью домена.Аргумент архитектуры, таким образом, является спорным.

Если идея состоит в том, чтобы запретить пользователям взламывать вредоносные HTTP POST, использующие преимущества привязки модели ASP.NET MVC к изменяемым полям, им нельзя разрешать изменять, тогда A)домен должен обеспечивать выполнение этого требования, и B) действия должны предоставлять белые списки обновляемых свойств для подшивки модели.

Если ваш домен не демонстрирует нечто сумасшедшее, например, живой граф объектов в памяти вместо сущностикопии, ViewModels впустую усилия.Поэтому, чтобы ответить на ваш вопрос, сохраните валидацию домена в модели домена.

...