Professional ASP.NET MVC 2 (2010) attributes that to Phil Haack
Note that redirecting the request only upon success technically violates the PRG pattern, but because no changes where made to the state of anything when validation failed, it doesn’t suffer from most of the problems with not following the PRG pattern. Phil likes to call this loose PRG as opposed to strict PRG.
The pattern itself seems to go back to this 2004 article/2003 discussion:
This approach provides a clean Model-View-Controller solution.
Aside: Server MVC has always played it fast and loose with Trygve Reenskaug MVC.
But please dear browser, do not save snapshots of a live program, because they may not represent actual Model state anymore.
…
Now I ask the same question again: what would a user see if he clicks Back button after submitting a form? You know the correct answer already: the user of a well-designed web application would see a View which represents current Model state. This View would be presented in a way that resubmitting of the same data would be impossible.
Sorry, in 2019 browsers still don’t work that way (bfcache, “Backward-Forward Cache”). Even after a successful redirect I can back up to the submit form and those last valid values will reappear in the form fields and I can click that submit button again.
If you need to defeat cached pages you need to employ token matching and remove the hidden form token from the server session after the resource has been successfully created (the standard CSRF token doesn’t stop double submission from a recently cached form).
Browser-side the pageshow event could be used to clear the input fields - but that will only work when JavaScript is enabled.
(1) Create Item is called from a link on some other web page when a new object should be created. This action constructs empty business object and stores it in the temporary area called Current Items, which itself can be stored in the database or in the session; then redirects to Display Item.
This suggests that each user has their own /users/new
resource (otherwise it couldn’t be stored in the session) - this violates the notion that /users/new
uniquely addresses a single resource.
(2-2) User fills out object value and submits HTML form to the Store Item action. If object is not accepted, it is kept in the Current Items area, server redirects back to Display Item action, which reads invalid object along with error messages from the Current Items and redisplays it in the form. If Item Page needs to be is refreshed, it loads the same object from Current Items again.
i.e. once again - my /users/new
would show my last errors while your /users/new
would show your last errors - so /users/new
isn’t uniquely addressing a resource.
Furthermore we’re now in a situation where errors will change “model state” because the errors are part of the “model state”. So rather than saying that redirect on failure is a requirement, it is more to the point that redirect on state change is a requirement.
If your philosophy is to not accept invalid data then you aren’t going to store “half-baked”, inconsistent items - which is what is happening in the “Current Items” area.
If a user clicks Back button on result page (3) after successfully creating and storing new object, he returns to Display Item action (2).
This completely ignores the existence of the “Backward-Forward Cache” in browsers. (Though it’s presumably preventable with Cache-Control: no-store
)
Ultimately the article reads like somebody is trying to coerce their vision of MVC/OO onto the web.
As I explained above with “Current Items Storage” each user/session would have a different /users/new
resource anyway - there is no way you could GET /user/new
with the validation errors unless you are in the same user/session context.
Phoenix is pragmatic about it - the form validation errors are ephemeral - they aren’t associated with an addressable resource. The aim is to maintain only valid resources, validation errors are ephemeral (short of what is written to the log).
- when you
GET \users
you will always get a list of users - when you
GET \users\new
you will always get an empty form
I’ve seen people argue that using the post action to render a view violates REST.
The action performed by the POST method might not result in a resource that can be identified by a URI. In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result.
So 200 OK
for the validation errors is permissible as no resource is created.
If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header (see section 14.30).
This is the ideal REST case - typically used by APIs.
However, the 303 (See Other) response can be used to direct the user agent to retrieve a cacheable resource.
This is what is used to guide browsers to the newly created resource.
FYI: SRP/SOLID
“Gather together those things that change for the same reason, and separate those things that change for different reasons.”
new.html
combines for user convenience entry fields with “validation error placeholders”.UserController.new
usesnew.html
strictly for the entry fields.UserController.create
usesnew.html
for both the entry values and validation errors.- Separate
new.html
andcreate_error.html
views would duplicate markup and functionality. It makes sense to combine them into onenew.html
, especially as they are essentially coupled anyway.
If so (and that’s also the case), would it still be a REST violation, considering that the action is started by the controller (because the view is used indirectly for the same purpose)?
new.html
is the representation forGET /users/new
(i.e. it’s an empty form)index.html
is the representation forGET /users
- in the case of
create
(POST /users
)new.html
is “reused” to convey the error information - i.e .it is not a representation of any particular resource.
I opened this thread already thinking that since everything has to be passed explicitly as a param it probably couldn’t be achieved.
It’s just not something that is covered out-of-the-box.
You are free to implement your own “Current Items” scheme - though in my personal view it’s an un-webby thing to do.