Отдельная ViewModel для действий чтения, создания и обновления в ASP.NET MVC.

Я использую тот же ViewModel в ASP.NET MVC проектах, но для тысячи записей лучше не извлекать неиспользуемые записи из базы данных. Например, предположим UserViewModel для ситуаций Чтение, Создание и Обновление, как показано ниже:

public class UserViewModel
{
    public int Id { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }

    public string ConfirmPassword { get; set; }

    public bool EmailConfirmed { get; set; }        

    public virtual int AccessFailedCount { get; set; }

    public virtual bool LockoutEnabled { get; set; }

    public virtual DateTime? LockoutEndDateUtc { get; set; }

    public virtual string PasswordHash { get; set; }

    public virtual string PhoneNumber { get; set; }

    public virtual bool PhoneNumberConfirmed { get; set; }

    public virtual string SecurityStamp { get; set; }

    public virtual bool TwoFactorEnabled { get; set; }
}

Чтение. При отображении сведений о записи мне нужно получить все свойства, кроме пароля (я знаю, что могу получить данные и без ViewModel, но иногда мне нужно объединить несколько представлений в ViewModel, и это тоже аналогичная ситуация).

Создать. При создании новой записи мне не нужно использовать идентификатор, EmailConfirmed, AccessFailedCount и т. д. Колонны.

Обновление. При обновлении записи мне также не нужно использовать некоторые свойства.

В этой сцене, как лучше всего использовать ViewModel? Создать отдельный ViewModel, то есть ReadUserViewModel, CreateUserViewModel и UpdateUserViewModel, или использовать один и тот же ViewModel для одной и той же группы данных? Любая помощь будет оценена по достоинству.


person Jack    schedule 27.08.2016    source источник
comment
Целью модели представления является представление данных в представлении, чтобы отдельные модели представления имеет смысл. И вы всегда можете иметь базовую модель представления, содержащую общие для всех свойства, чтобы избежать дублирования.   -  person    schedule 27.08.2016
comment
@StephenMuecke Я уже читал полезный пост, который вы предложили. В этом случае вы имеете в виду, что нет необходимости использовать отдельную модель представления, и лучше использовать одну и ту же модель представления для каждого действия, то есть чтения, создания, обновления? Если да, то должны ли мы также использовать ViewModel при извлечении данных вместо того, чтобы извлекать их напрямую из DbContext? В контроллере я извлекаю его из базы данных и заполняю ViewModel, прежде чем вернуться в представление. Это хороший подход?   -  person Jack    schedule 27.08.2016
comment
Лично я всегда использую модель представления, специфичную для представления (в вашем случае class UserBaseVM содержит свойства, общие для всех представлений (например, Email), а затем создаю модель представления для каждого представления, наследуемого от UserBaseVM, например, class UserCreateVM : UserBaseVM будет содержать свойства, относящиеся к созданию нового пользователя (например, ConfirmPassword)   -  person    schedule 27.08.2016
comment
Да вроде лучше и почище. Любая идея относительно второй части моего вопроса (... должны ли мы также использовать ViewModel при извлечении данных вместо этого...)?   -  person Jack    schedule 27.08.2016
comment
Извините, не уверен, что вы имеете в виду. Обычно вы должны использовать что-то вроде var model = db.Users.Select(x => new UserVM() { Email = x.Email, ..... }); для вызова базы данных и проецирования данных в модель представления, прежде чем возвращать их в представление (или использовать такие инструменты, как автосопоставитель   -  person    schedule 27.08.2016
comment
Да, я именно это и имел ввиду и возвращаю данные в View так. Но также можно отправлять данные напрямую (без заполнения и использования ВМ) как db.Users.ToList(); но я не уверен, хорошая это идея или нет. Это?   -  person Jack    schedule 27.08.2016
comment
Давайте продолжим обсуждение в чате.   -  person    schedule 27.08.2016


Ответы (2)


Мой ответ довольно поздний, но я надеюсь, что он все еще помогает кому-то там.

Я согласен с тем, что указала Иветт Коломб, но я думаю, что в некоторых случаях лучше иметь только 1 представление и модель представления для действий создания и обновления. На самом деле я даже написал об этом сообщение в блоге, вы можете найти его здесь, если вам интересно: https://blog.sandervanlooveren.be/posts/mvc-best-practices-for-create-update/.

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

Ваша baseViewModel может выглядеть примерно так (я сделал это максимально многоразовым):

public abstract class BaseFormViewModel<T>
{
   public bool IsUpdate => !GetId().Equals(default(T));

   protected abstract T GetId();

   /// <summary>
   /// Gets the action name based on the lambda supplied
   /// </summary>
   /// <typeparam name="TController"></typeparam>
   /// <param name="action"></param>
   /// <returns></returns>
   protected string GetActionName<TController>(Expression<Func<TController, ActionResult>> action) where TController : Controller
   {
       return ((MethodCallExpression)action.Body).Method.Name;
   }
}

И, на ваш взгляд, у вас может быть что-то вроде этого:

@using (Html.BeginForm(Model.ActionName, "Person", FormMethod.Post, new  { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    if (Model.IsUpdate)
    {
        @Html.HiddenFor(m => m.Person.Id)
    }
    <div class="form-group">
        @Html.LabelFor(m => m.Person.Firstname, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Person.Firstname, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Person.Firstname)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Person.Lastname, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Person.Lastname, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Person.Lastname)
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Save" />
        </div>
    </div>
}

См. мой блог для получения дополнительной информации о том, как использовать эту ViewModel в полной мере.

person Sander Van Looveren    schedule 15.02.2017

Я бы использовал отдельные модели просмотра для деталей (просмотр только для чтения), создания и редактирования.

Самый простой способ повторно использовать код/представления, которые повторяются в проекте, — использовать частичные представления. Поэтому, если на странице есть повторяющийся аспект, во что бы то ни стало используйте частичное представление и включите его в несколько разных представлений.

Что касается создания/обновления или просмотра сведений, IMO проще использовать отдельный тип представления для этих отдельных представлений. Понятно, что код должен быть модульным и не иметь повторяющегося кода, но наступает момент, когда сложность в попытке достичь максимальной модульности может перевесить повторение некоторых полей в форме.

Представление для «Создания» будет представлять пустую форму, поэтому вы не получаете никаких значений из базы данных или повторно используете данные как таковые, а добавляете данные в базу данных. Таким образом, повторное использование одного и того же представления для этого ограничено.

  1. Представление создания передаст детали в форму с полями для создания нового объекта.

  2. Представление редактирования передаст идентификатор этого объекта для заполнения полей формы, и это вместе с идентификатором будет передано контроллеру.

  3. Представление сведений будет передавать идентификатор этого объекта для заполнения полей таблицы (возможно) и не требует сбора формы.

Таким образом, хотя 1 и 2, две формы, имеют общие поля, существует проблема управления тем, где идентификатор должен быть передан в эту форму, и как и когда это определяется в потоке вашей программы.

Несмотря на то, что между 2 и 3 есть общие данные, представление представления совершенно другое, форма, скажем, таблица, и любые поля формы должны быть сделаны только для чтения, чтобы форма редактирования имитировала отображение представления сведений.

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

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

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

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

person Community    schedule 27.08.2016
comment
Большое спасибо за ваш ответ. Как сказал Стефан, также неплохо наследовать модель, содержащую те же свойства. Я буду иметь в виду все ваши объяснения. Проголосовал+ - person Jack; 27.08.2016
comment
С другой стороны, если у вас есть опыт работы с ASP.NET Identity, не могли бы вы взглянуть на эта серьезная проблема? - person Jack; 27.08.2016