【问题标题】:ActionResult parameters with no default constructor没有默认构造函数的 ActionResult 参数
【发布时间】:2012-12-08 05:47:39
【问题描述】:

显然有很多方法可以做到这一点,但我想我会要求就这些方法的优缺点提供一些反馈。

首先,NerdDinner 教程的 Edit Action 的形式是(比如 Form A):

[HttpPost]
public ActionResult Edit(int id, FormCollection collection) {

在我看来,如果你很好地塑造你的 ViewModel 以匹配你的观点,那么 Form B 的方法:

[HttpPost]
public ActionResult Edit(MyViewModel mvm) {

似乎是一种更好、更清洁的方法。然后,我只需将 VM 属性映射到模型属性并保存。但是,如果此 ViewModel 中嵌入了其他通过构造函数初始化的实体(例如在 nerddinner 教程中),则如果没有默认构造函数,则此编辑操作将失败,您必须使用第一种方法。

那么,第一个问题是您是否同意通常 B 型通常更好?有缺点吗?

其次,如果使用 Form B,那么装饰器类型验证似乎需要在 ViewModel 中。在 ViewModel 中嵌入实体并仅在实体级别进行验证是否有优势?

【问题讨论】:

    标签: asp.net asp.net-mvc viewmodel


    【解决方案1】:

    这是一个非常笼统的 SO 问题。

    第一个问题是您是否同意通常形式 B 通常更好?

    我唯一不使用Form B的时候是我上传文件的时候。否则,我不相信任何人都需要使用 Form A。我认为人们使用 Form A 的原因是对 ASP.Net 版本的 MVC 的能力缺乏了解。

    其次,如果使用 Form B,那么装饰器类型验证似乎需要在 ViewModel 中。

    有点/它取决于。我给你举个例子:

    public IValidateUserName
    {
      [Required]
      string UserName { get; set; }
    }
    
    public UserModel
    {
      string UserName { get; set; }
    }
    
    [MetadataType(typeof(IValidateUserName))]
    public UserValiationModel : UserModel
    {
    }
    

    验证装饰器位于接口中。我在派生类上使用 MetadataType 来验证派生类型。我个人喜欢这种做法,因为它允许可重复使用的验证,而且 MetadataType/Validation 不是 ASP.NET 核心功能的一部分,因此它可以在 ASP.Net (MVC) 应用程序之外使用。

    在 ViewModel 中嵌入实体有什么好处吗..

    是的,我尽我最大的努力从不将基本模型传递给视图。这是我不做的一个例子:

    public class person { public Color FavoriteColor { get; set; } }
    
    ActionResult Details()
    {
      Person model = new Person();
      return this.View(model);
    }
    

    当您想将更多数据传递给您的视图(对于局部数据或布局数据)时会发生什么?大多数情况下,该信息与 Person 无关,因此将其添加到 Person 模型是没有意义的。相反,我的模型通常看起来像:

    public class DetailsPersonViewModel()
    {
      public Person Person { get; set; }
    }
    
    public ActionResult Details()
    {
      DetailsPersonViewModel model = new DetailsPersonViewModel();
      model.Person = new Person();
      return this.View(model);
    }
    

    现在我可以添加所需的数据DetailsPersonViewModel,该视图需要超出 Person 知道的范围。例如,假设这将显示一个包含所有颜色的 for 供 Person 选择收藏夹。所有可能的颜色都不是人的一部分,也不应该是人模型的一部分,所以我会将它们添加到 DetailPersonViewModel。

    public class DetailsPersonViewModel()
    {
      public Person Person { get; set; }
      public IEnumerable<Color> Colors { get; set; }
    }
    

    .. 只在实体级别进行验证?

    System.ComponentModel.DataAnnotations 并非旨在验证属性的属性,因此请执行以下操作:

    public class DetailsPersonViewModel()
    {
      [Required(property="FavoriteColor")]
      public Person Person { get; set; }
    }
    

    不存在也没有意义。为什么 ViewModel 不应该包含需要验证的实体的验证。

    如果没有默认构造函数,此编辑操作将失败,您必须使用第一种方法。

    正确,但是为什么 ViewModel 或 ViewModel 中的实体没有无参数构造函数?听起来像是一个糟糕的设计,即使对此有一些要求,也可以通过 ModelBinding 轻松解决。这是一个例子:

    // Lets say that this person class requires 
    // a Guid for a constructor for some reason
    public class Person
    {
      public Person(Guid id){ }
      public FirstName { get; set; }
    }
    
    public class PersonEditViewModel
    {
      public Person Person { get; set; }
    }
    
    public ActionResult Edit()
    {
      PersonEditViewModel model = new PersonEditViewModel();
      model.Person = new Person(guidFromSomeWhere);
    
      return this.View(PersonEditViewModel);
    }
    
    //View 
    @Html.EditFor(m => m.Person.FirstName)
    
    //Generated Html
    <input type="Text" name="Person.FirstName" />
    

    现在我们有了一个用户可以输入新名字的表单。我们如何取回这个构造函数中的值?很简单,ModelBinder 不关心它绑定到什么模型,它只是将 HTTP 值绑定到匹配的类属性。

    [MetadataType(typeof(IPersonValidation))]
    public class UpdatePerson
    {
      public FirstName { get; set; }
    }  
    
    public class PersonUpdateViewModel
    {
      public UpdatePerson Person { get; set; }
    }
    
    [HttpPost]
    public ActionResult Edit(PersonUpdateViewModel model)
    {
      // the model contains a .Person with a .FirstName of the input Text box
      // the ModelBinder is simply populating the parameter with the values
      // pass via Query, Forms, etc
    
      // Validate Model
    
      // AutoMap it or or whatever
    
      // return a view
    }
    

    【讨论】:

    • 首先,感谢您的彻底回复 - 非常好。 “我尽我最大的努力从不将基本模型传递给视图”当然,但这并不是我想要的。问题是关于 ViewModels 是否应该有像 ViewModel.Person.Name 和 ViewModel.Name 这样的嵌入式实体,你解包并映射回更深的实体,以及你是否应该在 ViewModel.Name 而不是 ViewModel.Person 上使用装饰器验证 - 知道我的意思是什么??
    • 我实际上回答了这个问题。是的,它很长 :) 我尝试始终使用 ViewModel.Person.Name,据说它需要对 Person 而不是 ViewModel 进行验证,因为你不能真正在 ViewModel 上验证一个人(你可以但不是如何验证旨在使用)。
    • “ModelBinder 不关心它绑定到什么模型,”好的,所以如果元素被指定为 ViewModel,那么 Person 级别的验证装饰器将在视图中以相同的方式处理。 Person.PropertyName 对吗?和传入 Person 模型一样吗?
    • 只要参数签名匹配。也就是说,当您在提交时 EditFor MyClass1.Person.Name 的类/属性时,您可以绑定到 .Person.Name。在我的示例中,PersonEditViewModelPersonUpdateViewModel。类不必匹配,只需属性名称的层次结构即可。
    【解决方案2】:

    我还没有看过 NerDinner 项目,但是,我通常会尽量避免在操作的 POST 中使用 ViewModel,而是只提交“表单”的元素。

    例如,如果 ViewModel 有一个 Dictionary 用于某种下拉菜单,则不会提交整个下拉列表,只会提交选定的值。

    我的一般做法是:

    [HttpGet]
    public ActionResult Edit(int id) 
    {
         var form = _service.GetForm(id);
    
         var pageViewModel = BuildViewModel(form);
    
         return View(pageViewModel);
    }
    
    [HttpPost]
    public ActionResult Edit(int id, MyCustomForm form) 
    {
         var isSuccess = _service.ProcessForm(id);    
    
          if(isSuccess){
             //redirect
          }
    
          //There was an error. Show the form again, but preserve the input values
          var pageViewModel = BuildViewModel(form);
    
          return View(pageViewModel);
    }
    
    private MyViewModel BuildViewModel(MyCustomForm form)
    {
         var viewModel = new MyViewModel();
    
         viewModel.Form = form;
         viewModel.StateList = _service.GetStateList();
    
        return viewModel;
    }
    

    【讨论】:

    • 我想我不太清楚你的意思是“整个下拉列表不会被提交”??我有一个带有选择列表的 ViewModel。列表中的选定值和原始值都从帖子中返回...
    • &lt;select&gt; 中的所有值如何发布到您的操作?
    • 它们不是,只有选定的值会被发布。这就是我在 GET 和 POST 上创建 ViewModel 的原因。在 GET 中,我从一个空表单开始,在 POST 中,我有提交的表单。在每种情况下,我都需要获取状态列表。
    • @Erik NerdDinner 中的示例在 ViewModel(带有封装数据)中有一个静态 string[] 属性,该属性填充 ViewModel 构造函数中的 SelectList 属性。我认为,每当 MVC 重新创建这个对象时,它也不得不重新创建整个列表。这些项目没有发布,它们只是用对象重新创建...
    猜你喜欢
    • 1970-01-01
    • 2023-03-20
    • 2016-07-18
    • 1970-01-01
    • 1970-01-01
    • 2017-09-27
    • 2012-06-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多