ViewModel Best Practices - PullRequest
       93

ViewModel Best Practices

234 голосов
/ 20 марта 2009

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

В основном у меня были следующие вопросы:

  1. Мне обычно нравится иметь один класс / файл. Имеет ли это смысл с ViewModel , если он создается только для передачи данных из контроллера в представление?
  2. Если ViewModel принадлежит в своем собственном файле, и вы используете структуру каталогов / проектов, чтобы отделить вещи, то где находится файл ViewModel ? В каталоге Controllers ?

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

EDIT: Глядя на пример приложения NerdDinner на CodePlex, кажется, что ViewModels являются частью контроллеров , но мне все еще неудобно, что они не находятся в своих собственных файлах.

Ответы [ 11 ]

208 голосов
/ 29 марта 2009

Я создаю то, что я называю «ViewModel» для каждого представления. Я поместил их в папку с именем ViewModels в моем веб-проекте MVC. Я называю их в честь контроллера и действия (или представления), которые они представляют. Поэтому, если мне нужно передать данные в представление SignUp на контроллере Membership, я создаю класс MembershipSignUpViewModel.cs и помещаю его в папку ViewModels.

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

Это также хорошо работает для составных моделей представления, которые содержат свойства, относящиеся к типу других моделей представления. Например, если у вас есть 5 виджетов на странице индекса в контроллере членства, и вы создали ViewModel для каждого частичного представления - как вы передаете данные из действия Index в партиалы? Вы добавляете свойство в MembershipIndexViewModel типа MyPartialViewModel и при рендеринге частичного вы передаете в Model.MyPartialViewModel.

Делая это таким образом, вы можете настроить частичные свойства ViewModel без необходимости вообще менять представление индекса. Он по-прежнему просто передается в Model.MyPartialViewModel, поэтому меньше шансов, что вам придется пройти через всю цепочку партиалов, чтобы что-то исправить, когда все, что вы делаете, это добавление свойства в частичную ViewModel.

Я также добавлю пространство имен «MyProject.Web.ViewModels» в web.config, чтобы я мог ссылаться на них в любом представлении, даже не добавляя явный оператор импорта для каждого представления. Просто делает его немного чище.

121 голосов
/ 07 октября 2009

Разделять классы по категориям (контроллеры, ViewModels, фильтры и т. Д.) - нонсенс.

Если вы хотите написать код для раздела Home на своем веб-сайте (/), то создайте папку с именем Home и поместите туда HomeController, IndexViewModel, AboutViewModel и т. Д. И все связанные классы, используемые действиями Home.

Если у вас есть общие классы, например ApplicationController, вы можете поместить его в корень вашего проекта.

Зачем разделять связанные вещи (HomeController, IndexViewModel) и хранить вещи, которые вообще не имеют отношения (HomeController, AccountController)?


Я написал сообщение в блоге на эту тему.

21 голосов
/ 26 марта 2009

Я храню свои классы приложений в подпапке «Core» (или отдельной библиотеке классов) и использую те же методы, что и в примере приложения KIGG , но с небольшими изменениями, чтобы сделать мои приложения более СУХИМ .

Я создаю класс BaseViewData в / Core / ViewData /, где храню общие свойства для всего сайта.

После этого я также создаю все мои классы ViewData view в той же папке, которые затем наследуются от BaseViewData и имеют специфические свойства вида.

Затем я создаю ApplicationController, от которого происходят все мои контроллеры. ApplicationController имеет общий метод GetViewData следующим образом:

protected T GetViewData<T>() where T : BaseViewData, new()
    {
        var viewData = new T
        {
           Property1 = "value1",
           Property2 = this.Method() // in the ApplicationController
        };
        return viewData;
    }

Наконец, в своем действии контроллера я делаю следующее, чтобы построить мою модель ViewData

public ActionResult Index(int? id)
    {
        var viewData = this.GetViewData<PageViewData>();
        viewData.Page = this.DataContext.getPage(id); // ApplicationController
        ViewData.Model = viewData;
        return View();
    }

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

14 голосов
/ 20 марта 2009

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

Имеет смысл иметь ваши классы ViewModel в своих собственных файлах, в своем собственном каталоге. В моих проектах у меня есть подпапка папки Models, которая называется ViewModels. Вот где живут мои ViewModels (например, ProductViewModel.cs).

12 голосов
/ 26 марта 2009

