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

Asp.net mvc 在ASP.NET MVC ViewModel类中获取数据?,asp.net-mvc,Asp.net Mvc,对于那些在ASP.NET MVC中创建ViewModels(供类型化视图使用)的用户,您更喜欢从ViewModel中的服务/存储库中获取数据,还是从控制器类中获取数据 例如,我们首先让ViewModels本质上是DTO,并允许我们的控制器获取数据(非常简单的示例假设用户只能更改员工姓名): 公共类EmployeeViewModel { 公共字符串名称;//回发 public int Num;//发回 public IEnumerable Dependents;//静态 公共IEnumerable

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

例如,我们首先让ViewModels本质上是DTO,并允许我们的控制器获取数据(非常简单的示例假设用户只能更改员工姓名):

公共类EmployeeViewModel
{
公共字符串名称;//回发
public int Num;//发回
public IEnumerable Dependents;//静态
公共IEnumerable;//静态
}
公共类EmployeeController()
{
...
公共行动结果员工(int empNum)
{
Models.EmployeeViewModel=新模型.EmployeeViewModel();
model.Name=\u empSvc.FetchEmployee(empNum).Name;
model.Num=empNum;
model.Dependents=\u peopleSvc.FetchDependentsForView(empNum);
model.partners=\u peopleSvc.FetchDependentsForView(empNum);
返回视图(模型);
}
[接受动词(HttpVerbs.Post)]
公共行动结果员工(Models.EmployeeViewModel)
{
if(!\u empSvc.ValidateAndSaveName(model.Num,model.Name))
{
model.Dependents=\u peopleSvc.FetchDependentsForView(model.Num);
model.partners=\u peopleSvc.FetchDependentsForView(model.Num);
返回视图(模型);
}
this.RedirectToAction(c=>c.Index());
}
}
这一切似乎都很好,直到我们开始创建带有许多下拉列表等的大视图(40多个字段)。由于屏幕将有一个GET和POST操作(如果存在验证错误,POST将返回一个视图),我们将复制代码并使ViewModels比它们可能应该的更大

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


想法?

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

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

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

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

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

根据布局的不同,40多个字段可能会创建一个混乱的视图。根据数据的类型,我将尝试使用某种选项卡或向导界面跨越多个视图中的许多字段

还有更多;-)您可以获取模型绑定器或操作筛选器。对于第二种选择,请查看Jimmy Bogard的博客。我个人是用模型装订的。我使用的ViewModel如下所示:。它由我的自定义模型活页夹处理:

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)
      ...;
}
公共对象绑定模型(ControllerContext c,BindingContext b)
{
var id=b.ValueProvider[b.ModelName];//不记得确切的语法
var repository=ServiceLocator.GetInstance(GetRepositoryType(b.ModelType));
var obj=repository.Get(id);
if(obj==null)
b、 AddModelError(b.ModelName,“未在数据库中找到”);
返回obj;
}
公共操作结果操作(EntityViewModel订单)
{
如果(!ModelState.IsValid)
...;
}
您还可以在中看到模型绑定器访问存储库的示例

至于视图模型中的静态数据,我仍在探索方法。例如,可以让视图模型记住实体而不是列表,以及

公共类MyViewModel { 公共MyViewModel(订单、IEmployeesSvc\u svc) { }

  public IList<Employee> GetEmployeesList()
  {
      return _svc.GetEmployeesFor(order.Number);
  }
public IList GetEmployeesList()
{
返回_svc.GetEmployeesFor(订单号);
}
}

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

这里常见的方法是,每当我看到重复代码时,我都会设法消除它。执行域视图模型编组和存储库查找的100个控制器操作是一个糟糕的情况。单一型号活页夹采用通用方式是一种很好的解决方案。

这里有另一种解决方案:

要点如下:

  • 映射是由中介完成的——在本例中,它是AutoMapper,但也可以是您自己的类(尽管需要编写更多代码)。这使得Domain和ViewModel都集中在域/表示逻辑上。中介(映射器)将包含(大部分是自动的)映射逻辑,包括注入的服务
  • 映射
      public IList<Employee> GetEmployeesList()
      {
          return _svc.GetEmployeesFor(order.Number);
      }
    
    public class WholeViewModel
    {
       public Part1ViewModel ModelPart1 { get; set; }
       public Part2ViewModel ModelPart2 { get; set; }
    }
    
    container.Resolve(typeof(IViewModelMapper<>).MakeGenericType(mysourcetype, mydesttype))
    
    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")
      }
    }
    
    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;
    }
    
    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)
      }
    }
    
    // place this somewhere in your globals, or base controller constructor
    Mapper.CreateMap<Employee, EmployeeViewModel>();
    
    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));
        }
      } 
    }