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

Получение данных в классе ASP.NET MVC ViewModel?

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

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

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).

Мысли?

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

Там больше, чем;-) Вы можете получить в модуле связывания или фильтра действий. Для второго варианта, проверьте блог Jimmy Bogard где-нибудь около здесь. Я лично делаю это в модельных вяжущих. Я использую ViewModel следующим образом: Мое пользовательское связывание объектов ASP.NET MVC: это хорошее решение?. Он обрабатывается моим пользовательским связующим устройством:

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.

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

открытый класс MyViewModel  {     public MyViewModel (порядок заказа, IEmployeesSvc _svc)     {     }

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

}

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

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

Ответ 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 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 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)
  }
}

Создайте свой сервисный уровень и зарегистрируйте свой ModelBinder как обычно.

Ответ 4

Здесь другое решение: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/29/how-we-do-mvc-view-models.aspx

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

  • Отображение выполняется посредником - в этом случае это AutoMapper, но он может быть вашим собственным классом (хотя и больше для кода). Это удерживает как Domain, так и ViewModel сосредоточенными на логике домена/представления. Медиатор (mapper) будет содержать (в основном автоматическую) логику для сопоставления, включая внедренные службы.
  • Сопоставление применяется автоматически, все, что вы делаете, это указать фильтру действия источника/назначения - очень чисто.
  • (Кажется, это важно для вас). AutoMapper поддерживает вложенные сопоставления/типы, поэтому вы можете комбинировать ViewModel с несколькими независимыми моделями просмотра, чтобы ваш "экран DTO" не был грязным.

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

Отправляя это позже... Баунти почти закончилась. Но...

Еще один просмотрщик - 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(). Но, это только я.