Сопоставление атрибутов проверки с сущности домена в DTO
У меня есть стандартная сущность уровня домена:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set;}
}
который имеет какие-то атрибуты проверки:
public class Product
{
public int Id { get; set; }
[NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
public string Name { get; set; }
[NotLessThan0]
public decimal Price { get; set;}
}
Как видите, я составил эти атрибуты полностью. Какая структура проверки (NHibernate Validator, DataAnnotations, ValidationApplicationBlock, Castle Validator и т. Д.) Используется здесь не имеет значения.
В моем клиентском слое у меня также есть стандартная настройка, в которой я не использую сами сущности Домена, а вместо этого сопоставляю их с ViewModels (он же DTO), который использует мой слой вида:
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set;}
}
Давайте теперь скажем, что я хочу, чтобы мой клиент / представление могли выполнять некоторые базовые проверки на уровне свойств.
Единственный способ сделать это - повторить определения валидации в объекте ViewModel:
public class ProductViewModel
{
public int Id { get; set; }
// validation attributes copied from Domain entity
[NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
public string Name { get; set; }
// validation attributes copied from Domain entity
[NotLessThan0]
public decimal Price { get; set;}
}
Это явно неудовлетворительно, так как теперь я повторил бизнес-логику (проверка на уровне свойств) в слое ViewModel (DTO).
Так что можно сделать?
Предполагая, что я использую инструмент автоматизации, такой как AutoMapper, для сопоставления моих сущностей Домена с моими DTO ViewModel, не было бы также здорово каким-то образом перенести логику проверки для сопоставленных свойств в ViewModel?
Вопросы:
1) Это хорошая идея?
2) Если так, можно ли это сделать? Если нет, каковы альтернативы, если таковые имеются?
Заранее благодарю за любой вклад!
7 ответов
Если вы используете что-то, поддерживающее DataAnnotations, вы должны иметь возможность использовать класс метаданных для хранения ваших атрибутов проверки:
public class ProductMetadata
{
[NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
public string Name { get; set; }
[NotLessThan0]
public decimal Price { get; set;}
}
и добавьте его в атрибут MetadataTypeAttribute для объекта домена и DTO:
[MetadataType(typeof(ProductMetadata))]
public class Product
а также
[MetadataType(typeof(ProductMetadata))]
public class ProductViewModel
Это не будет работать из коробки со всеми валидаторами - вам может понадобиться расширить выбранную платформу валидации для реализации аналогичного подхода.
Цель проверки состоит в том, чтобы убедиться, что данные, поступающие в ваше приложение, соответствуют определенным критериям, с учетом этого единственное место, в котором имеет смысл проверять ограничения свойств, подобные тем, которые вы определили здесь, находится в точке, где вы принимаете данные от ненадежный источник (то есть пользователь).
Вы можете использовать что-то вроде "денежного шаблона", чтобы повысить валидацию в вашей системе типов доменов и использовать эти типы доменов в модели представления, где это имеет смысл. Если у вас более сложная проверка (т.е. вы выражаете бизнес-правила, требующие большего знания, чем те, которые выражены в одном свойстве), они относятся к методам в доменной модели, которые применяют изменения.
Короче говоря, добавьте атрибуты проверки данных в ваши модели представлений и не включайте их в ваши доменные модели.
Почему бы не использовать интерфейс для выражения своих намерений? Например:
public interface IProductValidationAttributes {
[NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
string Name { get; set; }
[NotLessThan0]
decimal Price { get; set;}
}
Оказывается, что AutoMapper может сделать это для нас автоматически, что является лучшим сценарием.
Пользователи AutoMapper: перенести атрибуты проверки в модель представления?
http://groups.google.com/group/automapper-users/browse_thread/thread/efa1d551e498311c/db4e7f6c93a77302?lnk=gst&q=validation
Я не удосужился опробовать предлагаемые решения там, но собираюсь в ближайшее время.
Я уже давно обдумывал это. Я полностью понимаю ответ Брэда. Однако давайте предположим, что я хочу использовать другую платформу проверки, которая подходит для аннотирования как доменных сущностей, так и моделей представления.
Единственное решение, которое я могу придумать на бумаге, который все еще работает с атрибутами, - это создать еще один атрибут, который "указывает" на свойство объекта домена, которое вы отражаете в своей модели представления. Вот пример:
// In UI as a view model.
public class UserRegistration {
[ValidationDependency<Person>(x => x.FirstName)]
public string FirstName { get; set; }
[ValidationDependency<Person>(x => x.LastName)]
public string LastName { get; set; }
[ValidationDependency<Membership>(x => x.Username)]
public string Username { get; set; }
[ValidationDependency<Membership>(x => x.Password)]
public string Password { get; set; }
}
Платформа, подобная xVal, может быть расширена для обработки этого нового атрибута и запуска атрибутов проверки для свойства класса зависимостей, но со значением свойства вашей модели представления. У меня просто не было времени конкретизировать это.
Какие-нибудь мысли?
Если вы используете рукописные доменные объекты, почему бы не поместить свои доменные объекты в их собственную сборку и использовать эту же сборку как на клиенте, так и на сервере. Вы можете повторно использовать те же проверки.
Прежде всего, нет понятия "стандартная" сущность домена. Для меня стандартная доменная сущность не имеет сеттеров для начала. Если вы воспользуетесь этим подходом, у вас может быть более значимый API, который фактически передает что-то о вашем домене. Таким образом, у вас может быть служба приложений, которая обрабатывает ваш DTO, создает команды, которые вы можете выполнять непосредственно против ваших доменных объектов, таких как SetContactInfo, ChangePrice и т. Д. Каждый из них может вызывать ValidationException, который, в свою очередь, вы можете собирать в своем сервисе и представлять в Пользователь. Вы все еще можете оставить свои атрибуты в свойствах dto для простой проверки уровня атрибута / свойства. Для чего-либо еще, обратитесь к вашему домену. И даже если это приложение CRUD, я бы не стал подвергать свои доменные сущности уровню представления.
Отказ от ответственности: я знаю, что это старое обсуждение, но оно было ближе всего к тому, что я искал: сохранение СУХОЙ путем повторного использования атрибутов проверки. Надеюсь, это не так уж и далеко от исходного вопроса.
В моей ситуации я хотел сделать сообщения об ошибках доступными в представлениях.NET и других моделях представления. Наши сущности практически не имеют бизнес-логики и в основном предназначены для хранения данных. Вместо этого у нас есть большая модель просмотра с проверкой и бизнес-логикой, где я хочу повторно использовать сообщения об ошибках. Поскольку пользователи получают только сообщения об ошибках, я считаю, что это актуально, так как это важно легко поддерживать.
Мне не удалось найти реальный способ удалить логику из частичных ViewModels, но я нашел способ передать одно и то же сообщение ErrorMessage, чтобы его можно было поддерживать из одной точки. Поскольку сообщения ErrorMessages привязаны к представлению, оно также может быть частью ViewModel. Константы считаются статическими членами, поэтому, определяя сообщения об ошибках как константы публичной строки, мы можем получить к ним доступ вне класса.
public class LargeViewModel
{
public const string TopicIdsErrorMessage = "My error message";
[Required(ErrorMessage = TopicIdsErrorMessage)]
[MinimumCount(1, ErrorMessage = TopicIdsErrorMessage)]
[WithValidIndex(ErrorMessage = TopicIdsErrorMessage)]
public List<int> TopicIds { get; set; }
}
public class PartialViewModel
{
[Required(ErrorMessage = LargeViewModel.TopicIdsErrorMessage]
public List<int> TopicIds { get; set; }
}
В нашем проекте мы использовали собственный html для раскрывающихся списков, поэтому мы не могли использовать помощник @Html.EditorFor в razor, поэтому мы не могли использовать ненавязчивую проверку. Теперь, когда сообщение об ошибке доступно, мы можем применить необходимые атрибуты:
@(Html.Kendo().DropDownList()
.Name("TopicIds")
.HtmlAttributes(new {
@class = "form-control",
data_val = "true",
data_val_required = SupervisionViewModel.TopicIdsErrorMessage
})
)
Предупреждение: вам может потребоваться перекомпилировать все связанные проекты, которые полагаются на значения const...