ASP.NET MVC - 绑定模型或视图模型的模型绑定

3

我有一个控制器,它返回一个视图,并传递一个视图模型,该模型具有用于显示视图的属性(下拉选择项列表等)。

但是当我将它提交到服务器时,我有一个不同的模型类,它具有这些下拉框的所选值。在我的HttpPost控制器操作中,在执行任何处理之前,我检查(ModelState.IsValid),但当它为false时,我会'return View(model)'回来。

但由于该视图绑定到ViewModel上,而我的Post操作接受实际的模型,因此当我提交表单并且验证错误显示在视图上时,我会收到错误消息'The model item passed into the dictionary is of type 'Model', but this dictionary requires a model item of type 'ViewModel''。

如何解决这个问题?在使用强类型视图、传递视图模型但提交到不同模型的情况下,最佳实践是什么?

代码:

 public ActionResult Buy()
    {
      BuyVM buyVM = GetBuyVM();
      return View(buyVM);
    }

   [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Buy(BuyModel model)
    {
      if (ModelState.IsValid)
        {
        // Do Procesing
        return View("Success");
        }
      return View(model);
    }

 public class BuyVM
    {
        public SelectList PurchaseDateList { get; set; }

        public SelectList BedroomsList { get; set; }

        public SelectList StoriesList { get; set; }

        [Required]
        public string SquareFootage { get; set; }

        [Required]
        public string PreferredCityLocations { get; set; }

        public string AdditionalInfo { get; set; }
    }

 public class BuyModel 
    {
        public string PurchaseDateList { get; set; }
        public string BedroomsList { get; set; }
        public string StoriesList { get; set; }
        public string SquareFootage { get; set; }
        public string PreferredCityLocations { get; set; }
        public string AdditionalInfo { get; set; }
    }

 private static BuyVM GetBuyVM()
        {
            BuyVM buyVM = new BuyVM();

            buyVM.PurchaseDateList = new SelectList(new[] { "Immediately", "1 to 3 months", "4 to 6 months", "More than 6 months" });
            buyVM.BedroomsList = new SelectList(new[] { "1", "2", "3", "4", "5+" });
            buyVM.StoriesList = new SelectList(new[] { "1", "2", "Does not matter" });

            return buyVM;
        }

Buy.cshtml

    @model Models.BuyVM
    // html
 @Html.DropDownListFor(m => m.PurchaseDateList, Model.PurchaseDateList, new { @class = "form-control" })

 @Html.DropDownListFor(m => m.BedroomsList, Model.BedroomsList, new { @class = "form-control" })

因此,当我在HTTPPost中返回View(model)时,如果有验证错误(JQueryVal),我尝试通过将模型传回视图来显示验证错误。但是我遇到了这种类型不匹配的问题。


请发布您的代码。没有代码,我们无法提供帮助。 - A_Sk
当模型出现错误时,您需要在调用视图之前在代码中重新构建相同的模型,因此您需要重置与该模型不相关联的项目,例如查找列表等。 - Steve Greene
新增代码 @user3540365 和 Steve! - Adi Sekar
我目前的工作还算可以,对于客户端验证来说没问题,但是我的服务器端验证仍然存在这个问题。 - Adi Sekar
@AdiSekar,要通知用户,请像我这样在评论中开始:)。稍后我会发布一个答案,向您展示如何正确地执行此操作。 - user3559349
显示剩余6条评论
3个回答

4

首先,您的数据模型名称没有意义。一个名为BedroomsList的属性暗示着它是一组Bedrooms,然而该属性实际上是一个string类型。因此,请从命名属性开始,以便描述它们的含义,使其他人可以理解您的代码。

public class BuyModel 
{
  public string PurchaseDate { get; set; }
  public string Bedrooms { get; set; }
  public string Stories { get; set; }
  public string SquareFootage { get; set; }
  public string PreferredCityLocations { get; set; }
  public string AdditionalInfo { get; set; }
}

相应的视图模型需要包含这些属性以及SelectList属性。

public class BuyVM
{
  public string PurchaseDate { get; set; }
  public string Bedrooms { get; set; }
  public string Stories { get; set; }
  [Required]
  public string SquareFootage { get; set; }
  [Required]
  public string PreferredCityLocations { get; set; }
  public string AdditionalInfo { get; set; }
  public SelectList PurchaseDateList { get; set; }
  public SelectList BedroomsList { get; set; }
  public SelectList StoriesList { get; set; }
}

