2009-09-23 2 views
12

Для тех, кто создает ViewModels (для использования с помощью типизированных представлений) в ASP.NET MVC, вы предпочитаете извлекать данные из службы/репозитория из классов ViewModel или классов контроллера?Получение данных в классе ASP.NET MVC ViewModel?

Например, мы начали, имея ViewModels по существу является DTOs и позволяет нашим контроллеры для выборки данных (грубо упрощен пример предполагает, что пользователь может изменить только имя сотрудника):

public class EmployeeViewModel 
{ 
    public String Name; //posted back 
    public int Num; //posted back 
    public IEnumerable<Dependent> Dependents; //static 
    public IEnumerable<Spouse> Spouses; //static 
} 

public class EmployeeController() 
{ 
    ... 
    public ActionResult Employee(int empNum) 
    { 
     Models.EmployeeViewModel model = new Models.EmployeeViewModel(); 
     model.Name = _empSvc.FetchEmployee(empNum).Name; 
     model.Num = empNum; 
     model.Dependents = _peopleSvc.FetchDependentsForView(empNum); 
     model.Spouses = _peopleSvc.FetchDependentsForView(empNum); 
     return View(model); 
    } 

    [AcceptVerbs(HttpVerbs.Post)] 
    public ActionResult Employee(Models.EmployeeViewModel model) 
    { 
     if (!_empSvc.ValidateAndSaveName(model.Num, model.Name)) 
     { 
      model.Dependents = _peopleSvc.FetchDependentsForView(model.Num); 
      model.Spouses = _peopleSvc.FetchDependentsForView(model.Num); 
      return View(model); 
     } 
     this.RedirectToAction(c => c.Index()); 
    } 
} 

Это все казалось хорошо, пока мы начали создавать большие виды (более 40 полей) со многими выпадающими списками и т. д. Поскольку на экранах было бы действие GET и POST (с возвратом POST-представления, если была ошибка проверки), мы будем дублировать код и делать ViewModels больше, чем они, вероятно, должны быть.

Я думаю, что альтернативой было бы получение данных через Сервис в ViewModel. Меня беспокоит то, что у нас были бы некоторые данные, заполненные из ViewModel, а некоторые из Controller (например, в приведенном выше примере, Name будет заполнено из контроллера, поскольку оно является опубликованным значением, тогда как зависимые и супруги будут заполнены через некоторые тип функции GetStaticData() в ViewModel).

Мысли?

+11

IEnumerable ? Что такое шаблон полигама? :-D –

ответ

7

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

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

Были случаи, когда я применил методы преобразования в ViewModel. Например, мне нужно преобразовать ViewModel в соответствующий объект или мне нужно загрузить ViewModel с данными из Entity.

Чтобы ответить на ваш вопрос, я предпочитаю извлекать данные из с помощью методов управления/действий.

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

В зависимости от компоновки 40 полей плюс могут создать загроможденное изображение. В зависимости от типа данных я бы попытался охватить многие поля в нескольких представлениях с помощью какого-либо интерфейса с вкладками или мастеров.

+0

Спасибо за отзыв. Это для безопасного клиентского приложения, а не для публичного приложения, поэтому 40 полей обычно не смешны (хотя большинство наших более 100 запланированных представлений ближе к 10-15 полям). Когда вы говорите, что вы создаете выпадающий сервис, вы имеете в виду то же самое, что я сделал выше, или служба заполняет объект SelectList и передает его в ViewModel? –

+0

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

+0

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

3

Есть больше, чем это ;-) Вы можете получить в качестве связующего материала или фильтра действий. Для второго варианта, проверьте блог Джимми Богарда где-то около here. Я лично делаю это в модельных вяжущих. Я использую ViewModel следующим образом: My custom ASP.NET MVC entity binding: is it a good solution?. Он обрабатывается моей пользовательской модели вяжущего:

