0

I am new to MVC3 and am trying to write a blog application as a learning tool.

I've created a database object for the blog post and generated a controller using the Controller with Read/Write actions and views using Entity Framework to control the entity.

I'm having troubles with the edit commands. There are about 6 properties for a blog post but I only want to allow the edit to modify the title and content of the post. My code is as follows:

public ActionResult Edit(int id)
    {
        blog_Post blog_post = db.blog_Post.Find(id);
        return View(blog_post);
    }

    //
    // POST: /Post/Edit/5

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

@model BlogVersion1._0.blog_Post

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
    <legend>blog_Post</legend>

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

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

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

<div>
@Html.ActionLink("Back to List", "Index")
</div>

The problem that comes about is in the public ActionResult Edit(blog_Post blog_post) method. In the Edit(int id) method, I have put a breakpoint in and I can see that blog_post is being properly passed to the view (including all of its properties populated).

But the blog_post being returned to the [HttpPost] method is missing properties for UserId, DateCreated, etc. An exception is obviously thrown on the db.SaveChanges call as required foreign keys are missing.

How do I ensure that all properties are returned to the second edit method to properly make the update?

Jesse Carter
  • 20,062
  • 7
  • 64
  • 101

4 Answers4

7

Because you are not sending the values of those elements from your form when you do the POST ing. One way to fix this is to keep them inside the form using Hidden Variables

@using(Html.BeginForm())
{
  @Html.EditorFor(model => model.Title)
  @Html.HiddenFor(model => model.UserId)
  <input type="submit" />
}

I think the clean solution is to "Dont use the Domain model in the view, Use a ViewModel with necessary properties for the View. In this case, obviously CreatedDate should not be something the View should supply. It should be something the code will be filled to the object.

So create a ViewModel for this

public class BlogPostViewModel
{
  public int ID { set;get;}
  public string Title { set;get;}
  public string Description { set;get;}  
}

and use this for transfering data from View to controller and viceversa

public ActionResult Edit(int id)
{
  var domainObject=repo.GetPost(id);
  var viewModel=new BlogPostViewModel();
  viewModel.ID=domainObject.ID;
  viewModel.Title=domainObject.Title;
 //map other REQUIRED properties also
  return View(viewModel);
}

Your view will be strongly typed to this

@model BlogPostViewModel
@using(Html.BeginForm())
{
  @Html.EditorFor(model => model.Title)
  @Html.HiddenFor(model => model.Description)
  <input type="submit" />
}

In the POST action,map it back to the domain object and save

[HttpPost]
public ActionResult Edit(BlogPostViewModel model)
{
 if(ModelState.IsValid)
 {
   var domainObject=new blog_Post();
   domainObject.Title=model.Title;

   domainObject.ModifiedDate=DateTime.Now;
   //set other properties also

   repo.Update(domainObject);
   return RedirecToAction("Success");
 }
 return View(model);
}

Instead of manually mapping properties one by one you can consider using AutoMapper library which does this for you in one line of code!

Shyju
  • 214,206
  • 104
  • 411
  • 497
3

Just add hidden fields for all other, non-editable properties.

@Html.HiddenFor(model => model.Id)

These field will be included in POST and hence, model binder will correctly put them into your blog_post instance.

On the other side, you should really be using view models - simple POCO classes that will be models for your views. Using entity models directly is not recommended.

Here's some info on that:
ASP.NET MVC ViewModel Pattern
http://stephenwalther.com/archive/2009/04/13/asp-net-mvc-tip-50-ndash-create-view-models.aspx

Community
  • 1
  • 1
Miroslav Popovic
  • 12,100
  • 2
  • 35
  • 47
1

The model binder will only populate the properties that are POSTed in the HTTP request. Your view only contains Title and PostContent.

You either need to include hidden fields for each of the missing properties. Or just the ID property and then do a database lookup for the rest.

greg84
  • 7,541
  • 3
  • 36
  • 45
1

For your case, I think You should rather use the HtmlHelper extension method "EditorForModel" instead of calling "EditorFor" for each property. You are complicating your life using EditorFor on each property (and as gred84 is saying, it doesn't post the non displayed properties in the HTTP request in you context).

In your blog_Post model class you should flag each property that you don't want to be edited with the attribute [HiddenInput(DisplayValue = false)]

Then instead of all your code in the view, you can simply have (simplified - without validation summary)

@using (Html.BeginForm())
{        
   @Html.EditorForModel()
   <input type="submit" value="Save" />   
} 
darkey
  • 3,672
  • 3
  • 29
  • 50