Подтвердить что ты не робот

Атрибуты проверки сопоставления от объекта домена до DTO

У меня есть стандартный объект Domain Layer:

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 и т.д.) Не используется здесь.

На моем клиентском уровне у меня также есть стандартная настройка, в которой я сам не использую сущности Domain, а вместо этого сопоставляю их с ViewModels (aka 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, для сопоставления объектов домена с моими объектами ViewModel, не было бы также здорово каким-то образом передать логику проверки для отображаемых свойств в ViewModel?

Вопросы:

1) Это хорошая идея?

2) Если да, можно ли это сделать? Если нет, каковы альтернативы, если они есть?

Заранее благодарю за любой ввод!

4b9b3361

Ответ 1

Если вы используете что-то, поддерживающее 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

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

Ответ 2

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

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

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

Ответ 3

Оказывается, AutoMapper может сделать это для нас автоматически, что является наилучшим вариантом.

Пользователи AutoMapper: перенести атрибуты проверки на viewmodel?
http://groups.google.com/group/automapper-users/browse_thread/thread/efa1d551e498311c/db4e7f6c93a77302?lnk=gst&q=validation#db4e7f6c93a77302

Мне не удалось опробовать предлагаемые решения там, но намереваются в ближайшее время.

Ответ 4

Почему бы не использовать интерфейс, чтобы выразить свои намерения? Например:

public interface IProductValidationAttributes {
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    string Name { get; set; }

    [NotLessThan0]
    decimal Price { get; set;}
}

Ответ 5

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

Ответ 6

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

Единственное решение, которое я могу найти на бумаге, которая все еще работает с атрибутами, - это создать еще один атрибут, который "указывает" на свойство сущности домена, которое вы зеркалируете в своей модели представления. Вот пример:

// 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, можно было бы расширить, чтобы обработать этот новый атрибут и запустить атрибуты проверки в свойстве класса зависимостей, но с вашим значением свойства модели представления. У меня просто не было времени, чтобы плоть это больше.

Любые мысли?

Ответ 7

Прежде всего, нет понятия "стандартного" объекта домена. Для меня стандартная сущность домена не имеет никаких настроек для начала. Если вы примете такой подход, вы можете иметь более значимый api, который фактически передает что-то о вашем домене. Таким образом, у вас может быть служба приложений, которая обрабатывает ваш DTO, создает команды, которые вы можете выполнять непосредственно против ваших объектов домена, таких как SetContactInfo, ChangePrice и т.д. Каждый из них может поднять ValidationException, которые, в свою очередь, вы можете собирать в своей службе и представлять Пользователь. Вы можете оставить свои атрибуты в свойствах dto для простой проверки уровня атрибута/свойства. Для чего-либо еще обратитесь к своему домену. И даже если это приложение CRUD, я бы не стал подвергать объекты моего домена представлению.

Ответ 8

Отказ от ответственности: я знаю, что это старая дискуссия, но она была ближе всего к тому, что я искал: сохранение СУХОГО путем повторного использования атрибутов проверки. Надеюсь, это не так уж далеко от первоначального вопроса.

В моей ситуации я хотел сделать сообщения об ошибках доступными в представлениях .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 в бритве, поэтому мы не могли использовать ненавязчивую проверку. Имея доступное сообщение об ошибке, мы теперь можем применить необходимые атрибуты:

    @(Html.Kendo().DropDownList()
        .Name("TopicIds")
        .HtmlAttributes(new {
            @class = "form-control",
            data_val = "true",
            data_val_required = SupervisionViewModel.TopicIdsErrorMessage
        })
    )

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