5
votes

I can't figure out how to have navigation properties for my model (EF4) not be null after I post. I have the same problem on several basic pages.

i.e. I have a table Estimate with a foreign key of LoginID (from the Login table).

In my HTTPGet Edit method, I find the specific estimate I want:

Estimate estimate = db.Estimates.Find(id)

From here I can access the Login navigation property and any of the properties of that record from the Login table.

I pass this estimate as the model for my strongly typed view. When my HTTPPost Edit method gets called (and the model is passed to it as a parameter), my Login navigation property is null and I can't access anything in it. I can get around it of course because I do have the LoginID field in my estimate class, but it feels really clunky and like I'm missing out on a key benefit of the entity framework.

I have this same problem on multiple pages where I'm passing a model with a navigation property to my view and the view is returning the model with a null navigation property.

Below is an example of the code I was having trouble with:

    [HttpPost]
    public ActionResult Edit(Estimate estimate)
    {

        var test = estimate.Login.CompanyProfileID;
        ...

I can access Model.Login and all of it's properties just fine in the view, so I know it's getting passed to the view properly. It just doesn't pass the navigation property back to the controller when my form submits.

4
Can you post view and model code? - uowzd01
Is your view binded to Navigation model or you have some viewmodel, that's why you need to show us the code for your view - uowzd01

4 Answers

6
votes

What is happening is that MVC uses something called 'Model Binding', which matches all the fields that POST passes in the page request to matching properties of the parameter of your action.

So, if the property is not included in the input fields in the POST page, it will not be bound to the parameter in your POST action, and thus, will be null.

So, you could either include a hidden field for every property in Login, e.g.

@Html.HiddenFor(m => m.Login.ID)
@Html.HiddenFor(m => m.Login.Name)

Or, just do as you describe in your workaround - ie requery the database based on the Id. Although this may seem clunky, this is a perfectly valid way of doing things, as it avoids having to pass hidden fields around all over the place.

1
votes

The Estimate object you're getting back is limited by the view's field (along with any any Bind attributes). Either add hidden fields for the properties you want OR reload the object from the context.

A note of caution (the whole reason I'm posting this answer to a long dead question) is that for EF6, if you reload the object from context after a .SaveChanges on a partially EntityState.Modified (because you were being clever) it will give you a CACHED and incompleted version of the entity. You have to do a context.Entity(estimate).Reload() instead of a .Find or .Where

0
votes

MVC does not have any concept of viewstate, or of automagically persisting data in the view. Any property you want returned to you via your POST must be present in the form as a form field. In this case, you'd need a hidden field to store the LoginID.

@Html.HiddenFor(m => m.LoginID)
0
votes

I have similar case as yours, In post action I pull original item from database, then depending on what I need to do, copy all properties that are edited to the item I pulled and save original with copied properties(the ones that might be edited by user), or fill posted model with missing properties and then saving it. I'm not sure if this is a correct way of doing it, but it works for me. When you pull original item from db you can the acesss any property you want(given that the user was not able to change it).

Example:

 [Authorize]
        public ActionResult Edit(int id)
        {
            var movie = movieService.GetMovieById(id);
            if (new UserService().Current().UserId == movie.UserID)
            {
                EditMovieModel model = new EditMovieModel(id);
                return View(model);
            }
            else
            {
                throw new System.Web.HttpException(403, "403 Forbidden");
            }
        }

        [HttpPost]
        [Authorize]
        public ActionResult Edit(Movie movie)
        {
            var realMovie = movieService.GetMovieById(movie.ID);
            if (new UserService().Current().UserId == realMovie.UserID)
            {
                realMovie.Text = HtmlSanitizer.sanitize(movie.Plot);
                realMovie.CategoryID = movie.CategoryID;
                movieService.Update(realMovie);
            }
            else
            {
                throw new System.Web.HttpException(403, "403 Forbidden");
            }
            return RedirectToAction("Movie", new { id = realMovie.ID, title = realMovie.Permalink });
        }