public object BindModel(ControllerContext c, BindingContext b) 
{ 
    var id = b.ValueProvider[b.ModelName]; // don't remember exact syntax 
    var repository = ServiceLocator.GetInstance(GetRepositoryType(b.ModelType)); 
    var obj = repository.Get(id); 
    if (obj == null) 
    b.ModelState.AddModelError(b.ModelName, "Not found in database"); 
    return obj; 
} 

public ActionResult Action(EntityViewModel<Order> order) 
{ 
    if (!ModelState.IsValid) 
     ...; 
} 

Вы также можете увидеть пример модели связующего делает доступ к репозиторию в S#arp Architecture.

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

общественного класса MyViewModel { общественного MyViewModel (порядок заказа, IEmployeesSvc _svc) { }

public IList<Employee> GetEmployeesList() 
    { 
     return _svc.GetEmployeesFor(order.Number); 
    } 

}

Вы решаете, как вы вводите _svc в ViewModel, но в основном это то же самое, что и для контроллера. Просто остерегайтесь того, что ViewModel также создается MVC с помощью конструктора без параметров, поэтому вы либо используете ServiceLocator, либо расширяете MVC для создания ViewModel - например, внутри своего настраиваемого связующего объекта. Или вы можете использовать подход Джимми Богарда с AutoMapper, который также поддерживает контейнеры IoC.

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

1

Вот еще одно решение: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/29/how-we-do-mvc-view-models.aspx

Основные пункты там:

  1. Mapping делается с помощью посредника - в данном случае это AutoMapper, но это может быть ваш собственный класс (хотя больше кода). Это удерживает как Domain, так и ViewModel сосредоточенными на логике домена/представления. Медиатор (картограф) будет содержать (в основном автоматическую) логику для картографирования, включая внедренные службы.
  2. Сопоставление применяется автоматически, все, что вы делаете, это указать фильтру действия источника/назначения - очень чисто.
  3. (Похоже, это важно для вас). AutoMapper поддерживает вложенные сопоставления/типы, поэтому вы можете использовать ViewModel в нескольких независимых моделях просмотра, чтобы ваш «экран DTO» не был грязным.

Как и в этой модели:

public class WholeViewModel 
{ 
    public Part1ViewModel ModelPart1 { get; set; } 
    public Part2ViewModel ModelPart2 { get; set; } 
} 

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

Если вы не хотите AutoMapper, у вас есть есть интерфейсы IViewModelMapper, а затем ваш контейнер IoC поможет вашему фильтру действий, чтобы найти соответствующий

container.Resolve(typeof(IViewModelMapper<>).MakeGenericType(mysourcetype, mydesttype)) 

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

3

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

К счастью, структура ASP.NET MVC дает нам больше точек интеграции, в частности ModelBinder.

У меня есть осуществление общего ModelBinder вытягивать информацию из сервисного слоя в: -

http://www.iaingalloway.com/going-further-a-generic-servicelayer-modelbinder

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

Если вы прилежны, ваши методы GET даже не должны знать, что сервисный уровень существует.

решение, вероятно, выглядит что-то вроде: -

метод действия контроллера: -

public ActionResult Details(MyTypeIndexViewModel model) 
{ 
    if(ModelState.IsValid) 
    { 
    return View(model); 
    } 
    else 
    { 
    // Handle the case where the ModelState is invalid 
    // usually because they've requested MyType/Details/x 
    // and there's no matching MyType in the repository 
    // e.g. return RedirectToAction("Index") 
    } 
} 

ModelBinder: -

public object BindModel 
(
    ControllerContext controllerContext, 
    BindingContext bindingContext 
) 
{ 
    // Get the Primary Key from the requestValueProvider. 
    // e.g. bindingContext.ValueProvider["id"] 
    int id = ...; 

    // Get an instance of your service layer via your 
    // favourite dependancy injection framework. 
    // Or grab the controller's copy e.g. 
    // (controllerContext.Controller as MyController).Service 
    IMyTypeService service = ...; 

    MyType myType = service.GetMyTypeById(id) 

    if (myType == null) 
    { 
    // handle the case where the PK has no matching MyType in the repository 
    // e.g. bindingContext.ModelState.AddModelError(...) 
    } 


    MyTypeIndexViewModel model = new MyTypeIndexViewModel(myType); 

    // If you've got more repository calls to make 
    // (e.g. populating extra fields on the model) 
    // you can do that here. 

    return model; 
} 

