Повторное использование атрибутов проверки в пользовательских ViewModels

StackOverflow https://stackoverflow.com/questions/2059932

Вопрос

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

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

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

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

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

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

Спасибо,

Адриан

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

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

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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);
     }
}

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

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

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

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