Архитектура ASP.NET MVC: ViewModel по составу, наследованию или дублированию? - PullRequest
54 голосов
/ 05 августа 2011

Сначала я использую ASP.NET MVC 3 и Entity Framework 4.1 Code.

Допустим, у меня есть объект 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.Путем дублирования

(предложено Malte Clasen)

Модель представления будет иметь все свойства модели плюс дополнительные. 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 ]

26 голосов
/ 05 августа 2011

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

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

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

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

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

6 голосов
/ 05 августа 2011

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

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

2 голосов
/ 11 октября 2012

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

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

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

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

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

1 голос
/ 05 августа 2011

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

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

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

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

0 голосов
/ 05 августа 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...