Missing something in "Delete" post method in MVC (EF 4.1) - asp.net-mvc

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();
}
}

Related

how to return to filled out form from other view in mvc

I am not very experienced in programming, so I apologize in advance if this is a very obvious question.
I have a form in which the user types in his information. It is validated in the HttpPost then sent to another page where the contact information are shown so the user can check, if everything is in order.
if not there is a button for going back to the previous form to correct this.
Problem: Everything works perfect, except the going back for correction.
How can I go back to the filled out form?
Below is my code so far:
Index-Controller:
public ActionResult DefaultForm(QuestionViewModel viewModel)
{
//method i wrote for populating the dropdown
dropdownPopulate(viewModel);
return View(viewModel);
}
[HttpPost]
public ActionResult DefaultForm(QuestionViewModel viewModel, string tbButton)
{
if (ModelState.IsValid)
{
try
{
if (tbButton.Equals("questsend"))
{
return View("Verify", viewModel);
}
else if (tbButton.Equals("questupload"))
{
return View(viewModel);
}
else
{
dropdownPopulate(viewModel);
return View("DefaultForm", viewModel);
}
}
catch
{
dropdownPopulate(viewModel);
return View(viewModel);
}
}
else
{
dropdownPopulate(viewModel);
return View(viewModel);
}
}
Verify-Controller:
public ActionResult Verify(QuestionViewModel viewModel)
{
return View(viewModel);
}
[HttpPost]
public ActionResult Verify(QuestionViewModel viewModel, string tbButton)
{
#region button verarbeitung
if (tbButton.Equals("questkorr"))
{
return RedirectToAction("DefaultForm", viewModel);
}
else if (tbButton.Equals("questsend"))
{
return RedirectToAction("SomeOtherPage");
}
#endregion
return View(viewModel);
}
Those are the start and end in the view:
Defaultform
#using (Html.BeginForm("DefaultForm", "QuestionForm", FormMethod.Post, new { id = "questionform" }))
{
<button class="btn btn-default btnusz" type="submit" name="tbButton" value="questsend">Senden</button>
Verify:
#using (Html.BeginForm("Verify", "QuestionForm", FormMethod.Post, new { id = "verifyform" }))
{
<button class="btn btn-default btnusz" type="submit" name="tbButton" value="questkorr">Korrigieren</button>
<button class="btn btn-default btnusz" type="submit" name="tbButton" value="questsend">Senden</button>
If i need to add more code or information to the question, please tell me.
You could place an extra method in your controller where you create an instance of your model and populate it with the information, then use that model to show the details, if you need to correct them, simply call the "New" page and pass that model. If you don't need to correct anything, use the model to persist the data.
Or an easier way, you could try with javascript:
Go back

Clear fields after success

I have a page with 2 input type=text..
#model MVC3.ViewModel.TalkToUsVM
#using (Html.BeginForm())
{
<ul>
<li>#Html.TextBoxFor(m => m.TalkToUsRequest.Name)</li>
<li>#Html.TextBoxFor(m => m.TalkToUsRequest.Email)</li>
</ul>
<input type="submit" value="Save"/>
}
in my controller I do this:
[HttpPost]
public ActionResult Create(TalkToUsRequest talkToUsRequest)
{
var vm = new TalkToUsVM();
if (TryValidateModel(talkToUsRequest))
{
vm.Result = "Success";
return View("Create",vm);
}
vm = new TalkToUsVM
{
Result = "Errrooooooor",
TalkToUsRequest = talkToUsRequest
};
return View(vm);
}
so the problem.. when my model is valid, I set the result to "Success" and in this point vm.TalkToUsRequest is null.. but when page is rendered, all fields are with the same value that when I submited.. even I setting vm.TalkToUsRequest = null!!
How can I clear this fields?
So in this scenario you have to clear your model state if you return back to the same view.
Try following:
ModelState.Clear();
return View(vm);
}
Your answer :
TryUpdateModel(yourmodelname);
and it will update your view state
and if you also want to clear all Modelstate.error at the same time you can
also use :
ModelState.Clear();

Why is my ASP.NET MVC Edit Form not retaining data?

On my edit view, the information doesn't seem to be displayed on the form in the textboxes. Any idea why this is happening? Any help would be greatly appreciated.
Here's how my edit functions inside the controller look like:
[HttpGet]
[Authorize(Roles = "Admin")]
public ActionResult Edit(int id)
{
var logic = new ContactBUS();
var user = logic.GetContact(id);
var mUser = Membership.GetUser(user.Username);
bool memUserExists = doesUserExist(mUser);
if (memUserExists)
{
var model = new RoleListViewModel
{
AllRoles = Roles.GetAllRoles().ToList()
};
return View(model);
}
return View(logic.GetContact(id));
}
[HttpPost]
[Authorize(Roles = "Admin")]
public ActionResult Edit(Contact contact)
{
var logic = new ContactBUS();
if (ModelState.IsValid)
{
logic.EditContact(contact);
return RedirectToAction("List");
}
else
return View(contact);
}
}
Edit.cshtml:
#model ContactWeb.Models.RoleListViewModel
<h2>Edit</h2>
<div style="float:left;width:350px;">
#{Html.RenderPartial("Form", new ContactWebLibrary.Contact());}
</div>
and Form.cshtml:
#model ContactWebLibrary.Contact
#using (Html.BeginForm()) {
<input type="hidden" value="#Model.Id" />
<fieldset id="ContactEditor">
<legend>Fields</legend>
<div>
#Html.LabelFor(c=>c.FirstName, "First Name")
#Html.TextBoxFor(c=>c.FirstName)
#Html.ValidationMessageFor(c=>c.FirstName)
</div>
<div>
#Html.LabelFor(c=>c.LastName, "Last Name")
#Html.TextBoxFor(c=>c.LastName)
#Html.ValidationMessageFor(c=>c.LastName)
</div>
...
<input type="submit" value="#(Model.Id == 0 ? "Create" : "Edit" )" />
</fieldset>
}
If memUserExists is true then a new RolesListViewModel is passed to the Edit view. This in turn passes a brand new Contact model to the partial view each time this partial view is rendered:
#{Html.RenderPartial("Form", new ContactWebLibrary.Contact());}
So the contact used in the partial will not contain any information to display, hence, no values are being displayed.
Does logic.GetContact(id) return a RoleListViewModel? Otherwise, when memUserExists is false, I don't think the following line would work when returning the Edit view:
return View(logic.GetContact(id));
And also, the following line in your [HttpPost]:
return View(contact);
This passes a Contact object to a view that is expecting a RoleListViewModel.
Hope this helps.