Нет подходящего места для хранения ваших моделей. Вы можете хранить их в отдельной сборке, если проект большой и в нем много ViewModels (объектов передачи данных). Также вы можете хранить их в отдельной папке проекта сайта. Например, в Oxite они размещены в проекте Oxite, который также содержит множество различных классов. Контроллеры в Oxite перемещены в отдельный проект, а представления также находятся в отдельном проекте.
В CodeCampServer ViewModels имеют имя * Form и помещаются в проект пользовательского интерфейса в папку Models.
В проекте MvcPress они размещены в проекте Data, который также содержит весь код для работы с базой данных и немного больше (но я не рекомендовал этот подход, это только для примера)
Таким образом, вы можете видеть, что есть много точек зрения. Я обычно храню свои ViewModels (объекты DTO) в проекте сайта. Но когда у меня более 10 моделей, я предпочитаю переносить их на отдельные сборки. Обычно в этом случае я перемещаю контроллеры в отдельную сборку.
Другой вопрос, как легко отобразить все данные из модели в вашу модель представления. Предлагаю взглянуть на библиотеку AutoMapper . Мне это очень нравится, это делает всю грязную работу за меня.
И я также предлагаю посмотреть на проект SharpArchitecture . Он обеспечивает очень хорошую архитектуру для проектов и содержит множество интересных фреймворков и руководств, а также большое сообщество.

6 голосов
/ 08 июля 2010

Вот фрагмент кода из моих лучших практик:

    public class UserController : Controller
    {
        private readonly IUserService userService;
        private readonly IBuilder<User, UserCreateInput> createBuilder;
        private readonly IBuilder<User, UserEditInput> editBuilder;

        public UserController(IUserService userService, IBuilder<User, UserCreateInput> createBuilder, IBuilder<User, UserEditInput> editBuilder)
        {
            this.userService = userService;
            this.editBuilder = editBuilder;
            this.createBuilder = createBuilder;
        }

        public ActionResult Index(int? page)
        {
            return View(userService.GetPage(page ?? 1, 5));
        }

        public ActionResult Create()
        {
            return View(createBuilder.BuildInput(new User()));
        }

        [HttpPost]
        public ActionResult Create(UserCreateInput input)
        {
            if (input.Roles == null) ModelState.AddModelError("roles", "selectati macar un rol");

            if (!ModelState.IsValid)
                return View(createBuilder.RebuildInput(input));

            userService.Create(createBuilder.BuilEntity(input));
            return RedirectToAction("Index");
        }

        public ActionResult Edit(long id)
        {
            return View(editBuilder.BuildInput(userService.GetFull(id)));
        }

        [HttpPost]
        public ActionResult Edit(UserEditInput input)
        {           
            if (!ModelState.IsValid)
                return View(editBuilder.RebuildInput(input));

            userService.Save(editBuilder.BuilEntity(input));
            return RedirectToAction("Index");
        }
}
5 голосов
/ 23 сентября 2009

Мы добавляем все наши ViewModel в папку Models (вся наша бизнес-логика находится в отдельном проекте ServiceLayer)

4 голосов
/ 20 марта 2009

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

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

2 голосов
/ 21 мая 2009

В нашем случае у нас есть Модели вместе с Контроллерами в проекте, отдельном от Представлений.

Как правило, мы пытались переместить и избежать большей части материала ViewData ["..."] в ViewModel, таким образом, мы избегаем приведений и магических строк, что хорошо.

ViewModel также содержит некоторые общие свойства, такие как информация о нумерации страниц для списков или информация заголовка страницы для рисования панировочных сухарей и заголовков. В настоящий момент, по моему мнению, базовый класс содержит слишком много информации, и мы можем разделить ее на три части: самую основную и необходимую информацию для 99% страниц в модели базового представления, а затем модель для списков и модель. для форм, которые содержат конкретные данные для этих сценариев и наследуются от базового.

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

0 голосов
/ 06 июля 2014

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

class ViewModelBase 
{
  public bool HasError {get;set;} 
  public string ErrorMessage {get;set;}
  public List<string> UserRoles{get;set;}
}

В базовом классе контроллера есть метод наподобие PopulateViewModelBase (), этот метод заполнит контекстные данные и пользовательские роли. HasError и ErrorMessage, установите эти свойства, если есть исключение при извлечении данных из service / db. Привязать эти свойства к представлению, чтобы показать ошибку. Пользовательские роли могут использоваться для отображения скрытого раздела в представлении на основе ролей.

Чтобы заполнить модели представления различными действиями get, это можно сделать согласованным с помощью базового контроллера с абстрактным методом FillModel

class BaseController :BaseController 
{
   public PopulateViewModelBase(ViewModelBase model) 
{
   //fill up common data. 
}
abstract ViewModelBase FillModel();
}

В контроллерах

class MyController :Controller 
{

 public ActionResult Index() 
{
   return View(FillModel()); 
}

ViewModelBase FillModel() 
{ 
    ViewModelBase  model=;
    string currentAction = HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString(); 
 try 
{ 
   switch(currentAction) 
{  
   case "Index": 
   model= GetCustomerData(); 
   break;
   // fill model logic for other actions 
}
}
catch(Exception ex) 
{
   model.HasError=true;
   model.ErrorMessage=ex.Message;
}
//fill common properties 
base.PopulateViewModelBase(model);
return model;
}
}
...