接下来,删除您的GetBuyVM()方法,并用控制器中的一个方法替换它,该方法将填充选择列表,以便在ModelState无效并且需要返回视图并更改POST方法参数时,您也可以调用该方法到您的视图模型(因为您的视图是基于BuyVM的,所以必须将其发送回BuyVM,而不是BuyModel)。

public ActionResult Buy()
{
  BuyVM model = new BuyVM(); // initalise an instance of the view model
  ConfigureViewModel(model);
  return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Buy(BuyVM model)
{
  if (!ModelState.IsValid)
  {
    ConfigureViewModel(model);
    return View(model);
   }
   Initialize your data model and map the view model properties to it
   BuyModel dataModel = new BuyModel()
   {
     PurchaseDate = model.PurchaseDate,
     Bedrooms = model.Bedrooms,
     ....
   };
   // save the data model
   return View("Success");
}

private ConfigureViewModel(BuyVM model)
{
  model.PurchaseDateList = new SelectList(new[] { "Immediately", "1 to 3 months", "4 to 6 months", "More than 6 months" });
  model.BedroomsList = new SelectList(new[] { "1", "2", "3", "4", "5+" });
  model.StoriesList = new SelectList(new[] { "1", "2", "Does not matter" });
}

最后,在视图中将您的属性绑定(PurchaseDate,而不是PurchaseDateList)。
@Html.DropDownListFor(m => m.PurchaseDate, Model.PurchaseDateList)
@Html.DropDownListFor(m => m.Bedrooms, Model.BedroomsList)

0

在回传时,如果视图模型有效,则将其移动到实体模型。请注意,Post 使用视图模型可以保护您免受暴露通常被认为是不安全的实体模型的风险。像 AutoMapper 这样的工具非常适合此操作,但是您也可以手动完成:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Buy(BuyVM buyVM)
{

  if (ModelState.IsValid)
    {
    var buyModel = new BuyModel {
      PurchaseDateList = buyVM.PurchaseDateList,
      BedroomsList = buyVM.BedroomsList,
      ...
     };
     // Do Procesing, Save Entity Model
  }
  // Otherwise, reset unbound fields on viewmodel
  buyVM.List = GetList();
  ...
  return View(buyVM);
}

MVC 会自动返回错误信息。


我尝试了这个,但是在Post操作的断点之前,我遇到了一个错误“此对象没有无参构造函数定义”。BuyVM中有所有要绑定到视图中下拉列表的选择项,这样在POST操作中会获取选定的值吗? - Adi Sekar
哪段代码崩溃了?绑定列表无法返回到Post,因此您需要重建它们。您的GetBuyVM()是否填充了它们? - Steve Greene
在提交表单后,模型绑定“BuyVM”到我的Post操作期间崩溃。因此,它甚至没有到达我的post操作,尝试绑定到BuyVM时失败。 - Adi Sekar
需要查看视图,但你尝试过调试它以查看崩溃的行吗?另一个选项是注释掉一些内容,以获得最小工作版本,然后逐步添加内容。 - Steve Greene

-1

在返回视图之前,您需要重新构建视图模型——也就是说,将您的模型包括在内,在返回视图之前重建所有下拉菜单等。

您还可以考虑找到一种方法,在Post操作中直接使用视图模型。


重建视图后,我的验证错误是否仍然存在?如果我重建虚拟机并通过它传递,那么必填字段和其他验证是否有效? - Adi Sekar
无论如何,验证错误都会保持不变。正如Steve Greene在下面提到的,将ViewModel传递给Post方法仍然是更好的做法。当您这样做时,您可以通过使用ViewModel.modelName在模型上进行所有需要的处理。然而,当模型无效时,您仍然需要重新构建下拉列表。 - Platte Gruber
将VM传递到post中似乎不起作用。在到达Post操作之前,我会收到此错误“未为此对象定义无参数构造函数”。BuyVM具有要绑定到视图中的下拉列表项,这是否具有所选值,或者我们应该明确传递它?我的视图中的DD使用DD名称与模型进行了模型绑定。 - Adi Sekar
验证错误无论如何都不会保持不变!BuyModel没有任何验证属性,因此使用if (ModelState.IsValid)几乎没有任何意义,因为BuyModel永远不会无效。 - user3559349

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接