asp.net-mvc ASP.NET MVC 5 模型绑定编辑视图

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21286538/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 03:28:13  来源:igfitidea点击:

ASP.NET MVC 5 Model-Binding Edit View

asp.net-mvcmodel-bindingasp.net-mvc-5scaffold

提问by Jazimov

I cannot come up with a solution to a problem that's best described verbally and with a little code. I am using VS 2013, MVC 5, and EF6 code-first; I am also using the MvcControllerWithContext scaffold, which generates a controller and views that support CRUD operations.

我无法想出一个最好的口头描述和一小段代码的问题的解决方案。我使用 VS 2013、MVC 5 和 EF6 代码优先;我还使用了 MvcControllerWithContext 脚手架,它生成支持 CRUD 操作的控制器和视图。

Simply, I have a simple model that contains a CreatedDate value:

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

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

The included MVC scaffold uses the same model for its index, create, delete, details, and edit views. I wantthe CreatedDate in the 'create' view; I do notwant it in the 'edit' view because I do not want its value to change when the edit view is posted back to the server and I don't want anyone to be able to tamper with the value during a form-post.

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

Ideally, I don't want the CreatedDate to ever get to the Edit view. I have found a few attributes I can place on the model's CreatedDate property (for example, [ScaffoldColumn(false)]) that prevent it from appearing on the Edit view, but then I'm getting binding errors on postback because the CreatedDate ends up with a value of 1/1/0001 12:00:00 AM. That's because the Edit view is not passing a value back to the controller for the CreatedDate field.

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

I don't want to implement a solution that requires any SQL Server changes, such as adding a trigger on the table that holds the CreatedDate value. If I wanted to do a quick-fix, I would store the CreatedDate (server-side, of course) before the Edit view is presented and then restore the CreatedDate on postback--that would let me change the 1/1/0001 date to the CreatedDate EF6 pulled from the database before rendering the view. That way, I could send CreatedDate as a hidden form field and then overwrite its value in the controller after postback, but I don't have a good strategy for storing server-side values (I don't want to use Session variable or the ViewBag).

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

I looked at using [Bind(Exclude="CreatedDate")], but that doesn't help.

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

The code in my controller's Edit post-back function looks like this:

我的控制器的 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);
}

I thought I might be able to examine the db.Entry(warrantymodel) object within the ifblock above and examine at the OriginalValue for CreatedDate, but when I try to access that value (as shown next), I get an exception of type 'System.InvalidOperationException':

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

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

If I could successfully examine the original CreatedDate value (i.e., the one that is already in the database) I could just overwrite whatever the CurrentValue is. But since the above line of code generates an exception, I don't know what else to do. (I thought about querying the database for the value but that's just silly since the database was already queried for the value before the Edit view was rendered).

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

Another idea I had was to change the IsModified value to false for the CreatedDate value but when I debug then I discover that it is already is set to false in my 'if' block shown earlier:

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

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

I am out of ideas on how to handle this seemingly simple problem. In summary, I do not want to pass a model field to an Edit view and I want that field (CreatedDate, in this example) to maintain its original value when the other Edit fields from the view are posted back and persisted to the database using db.SaveChanges().

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

Any help/thoughts would be most appreciated.

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

Thank you.

谢谢你。

回答by Mister Epic

You should leverage ViewModels:

您应该利用 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; }
}

The intention of a ViewModel is a bit different than that of a domain model. It provides the view with just enough information it needs to render properly.

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

ViewModels can also retain information that doesn't pertain to your domain at all. It could hold a reference to the sorting property on a table, or a search filter. Those certainly wouldn't make sense to put on your domain model!

ViewModel 还可以保留根本不属于您的域的信息。它可以保存对表的排序属性或搜索过滤器的引用。那些放在你的领域模型上肯定没有意义!

Now, in your controllers, you map properties from the ViewModels to your domain models and persist your changes:

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

Furthermore, ViewModels are great for amalgamating data from multiple models. What if you had a details view for your warranties, but you also wanted to see all servicing done under that warranty? You could simply use a ViewModel like this:

此外,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 are simple, flexible, and very popular to use. Here is a good explantion of them: http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/

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

You're going to end up writing a lot of mapping code. Automapperis awesome and will do most of the heavy lifting: http://automapper.codeplex.com/

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

回答by cheny

This is not an answer for the questions, but it might be critical for those who is using Bind() and facing different problems. When I was searching "why Bind() clears out all pre-existing but not-bound values", I found this:

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

(in the HttpPost Edit()) The scaffolder generated a Bind attribute and added the entity created by the model binder to the entity set with a Modified flag. That code is no longer recommended because the Bind attribute clears out any pre-existing data in fields not listed in the Include parameter. In the future, the MVC controller scaffolder will be updated so that it doesn't generate Bind attributes for Edit methods.

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

from a official page (last updated in 2015, March): 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

来自官方页面(上次更新于 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

According to the topic:

根据题目:

  1. Bind is not recommended and will be removed in the future from the auto-generated codes.

  2. TryUpdateModel() is now the official solution.

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

  2. TryUpdateModel() 现在是官方解决方案。

You can search "TryUpdateModel" in the topic for details.

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

回答by Moynul Haque Biswas

It may solve your problem

它可能会解决您的问题

In Model: Use ?

在模型中:使用 ?

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

After form submit:

表单提交后:

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

回答by stenlytw

+1 for cheny's answer. Use TryUpdateModel instead of Bind.

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


If you want to use View Model, you can use Automapper and configure it to skip null values so the existing data still exists in the domain model.

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

Example:

例子:

Model:

模型:

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

ViewModel:

视图模型:

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

Controller:

控制器:

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

回答by Mubarak

try to remove the Create date prompt text box in the Edit view. In my application, the scaffold generated Edit and Create Views contain the Primary key which is generated in the database.

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

回答by Evgeniy

Controller:

控制器:

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