ViewModel: -

public class MyTypeIndexViewModel 
{ 
    public MyTypeIndexViewModel(MyType source) 
    { 
    // Bind all the properties of the ViewModel in here, or better 
    // inherit from e.g. MyTypeViewModel, bind all the properties 
    // shared between views in there and chain up base(source) 
    } 
} 

Buil d ваш уровень обслуживания и зарегистрируйте свой ModelBinder как обычно.

+1

Ваши идеи интригуют меня, и я хочу подписаться на ваш информационный бюллетень. – anewcomer

0

Рассмотрите возможность передачи своих услуг в пользовательский ViewModel на свой конструктор (ala Dependency Injection). Это удаляет код модели с вашего контроллера и позволяет сосредоточиться на управлении логическим потоком приложения. Пользовательские модели ViewModels - идеальное место для абстрагирования подготовки таких вещей, как SelectLists, от которых зависят ваши отвратители.

Много кода в контроллере для таких вещей, как получение данных, не считается лучшей практикой. Основная ответственность контроллера заключается в «управлении» потоком приложения.

0

Передача этого опоздала ... Баунти почти закончилась. Но ...

Другой картографа, чтобы посмотреть на это Automapper: http://www.codeplex.com/AutoMapper

И обзор о том, как использовать его: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx

мне очень нравится это синтаксис.

// place this somewhere in your globals, or base controller constructor 
Mapper.CreateMap<Employee, EmployeeViewModel>(); 

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

public class EmployeeController() 
{ 
    private IEmployeeService _empSvc; 
    private ISpouseService _peopleSvc; 

    public EmployeeController(
     IEmployeeService empSvc, ISpouseService peopleSvc) 
    { 
    // D.I. hard at work! Auto-wiring up our services. :) 
    _empSvc = empSvc; 
    _peopleSvc = peopleSvc; 

    // setup all ViewModels here that the controller would use 
    Mapper.CreateMap<Employee, EmployeeViewModel>(); 
    Mapper.CreateMap<Spouse, SpouseViewModel>(); 
    } 

    public ActionResult Employee(int empNum) 
    { 
    // really should have some validation here that reaches into the domain 
    // 

    var employeeViewModel = 
     Mapper.Map<Employee, EmployeeViewModel>(
      _empSvc.FetchEmployee(empNum) 
     ); 

    var spouseViewModel = 
     Mapper.Map<Spouses, SpousesViewModel>(
      _peopleSvc.FetchSpouseByEmployeeID(empNum) 
     ); 

    employeeViewModel.SpouseViewModel = spouseViewModel; 

    return View(employeeViewModel);  
    } 

    [AcceptVerbs(HttpVerbs.Post)] 
    public ActionResult Employee(int id, FormCollection values)  
    { 
    try 
    { 
     // always post to an ID, which is the employeeID 
     var employee = _empSvc.FetchEmployee(id); 

     // and bind using the built-in UpdateModel helpers. 
     // this will throw an exception if someone is posting something 
     // they shouldn't be posting. :) 
     UpdateModel(employee); 

     // save employee here 

     this.RedirectToAction(c => c.Index()); 
    } 
    catch 
    { 
     // check your domain model for any errors. 
     // check for any other type of exception. 
     // fail back to the employee screen 
     RedirectToAction(c => c.Employee(id)); 
    } 
    } 
} 

Я вообще стараюсь держаться подальше от сохранения нескольких объектов на действие контроллера. Вместо этого я бы реорганизовал объект домена сотрудника на методы AddSpouse() и SaveSpouse(), которые возьмут объект Супруг. Эта концепция известна как AggregateRoots, контролирующая все зависимости от корня - это объект Employee(). Но это всего лишь я.

Смежные вопросы