【问题标题】:ASP.NET MVC 5 Model-Binding Edit ViewASP.NET MVC 5 模型绑定编辑视图
【发布时间】:2014-02-12 17:22:32
【问题描述】:

对于一个最好用口头和少量代码描述的问题,我想不出一个解决方案。我正在使用 VS 2013、MVC 5 和 EF6 代码优先;我也在使用 MvcControllerWithContext 脚手架,它生成支持 CRUD 操作的控制器和视图。

简单地说,我有一个包含 CreatedDate 值的简单模型:

public class WarrantyModel
{
    [Key]
    public int Id { get; set; }
    public string Description { get; set; }
    DateTime CreatedDate { get; set; }
    DateTime LastModifiedDate { get; set; }
}

包含的 MVC 脚手架对其索引、创建、删除、详细信息和编辑视图使用相同的模型。我想要“创建”视图中的 CreatedDate;我希望它出现在“编辑”视图中,因为我不希望它的值在编辑视图回发到服务器时发生变化,并且我不希望任何人能够篡改表单发布期间的值。

理想情况下,我不希望 CreatedDate 进入编辑视图。我发现了一些可以放置在模型的 CreatedDate 属性上的属性(例如,[ScaffoldColumn(false)]),这些属性可以防止它出现在编辑视图中,但是我在回发时遇到绑定错误,因为 CreatedDate 结束了值为 1/1/0001 12:00:00 AM。这是因为 Edit 视图没有将 CreatedDate 字段的值传回控制器。

我不想实现需要任何 SQL Server 更改的解决方案,例如在包含 CreatedDate 值的表上添加触发器。如果我想做一个快速修复,我会在显示编辑视图之前存储 CreatedDate(当然是服务器端),然后在回发时恢复 CreatedDate——这会让我更改 0001 年 1 月 1 日的日期到在呈现视图之前从数据库中提取的 CreatedDate EF6。这样,我可以将 CreatedDate 作为隐藏表单字段发送,然后在回发后在控制器中覆盖其值,但我没有存储服务器端值的好策略(我不想使用 Session 变量或查看包)。

我查看了使用 [Bind(Exclude="CreatedDate")],但这没有帮助。

我的控制器的 Edit post-back 函数中的代码如下所示:

