ASP.NET MVC How to Prevent re-POST - asp.net-mvc

Using MVC 4.
When an order is placed on our site the order is POSTed to:
[HttpPost]
public ActionResult ConfirmOrder(ABCModel model)
{
//Do Stuff
return View("ConfirmedOrder", model);
}
The user sees the Confirmed page.
If they press REFRESH in their browser, the page POSTs again.
Is there a way in MVC to prevent the POST again, perhaps in a redirect or some sort?

Instead of doing
return View("ConfirmedOrder", model)
separate your confirmation logic into a controller and do
return RedirectToAction("ConfirmOrderActionName").
Here your ConfirmOrderActionName controller can retrieve the order information from the data store and sent it to its own view, or your ConfirmedOrder view.
P.S.
Note that RedirectToAction() helper method also returns a type of ActionResult (just like returning a View() does).
If you're interested see:
MSDN: Controllers and Action Methods in ASP.NET MVC Applications and MSDN: ActionResult Class

You might want to redesign the logic a little bit. It is a command problem in Shopping Cart check out.
Here is how most Shopping Cart works -
Step 1. Cart (Create a Session here)
... Shipping, Payment and so on
Step 2: ConfirmOrder - Get (If no Session, redirect to Cart page.)
ConfirmOrder - Post (If no Session, redirect to Cart page. If valid and
check out successful, redirect to Complete page)
Step 3: Complete (Clear the Session)

Related

How can I make an MVC POST return me to the previous page?

I have the following action which is called from a screen with a list of records.
[HttpPost]
//[Authorize(Roles = "admin")]
public ActionResult Edit(EditViewModel itemView)
{
Once the action has completed I would like to return to the page where the action was called from. However I don't want to refresh that page. I just want to go back to the populated screen using something similar to the "Previous" button in the browser. Right now when I click "save" my action does the following which is not what I want:
return RedirectToAction("Index");
Is there some way to Redirect to previous page with MVC3?
Something like the Stackoverflow functionality after I click edit to edit an answer where it returns to the post.
Instead of a redirection,
[HttpGet]
public ActionResult Index()
{
/// preform any processing necessary for your index page on GET
return View("Index");
}
[HttpPost]
public ActionResuit Edit(EditViewModel itemView)
{
if (ModelState.IsValid)
{
/// do whatever you want with your model...
}
// return the contents as they'd be rendered by the Index action
return Index();
}
Note that with this method the URL in the browser will still display the Edit url (like /area_name/edit), but you can fix that by:
Using a redirect (which you've said you don't want to do)
Using JavaScript to update the URL, or use history.back() as #AlanStephens suggested
Probably other methods that don't immediately come to mind.
However, I'd question whether this is really the best approach. Typically, users expect different URLs to do different things.
Or, if I understand you correctly and the edit action is being called from the Index page,
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost] /// from a form on Index
public ActionResult Index(EditViewModel model)
{
if (ModelState.IsValid)
{
////
}
return View();
}
and just take the /Edit out of play entirely. Again, I don't really care for this approach.
Based off the code you've given, it looks like you've got a paginated screen, with the ability to click edit on each row. Here's how I've solved this problem in the past.
On the Index page, when the page loads, whether it be from the main index or a paging method, add the following:
Session["CurrentUrl"] = Request.Url.ToString();
So now, at the end of the POST method for your edit page, do:
return Session["CurrentUrl"] == null ?
Index() :
Redirect(Session["CurrentUrl"]);
What you described is easily achieved using ajax calls. That way you perform whatever action you like and afterwards (on successful response), you can easily navigate from the current page, using javascript.
If you POST to a page and in response you return the same view you receive with a GET request (index page), then some users might hit F5 to reload that index page and get a warning in the browser, which actually says it will send the POST request again. This is pretty confusing for users and not really user friendly (not to mention numerous concerns related to idem-potency of it).
Although you don't like the redirect approach, because of the additional response, I guess, I should say that, in MVC, this is the correct way to do it, assuming you don't want to use ajax calls.

How to have POST ActionHandler return the GET ActionHandler by a custom Attribute?

