Following this example .. http://msdn.microsoft.com/en-us/data/gg685489
I am running into issues with Delete functionality.
[HttpPost]
public ActionResult Delete(int id, Blog blog)
{
try
{
using (var db = new BlogDataEntities())
{
//Having issue here.. as soon as the next line is run in debug
//mode .. it goes to catch.. and returns a view with null values.
db.Entry(blog).State = System.Data.EntityState.Deleted;
db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
In the parameters I checked 'blog' does not get the actual blog model that needs to be deleted. All the other methods work fine (Edit, Delete (get)..etc..
but Delete post fails. Am I missing something? thanks in advance for the help.
EDIT:
view code
@model DBFirstMVC.Models.Blog
@{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>Blog</legend>
<div class="display-label">Title</div>
<div class="display-field">@Model.Title</div>
<div class="display-label">BloggerName</div>
<div class="display-field">@Model.BloggerName</div>
</fieldset>
@using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" /> |
@Html.ActionLink("Back to List", "Index")
</p>
}
EDIT 2:
Non Razor Code in view:
<% using (Html.BeginForm()) { %>
<p>
<input type="submit" value="Delete" /> |
<%: Html.ActionLink("Back to List", "Index") %>
</p>
<% } %>
EDIT 3: (I tried in aspx)
<% using (Html.BeginForm()) { %>
<p>
<%=Html.DisplayForModel();%> //Tried Html.EditorForModel also..
<input type="submit" value="Delete" /> |
<%: Html.ActionLink("Back to List", "Index") %>
</p>
<% } %>
FINAL EDIT (Corrected Solution)
@model DBFirstMVC.Models.Blog
@{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
@using (Html.BeginForm()) {
<p>
<fieldset>
<legend>Blog</legend>
<div class="display-label">Title</div>
<div class="display-field">@Model.Title</div>
<div class="display-label">BloggerName</div>
<div class="display-field">@Model.BloggerName</div>
<input type="submit" value="Delete" /> |
@Html.ActionLink("Back to List", "Index")
</fieldset>
</p>
}
The context probably doesn't have an Entry for your Blog because it isn't attached to the Context.
You probably need to retrieve the Blog first and then mark it as deleted using the Entry
method:
[HttpPost]
public ActionResult Delete(int id, Blog blog)
{
try
{
using (var db = new BlogDataEntities())
{
// retrieve the blog from the database
var realBlog = db.Blogs.Find(blog.Id);
// nothing to do here, just redirect
if( realBlog == null )
return RedirectToAction("Index");
// since you have the entity just do this instead:
db.Blogs.Remove(realBlog);
db.SaveChanges();
}
return RedirectToAction("Index");
}
catch( Exception )
{
return View();
}
}
I don't really agree with the idea of using your entities as your models though. You should use View Models instead.
EDIT
Since you now are saying that Blog isn't being passed, try this:
@model Blog
@using ( Html.BeginForm() )
{
@Html.EditorForModel()
<input type="submit" value="Delete" />
}
You weren't actually giving the model binder any of the details it needed to construct your model.
is it possible to try the following:
[HttpPost]
public ActionResult Delete(Blog deletedBlog)
{
try
{
using (var db = new BlogDataEntities())
{
// get blog entry from db context!!
Blog blog = db.Blogs.Find(deletedBlog.Id);
//Having issue here.. as soon as the next line is run in debug
//mode .. it goes to catch.. and returns a view with null values.
db.Entry(blog).State = System.Data.EntityState.Deleted;
db.SaveChanges();
}
return RedirectToAction("Index");
}
catch(Exception e)
{
// should catch more specific exception
// but catching 'e' should be a start
return View();
}
}
[Update] - pass in your Blog model from the view, tho as Dismissile says, you should really use a viewmodel, rather than the entity model for this purpose.
Also, you should catch the inner exception message and examine that for further clues.
The blog
parameter in your Delete
Action is null most likely because you are only posting the blog's id, not the entire blog object. I would either modify the Delete Action to accept just the id (per Dismissile's answer), or modify the Delete View to post the entire blog object and remove the id from the Action (since it belongs to the blog object):
[HttpPost]
public ActionResult Delete(Blog blog)
{
try
{
using (var db = new BlogDataEntities())
{
db.Entry(blog).State = System.Data.EntityState.Deleted;
db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
I posted this in a few comments, but I felt it merited a separate answer for an "alternative".
Your controllers should be slim and adhere as best as possible to the single responsibility principle.
BlogController
public class BlogController : Controller
{
private BlogService blogService;
public BlogService()
{
blogService = new BlogService();
}
[HttpPost]
public ActionResult Delete(int id)
{
// make sure the user has permission to delete before actually deleting
// now that we know the user has permission
if (blogService.Delete(id))
{
return RedirectToAction("Index");
}
else
{
return View();
}
}
}
Now you'll have a reusable service layer that adheres to the single responsibility principle.
BlogService
public class BlogService
{
private BlogDataEntities dc;
public BlogService()
{
dc = new BlogDataEntities();
}
public bool Delete(int Id)
{
try
{
var blog= (from b in dc.Blogs where Blog.ID == Id select b).First();
// blog doesn't exist, exit and return false.
if( blog == null )
return false;
// blog exists, remove it
dc.Blogs.Remove(blog);
// push the delete to the database
SaveChanges();
// it worked, return true.
return true;
}
catch(System.Exception ex)
{
// an error happened, handle it and return false.
return false;
}
}
// I like to keep my `SubmitChanges()` Separate in case I need to
// stack up a few processes before hitting the database.
public void SaveChanges()
{
dc.SaveChanges();
}
}