How do I create a httppost getting same parameters from httpget?

I have a controller to show up a model (User) and want to create a screen just with a button to activate. I don't want fields in the form. I already have the id in the url. How can I accomplish this?
Use [ActionName] attribute - this way you can have the URLs seemingly point to the same location but perform different actions depending on the HTTP method:
[ActionName("Index"), HttpGet]
public ActionResult IndexGet(int id) { ... }
[ActionName("Index"), HttpPost]
public ActionResult IndexPost(int id) { ... }
Alternatively you can check the HTTP method in code:
public ActionResult Index(int id)
{
if (string.Equals(this.HttpContext.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase))
{ ... }
}
A bit late to the party on this but I found an easier solution to what I think is a fairly common use-case where you prompt on GET ("are you sure you want to blah blah blah?") and then act on POST using the same argument(s).
The solution: use optional parameters. No need for any hidden fields and such.
Note: I only tested this in MVC3.
public ActionResult ActivateUser(int id)
{
return View();
}
[HttpPost]
public ActionResult ActivateUser(int id, string unusedValue = "")
{
if (FunctionToActivateUserWorked(id))
{
RedirectToAction("NextAction");
}
return View();
}
On a final note, you can't use string.Empty in place of "" because it must be a compile-time constant. And it's a great place to put funny comments for someone else to find :)
You could use a hidden field inside the form:
<% using (Html.BeginForm()) { %>
<%= Html.HiddenFor(x => x.Id) %>
<input type="submit" value="OK" />
<% } %>
or pass it in the action of the form:
<% using (Html.BeginForm("index", "home",
new { id = RouteData.Values["id"] }, FormMethod.Post)) { %>
<input type="submit" value="OK" />
<% } %>
My approach is not to add an unused parameter as that seems like it would cause confusion, and is in general bad practice. Instead, what I do is append "Post" to my action name:
public ActionResult UpdateUser(int id)
{
return View();
}
[HttpPost]
public ActionResult UpdateUserPost(int id)
{
// Do work here
RedirectToAction("ViewCustomer", new { customerID : id });
}
The easiest way for such simple situation is to give a name to submit button and check in action if it has value or not.
If it has the value, then it Post action, if not, then it Get action :
<% using (Html.BeginForm("index", "home",
new { id = RouteData.Values["id"] }, FormMethod.Post)) { %>
<input type="submit" value="OK" name="btnActivate" />
<% } %>
For Cs you can combine get and post controller methods in one:
public ActionResult Index(int? id, string btnActivate)
{
if (!string.IsNullOrEmpty(btnActivate))
{
Activate(id.Value);
return RedirectToAction("NextAction");
}
return View();
}

How do I redirect to the previous action in ASP.NET MVC?

