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

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

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

Скажем, у меня есть объект 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)

Вопросы

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

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

4b9b3361

Ответ 1

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

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

Наконец, использование действительно плоской 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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

Ответ 4

Я слишком сильно не использую модели 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: - Состав против Наследования - это действительно зависит от целевого пользователя приложения. Если это приложение для интрасети, где безопасность меньше проблемы, а среда пользователя/браузера контролируется, просто используйте проверку на стороне клиента, то есть: состав.

Ответ 5

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

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

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