Сопоставление атрибутов проверки с сущности домена в 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...

Другие вопросы по тегам