Архитектура ASP.NET MVC: ViewModel по составу, наследованию или дублированию?

Я использую ASP.NET MVC 3 и Entity Framework 4.1 Code First.

Допустим, у меня есть User юридическое лицо:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }        
}

При редактировании в моем UserController Я хочу добавить PasswordConfirmation поле и убедитесь, что PasswordConfirmation == Password

1. По составу

Моя первая попытка была:

public class EditUserModel
{
    [Required]
    public User User { get; set; }

    [Compare("User.Password", ErrorMessage = "Passwords don't match.")]
    public string PasswordConfirmation { get; set; }
}

В этом случае проверка на стороне клиента работает но (Правка: проверка на стороне клиента работала случайно). Не работает, а проверка на стороне сервера завершается неудачно со следующим сообщением: Не удалось найти свойство с именем User.Password

Изменить: я думаю, что лучшее решение, в этом случае, будет создавать пользовательские CompareAttribute

Внедрение IValidatableObject

public class EditUserModel : IValidatableObject
{
    [Required]
    public User User { get; set; }
    public string PasswordConfirmation { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if(this.PasswordConfirmation != this.User.Password)
            return new[] { new ValidationResult("Passwords don't match", new[] { "PasswordConfirmation " }) };

        return new ValidationResult[0];
    }
}

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

2. По наследству

public class EditUserModel : User
{
    [Compare("Password", ErrorMessage = "Passwords don't match.")]
    public string PasswordConfirmation  { get; set; }
}

При попытке сохранить напрямую EditUserModel с помощью EF это не работает, я получаю сообщение об ошибке EditUserModel метаданные, поэтому я использую AutoMapper для преобразования из User в EditUserModel и в обратном направлении. Это решение работает, но оно более сложное, потому что мне нужно преобразовать модель в модель представления и обратно.

3. Дублированием

(Предложено Малте Класеном)

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

public class EditUserModel {    
  public string Name { get; set; }    
  public string Email { get; set; }    
  public string Password { get; set; }   
  [Compare("Password", ErrorMessage = "Passwords don't match.")]     
  public string ConfirmPassword { get; set; }        
}

Это решение мне нравится меньше всего из-за дублирования кода (DRY)

Вопросы

Каковы плюсы и минусы наследования, состава и дублирования в этом случае?

Существует ли простой способ проверки как на стороне клиента, так и на стороне сервера, без необходимости преобразования модели в модель представления и обратно?

5 ответов

Решение

Борясь с этим вопросом раньше, я в разных случаях сталкивался со всеми тремя. В целом, большинство мнений, которые я видел, одобряют дублирование в проекте MVC, с ViewModel, созданным специально для каждого представления. Таким образом, соглашение, которое вы бы использовали, является чем-то вроде UserDetailsViewModel а также UserCreateViewModel, Как вы сказали, в этот момент AutoMapper или какой-либо другой инструмент автоматического сопоставления будут использоваться для преобразования ваших доменных объектов в эти плоские ViewModels.

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

Наконец, использование действительно плоской ViewModel обеспечивает более чистую разметку. Когда я использовал композицию, я часто допускал ошибки при создании HTML-элементов с именами, похожими на User.Address.Street, Плоская ViewModel снижает, по крайней мере, мою вероятность сделать это (я знаю, я всегда мог использовать подпрограммы HtmlHelper для создания элементов, но это не всегда выполнимо).

В последние дни мои недавние проекты также в значительной степени требовали отдельных ViewModels. Все они основаны на NHibernate, а использование прокси на объектах NHibernate не позволяет использовать их непосредственно для представлений.

Обновление - вот хорошая статья, на которую я ссылался в прошлом: http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx

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

public class EditUserModel {    
  public string Name { get; set; }    
  public string Email { get; set; }    
  public string Password { get; set; }        
  public string ConfirmPassword { get; set; }        
}

если идентификатор хранится в URL. Если вы хотите избежать ручного копирования между экземплярами User и EditorUserModel, AutoMapper может вам помочь. Таким образом, вы можете легко отделить строку пароля в вашей модели представления от хэша пароля в вашей доменной модели.

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

У вас есть модель пользователя со всей проверкой:

public class UserModel
{
    [Required]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }        
}

Вы сочиняете предыдущую модель с новой моделью

public class EditUserModel
{
    public UserModel User { get; set; }

    [Required]
    public string PasswordConfirmation { get; set; }
}

Хитрость в действии, вы можете получить более одной модели:

[HtttPost]
public ActionResult UpdateInformation(UserModel user, EditUserModel editUserModel) {
    if (ModelState.IsValid) {
         // copy the inner model to the outer model, workaround here:
         editUserModel.User = user
         // do whatever you want with editUserModel, it has all the needed information
    }
}

Таким образом, проверка работает как ожидалось.

Надеюсь это поможет.

Я не слишком часто использую Entity Models, я предпочитаю модели LINQ - SQL, поэтому это может быть неверно:

Почему бы не использовать класс метаданных, который применяется к сущности? При использовании LINQ - SQL назначенные метаданные учитываются как для проверки на стороне клиента, так и на стороне сервера.

Из того, что я понимаю, применение атрибута [MetaDataType] похоже на наследование, только оно работает без реализации нового класса (модели) для изменений базовой сущности.

Кроме того, другой вариант, который вы можете попробовать, - это создание собственного атрибута - я делал это один раз для аналогичной цели. По сути, флаг, который указывает на постоянство члена.

Так что я хотел бы определить сущность следующим образом:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }     

    [DoNotPersist]   
    public string ConfirmPassword {get; set;}

}

Кроме того, я не знаю, что вы делаете для хранения данных, но я подключил переопределение к функциям OnInserting, OnEditing, OnDeleting для моего DataContext, который в основном удалил все члены, имеющие мой пользовательский атрибут.

Мне нравится этот простой метод, потому что мы используем много временных, довольно алгоритмических данных для каждой модели (создание хороших пользовательских интерфейсов для Business Intelligence), которые не сохраняются в базе данных, но используются везде внутри функций модели, контроллеров и т. Д., Поэтому мы используем зависимость Внедрение во все репозитории и контроллеры моделей, поэтому у нас есть все эти дополнительные точки данных для каждой таблицы, с которой можно играть.

Надеюсь, это поможет!

PS: - Состав против наследования - это действительно зависит от целевого пользователя приложения. Если это приложение для интрасети, где безопасность менее важна и среда пользователя / браузера контролируется, тогда просто используйте проверку на стороне клиента, то есть состав.

Я предпочел бы композицию по наследству.

В случае вашего пароля пользователя, похоже, что вы на самом деле храните пароль в таблице Users в виде открытого текста, который ОЧЕНЬ, ОЧЕНЬ ПЛОХО.

Вы должны хранить только соленый хеш, а ваш EditUserModel должно иметь два строковых свойства для пароля и подтверждения пароля, которые НЕ являются полями в вашей таблице.

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