代码之家  ›  专栏  ›  技术社区  ›  Beep beep

在ASP.NET MVC ViewModel类中获取数据?

  •  12
  • Beep beep  · 技术社区  · 15 年前

    对于那些在ASP.NET MVC中创建视图模型(供类型化视图使用)的用户,您更喜欢从视图模型或控制器类中的服务/存储库获取数据吗?

    例如,我们首先让视图模型本质上是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会返回一个视图),所以我们会复制代码并使视图模型比它们应该的更大。

    我认为另一种选择是通过ViewModel中的服务获取数据。我担心的是,我们会从viewModel和控制器中填充一些数据(例如,在上面的示例中,名称将从控制器中填充,因为它是一个已发布的值,而从属项和配偶将通过viewModel中的某种类型的getStaticData()函数填充)。

    思想?

    6 回复  |  直到 12 年前
        1
  •  7
  •   Chuck Conway    15 年前

    我也遇到了同样的问题。当代码对于操作方法来说太大时,我开始为每个操作创建类。是的,您将在类中进行一些数据检索,在控制器方法中进行一些数据检索。另一种选择是将所有的数据检索都放在类中,但是有一半的类是您真正不需要的,它们将是为了一致性而创建的,或者所有的数据检索都放在控制器方法中,但是同样,这些方法中的一些太复杂,需要被抽象成类…所以选择你的毒药。我宁愿有一点矛盾,并有正确的工作解决方案。

    至于将行为放入ViewModel,我不这么认为,ViewModel的目的是成为一个用于设置和从视图中提取值的瘦类。

    在某些情况下,我在ViewModel中放置了转换方法。例如,我需要将ViewModel转换为相应的实体,或者需要从实体加载带有数据的ViewModel。

    要回答您的问题,我更喜欢在控制器/操作方法中从中检索数据。

    通常,对于下拉列表,我创建一个下拉服务。下拉列表往往是跨视图的相同数据。通过服务中的下拉列表,我可以将它们用于其他视图和/或缓存它们。

    根据布局,40多个字段可以创建杂乱的视图。根据数据的类型,我将尝试在多个视图中跨越多个字段,并使用某种选项卡或向导界面。

        2
  •  3
  •   Community CDub    7 年前

    还有更多;-)您可以在模型绑定器或动作过滤器中获取。第二种选择是在附近的某个地方查看吉米·博加德的博客。 here . 我个人用活页夹做的。我使用这样的视图模型: 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);
      }
    

    }

    您可以决定如何向ViewModel中注入\uSVC,但它基本上与控制器相同。请注意,ViewModel也是由MVC通过无参数构造函数创建的,因此您可以使用ServiceLocator或扩展MVC来创建ViewModel—例如,在自定义模型绑定器中。或者,您可以使用JimmyBogard的方法和AutoMapper,后者也支持IOC容器。

    这里的常见方法是,每当我看到表示代码时,我都希望消除它。100个控制器操作执行域视图模型编组和存储库查找是一个坏情况。单模型绑定器以常规方式执行是一个很好的方法。

        3
  •  3
  •   Iain Galloway    15 年前

    我不会从您的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;
    }
    

    VIEW模型:

    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
  •  1
  •   queen3    15 年前

    另一个解决方案是: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/29/how-we-do-mvc-view-models.aspx

    这里的要点:

    1. 映射是由中介完成的——在本例中,它是automapper,但它可以是您自己的类(不过更多的是代码)。这使得域和视图模型都集中在域/表示逻辑上。中介(映射器)将包含(大部分是自动的)映射逻辑,包括注入的服务。
    2. 映射是自动应用的,您所要做的就是告诉操作过滤器源/目标类型-非常干净。
    3. (对您来说似乎很重要)automapper支持嵌套的映射/类型,因此您可以将视图模型与几个独立的视图模型组合在一起,这样您的“屏幕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
  •  0
  •   DrivenDevelopment    15 年前

    考虑将服务传递到其构造函数上的自定义ViewModel(ala依赖项注入)。这将从控制器中删除模型填充代码,并使其能够专注于控制应用程序的逻辑流。自定义视图模型是提取下拉列表所依赖的选择列表等内容的理想场所。

    在控制器中,许多用于检索数据的代码被认为不是最佳实践。控制器的主要职责是“控制”应用程序的流程。

        6
  •  0
  •   eduncan911    15 年前

    延迟提交此…赏金快结束了。但是…

    要查看的另一个映射器是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个ViewModel。我将重构为:

    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));
        }
      } 
    }
    

    我通常试图避免在控制器操作中保存多个实体。相反,我将重构Employee域对象,使其具有addSpouse()和saveSpouse()方法,这些方法将采用配偶的对象。这个概念被称为aggregateroots,控制根的所有依赖项,根是employee()对象。但是,那只是我。