在提交信息时,模型属性值为空。

3

我有一个简单的应用程序,它基于asp.net教程中使用的MVC音乐商店,允许用户输入他们正在观看的电视节目的详细信息,以便记录每个节目更新到哪一集。

我的应用程序中的主要实体是Show:

Public class Show
{
      // a few automatic properties
      public int id { get; set; }
      public Genre Genre { get; set; }
}

而且还有Genre类
Public partial class Genre
{
    public int GenreId { get; set; }
    public string Name { get; set; }
    public List<Show> Shows { get; set; }
}

我的控制器中的POST编辑方法是由VS自动生成的:

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(Show show)
    {
        if (ModelState.IsValid)
        {
            db.Entry(show).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(show);
    }

当我在调试器中检查数据库时,我可以看到Show对象(无论数据库中有多少个,我通过表单种子设置了两个,但通常会添加第三个)都有我输入的流派,但是在编辑操作返回视图后,Genre为空,而所有其他值都已更新。为什么会这样?
我认为可能是genre字段为空,因为我没有进行实例化,但我在哪里进行实例化呢?我不希望每次设置Show流派时都创建一个新的流派。我只想让节目具有流派引用,以便最终允许用户选择流派并查看他们关注的该流派的所有节目。
编辑:这是我的编辑视图。详情视图看起来也非常相似。
@model watchedCatalogue2.Models.Show

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>@Html.DisplayFor(m => m.Name)</legend>

        @Html.HiddenFor(m => m.id)

        <div class="editor-label">
            @Html.LabelFor(m => m.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(m => m.Name)
            @Html.ValidationMessageFor(m => m.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(m => m.Genre)
        </div>
        <div class="editor-field">
            @Html.EditorFor(m => m.Genre.Name)
            @Html.ValidationMessageFor(m => m.Genre.Name)
        </div>

        <div class="editor-label">
            @Html.Label("Number of episodes")
        </div>
        <div class="editor-field">
            @Html.EditorFor(m => m.NoEpisodes)
            @Html.ValidationMessageFor(m => m.NoEpisodes)
        </div>

        <div class="editor-label">
            @Html.Label("Number of episodes watched")
        </div>
        <div class="editor-field">
            @Html.EditorFor(m => m.NoEpisodesWatched)
            @Html.ValidationMessageFor(m => m.NoEpisodesWatched)
        </div>

        <div class="editor-label">
            @Html.Label("Watching State")
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(m => m.State,      watchedCatalogue2.Models.Show.WatchStateItems)
        </div>

        <div class="editor-label">
            @Html.LabelFor(m => m.Description)
        </div>
        <div class="editor-field">
            @Html.EditorFor(m => m.Description)
            @Html.ValidationMessageFor(m => m.Description)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>

此外,为了确保正确,我控制器中的db指的是我的CatalogueEntities类:
namespace watchedCatalogue2.Models
{
    public class CatalogueEntities : DbContext
    {
        public DbSet<Genre> Genres { get; set; }
        public DbSet<Show> Shows { get; set; }
    }
}

@AndrewBacker,GenreId在Genre对象上。Show对象只是对Genre对象的引用。在我的编辑视图中,您应该能够看到我正在做什么。我试图将用户输入的内容绑定到Show.Genre.Name。即使在db.SaveChanges()之后,它似乎也无法保留,而我认为它会将任何新内容写入数据库。我发现在调用db.dispose之后(当然是在SaveChanges之后),流派为空。 - Brendon Robey
帖子更新得很好。我认为现在回答可能会更容易 :) - Andrew
另外需要注意的是,我不会使用EditorFor,而是使用TextboxFor等。如果您有自定义编辑器,例如针对DateTime(尽管我倾向于使用jQuery),或者自定义模型,则可以使用。 - Andrew
我无法确切地回忆起为什么,除了使用TextboxFor可以添加额外的HTML属性之外,但我们一直遇到一些边角情况,这些情况让我们回到了TextboxFor。编辑器是可以的,但需要编写自定义模板(复杂),最终导致视线不清。实际上,现在我们所有的应用程序都使用像TextboxFor这样的自定义助手来保持简单(它们通常在内部调用TextboxFor等)。EditorFor在理论上很好,但我看不到有必要全局替换<input type =“text”> - Andrew
还有一件事,EditorFor是“智能”的,并为您选择编辑器/模板。这只是多余的间接层次,会丢失一些功能。如果我想要一个文本框,我希望在html中说“textboxFor”。然后我们有了一个DropdownFor用于下拉列表等,而无需数据注释。现在我们的html告诉我们将要呈现的内容。 - Andrew
显示剩余2条评论
3个回答

2

问题可能在于MVC中,并不是所有的数据都被保留/发布到POST方法中,而只有那些在视图中呈现为可编辑元素并包含在模型中的数据。

最简单的解决方法是将其呈现为隐藏字段,如下所示:

MVC 2.0及更早版本:

<%: Html.HiddenFor(m => m.GenreId) %>

Razor:

@Html.HiddenFor(m => m.GenreId)

如果您使用AJAX或类似的技术,您也可以将该值作为参数发送/传递。同时请注意,如果您使用ModelState.IsValid功能,可能会将隐藏字段渲染为空值,并且很可能会出现问题。要解决此问题(这并不总是建议),只需在代码中添加ModelState.Remove("TheKey");,在您的情况下可能是ModelState.Remove("GenreID");

如果我正确理解了你的回答,你是说除非数据在视图上(无论可见还是隐藏),否则数据不会被保留。我已经更新了我的原始问题,包括我从中获取数据的视图,并且那里有一个可见的GenreId字段,所以如果我错了,请纠正我,隐藏字段是不需要的。 - Brendon Robey
1
我在视图中没有看到隐藏字段用于流派ID。如果没有隐藏字段,浏览器就不会将GenreID重新提交给控制器。此外,在视图中不使用数据库实体,并创建一个自定义模型类(验证、模型绑定等)是一个好主意。如果只是显示数据而不进行编辑等操作,则可以直接使用数据库实体。 - Andrew
请原谅我的无知,但在我的视图顶部,我有“@model watchedCatalogue2.Models.Show”。这不是告诉视图我的模型是Show类吗?那么我不是在引用模型类而不是DB实体(我相信是“Shows”(复数)和“Genres”)?另外,为什么GenreID需要在表单中呢?我有一个可见的字段用于Genre名称,它是genre类的属性。框架是否需要特定的ID字段,以便与数据库映射相关的某些原因? - Brendon Robey
我已经进行了一些尝试,通过在编辑视图中添加一个隐藏字段GenreID,POST编辑方法从未进入if循环,因为ModelState.IsValid现在为false。当我查看ModelStateDictionary时,它有许多键,其中之一是仍然为空的GenreID。 - Brendon Robey
补充我的上一个评论,ModelState字典还包含在表单中输入的值,但它不是null。只有GenreID是null,但我不知道该怎么办。 - Brendon Robey
在我的答案底部添加了关于ModelState.IsValid的内容。如果您永远不打算编辑或验证此字段,那么可以删除ModelState键吗? - EternalWulf

1
如果我理解问题正确的话,您需要以某种方式在表单上呈现它,以便数据得以维护,要注意Web是无状态的,如果值在提交表单时不存在,则不会绑定回到您的视图模型中。
在您的视图中,在表单中包含以下内容:
@Html.HiddenFor(model => model.GenreId)
或者根据您在Get请求中URL的设置,您可以更改POST方法签名以包括id。

请看我的评论,针对HowlinWulf的回答。我已经在表单上呈现了它,但是当db上下文被释放时,数据似乎仍然被丢弃了。 - Brendon Robey
抱歉,使用更新代码时,您需要为已有的Id添加hiddenfor,但我建议使用以下代码:@Html.HiddenFor(model => model.Genre.GenreId)。请注意,按照惯例,每个表的主键通常称为“Id”,而不是实体名称。 - LightningShield
啊,谢谢你。这样读起来更好。我已经玩了几个小时了。它没有解决问题,流派名称仍未保存到数据库中,但它已经改变了。ModelState现在无效,这会阻止编辑操作进入if循环以保存更改到数据库中,但从我所看到的来看,它是无效的,因为Genre为空,所以我面临着一个非常相似的问题。 - Brendon Robey

0
经过一些研究,我注意到示例中的许多模型类都引用了其他标记为虚拟的模型。我想我没有什么可失去的,所以我修改了我的 Show 类,使得 public Genre Genre { get; set; } 现在变成了 public virtual Genre Genre { get; set; }。突然间一切都正常了。如果有人知道为什么它有效,能否向我解释一下,因为我迄今看到的只是关于延迟加载的引用,与我的问题无关。

感谢大家的帮助。


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