Reputation: 63
I have a simple application that I've loosely based off the MVC Music store used in a tutorial on asp.net that allows a user to enter details about TV shows that they are watching so they can keep track of which episode they're up to in each show.
The main entities in my application are Show:
Public class Show
{
// a few automatic properties
public int id { get; set; }
public Genre Genre { get; set; }
}
and the Genre class
Public partial class Genre
{
public int GenreId { get; set; }
public string Name { get; set; }
public List<Show> Shows { get; set; }
}
My POST edit method in my controller was generated by VS for me:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Show show)
{
if (ModelState.IsValid)
{
db.Entry(show).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(show);
}
When I inspect db in the debugger, I can see that the Show objects (however many are in the database at the time. I seed it with two but usually add a third via the form) all have the genres I've entered but after the Edit action returns the view, the Genre is null while all the other values are updated. Why is this?
I thought perhaps the genre field was null because I wasn't instantiating them but where would I do that? I don't want a new genre created every time a Show's genre is set. I just want a show to have a genre reference so that I can eventually allow the user to select a genre and see all the shows they're following of that genre.
EDIT: Here's my Edit View. The Details view also looks very similar.
@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>
Also, for good measure, db in my controller refers to my CatalogueEntities class:
namespace watchedCatalogue2.Models
{
public class CatalogueEntities : DbContext
{
public DbSet<Genre> Genres { get; set; }
public DbSet<Show> Shows { get; set; }
}
}
Upvotes: 3
Views: 472
Reputation: 773
The problem is probably the fact that in MVC all the data is not retained/posted onto the POST method, but only those rendered as an editable element on the View and contained in the Model.
The simplest work-around will be to render it as a hidden field, as follows:
MVC 2.0 and earlier:
<%: Html.HiddenFor(m => m.GenreId) %>
Razor:
@Html.HiddenFor(m => m.GenreId)
If you use AJAX or something similar, you could also just send/pass the value as a parameter.
Also note, if you make use of the ModelState.IsValid functionality, it is possible and very probable that the Hidden fields would be rendered as null, and most probably as a problem. To remove this, which is not always advised, just add ModelState.Remove("TheKey");
, in your case it is probably, ModelState.Remove("GenreID");
Upvotes: 2
Reputation: 63
After some research, I noticed many of the model classes in the examples had references to other models that were marked as virtual. I figured I had nothing to lose so I modified my Show class so public Genre Genre { get; set; }
now reads public virtual Genre Genre { get; set; }
. Suddenly it all works. I'd appreciate if someone who knows why it works could explain it to me because all I've seen thus far have been references to lazy loading but nothing relating to my problem.
Thanks for the help, everyone.
Upvotes: 0
Reputation:
If I'm following the problem right, you need to render it on the form in some way for the data to be maintained, bearing in mind the web is stateless, if the value isn't present when the form is posted, it won't be bound back on to your view model.
On your view include in the form:
@Html.HiddenFor(model => model.GenreId)
Or depending on what your URL is like on your Get, you could change your POST method signature to include the id.
Upvotes: 1