public ActionResult Edit([Bind(Include="Id,Description,CreatedDate,LastModifiedDate")] WarrantyModel warrantymodel)
{
    if (ModelState.IsValid)
    {
        db.Entry(warrantymodel).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(warrantymodel);
}

我认为我可以检查上面if 块中的 db.Entry(warrantymodel) 对象并检查 CreatedDate 的 OriginalValue,但是当我尝试访问该值时(如下所示),我得到'System.InvalidOperationException' 类型的异常:

var originalCreatedDate = db.Entry(warrantymodel).Property("CreatedDate").OriginalValue;

如果我可以成功检查原始 CreatedDate 值(即数据库中已经存在的值),我可以覆盖 CurrentValue 的任何值。但是由于上面这行代码产生了异常,我不知道还能做什么。 (我想过在数据库中查询该值,但这很愚蠢,因为在呈现 Edit 视图之前已经查询了该数据库的值)。

我的另一个想法是将 CreatedDate 值的 IsModified 值更改为 false,但是当我调试时,我发现它已经在前面显示的“if”块中设置为 false:

bool createdDateIsModified = db.Entry(warrantymodel).Property("CreatedDate").IsModified;

我不知道如何处理这个看似简单的问题。总之,我不想将模型字段传递给编辑视图,并且我希望该字段(在此示例中为 CreatedDate)在视图中的其他编辑字段被回发并使用持久化到数据库时保持其原始值db.SaveChanges()。

任何帮助/想法将不胜感激。

谢谢。

【问题讨论】:

    标签: asp.net-mvc model-binding asp.net-mvc-5 scaffold


    【解决方案1】:

    您应该利用 ViewModel:

    public class WarrantyModelCreateViewModel
    {
        public int Id { get; set; }
        public string Description { get; set; }
        DateTime CreatedDate { get; set; }
        DateTime LastModifiedDate { get; set; }
    }
    
    public class WarrantyModelEditViewModel
    {
        public int Id { get; set; }
        public string Description { get; set; }
        DateTime LastModifiedDate { get; set; }
    }
    

    ViewModel 的意图与域模型的意图有点不同。它为视图提供了正确渲染所需的足够信息。

    ViewModel 还可以保留与您的域完全不相关的信息。它可以包含对表的排序属性或搜索过滤器的引用。放在你的领域模型上肯定没有意义!

    现在,在您的控制器中,您将 ViewModel 中的属性映射到您的域模型并保留您的更改:

    public ActionResult Edit(WarrantyModelEditViewModel vm)
    {
        if (ModelState.IsValid)
        {
            var warrant = db.Warranties.Find(vm.Id);
            warrant.Description = vm.Description;
            warrant.LastModifiedDate = vm.LastModifiedDate;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(warrantymodel);
    }
    

    此外,ViewModel 非常适合合并来自多个模型的数据。如果您有保修的详细视图,但您还想查看在该保修下完成的所有服务,该怎么办?您可以像这样简单地使用 ViewModel:

    public class WarrantyModelDetailsViewModel
    {
        public int Id { get; set; }
        public string Description { get; set; }
        DateTime CreatedDate { get; set; }
        DateTime LastModifiedDate { get; set; }
        List<Services> Services { get; set; }
    }
    

    ViewModel 简单、灵活并且非常受欢迎。这是对它们的一个很好的解释:http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/

    您最终会编写大量映射代码。 Automapper 很棒,可以完成大部分繁重的工作:http://automapper.codeplex.com/

    【讨论】:

    • 我将使用您建议的自定义编辑视图模型。但是,我希望避免在回发时重新查询数据库。 Microsoft 在其函数签名中使用了 Bind 表达式:public ActionResult Edit([Bind(Include="Id,Description,CreatedDate,LastModifiedDate")] WarrantyModel warrantymodel) 你的 db.Warranties.Find(vm.Id) 和映射做同样的事情吗?
    • 您好,我认为使用相同的视图模型会更容易,然后使用 Automapper 并将其配置为跳过空值,这样现有数据仍然存在于域模型中。请在下面查看我的答案。
    【解决方案2】:

    这不是问题的答案,但对于使用 Bind() 并面临不同问题的人来说可能很重要。当我搜索“为什么 Bind() 清除所有预先存在但未绑定的值”时,我发现了这个:

    (在 HttpPost Edit() 中)脚手架生成了一个 Bind 属性,并将模型绑定器创建的实体添加到带有 Modified 标志的实体集中。不再推荐使用该代码,因为 Bind 属性会清除未在 Include 参数中列出的字段中的任何预先存在的数据。将来,MVC 控制器脚手架将更新,使其不会为 Edit 方法生成 Bind 属性。

    来自官方页面(最后更新于 2015 年 3 月): http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application#overpost

    根据题目:

    1. 不建议绑定,将来会从自动生成的代码中删除。

    2. TryUpdateModel() 现已成为官方解决方案。

    您可以在主题中搜索“TryUpdateModel”了解详情。

    【讨论】:

      【解决方案3】:

      或许能解决你的问题

      在模型中: 使用?

      public class WarrantyModel
      {
          [Key]
          public int Id { get; set; }
          public string Description { get; set; }
          DateTime? CreatedDate { get; set; }
          DateTime? LastModifiedDate { get; set; }
      }
      

      表单提交后:

      public ActionResult Edit([Bind(Include = "Id,Description,CreatedDate,LastModifiedDate")] WarrantyModel warrantymodel)
      {
          if (ModelState.IsValid)
          {
              db.Entry(warrantymodel).State = EntityState.Modified;
              db.Entry(warrantymodel).Property("CreatedDate").IsModified=false
              db.SaveChanges();
              return RedirectToAction("Index");
          }
          return View(warrantymodel);
      }
      

      【讨论】:

      • 它确实解决了我的问题:更新记录后,创建日期总是消失。使用 @moynul-haque-biswas db.Entry(model).Property("field").IsModified=false 可以单独保留任何选定的字段。像魅力一样工作!
      • 这个提示解决了我的问题。非常感谢。不应更新的属性必须放在实体状态标记为“已修改”之后。
      【解决方案4】:

      为cheny的答案+1。使用 TryUpdateModel 而不是 Bind。

      public ActionResult Edit(int id)
      {
          var warrantymodel = db.Warranties.Find(id);
          if (TryUpdateModel(warrantymodel, "", new string[] { "Id", "Description", "LastModifiedDate" }))
          {
              db.SaveChanges();
              return RedirectToAction("Index");
          }
          return View(warrantymodel);
      }
      

      如果您想使用 View Model,您可以使用 Automapper 并将其配置为跳过空值,以便现有数据仍然存在于域模型中。

      例子:

      型号:

      public class WarrantyModel
      {
          public int Id { get; set; }
          public string Description { get; set; }
          DateTime CreatedDate { get; set; }
          DateTime? LastModifiedDate { get; set; }
      }
      

      视图模型:

      public class WarrantyViewModel
      {
          public int Id { get; set; }
          public string Description { get; set; }
          DateTime? CreatedDate { get; set; }
          DateTime? LastModifiedDate { get; set; }
      }
      

      控制器:

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Edit([Bind(Include="Id,Description,LastModifiedDate")] WarrantyViewModel warrantyViewModel)
      {
          var warrantyModel = db.Warranties.Find(warrantyViewModel.Id);
          Mapper.Map(warrantyViewModel, warrantyModel);
          if (ModelState.IsValid)
          {
              db.Entry(warrantyModel).State = EntityState.Modified;
              db.SaveChanges();
              return RedirectToAction("Index");
          }
          return View(warrantyModel);
      }
      

      自动映射器:

      Mapper.CreateMap<WarrantyViewModel, WarrantyModel>()
          .ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
      

      【讨论】:

        【解决方案5】:

        尝试删除编辑视图中的创建日期提示文本框。在我的应用程序中,脚手架生成的编辑和创建视图包含在数据库中生成的主键。

        【讨论】:

        • 这不是解决方案。这个想法是始终使用脚手架生成视图,而不是在生成视图后手动编辑视图。您的方法的问题在于,如果您需要重新生成视图,则需要记住您手动更改了哪些视图,以使应用程序按照您需要的方式工作。
        • 感谢您的反馈 Jazimov,这是真的。但是如何让它识别数据库中生成的主键呢?因为它是自动生成的,所以我不需要在Edit中拥有它。
        • 我不完全理解你的问题,但一般来说,你不应该将任何信息传递给视图不需要的视图。在您的场景中,这是主键 - 不要将其发送到视图......实现该行为的一种方法是利用视图模型,如答案中所述。这能解决你的问题吗?
        【解决方案6】:

        控制器:

        ...
        warrantymodel.CreatedDate = DateTime.Parse(Request.Form["CreatedDate"]);
        ...
        

        【讨论】:

        • 我想说这是一个简短的答案,以帮助任何发现您的答案的用户,它可能会很好地提供一些上下文和/或解释
        • 从 Request 对象中获取值的建议带有对原始问题完全缺乏理解的味道,因为它不尊重视图模型的概念,也不适应脚手架的存在.
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多