ASP.NET MVC 5模型绑定编辑视图

时间:2014-01-22 15:05:50

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

我无法想出一个最好用口头和一些代码描述的问题的解决方案。我正在使用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)]),这些属性阻止它出现在Edit视图中,但随后我在回发时遇到绑定错误,因为CreatedDate结束了值为1/1/0001 12:00:00 AM。那是因为编辑视图没有将值传回给CreatedDate字段的控制器。

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

我看着使用[Bind(Exclude =“CreatedDate”)],但这没有帮助。

我的控制器的编辑回发功能中的代码如下所示:

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)对象并检查OriginalValue for CreatedDate,但是当我尝试访问该值时(如下所示),我得到'System.InvalidOperationException'类型的异常:

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

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

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

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

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

非常感谢任何帮助/想法。

谢谢。

6 个答案:

答案 0 :(得分:15)

您应该利用ViewModels:

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的意图与域模型的意图略有不同。它为视图提供了正确呈现所需的足够信息。

ViewModels还可以保留与您的域无关的信息。它可以保存对表或搜索过滤器上的排序属性的引用。那些穿上你的领域模型肯定没有意义!

现在,在您的控制器中,您将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; }
}

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

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

答案 1 :(得分:3)

这不是问题的答案,但对于那些使用Bind()并面临不同问题的人来说,这可能是至关重要的。当我搜索&#34;为什么Bind()清除所有已存在但未绑定的值&#34;时,我发现了这个:

  

(在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()现在是官方解决方案。

  3. 您可以搜索&#34; TryUpdateModel&#34;在主题中了解详情。

答案 2 :(得分:2)

它可以解决您的问题

在模型中: 使用?

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

答案 3 :(得分:1)

+1为cheny的回答。使用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);
}

Automapper:

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

答案 4 :(得分:0)

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

答案 5 :(得分:-1)

控制器:

...
warrantymodel.CreatedDate = DateTime.Parse(Request.Form["CreatedDate"]);
...
相关问题