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

3

17

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?

Palinode answered 4/4, 2011 at 11:48 Comment(0)
H
21

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.

Hardback answered 4/4, 2011 at 12:49 Comment(1)
This breaks PRG and does it BADLY. If you server have a request quota, like don't submit for 5 minutes or reset, what you state as a no changes on the server is false. Also a page refresh should set the page on an initial status while doing your suggestion, you end up redisplaying the errors and state. Assuming server changes are only introduced if the body has changed is not a good way to design your apps that runs on a protocol that have much more information on headers, verbs, etc. You must always assume post isn't indepotent if you want to achieve good HTTP request/response flow.Marketable
B
10

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.

Borghese answered 4/4, 2011 at 13:3 Comment(5)
I use a modified version of Kazi Manzur's solution for failed validation, and now I absolutely love "strict-PRG". I know it takes a little more effort, but strict-PRG really provides users with a very nice experience.Seamus
I can't find anything from any reputable source that supports the approach that Kazi Manzur's "solution" suggests. Out of the three linked blog posts he says advocate this approach, one does not load any more and the other two actually advocate vanilla PRG, where you only redirect on success, and return view on failure. This is an extremely bad methodology since among other things it requires state, which a good HTTP application, which is to say a RESTful application should not require.Transversal
@ChrisPratt: I don't know which posts you're referring to that do not load - all his (four) links under tip #13 load for me. The blog posts (and the answer, and the question...) are quite old, so what is considered "best practices" may have changed since then - today, it's quite possibly not the best way to handle showing a form after an invalid post. I still think this constitutes valuable knowledge, though - using an ActionFilter this way is one kind of hammer, but not everything is the matching kind of nail. As with everything, use it wisely and where appropriate, not blindly and always.Borghese
@Endri: The post is from 2011; I would be pretty surprised if the best practices in the current version of the framework weren't very different anyway.Borghese
Yeah I agree... but that's what I got from reading what to do with broken links when I don't know how to edit them. However it is still a valuable answer... probably better to edit to remove the article references.Maim
M
-1

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

Marketable answered 13/6, 2013 at 19:0 Comment(8)
I think you have created a straw man argument here: 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. You don't need to rewrite any code based on the accepted answer, you just re-display the view: return View(model);Palinode
If the view need that like going to the database to fetch some values or do some stuffling to create a viewmodel, you should go get that data on the 2 methods (the GET and the POST).Marketable
There are other ways of avoiding that code duplication - although in this case the model is posted back (note the signature), so no database access required.Palinode
I don't know how to avoid that can of code duplication so you may be right in that point. About the model posting, if i understand clear what you said, one would think that the viewmodel for posting does not have all the data needed to recreate the view. For example, a select list posted will only include the value of the selected one, note the actual select list.Marketable
Bart - this can work by having the ViewModel contain the available select options; and the view to render that. So, when validation fails in the POST and that controller action simply returns a View(model), no work is required to populate everything that the view needs.Preconcerted
@StevenSproat and that introduces overposting vulnerability. (info on overposting here: odetocode.com/blogs/scott/archive/2012/03/12/… )Marketable
Using TempData, which uses sessions, is an absolutely bloody horrible way of handling a form submission. PRG means redirecting on success, not on failure. On failure you return the view. That is proper REST. What you're doing here is anything but because namely REST does not support state, which TempData necessitates.Transversal
benfoster.io/blog/automatic-modelstate-validation-in-aspnet-mvc this explains this in detail.Marketable

© 2022 - 2024 — McMap. All rights reserved.