Our system is MVC2 authenticated by ADFS 2. So when a user clicks on a bookmark for http://www.foo.com/Car/Details/3, this hits our Car controller and calls our Details GET action handler and passes 3 in as the ID (all basic MVC stuff). So we have this ActionHandler decorated with a [Authorize] attribute and have ADFS2 hooked up, so the page then redirects to our auth server which then redirects back to our app, but with a POST (all basic ADFS stuff). The problem is that this redirect is a POST and therefore our Details POST handler is called, but clearly it doesn't have the data I need.
Now I have identified some code that detects this scenario and this code looks something like this:
[Authorize]
[MySpecialHttpGet]
public ActionResult Details(long id)
{
var model = GetModel(id);
return View(model);
}
[Authorize]
[MySpecialHttpPost]
public ActionResult Details(long id, ViewModel model)
{
/***START OF SPECIAL CODE***/
// If we were posted to by ADFS, redirect to the GET handler.
if (Request.Form["wa"] != null && Request.Form["wa"].ToLower().Contains("signin"))
{
// We were posted to here but need to respond with the GET view.
return Redirect(Request.Url.AbsoluteUri);
}
/***END OF SPECIAL CODE***/
var result = Something.SaveData(model);
return result.ActionResultToReturnWith;
}
The problem with this is that I need to do this on every single POST ActionHandler in the app and I really don't want to do that. Given that I already have custom attributes on all of these ActionHandlers, I would like to use those attributes to inject this functionality for me.
Now the MySpecialHttpGet and MySpecialHttpPost are nothing incredibly special that you really need to know about other than they extend ActionMethodSelectorAttribute. I would LIKE to add code in the MySpecialPost attribute to inject that functionality.
So my question:
How would I add code to perform this kind of check in this Attribute?
For now, we have not found the solution we wanted and are simply pasting that code (well, a function call with that code in it) at the beginning of EVERY controller.

Post Redirect Get in ASP.NET MVC And Validation With Restful URLs

I have a restful URL for the action of editing a page. This is implemented on the controller as an Edit method, which accepts GET requests and an Edit method that accepts POST requests.
This means you can visit the Edit URL and it will display a form for a GET or save a form for a POST.
[HttpGet]
public ActionResult Edit(int id) {
...
}
[HttpPost]
public ActionResult Edit(EditModel model) {
...
}
The Post-Redirect-Get (PRG) pattern seems pretty black and white, because it essentially redirects every POST back to a GET action. However, I need to be convinced that this is the correct thing to do.
My plan is that in the POST action, if the model is valid I will use the Post-Redirect-Get pattern to send the user to a reasonable location (probably the Index or Details action).
However, if there is a model validation problem, I still want to just display the view. I don't want to redirect the user, because it means stuffing the model and ModelState into temporary data and redirecting to the GET action - and then adding logic into the GET action to handle the temp data. I could avoid all of this by simply displaying the view.
Yes, if the user presses F5 it will re-submit the form and they will be presented with the "resubmission warning", but then the same page (asking them to fix validation errors). However, it seems unlikely that they will hit F5 and there is also no danger of a double-submission as the form will simply fail the validation once again.
If the validation passes, the user will be redirected and they will be safe from double submissions.
So should I implement additional code and stuff data into temp data in order to strictly follow the PRG pattern, or is it more sensible to use the PRG pattern when the form is valid and data is being stored?
You should only do the redirect if the form information is valid; in the case of submission errors, return the view from the same Edit method.
Doing it this way is compliant with PRG, because if your model is invalid, you are not allowing any changes to be made to the state of objects on the server. PRG is designed primarily to prevent multiple posts that can change the state of server objects (e.g., business objects, database tables, etc.) in unpredictable ways; in your validation example, however, the user can hit resubmit as many times as they want and they will always be sent back to the initial view with validation errors--nothing on the server changes. Therefore, you have it right: only issue the Redirect if your model passes validation in your presentation tier.
Even though Ken's answer does highlight an important fact - PRG doesn't necessarily mean "blindly return a redirect when posting" - you still might want to do redirect and preserve modelstate sometimes.
The easiest way to handle that scenario, is using action filters to export modelstate to the session (before redirecting), and then import modelstate (before executing the new action). Kazi Manzur Rashid has a couple of excellent blog posts (Part 1 Part 2) on best practices in ASP.NET MVC. They're quite old, but many of the tips there are still very much applicable. Tip number 13 in the first article is exactly what you're looking for.
PRG is the right thing to do.
You do a POST to the action and if modelstate is invalid, you just 'export' your modelstate data into a variable and redirect to the get action.
This has the benefit as opposed to the accepted answer that you don't need to rewrite code on the [Post] action to recreate the view.
on the get action you load the ModelState exported from the post one.
TempData is an excellent place to do this stuff, code will be something like this:
[HttpGet]
public ActionResult Edit(int id) {
// import model state from tempdata
...
}
[HttpPost]
public ActionResult Edit(EditModel model) {
// if modelstate is invalid
// save modelstate in tempdata
// redirect to Edit/{id}
// else
...
RedirectToAction("List")
}
this can be automated using AttributeFilters, there an excellent post create by #ben-foster here:
Automatic Validation of Modelstate

