6

I am trying to update a news post. The post has a date field called Created that is populated when the record is initially created. I don't include this when updating, so when using the below method, this is null and throws an error.

I am using MVC 5 and Entity Framework 6

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Title,Summary,Content")] Post post) {
    if (ModelState.IsValid) {
        db.Entry(post).State = EntityState.Modified;
        db.SaveChanges();

        return RedirectToAction("Index");
    }
    return View(post);
}

This method does work but it seems a bit clunky.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Title,Summary,Content")] Post post) {
    if (ModelState.IsValid) {
        var newsPost = db.Posts.Find(post.Id);
        if (newsPost == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); }
        newsPost.Title = post.Title;
        newsPost.Summary = post.Summary;
        newsPost.Content = post.Content;
        db.Entry(newsPost).State = EntityState.Modified;
        db.SaveChanges();

        return RedirectToAction("Index");
    }
    return View(post);
}

What is the best practice method of doing this?

Thanks!

Unw0und
  • 171
  • 1
  • 5
  • 1
    I believe using something like AutoMapper could be a way to go. – kamil-mrzyglod Jun 16 '15 at 10:53
  • 3
    With EF6 you should be able to use `db.Entry(post).State = EntityState.Modified;`followed by `db.Entry(post).Property(x => x.Created).IsModified = false;` before saving –  Jun 16 '15 at 10:55
  • 1
    I would look at the ViewModel pattern (http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx). Compose a view model, send it to your HttpGet method, post back to HttpPost method, validate, update your entity model. As Kamo pointed out, Automapper is great for this. Then you can use Html.HiddenFor on fields you don't want to update. – Steve Greene Jun 16 '15 at 15:35
  • @SteveGreene, thanks for the comment. I would like to avoid Html.HiddenFor for fields that I don't want to be modified as it is trivial for these to be changed through proxies or browser plugins etc. – Unw0und Jun 17 '15 at 01:48
  • 1
    [View Models](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc) solve this problem so you don't have to pass hidden fields. – Jasen Jun 17 '15 at 04:38

3 Answers3

7

EF also has a simple built-in "AutoMapper" that works with scalar values.

public class PostViewModel()
{
     public string Id {get;set;}
     public string Title {get;set;}
     public string Summary {get;set;}
     public string Content {get;set;}
}

public ActionResult Edit(PostViewModel viewModel)
{
    if (ModelState.IsValid) {
        var newsPost = db.Posts.Find(post.Id);
        ...
        db.Entry(newsPost).CurrentValues.SetValues(viewModel);
        ...
    }
}
jamesSampica
  • 12,230
  • 3
  • 63
  • 85
0

I use a repository method. Works really well. It copies the column which are different. Sort of PATCH against PUT

public void CopyUpdate<T> (T modelorig, T model) where T : class
    {
        _context.Entry(model).CurrentValues.SetValues(modelorig);

        _context.Set<T>().Add(model);
        _context.Entry(model).State = EntityState.Modified;
        _context.SaveChanges();
    }
BobSpring
  • 69
  • 1
  • 1
0

The "correct" way to do this, assuming you want to stick to Rest. Is to use HttpPatch.

https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-6.0

[HttpPatch]
[ValidateAntiForgeryToken]
public async Task<ActionResult> PatchAsync([FromBody] JsonPatchDocument<Post> patchDoc, int id) {
    
    var post = db.Find(id);
    patchDoc.ApplyTo(post, ModelState);

    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    await db.SaveChangesAsync();

    return new ObjectResult(post);
}
Aron
  • 15,464
  • 3
  • 31
  • 64