Is PRG a valid technique in Phoenix?

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.

RFC 2616 9.5 POST

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 uses new.html strictly for the entry fields.
  • UserController.create uses new.html for both the entry values and validation errors.
  • Separate new.html and create_error.html views would duplicate markup and functionality. It makes sense to combine them into one new.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 for GET /users/new (i.e. it’s an empty form)
  • index.html is the representation for GET /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.

1 Like