ASP.NET MVC - How to maintain ModelState from a different controller?

I have a HomeController with an Index action that shows the Index.aspx view. It has a username/password login section. When the user clicks the submit button, it POSTs to a Login action in the AccountController.
<% Html.BeginForm("Login", "Account", FormMethod.Post); %>
In that action, it tests for Username/Password validity and if invalid, sends the user back to the Login page with a message that the credentials were bad.
[HttpPost]
public ActionResult Login(LoginViewModel Model, string ReturnUrl)
{
User user = MembershipService.ValidateUser(Model.UserName, Model.Password);
if (user != null)
{
//Detail removed here
FormsService.SignIn(user.ToString(), Model.RememberMe);
return Redirect(ReturnUrl);
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return RedirectToAction("Index", "Home"); // <-- Here is the problem. ModelState is lost.
}
But here's the problem: the ValidationSummary is always blank because we're losing the Model when we RedirectToAction.
So the question is: How do I send the user to the action on a different controller without a Redirect?
As others have said it's common to return the view if validation fails but as you are calling from your account controller you will want to specify the full path of your view
return View("~/Views/Home/Index.aspx", model);
Or
It is also common to have a seperate login page and redirect to that page if the login fails. Both pages will submit to the same login action. Facebook does this for example.
Or
As you only want to display an error message
return RedirectToAction("Index", "Home", new { LoginAttempts = 1 });
then in your Index action read the LoginAttempts parameter and choose to display the error message accordingly.
Use TempData to save state between requests. Use special attributes for convenience as shown here.
Few moments to mention:
Don't return View directly from your POST-action, respect Post-Redirect-Get pattern.
Don't overuse TempData. It's only supposed to save model state right before redirect and to retrieve it right after being redirected.
Well you could always do this
return View("~/Views/Home/Index.aspx", myModel);
It's not a real redirect, the clients url will still point to /login/ but at least you have your modalstate
Three options
You could call the action directly, but the client side will not have its URL changed. So instead of calling RedirectToAction you could call the Index() method of the HomeController class directly.
HomeController c = new HomeController();
c.ViewData = this.ViewData;
return c.Index(data);
The one is a bit tricky. Maybe you will have to set other things as well apart from ViewData which is needed for ModelState.
You could as well use TempData dictionary and fill it with whatever data you want and use that.
The simplest one where you provide full path to the view
return View("~/Views/Home/Index.aspx", data);
A better suggestion used by big players
If we look at how other sites do this kind of scenario. Take for instance Twitter (As #David says Facebook apparently does it the same). You can sign in from the Home/Index action (so to speak if it was developed using Asp.net MVC). But when login fails it displays a separate login page, that displays validation errors. In your case it would be Account/SignIn. Which would make sense and you could directly return its view with validation errors. When everything would be ok, you'd do it as you do it now. Redirect back to Home/Index.
Try using
return View("Index", "Home", Model)

Asp.net mvc: Posting Back from the View

Is it considered better practice to post back to the same controller that did the rendering and redirecting from original controller if necessary? Or is it just the same if one jumps to different controllers from the view?
I create two overloaded actions in the controller, one to render the input form using an HTTP GET and the other to process the form post using an HTTP POST. Something like this:
public ViewResult Foo()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Foo( FormCollection form )
{
// process input
if (inputOK)
return RedirectToAction("Index");
return View();
}
The benefit of doing it this way is that if there's an error, the view gets re-rendered with any error and validation messages. If it's successful, there's a redirect to another action, which avoids the duplicate posting warning on browsers if a user refreshes the page - see Post/Redirect/Get on Wikipedia and this blog entry by Stephen Walther.
There are alternatives to taking a FormCollection, e.g. a list of simple parameters or binding to an object. See this article by ScottGu.
I think that the action that is being called should be contained within a relevant controller for that action. If the view needs to call the action it should call it from the relevant controller, not necessarily the controller that it was spawned from.
If you have an inventory controller you don't want to define actions that relate to administration even if an inventory screen might have an administration action on it, as an example.

Resources