Лучшие практики ViewModel
-
21-08-2019 - |
Вопрос
От этот вопрос, похоже , имеет смысл заставить контроллер создать Модель просмотра это более точно отражает модель, которую пытается отобразить представление, но мне любопытны некоторые соглашения (я новичок в шаблоне MVC, если это еще не было очевидно).
В принципе, у меня были следующие вопросы:
- Обычно мне нравится иметь один класс / файл.Имеет ли это смысл с Модель просмотра если он создается только для передачи данных с контроллера в представление?
- Если a Модель просмотра принадлежит своему собственному файлу, и вы используете структуру каталога / проекта, чтобы разделять вещи, где Модель просмотра файл принадлежит?В Контроллеры справочник?
По сути, на данный момент это все.Возможно, у меня возникнет еще несколько вопросов, но это беспокоит меня в течение последнего часа или около того, и, похоже, я могу найти последовательные рекомендации в другом месте.
Редактировать: Смотрю на образец Приложение для любителей закусок в CodePlex это выглядит так, как будто ViewModels являются частью Контроллеры, но мне все равно неудобно, что их нет в их собственных файлах.
Решение
Я создаю то, что я называю "ViewModel" для каждого представления.Я поместил их в папку под названием ViewModels в моем веб-проекте MVC.Я называю их в честь контроллера и действия (или представления), которые они представляют.Поэтому, если мне нужно передать данные в представление регистрации на контроллере членства, я создаю класс MembershipSignUpViewModel.cs и помещаю его в папку ViewModels.
Затем я добавляю необходимые свойства и методы для облегчения передачи данных из контроллера в представление.Я использую Automapper для перехода от моей ViewModel к модели предметной области и обратно, если это необходимо.
Это также хорошо работает для составных ViewModels, которые содержат свойства, относящиеся к типу других ViewModels.Например, если у вас есть 5 виджетов на странице индекса в контроллере членства, и вы создали ViewModel для каждого частичного просмотра - как вы передаете данные из действия индекса частичным?Вы добавляете свойство к MembershipIndexViewModel типа MyPartialViewModel и при рендеринге части вы передаете его в Model.MyPartialViewModel.
Выполнение этого таким образом позволяет вам настроить частичные свойства ViewModel без необходимости вообще изменять представление индекса.Это все еще просто передается в Модели.MyPartialViewModel таким образом, меньше шансов, что вам придется пройти через всю цепочку частичных элементов, чтобы что-то исправить, когда все, что вы делаете, это добавляете свойство к частичной ViewModel.
Я также добавлю пространство имен "MyProject.Web.ViewModels" в web.config, чтобы позволить мне ссылаться на них в любом представлении, даже не добавляя явную инструкцию import для каждого представления.Просто делает это немного чище.
Другие советы
Разделение классов по категориям (контроллеры, ViewModels, Фильтры и т.д.) - это нонсенс.
Если вы хотите написать код для домашнего раздела вашего веб-сайта (/), то создайте папку с именем Home и поместите туда HomeController, IndexViewModel, AboutViewModel и т.д.и все связанные классы, используемые Home actions.
Если у вас есть общие классы, такие как ApplicationController, вы можете поместить его в корень вашего проекта.
Зачем разделять связанные вещи (HomeController, IndexViewModel) и хранить вместе вещи, которые вообще не имеют никакого отношения (HomeController, AccountController)?
Я написал запись в блоге по поводу этой темы.
Я храню свои классы приложений во вложенной папке под названием "Core" (или отдельной библиотеке классов) и использую те же методы, что и КИГГ пример нанесения, но с некоторыми незначительными изменениями, чтобы сделать мои аппликации более СУХИМИ.
Я создаю класс BaseViewData в /Core/ViewData /, где я храню общие свойства всего сайта.
После этого я также создаю все мои классы ViewData в той же папке, которые затем являются производными от 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();
}
Я думаю, что это работает действительно хорошо, и это сохраняет ваши представления аккуратными, а ваши контроллеры - компактными.
Класс ViewModel предназначен для инкапсуляции нескольких фрагментов данных, представленных экземплярами классов, в один простой в управлении объект, который вы можете передать своему представлению.
Было бы разумно иметь ваши классы ViewModel в их собственных файлах, в собственном каталоге.В моих проектах у меня есть подпапка папки Models под названием ViewModels.Вот где мои ViewModels (например ProductViewModel.cs
) жить.
Здесь нет подходящего места для хранения ваших моделей.Вы можете сохранить их в отдельной сборке, если проект большой и в нем много ViewModels (объектов передачи данных).Также вы можете хранить их в отдельной папке проекта сайта.Например, в Оксит они размещены в проекте Oxite, который также содержит множество различных классов.Контроллеры в Oxite перемещены в отдельный проект, и представления тоже находятся в отдельном проекте.
В Сервер Кодек - Кэмпов ViewModels называются *Form и помещаются в проект пользовательского интерфейса в папке Models.
В MvcPress project они размещены в Data project, который также содержит весь код для работы с базой данных и немного больше (но я не рекомендовал этот подход, это только для примера)
Таким образом, вы можете видеть, что существует много точек зрения.Обычно я храню свои ViewModels (объекты DTO) в проекте сайта.Но когда у меня более 10 моделей, я предпочитаю перенести их в отдельную сборку.Обычно в этом случае я тоже переношу контроллеры на отдельную сборку.
Другой вопрос заключается в том, как легко сопоставить все данные из model с вашей ViewModel.Я предлагаю взглянуть на Автоматический преобразователь библиотека.Мне это очень нравится, оно делает всю грязную работу за меня.
И я также предлагаю посмотреть на Четкая архитектура проект.Он обеспечивает очень хорошую архитектуру для проектов и содержит множество классных фреймворков и руководств, а также отличное сообщество.
вот фрагмент кода из моих лучших практик:
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");
}
}
Мы помещаем все наши ViewModels в папку Models (вся наша бизнес-логика находится в отдельном проекте ServiceLayer).
Лично я бы посоветовал, если ViewModel не является чем-то тривиальным, то использовать отдельный класс.
Если у вас более одной модели представления, то я полагаю, имеет смысл разделить ее хотя бы в каталоге.если модель представления позже будет совместно использована, то пространство имен, подразумеваемое в каталоге, упростит переход к новой сборке.
В нашем случае у нас есть Модели вместе с Контроллерами в проекте, отдельном от представлений.
Как эмпирическое правило, мы попытались перенести и избежать большую часть содержимого ViewData["..."] в ViewModel, таким образом, мы избегаем приведений и магических строк, что хорошо.
ViewModel также содержит некоторые общие свойства, такие как информация о разбиении на страницы для списков или информация о заголовке страницы для рисования переходов и заголовков.На данный момент базовый класс, на мой взгляд, содержит слишком много информации, и мы можем разделить его на три части: самую базовую и необходимую информацию для 99% страниц в базовой модели представления, а затем модель для списков и модель для форм, которые содержат конкретные данные для этих сценариев и наследуются от базовой.
Наконец, мы реализуем модель представления для каждого объекта для работы с конкретной информацией.
код в контроллере:
[HttpGet]
public ActionResult EntryEdit(int? entryId)
{
ViewData["BodyClass"] = "page-entryEdit";
EntryEditViewModel viewMode = new EntryEditViewModel(entryId);
return View(viewMode);
}
[HttpPost]
public ActionResult EntryEdit(Entry entry)
{
ViewData["BodyClass"] = "page-entryEdit";
#region save
if (ModelState.IsValid)
{
if (EntryManager.Update(entry) == 1)
{
return RedirectToAction("EntryEditSuccess", "Dictionary");
}
else
{
return RedirectToAction("EntryEditFailed", "Dictionary");
}
}
else
{
EntryEditViewModel viewModel = new EntryEditViewModel(entry);
return View(viewModel);
}
#endregion
}
код в модели представления:
public class EntryEditViewModel
{
#region Private Variables for Properties
private Entry _entry = new Entry();
private StatusList _statusList = new StatusList();
#endregion
#region Public Properties
public Entry Entry
{
get { return _entry; }
set { _entry = value; }
}
public StatusList StatusList
{
get { return _statusList; }
}
#endregion
#region constructor(s)
/// <summary>
/// for Get action
/// </summary>
/// <param name="entryId"></param>
public EntryEditViewModel(int? entryId)
{
this.Entry = EntryManager.GetDetail(entryId.Value);
}
/// <summary>
/// for Post action
/// </summary>
/// <param name="entry"></param>
public EntryEditViewModel(Entry entry)
{
this.Entry = entry;
}
#endregion
}
Проекты:
DevJet.Web ( веб-проект ASP.NET MVC )
DevJet.Web.App.Dictionary ( отдельный проект библиотеки классов)
в этом проекте я создал несколько папок, таких как:DAL, BLL, BO, ВИРТУАЛЬНАЯ машина (папка для просмотра моделей)
Создайте базовый класс модели представления , который имеет обычно требуемые свойства , такие как результат операции и контекстные данные , вы также можете поместить текущие пользовательские данные и роли
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;
}
}