Lets suppose that I have some pages
some.web/articles/details/5
some.web/users/info/bob
some.web/foo/bar/7
that can call a common utility controller like
locale/change/es or authorization/login
How do I get these methods (change, login) to redirect to the previous actions (details, info, bar) while passing the previous parameters to them (5, bob, 7)?
In short: How do I redirect to the page that I just visited after performing an action in another controller?
try:
public ActionResult MyNextAction()
{
return Redirect(Request.UrlReferrer.ToString());
}
alternatively, touching on what darin said, try this:
public ActionResult MyFirstAction()
{
return RedirectToAction("MyNextAction",
new { r = Request.Url.ToString() });
}
then:
public ActionResult MyNextAction()
{
return Redirect(Request.QueryString["r"]);
}
If you want to redirect from a button in the View you could use:
#Html.ActionLink("Back to previous page", null, null, null, new { href = Request.UrlReferrer})
If you are not concerned with unit testing then you can simply write:
return Redirect(ControllerContext.HttpContext.Request.UrlReferrer.ToString());
A suggestion for how to do this such that:
the return url survives a form's POST request (and any failed validations)
the return url is determined from the initial referral url
without using TempData[] or other server-side state
handles direct navigation to the action (by providing a default redirect)
.
public ActionResult Create(string returnUrl)
{
// If no return url supplied, use referrer url.
// Protect against endless loop by checking for empty referrer.
if (String.IsNullOrEmpty(returnUrl)
&& Request.UrlReferrer != null
&& Request.UrlReferrer.ToString().Length > 0)
{
return RedirectToAction("Create",
new { returnUrl = Request.UrlReferrer.ToString() });
}
// Do stuff...
MyEntity entity = GetNewEntity();
return View(entity);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(MyEntity entity, string returnUrl)
{
try
{
// TODO: add create logic here
// If redirect supplied, then do it, otherwise use a default
if (!String.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
else
return RedirectToAction("Index");
}
catch
{
return View(); // Reshow this view, with errors
}
}
You could use the redirect within the view like this:
<% if (!String.IsNullOrEmpty(Request.QueryString["returnUrl"])) %>
<% { %>
Return
<% } %>
In Mvc using plain html in View Page with java script onclick
<input type="button" value="GO BACK" class="btn btn-primary"
onclick="location.href='#Request.UrlReferrer'" />
This works great. hope helps someone.
#JuanPieterse has already answered using #Html.ActionLink so if possible someone can comment or answer using #Url.Action
I'm using .Net Core 2 MVC , and this one worked for me,
in the controller use
HttpContext.Request.Headers["Referer"];
Pass a returnUrl parameter (url encoded) to the change and login actions and inside redirect to this given returnUrl. Your login action might look something like this:
public ActionResult Login(string returnUrl)
{
// Do something...
return Redirect(returnUrl);
}
You could return to the previous page by using ViewBag.ReturnUrl property.
To dynamically construct the returnUrl in any View, try this:
#{
var formCollection =
new FormCollection
{
new FormCollection(Request.Form),
new FormCollection(Request.QueryString)
};
var parameters = new RouteValueDictionary();
formCollection.AllKeys
.Select(k => new KeyValuePair<string, string>(k, formCollection[k])).ToList()
.ForEach(p => parameters.Add(p.Key, p.Value));
}
<!-- Option #1 -->
#Html.ActionLink("Option #1", "Action", "Controller", parameters, null)
<!-- Option #2 -->
Option #2
<!-- Option #3 -->
Option #3
This also works in Layout Pages, Partial Views and Html Helpers
Related: MVC3 Dynamic Return URL (Same but from within any Controller/Action)
For ASP.NET Core
You can use asp-route-* attribute:
<form asp-action="Login" asp-route-previous="#Model.ReturnUrl">
Other in details example:
Imagine that you have a Vehicle Controller with actions
Index
Details
Edit
and you can edit any vehicle from Index or from Details, so if you clicked edit from index you must return to index after edit
and if you clicked edit from details you must return to details after edit.
//In your viewmodel add the ReturnUrl Property
public class VehicleViewModel
{
..............
..............
public string ReturnUrl {get;set;}
}
Details.cshtml
<a asp-action="Edit" asp-route-previous="Details" asp-route-id="#Model.CarId">Edit</a>
Index.cshtml
<a asp-action="Edit" asp-route-previous="Index" asp-route-id="#item.CarId">Edit</a>
Edit.cshtml
<form asp-action="Edit" asp-route-previous="#Model.ReturnUrl" class="form-horizontal">
<div class="box-footer">
<a asp-action="#Model.ReturnUrl" class="btn btn-default">Back to List</a>
<button type="submit" value="Save" class="btn btn-warning pull-right">Save</button>
</div>
</form>
In your controller:
// GET: Vehicle/Edit/5
public ActionResult Edit(int id,string previous)
{
var model = this.UnitOfWork.CarsRepository.GetAllByCarId(id).FirstOrDefault();
var viewModel = this.Mapper.Map<VehicleViewModel>(model);//if you using automapper
//or by this code if you are not use automapper
var viewModel = new VehicleViewModel();
if (!string.IsNullOrWhiteSpace(previous)
viewModel.ReturnUrl = previous;
else
viewModel.ReturnUrl = "Index";
return View(viewModel);
}
[HttpPost]
public IActionResult Edit(VehicleViewModel model, string previous)
{
if (!string.IsNullOrWhiteSpace(previous))
model.ReturnUrl = previous;
else
model.ReturnUrl = "Index";
.............
.............
return RedirectToAction(model.ReturnUrl);
}

Resources