Home

News: Redirect After Post 2

  1. Redirect After Post 2 (49 messages)

    In Michael Jouravlev's first article he discussed a Redirect-after-Post pattern to get around problems with reloading a post. This article jumps into an implementation of the solution.

    Conclusion Do you think that this approach is anything but special? Well, it is. But it is far from being a standard.

    Recently I decided to use my eBay account again, but I forgot the password. I went to the password recovery page and filled out a small form. After I submitted the form, eBay informed me that a link to the password reset page was sent to my email address. Still having the confirmation page displayed, I clicked Refresh buttons several times. Guess, how many emails I received? What if instead of password recovery confirmation it were a payment confirmation?

    Read Redirect After Post 2

    Threaded Messages (49)

  2. Errata #1[ Go to top ]

    The action mapping "scope" attribute was lost in the process of cutting the fat off the working code. All action mappings must have scope = "request".
  3. If a web app framework aware of user session state is used instead on second and subsequent post you would get 'stale link' exception that can be processed in graceful way e.g. bringing used back to the page of 'last known good' session state.

    I've successfully implemented this in Tapestry to avoid glitches in 9-step online ordering process. It is completely insensitive to nasty 'back' button.

    I'm not sure you can do this with Struts out of the box (that's one of the reasons I switched to Tapestry) but the pattern is obvious: don't process same action twice, pay attention to the state change, if state is invalid for the action.
  4. Care to share?[ Go to top ]

    I have been looking into tapestry recently. Would you care to share how you did this?

    Regards,

    Joshua
  5. Care to share?[ Go to top ]

    Sure.

    I've put my own page (in a sense of Tapestry pages) instead of standard StaleLink in .application file. And in this page I've implemented checking user session state and sending internal redirect to page which processed "last known good" request from this user. As Tapestry keeps states of pages within the scope of the user session, from user point of view it was completely transparent. You press 'back' you click on link or submit a form, and you get again the page where you've pressed 'back' for a first time. You can also add some message to the top of the page like "hey you don't touch that 'back' button anymore!"
  6. event epoch counter[ Go to top ]

    wings is using an event epoch counter internally, that is incremented whenever an action has fired. As an application developer, you don't even see outdated events. The user gets the current session state displayed.
  7. If a web app framework aware of user session state is used instead on second and subsequent post you would get 'stale link' exception that can be processed in graceful way e.g. bringing used back to the page of 'last known good' session state.
    * If you are performing a re-POST, how do you avoid nasty browser messages "POSTDATA must be resent"?
    * I don't think that accepting a re-POST, evaluating it and comparing to a previous one is the right thing to do. I do not want to judge, did a user make a mistake and erroneously resubmitted a FORM, or did he do it because he wanted, say, to submit another order. I believe that it is easier to prevent resubmits, than to analyze them and to decide is it the same one as a previous one, and is not a "real" resubmit.
  8. I am not sure I understand the problem, but is it so hard to add "actionId" parameter to URL or hidden form field to detect resubmit ?
  9. View source on this page :

    <form method="post" name="Form1" action="/tss">

    <input type="hidden" name="service" value="direct/0/PostNewsReply/postReply.form"/>
    <input type="hidden" name="sp" value="S1"/>
    <input type="hidden" name="Form1" value="messageId,subject,useParentSubject,quoteOriginal,messageBody"/>
    <input type="hidden" name="messageId" value="l145234"/>

    Message id is just generated before submit. Is it something not trivial ?
  10. I am not sure I understand the problem, but is it so hard to add "actionId" parameter to URL or hidden form field to detect resubmit ?
    It's OK, I can ask it again. If you are performing a re-POST, how do you avoid browser message "POSTDATA must be resent"? Or they do not bother you, and you consider them part of normal user experience?
  11. I am not sure I understand the problem, but is it so hard to add "actionId" parameter to URL or hidden form field to detect resubmit ?
    It's OK, I can ask it again. If you are performing a re-POST, how do you avoid browser message "POSTDATA must be resent"? Or they do not bother you, and you consider them part of normal user experience?

    I see no problem in this message, probably it is possible to dissable this message in browser preferences. If you do not need to attach file then GET method can be used for form too to avoid this message.
  12. I see no problem in this message, probably it is possible to dissable this message in browser preferences.
    According to RFC2616/9.5, responses to POST request can be made cacheable using appropriate Cache-Control or Expires header fields. But the caching may not work on some simple browsers which do not have caching. Also, most websites set their after-POST results to expire immediately, and most browsers ask for confirmation if the POST is about to be resubmitted while navigating browser history.
    Anyway, it is much cleaner not to allow resubmit, that to distinguish between a real resubmit and a page refresh.
    If you do not need to attach file then GET method can be used for form too to avoid this message.
    This is a no-no, GET should not change server state.
  13. This is a no-no, GET should not change server state.
    Yes, I do not like this workaround too. If it is asumed GET doe's not change state, then it is very trivial to implement content cache. Probably redirect is not so bad idea too. I like idea to use state machines for this stuff, it is possible to make it configurable and can help to test many ways.
  14. GET changing server state[ Go to top ]

    This is a no-no, GET should not change server state.

    This strikes me as a needlessly pedantic approach. Anyone with a hit counter knows that GET requests change server state all the time.

    Likewise, there are plenty of times when you need to use POST when server state is not changing.

    Unfortunately we have a spec (HTML) that inappropriately ties the applications intent (GETting information or POSTing information) with non-identical mechanisms to handle those requests. It is akin to saying you can only use TCP to send data and UDP to receive data.
  15. IE problem[ Go to top ]

    AFAIK, this nasty confirmation message is an Internet Explorer problem primarily.

    Disabling cache control or setting the expiration date to the past or such doesn't help. Also, there does not seem to be a browser option to get rid of this kind of behaviour.

    :(
  16. Erratum #2[ Go to top ]

    Mozilla/Firefox does not reload page marked "no-cache", if a user goes back in the browser session history.

    Mozilla/Firefox reloads a page marked "no-store", if a user goes back in the history. This behavior is "as designed" for Mozilla, but have been broken for a while and was recently fixed in Firefox 1.0 RC (see Mozilla bug 252023).

    Pages which are marked as "no-cache", but which are served over SSL, are always reloaded by Mozilla.

    MSIE reloads a page in all aforementioned cases.

    If you want to keep your pages in sync with Model while using Mozilla, you need to mark your responses as "no-store". Struts has the convenient setting <controller nocache="true"/> in struts-config.xml file, but it does not set "no-store" header, it sets only the following headers:

      response.setHeader("Pragma", "No-cache");
      response.setHeader("Cache-Control", "no-cache");
      response.setDateHeader("Expires", 1);

    so you would need to patch Struts code.
  17. Redirect After Post 2[ Go to top ]

    Conclusion Do you think that this approach is anything but special? Well, it is. But it is far from being a standard. Recently I decided to use my eBay account again, but I forgot the password. I went to the password recovery page and filled out a small form. After I submitted the form, eBay informed me that a link to the password reset page was sent to my email address. Still having the confirmation page displayed, I clicked Refresh buttons several times. Guess, how many emails I received? What if instead of password recovery confirmation it were a payment confirmation? Read Redirect After Post 2

    Well good point about multiple emails sent. However I consider calling business logic from forms a hack and also I think that there are simplest ways to keep the last inserted/edited id (along with other usefull information like the page in a item list you've started insertion/update) in session scope, not that I'd be disturbed by the approach you've taken but I have seen too many overcomplicated dynamic web sites just for the sake of having a wonderfull mechanism to remember one or two things.

    Also if I remember well, WebWork allows you to redirect to an url with wildcards that would allow you to specify later the inserted/updated item id (e.g. viewItem.action?id={0} ).This reminds me of how many times I have promissed myself to take a closer look to WebWork (meaning to make a damn simple thing with it and get the feel of using it).

    Anyway "Redirect after post" is the way to go if you don't want your clients/customers to get the annoying Page expired, Want to resubmit etc., messages from browsers.
  18. Redirect After Post 2[ Go to top ]

    This is a valid idiom. Spring Framework actually has built-in support for it in its own MVC web layer, just by using a RedirectView...

    One thing that people should be aware of is that when this mechanism is used with something like Apache in front of the Servlet engine, is that if instead of using something like a direct Apache to servlet engine connector, you are using proxying, where the servlet engine doesn't even know the real external path the user used to get in, Apache needs to be configured to do proper URL rewriting/fixing on returned data (including the redirect sent back for this use case).
  19. GET and side-effects[ Go to top ]

    These two statements bothered me:

    "Note: createItem was originally invoked using pushbutton and POST method. But Struts throws an exception if an action corresponding to HTML FORM does not define a form bean. Because createItem action does not use form beans, the HTML FORM had to be changed to a link. Thus, createItem is now called using GET method."

    "deleteItem action removes an item from persistent storage. It receives item ID from the link on viewList.jsp page. Updating model in response to GET request is against semantics of GET method, but I decided to keep it that way."

    I know the site is a device to discuss your idiom. but using GET to incur a side-effect is terribly bad design. The framework of choice is not an excuse. Please fix these and update your article.
  20. GET and side-effects[ Go to top ]

    These two statements bothered me:"Note: createItem was originally invoked using pushbutton and POST method. But Struts throws an exception if an action corresponding to HTML FORM does not define a form bean. Because createItem action does not use form beans, the HTML FORM had to be changed to a link. Thus, createItem is now called using GET method.""deleteItem action removes an item from persistent storage. It receives item ID from the link on viewList.jsp page. Updating model in response to GET request is against semantics of GET method, but I decided to keep it that way."I know the site is a device to discuss your idiom. but using GET to incur a side-effect is terribly bad design. The framework of choice is not an excuse. Please fix these and update your article.
    You are absolutely right, using GET for updating server state is against GET semantics. If you have read my previous article, it explicitly stated that one should use POST for this purpose :) But the provided application is just an example. In case of createItem being called via a link, I wanted to get rid of unneeded form bean and this was the easy (and lazy) solution. I changed button to a link in the article to make listing for struts-config.xml as short as possible, but if you follow the link to the live application you will see, that it still has a pushbutton to create a new item :)

    The usssue with deleteItem is surely more bothersome. I have two other links for viewing and editing an item, so I wanted the delete operation to be consistent with other ones. Also, I do not like when a table shows a bunch of buttons. Sadly, ANCHOR tag does not allow to choose the request method type.

    Other people do the same anyway :) For example, yahoo mail uses simple links for "Flag message" or "Mark as unread" operations, which changes state of a mailbox. On the other hand, yahoo uses pushbutton for Delete operation.

    As I said, I realise (and stated it myself) that GET should return idempotent results. But I decided to get away with the link. The change to the FORM will be not be hard at all and does not affect the approach as a whole.

    And please do not forget, that despite the type of request which initiates deleteItem action, it is still redirected to a result page using (another) GET. Thus, the result page provides idempotent view. And only result page is stored in the browser cache.
  21. GET and side-effects[ Go to top ]

    "If you have read my previous article, it explicitly stated that one should use POST for this purpose :) But the provided application is just an example."

    It may be just an example, but the fact remains it is a bad example. It's all the more discouraging that you know it's a bad example and cite the fact that others do it as an excuse. I hope you and TSS get around to amending this article.

    "As I said, I realise (and stated it myself) that GET should return idempotent results."

    Nitpick: GET should not incur side-effects but what you GET is a state representation and may in fact change over time. The point is you don't use GET to change state. If your article had JavaBeans that changed state as a result of getXXX calls I doubt it would get to print.
  22. GET and side-effects[ Go to top ]

    The provided application is just an example.
    It may be just an example, but the fact remains it is a bad example. It's all the more discouraging that you know it's a bad example and cite the fact that others do it as an excuse.
    Well, you are right. One cannot evengelize great ideas with bad examples.

    (I) To call createItem properly the following changes should be made.

    Add itemFormInput form bean to createItem action mapping to make Struts happy:

      <action path = "/createItem"
              type = "com.xorsystem.items.CreateItemAction"
              name = "itemFormInput"
              scope = "request"
              input = "/WEB-INF/items/error.jsp"
              validate = "false">
              <forward name="itemCreated" path="/editItem.do" redirect="true"/>
      </action>


    viewList.jsp: change the following link

      <html:link page="/createItem.do">Create Item</html:link>

    to a form

      <html:form action="/createItem.do">
        <html:submit value="Create Item"/>
      </html:form>


    This does with the first issue, and it is how the live app is done.

    (II) The solution for deleteItem does not look pretty with buttons. Anyway, it adheres to specs. The easiest way would be to use DispatchAction.

    Its definition in struts-config.xml file would look like this:

      <action path = "/dispatchItem"
              type = "com.xorsystem.items.DispatchItemAction"
              name = "itemListForm"
              parameter = "method">
              <forward name="viewItem" path="/viewItem.do"/>
              <forward name="editItem" path="/editItem.do"/>
              <forward name="deleteItem" path="/deleteItem.do"/>
      </action>

    createItem action is called from a separate form directly to its own action.

    Create DispatchItemAction class inheriting from DispatchAction, and define methods with the same names as button values. These names will be: View, Edit, Create. I guess, someone can pick on me again that methods names start with capital letter. Well, if you want nice button values and nice method names, you can comb this sample app with your own nice javascript.

    Create one HTML form per each item row. Create a button for each operation (or just for deletion if you like) and assign its value attribute a name corresponding to a method name. Also, include item ID as a hidden input field in each form.

    This is it. Now you can see cool square buttons instead of links, and delete items using POST method. The source code in the zip file is updated, but the live application may take a while.
  23. Use Cocoon flowscript[ Go to top ]

    Still having the confirmation page displayed, I clicked Refresh buttons several times. Guess, how many emails I received? What if instead of password recovery confirmation it were a payment confirmation?

    Well, if you were using flowscript this would be easy to prevent. Your application could very easily know that it had already sent the email.
  24. Redirect After Post and JSF?[ Go to top ]

    Is there an easy way to get Redirect-After-Post working with JSF?
    I'm currently looking on a lot of Java Web-Frameworks, and JSF looked nice, but of course it looses a lot if its sex appeal when Redirect-After-Post doesn't work out of the box.

    Bye,

    Jürgen
  25. Redirect After Post and JSF?[ Go to top ]

    Is there an easy way to get Redirect-After-Post working with JSF? I'm currently looking on a lot of Java Web-Frameworks, and JSF looked nice, but of course it looses a lot if its sex appeal when Redirect-After-Post doesn't work out of the box.Bye,Jürgen
    I have not worked with JSF yet, just skipped really fast through the specs. But from what I've understood, redirection support is very limited. Chapter 2.1.2, "Faces Request Generates Faces Response" does not mention redirection on the diagram or in the text. Chapter 2.1.3, "Faces Request Generates Non-Faces Response" mentions redirection: "A Faces Request needs to be redirected to a different web application resource (via a call to HttpServletResponse.sendRedirect)."

    Looks like JSF phase is terminated right here: "It is then necessary to tell the JSF implementation that the response has already been created, so that the Render Response phase of the request processing lifecycle should be skipped. This is accomplished by calling the responseComplete() method on the FacesContext instance for the current request, prior to returning from event handlers or application actions." I understand this paragraph, as that redirection transfers the request outside JSF, thus redirection is supported merely as a way out.

    What is even worse, that if I am not mistaken, portlets do not support redirection at all. I cannot find it right now in the specs, but I believe that I've seen it somewhere. Please correct me, if I am wrong.

    See the other topic with GridSphere announcement. It supports portlet specification. Try to refresh any of the pages after you submit a form. You will get infamous "POSTDATA will be resent" message. This is sad.
  26. Redirect After Post and JSF?[ Go to top ]

    I'm far from being a JSF expert, so please take this with a grain of salt.

    In section 7.4.2 Default NavigationHandler Implementation of the JSF spec it says:

    If a matching <navigation-case> element was located, the <redirect/> element was specified in this <navigation-case>, and the application is not running in a Portlet environment, use the <to-view-id> element of the
    matching case to construct a context-relative path that corresponds to that view id, cause the current response to perform an HTTP redirect to this path, and call responseComplete() on the FacesContext instance for the current request.

    This sounds to me as if it were usable for the PRG pattern. What are your thoughts?
  27. Redirect After Post and JSF?[ Go to top ]

    I'm far from being a JSF expert, so please take this with a grain of salt.In section 7.4.2 Default NavigationHandler Implementation of the JSF spec it says:If a matching <navigation-case> element was located, the <redirect/> element was specified in this <navigation-case>, and the application is not running in a Portlet environment, use the <to-view-id> element of thematching case to construct a context-relative path that corresponds to that view id, cause the current response to perform an HTTP redirect to this path, and call responseComplete() on the FacesContext instance for the current request.This sounds to me as if it were usable for the PRG pattern. What are your thoughts?
    Andreas, I have to admit that I was wrong. Spec is a little vague about it, but you can use redirect element for navigation rule and it work very well. Because JSF stores view state on the server, nothing is lost during redirect, and you can browse back and forth freely and you can reload result page. I recompiled and run the test application and it works well. My apologies to JSF authors.
  28. RTFM[ Go to top ]

    In Michael Jouravlev's first article he discussed a Redirect-after-Post pattern to get around problems with reloading a post. This article jumps into an implementation of the solution. Conclusion Do you think that this approach is anything but special? Well, it is. But it is far from being a standard.

    It is a standard.

    http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
  29. Thank you[ Go to top ]

    It is a standard.http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
    Are we nitpicking? Yes, I am aware of RFC2616. What I meant is that this approach is not used in 100% of deployments of web applications. Should have used "commonly accepted" instead of "standard".
  30. Thank you[ Go to top ]

    It is a standard.http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
    Are we nitpicking? Yes, I am aware of RFC2616. What I meant is that this approach is not used in 100% of deployments of web applications. Should have used "commonly accepted" instead of "standard".

    Not nitpicking, just annoyed. I'm sick of clicking my "back" button and being warned by my browser that I'm about to re-submit a POST. You are too (or you wouldn't have bothered writing the above article, right?).

    The nasty knee-jerk response that I posted was provoked by your choice of framework. Struts is an HTTP hostile framework; it makes GET and POST look the same. They aren't the same.

    A framework should support new developers, not subvert them. It shouldn't be necessary to write an article to tell people how to make their framework redirect after a POST, it should be the framework's default behaviour.
  31. Thank you[ Go to top ]

    Struts is not HTTP-Hostile. Redirect after post works like a charm: you just need to put redirect="true" in your forward definition.
  32. Thank you[ Go to top ]

    I'm sick of clicking my "back" button and being warned by my browser that I'm about to re-submit a POST. You are too (or you wouldn't have bothered writing the above article, right?).
    You are right, I hate this too.
    Struts is an HTTP hostile framework; it makes GET and POST look the same. They aren't the same.A framework should support new developers, not subvert them. It shouldn't be necessary to write an article to tell people how to make their framework redirect after a POST, it should be the framework's default behaviour.
    Well... The article is about the general technique: redirecting after POST and loading the result page with a simple GET. It is not something that I invented, I just wanted to bring it up as a good solution against double submits, and as a way to improve user experience. Struts was my framework of choice to illustrate the solution just because I worked with Struts before. Though you are right, that it were the deficiencies in default Struts request handling that moved me to write this. I guess, if I had a better framework at the first place, I would not even notice the very issue of after-POST.

    But I would not say that Struts is HTTP hostile. It allowed me to do what I wanted, though I had to rethink how a request should be processed. After I came up with idea that ONE request should be processed with TWO action mappings, everything got easy and simple.

    The ASP.NET approach with its postbacks to the same form and with passing viewstate between forms in the request/response fields does not make me clap my hands either.
  33. It works well![ Go to top ]

    The article is about the general technique: redirecting after POST and loading the result page with a simple GET. It is not something that I invented, I just wanted to bring it up as a good solution against double submits, and as a way to improve user experience.
    Micheal,
    Thank you so much for sharing your idea. I will use this idiom, and have introduced to other project leads today, because it's simple and so powerful.

    Tak
  34. Michael,

    Just scanned your article and had to smile to myself as I recommended just such an approach to a colleague earlier in the day.

    The only comment that I have (not read the article in detail yet) is the sentence...

    "JSP pages are stored in protected WEB-INF directory and are not accessible from a browser."

    ...my personal experience in using this technique (which, like me, you probably got from the "Core J2EE Patterns" book) is that it doesn't work for all application servers. Weblogic was the one that I came across, but there may be others. Anyway, the alternative solution is to use the security constraint element within the applications web.xml file.
    (After moving all JSP files, that are not intended to be accessed from a browser, into the 'jsp' directory)
    ------------
      
     <security-constraint>
      <web-resource-collection>
       <web-resource-name>Sensitive</web-resource-name>
       <description>Resources that shouldn't be served directly to the browser</description>
       <url-pattern>/jsp/*</url-pattern>
       <http-method>GET</http-method>
       <http-method>POST</http-method>
      </web-resource-collection>
      <auth-constraint>
       <role-name>sensitive</role-name>
      </auth-constraint>
     </security-constraint>
     <security-role>
      <description>This role defines all of the users that are allowed to access JSP files directly (apart from in the root of the app) NOTE: NO users should be assigned this role as it is intended to be used as a blocking constraint.</description>
      <role-name>sensitive</role-name>
     </security-role>

    ------

    I can't claim authorship of this as I picked up up from a website (can't remember where) and it's simply part of the servlet spec, as far as I can see. I don't profess to be an expert on this subject but the technique works for me and I'm only too happy to accept any feedback that anybody may offer.
  35. Bob, I am glad you liked the article, the great minds think alike ;-) Thank you for advice. I borrowed the idea of storing JSPs in WEB-INF directory from Ted Husted's Struts tips, and this works great on Tomcat. I will verify how it works on Weblogic and will try your advice as well.
    ---
    michael_jouravlev [at] yahoo [dot] com
    www.superinterface.com
  36. Anything but special[ Go to top ]

    I had posted some comment about this pattern, a few weeks ago in the forums : http://www.theserverside.com/patterns/thread.tss?thread_id=20936#142725

    The best thing about this navigation pattern is that you allow users to use the 'Back' button of their browser with safety (I mean, without the scary, sometimes dangerous and often not understood 'RE-POST data ?' message). Great, huh ?

    This pattern leads to 2 types of Struts Actions :
    - 'views' that display a web page, forwarding the request to a JSP,
    - 'actions' that would execute a transaction (save, delete an item) and end with a sendRedirect.
  37. For instance...[ Go to top ]

    After posting my message, I hit the 'refresh' button of your browser (it's used pretty often by users, I'm sure you've got one, too).. and what do I get ? Yep, the very famous "do you want to re-post data" message. arrrg.
  38. Struts and Mozilla update[ Go to top ]

    I did not know when this happened, but Struts team updated their code, so no it includes "no-store" header, if Controller.nocache attribute in struts-config.xml is set to "true". The latest stable version 1.2.4 has this patch.

    Mozilla/Firefox 1.0 production release honors response headers only, it does not honor "meta http-equiv" tags on your HTML page to control caching. These tags may be processed by your server software to be converted to HTTP response headers. If your appserver does not do this, then Mozilla would not care. If you use Struts, install the latest release and use Controller.nocache attribute.
  39. K.I.S.S[ Go to top ]

    I'm amazed that it took the author 2 long articles to basically convey this simple message: Use redirect when changing object state or doing a write operation on a persistent store; otherwise, use forward.

    -Yves-
  40. What if a feedback is required?[ Go to top ]

    This seems to work fine. But what if one needs to give a feedback to the user about the previous operation.

    For eg. The user enters some data and clicks save, the system saves it and redirects to, say a Listing page, again. Here if the users needs to see a confirmation say "ABC saved", then the GET doesn't support that since there is no where that message is available. Unless of course, one resorts to the query string manipulation or stick it in the session.

    Any other ideas / thoughts. ??
  41. What if a feedback is required?[ Go to top ]

    This seems to work fine. But what if one needs to give a feedback to the user about the previous operation.For eg. The user enters some data and clicks save, the system saves it and redirects to, say a Listing page, again. Here if the users needs to see a confirmation say "ABC saved", then the GET doesn't support that since there is no where that message is available. Unless of course, one resorts to the query string manipulation or stick it in the session.Any other ideas / thoughts. ??
  42. What if a feedback is required?[ Go to top ]

    On a second thought.....

    After the successful save, the save message can be put in the session. In the listing page, one can pick it up from the session and delete it immediately from the session. Now the messge is available for the display once.

    If the user clicks the refresh button again, now that the message string in session is gone, the message part is avoided and just the listing is shown.

    So to answer myself : If a feedback is required, then after (post) update put the feedback message in session. Now Redirect after POST. In the new GET page, check for the "feedback" message in session and if available retrieve and delete from session and show it to user. Further refreshes of the get pages should be unaware of the feedback message also.
    This seems to work fine. But what if one needs to give a feedback to the user about the previous operation.For eg. The user enters some data and clicks save, the system saves it and redirects to, say a Listing page, again. Here if the users needs to see a confirmation say "ABC saved", then the GET doesn't support that since there is no where that message is available. Unless of course, one resorts to the query string manipulation or stick it in the session.Any other ideas / thoughts. ??
  43. What if a feedback is required?[ Go to top ]

    On a second thought.....After the successful save, the save message can be put in the session. In the listing page, one can pick it up from the session and delete it immediately from the session. Now the messge is available for the display once. If the user clicks the refresh button again, now that the message string in session is gone, the message part is avoided and just the listing is shown.So to answer myself : If a feedback is required, then after (post) update put the feedback message in session. Now Redirect after POST. In the new GET page, check for the "feedback" message in session and if available retrieve and delete from session and show it to user. Further refreshes of the get pages should be unaware of the feedback message also.
    This is basically what I did in the sample application which is discussed in the article. The UI object which is stored in the session, comprize business data and some UI information, like error messages or page title. Error messages can depend on business data or on previous UI action. If error message was created because of incorrect data operation, it will remain in the UI object and will be shown again on refresh. If error is a result of UI operation, then it will be cleared on refresh.

    You can try live application on my website. Create an item, put into some non-numeric data and save. You will get "Value must be a number" error which is not cleaned on refresh. On the other hand, if you create ten items, then when you try to create the eleventh, you will see "Storage exhausted" message. If you refresh the page, this message will disappear. This message is the result of a Store operation, but refreshing a page does not involve Store, so I considered this message inappropriate when the page is refreshed.
  44. What if a feedback is required?[ Go to top ]

    On a second thought.....After the successful save, the save message can be put in the session. In the listing page, one can pick it up from the session and delete it immediately from the session. Now the messge is available for the display once. If the user clicks the refresh button again, now that the message string in session is gone, the message part is avoided and just the listing is shown.So to answer myself : If a feedback is required, then after (post) update put the feedback message in session. Now Redirect after POST. In the new GET page, check for the "feedback" message in session and if available retrieve and delete from session and show it to user. Further refreshes of the get pages should be unaware of the feedback message also.
    This is basically what I did in the sample application which is discussed in the article. The UI object which is stored in the session, comprize business data and some UI information, like error messages or page title. Error messages can depend on business data or on previous UI action. If error message was created because of incorrect data operation, it will remain in the UI object and will be shown again on refresh. If error is a result of UI operation, then it will be cleared on refresh. You can try live application on my website. Create an item, put into some non-numeric data and save. You will get "Value must be a number" error which is not cleaned on refresh. On the other hand, if you create ten items, then when you try to create the eleventh, you will see "Storage exhausted" message. If you refresh the page, this message will disappear. This message is the result of a Store operation, but refreshing a page does not involve Store, so I considered this message inappropriate when the page is refreshed.

    Hi Mike
    I tried the live application, and when I tried to save the eleventh item, it gave a "storage exhausted" message. I tried to refresh and although the "Post Alert" didn't appear (since you've implemented the GET way), the error message kept appearing for each refresh! Same for UI errors too.

    Now in my project, I had a main listing page where a number of records are listed in a table format with edit/delete button. The user chooses one record and say edit. Now the system navigates to another Edit page, where the user edits and clicks save. After the successful save I need to take the user to the original listing page and also show a status of the save request (Like blah blah saved).

    I had implemented it the "redirect after post" way. So the error message should appear once, and not on a refresh. I did the session trick and it works!! Of course, along with that the usual evils associated with Session are also present. But I can live with that.
  45. Hi MikeI tried the live application, and when I tried to save the eleventh item, it gave a "storage exhausted" message. I tried to refresh and although the "Post Alert" didn't appear (since you've implemented the GET way), the error message kept appearing for each refresh! Same for UI errors too.
    Oops, online version differs a little from the one that I have on my development machine, I will update it.
  46. Online Version Differs from Zip Version[ Go to top ]

    Hello,

    I noticed that the online version of the application works perfectly well in terms of displaying error messages and the such.

    Unfortunately, the downloadable source doesn't have the same behaviour.

    Example:

    Online - when you "create" a new item and supply text when numbers are needed, you get redirect to the editItem.do page with a message saying "Value should be a number, entered:"... as expected.

    ZIP - when you do the same thing with the downloaded version of the app, you get redirected to editItem.do but this time, no error message is displayed.

    I am wondering what is happening here.

    Any thought?

    Thanks.
  47. So ? Which framework ?[ Go to top ]

    Michael, I am 100% with you in your struggle for the PRG pattern. You did a good job with your two navigation examples, using forwards and redirects. And I wanted to know what framework do you use to implement it. Struts, Spring MVC, Tapestry, JSF, ?
    None of those allows to natively implement the framework, and it's a BIG shame. So ? Two solutions : the first is to mess up with the framework, the second is to redefine the navigation layer.

    I had a look at your source code, it seems that you chose the first solution. Although your idea to use Output ActionForms as view beans is quite good, Struts was not thought this way : you end up calling your business layer in the validate() method of your OutputActionForm to populate it. That's baaad.
    Another remark is about how you add the "id=xxx" to the URI before performing the redirect : I think a good PRG implementation should take care of such a mechanism.
    Final remark, about form non-validation : The errors messages must be saved /temporarily/ in session, so you can display them back to the user after the REDIRECT.

    I started thinking about redefining the Struts' RequestProcessor, but it's such a mess to override its behavior. Then, I thought I would create my own PRG framework implementation from scratch - bad idea, there are enough frameworks on the market place.
    So, before going further, I'm looking now at JSF, but I feel like these navigation rules will quickly drive me crazy. Also, I have to say that I'm not a big fan of using events & eventlisteners in a web environment. They might lead developers to some horrible confusion.
  48. So ? Which framework ?[ Go to top ]

    You did a good job with your two navigation examples, using forwards and redirects. And I wanted to know what framework do you use to implement it. Struts, Spring MVC, Tapestry, JSF, ?None of those allows to natively implement the framework, and it's a BIG shame. So ? Two solutions : the first is to mess up with the framework, the second is to redefine the navigation layer.I had a look at your source code, it seems that you chose the first solution. Although your idea to use Output ActionForms as view beans is quite good, Struts was not thought this way : you end up calling your business layer in the validate() method of your OutputActionForm to populate it. That's baaad. Another remark is about how you add the "id=xxx" to the URI before performing the redirect : I think a good PRG implementation should take care of such a mechanism.

    It is common now to blame Struts in all possible sins. But Struts is not that bad. It was built on top of servlet/JSP technology, it is easy to understand for anyone who knows servlets/JSP, and it provides some additional helper services. But it keeps the idea of HTTP request/response relatively clean and observable. This is what I like about Struts. It is small. It allows me to work with HTTP, while providing some additional service to define controller components and to route the requests.

    So, having some basic HTTP knowledge, then servlet knowledge, then Struts experience, I got used to the simple request/response approach. Page-oriented event-driven design like JSF is something that I consider more restrictive. Struts allows to work closer to the browser, and to do some HTTP tricks that other frameworks do not allow.

    Case it point: I need to POST to a certain URL, update the model, then redirect. Then after redirect, I want to check the model state and to forward to a proper JSP. HOW CAN I DO THIS IN JSF? JSF is page-oriented. I must load a certain page, then perform action on it, then it would define the next page. I cannot implement my two-stage approach in JSF without a hack. Which I am thinking on now. Maybe bare JSP scriptlet will do?

    And of course, I would not be able to pass request parameters to redirected URL. I think that cannot do this in WebWork either, because in WebWork action returns string mapping only, and actions are separated from HTTP layer.

    The benefit of passing URL parameters back to redirected URL is that it allows to access data items uniformly either (1) from a page using link and item ID, or (2) through redirect, adding item ID to the URL. But apparently this does not work in most other frameworks, and even in Struts it is kinda hackish, so I am going to abandon the idea of uniform access, and to use session to store parameters needed after redirect.

    Calling business layer from validate() is not bad. Why? I have nothing else to do in validate(), I have the whole method to spare, which is called in the right time. What if it was called prepareView(), would you still think that it is bad? Anyway, if you want to make things "proper", define your own small action class instead of using ForwardAction.

    I wrote some thoughts about URL/redirect issues in my blog:
    Struts, WebWork, redirect and friendly URLs
    Why Struts form beans suck at validation
    Have an advice for me?
    Final remark, about form non-validation : The errors messages must be saved /temporarily/ in session, so you can display them back to the user after the REDIRECT.
    Am I not doing that? I am doing that ;) See Error handling
    I started thinking about redefining the Struts' RequestProcessor, but it's such a mess to override its behavior. Then, I thought I would create my own PRG framework implementation from scratch - bad idea, there are enough frameworks on the market place.So, before going further, I'm looking now at JSF, but I feel like these navigation rules will quickly drive me crazy. Also, I have to say that I'm not a big fan of using events &amp; eventlisteners in a web environment. They might lead developers to some horrible confusion.

    Right. The simple "request with params"/"response with page" approach is easier to understand (for me) and is more flexible. This is why I am still stuck with Struts. Actually, I have my own very lean addon for Struts, which makes it simple and easy to do redirects and to to keep messages in session. I am adding another component to it, Easy Wizard, which I use for small controlled flows. Will be ready in about a week. The whole CRUD example application got much simpler. Come back in about 10 days ;)
  49. So ? Which framework ?[ Go to top ]

    Thanks a lot, I'll check your struts "PRG addon" !
    Calling business layer from validate() is not bad.
    You are right, maybe it's just a matter of naming things.

    Also, about what you say about "friendly URLs" : I don't know what's a "nice-looking" URL (myapp/viewItem.do?id=6758 or myapp/item6758/view, what's the difference for the final application), and I don't even care about having an URL of the form /servlet/com.foo.bar.MyServlet?id=111.
    To me, the most important thing is not to have an unrefreshable URL like "deleteItem.do?id=123" displaying a list of items after a POST. And, SADLY, that's how most developers seem to use Struts. Any URL is friendly to me when it contains all the information that the server needs to display the correct page, i.e. when it is bookmarkable.
    This behavior should be so obvious for anyone...

    Finally, one last thing I regret about Struts : it cannot populate ActionForms recursively - or it's very limited on the issue. What if I have an Action Form containing 2 or n sub-forms, or if I want to be able to edit 2 or 3 items at one shot ? I read that it's something that JSF or Spring can do...

    By the way, Michael, did you try Struts Shale ?
  50. So ? Which framework ?[ Go to top ]

    Michael, I am 100% with you in your struggle for the PRG pattern. You did a good job with your two navigation examples, using forwards and redirects. And I wanted to know what framework do you use to implement it. Struts, Spring MVC, Tapestry, JSF, ?None of those allows to natively implement the framework, and it's a BIG shame. So ? Two solutions : the first is to mess up with the framework, the second is to redefine the navigation layer.
    Arnaud, I was busy with another project of mine, the Easy Wizard. Then I was integrating Easy Wizard into Struts, while trying to use the knowledge that I got. So, here it is, the first set of usable PRG components. Or, as I prefer to call this pattern now, "two-phase input processing" components for Struts:

    Project homepage: http://struts.sourceforge.net/strutsdialogs
    Live demos: http://www.superinterface.com/strutsdialog
    CRUD sample source code: in src\net\sf\dialogs\samples\crudaction
    directory of the dialogs-1.1.zip file.