Announcement Announcement Module
Collapse
No announcement yet.
Forms with redirects Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Forms with redirects

    In the web app I'm working on, we're trying to use the SimpleFormController and the POST/Redirect pattern. We also didn't want any "Warning! Page is the result of a POST request..." dialogs popping up when the user presses the back button.

    Some of the forms probably have some special processing too... for example, we have a form where a user can send an email to other users. There are "Add TO:", "Add CC:" and "Submit" buttons on this page. All of these are in one form and all POST to the same controller. If either "Add" is clicked the controller changes the form bean and then redisplays the form view, if it's a "Submit" then some validation is run and the success view is displayed.

    Originally, I tried using "redirect:<relative url>" for the form and success views. This works for success views, but causes problems with form views... you end up in an endless redirect loop when doing a form view. However, if there's no redirect on the form view, you get "Warning! POST..." messages going back to forms that either do some kind of submission to themselves (like the email form) or forms that have validation errors.

    OK... so I set the success view to a "redirect:<url>" and set the form view to the normal view name. Then in the forms that do submissions to themselves, instead of setting the view name of the ModelAndView of the form view, I set the "redirect:<url>" of the form view (Fortunately, all the urls of the view names in our project are viewName + ".htm". Unfortunately, there's no way to enforce this so someone could come along later, not follow the convention and break it. Also unfortunately, I can't seem to find a way to get the url of a view automatically...) This fixes forms that sometimes redirect back to themselves...

    But also handling validation errors required a bit more work, and the solution right now seems a bit more "hack-y"... I overrode 2 methods of SimpleFormController for each of the controllers...
    Code:
    // key used to store errors in the session
    private static final ERROR_SESSION_KEY = "EmailValidationErrors";
    
    protected void initBinder( HttpServletRequest request, ServletRequestDataBinder binder ) throws Exception
    {  
        // check for previous errors in the session
        Errors oldErrors = (Errors)request.getSession().getAttribute(ERROR_SESSION_KEY);
        if (oldErrors != null)
        {
            // errors were found. Remove them from session...
            request.getSession().removeAttribute(ERROR_SESSION_KEY);
            // ... and add them to the binder.
            binder.getBindingResult().addAllErrors(oldErrors);
        }
    }
    
    protected ModelAndView processFormSubmission( HttpServletRequest request, HttpServletResponse response, Object command, BindException errors ) throws Exception
    {
        ModelAndView mav = super.processFormSubmission(request, response, command, errors);
        // we're handling a POST, but we got the form view instead of success view.
        if (getFormView().equals(mav.getViewName()))
        {
            // and there are errors present...
            if (errors instanceof Errors)
            {
                // save errors in the session.
                request.getSession().setAttribute(ERROR_SESSION_KEY, errors);
            }
            
            // clear the model...
            // this is a workaround to a problem where any values set on the model
            // (like from the referenceData() method) would be appended to the 
            // URL.  Firefox chopped the URL off at 255 chars and was fine.  IE 
            // (of course) throws a "Server Not Found" :confused: error...
            mav.getModel().clear();
            
            // re-set the view name to redirect to the form view.
            mav.setViewName("redirect:/" + getFormView() + ".htm");
        }
        return mav;
    }
    Right now, this works... but I've only been using Spring and Spring MVC around 2 months - so my questions are: Is there something I'm missing? This seems to be a pretty standard thing to do - use redirects to get rid of "Warning! POST..." dialogs... Is there a better, standard way to do this? Should we be using a different controller than SimpleFormController? Also, has anyone had the patience to read this really long post?

  • #2
    Redirect-after-POST is actually about "Redirect after SUCCESSFUL POST".
    My recommendation is to stay away from any attempts to keep errors in session. This is just not_good and will add even more problems.

    Browser's "repost" warning is standard and I don't think you should try to suppress it. Just imagine a form which uploads files - what you gonna do, save files in a session?

    Comment


    • #3
      You likely want to override the 'isFormSubmission()' method. The default returns true for POST requests - therefore displaying the form for GET requests only.

      Comment


      • #4
        Originally posted by nekoval View Post
        Redirect-after-POST is actually about "Redirect after SUCCESSFUL POST".
        My recommendation is to stay away from any attempts to keep errors in session. This is just not_good and will add even more problems.

        Browser's "repost" warning is standard and I don't think you should try to suppress it. Just imagine a form which uploads files - what you gonna do, save files in a session?
        I'm not keeping errors in the session indefinitely - only between the POST and the GET redirect - that's what the code in initBinders does.

        I'm also not having any problems at all with the functionality of this... it works. I just think the code is clunky and there might be a better way to achieve it.

        If repost warnings are "standard", why bother with "Redirect after (any) POST" at all?

        I wouldn't save the file in the session - the filename supplied by the user? Yes, probably...

        Comment


        • #5
          Originally posted by Mark Fisher View Post
          You likely want to override the 'isFormSubmission()' method. The default returns true for POST requests - therefore displaying the form for GET requests only.
          I'm handling both POSTs and GETs for the form with the same controller...

          We did run into some problems early on with pages with SimpleFormControllers being the success views of previous pages with SimpleFormControllers... originally we overrode isFormSubmission() to only do submits on POSTs from the actual form URL... but the success view redirect ended up solving this problem once we added those.

          Comment


          • #6
            Originally posted by NPruett View Post
            If repost warnings are "standard", why bother with "Redirect after (any) POST" at all?
            That's exactly the question! I haven't seen any reasonable answers yet.

            Jörg

            Comment


            • #7
              Originally posted by NPruett View Post
              If repost warnings are "standard", why bother with "Redirect after (any) POST" at all?
              Because you don't want to trigger form submission twice. And you don't want any side effects when user clicks on Reload.

              As a real-life example you can look at how any PHP forum works - this one as well...

              Comment


              • #8
                Originally posted by nekoval View Post
                Because you don't want to trigger form submission twice. And you don't want any side effects when user clicks on Reload.
                I know that reasoning. But it is not reasonable

                To clarify: It just does not work, nothing prevents you to go 2 pages back at once. And where is the advantage of being redirected again instead of getting the repost warning.

                Don't fix things that are actually not broken

                Jörg

                Comment


                • #9
                  Originally posted by Jörg Heinicke View Post
                  To clarify: It just does not work, nothing prevents you to go 2 pages back at once. And where is the advantage of being redirected again instead of getting the repost warning.
                  That's right. Although I'm usually concerned about using Reload rather that trying to enjoy user who goes back twice.

                  Anyway, the point of original post is that Redirect-after-POST is already used, which is reasonable. However, I've mentioned that keeping submission state in session is not something widely used across the Web, and there are reasons to avoid it.

                  Comment


                  • #10
                    OK - here are two advantages -

                    If you redirect after *every* POST - no matter whether it is successful or whether it ends up redirecting to the form because of the flow of the controller or because of validation errors - you will *never* have the problem of re-POST-ing the form - because everything the user can go "Back" to or "Reload" from is the result of a GET. The *only* time you will ever POST is when a button is clicked in the form.

                    Another advantage is that the user can easily flow backwards or forwards through the web app without any interruptions (clicking OK on the POST warning), and without needing to remember to click a "Back" link in the page rather than use the "Back" button on the browser. Most users are just going to click the "Back" button (because it's always there and they are used to using it) rather than search the page for a "Back" link.


                    If saving form state in the session is "bad", then why does Spring's AbstractFormController provide a "sessionForm" property?

                    Comment


                    • #11
                      Originally posted by NPruett View Post
                      Another advantage is that the user can easily flow backwards or forwards through the web app without any interruptions (clicking OK on the POST warning), and without needing to remember to click a "Back" link in the page rather than use the "Back" button on the browser.
                      Sorry, I don't see how you can safely use Back button in your solution. No matter how many steps back I'm doing, session state remains the same and I will get the same errors. This will not look like going back. And if I want to change something and see different submission result, I have to repost, which gives me a report warning.

                      If saving form state in the session is "bad", then why does Spring's AbstractFormController provide a "sessionForm" property?
                      From what I see in javadocs, it is not intended for being used in redirects. It could be used for preserving object identity across requests, for example, this might be important for ORM tool. If you know Struts, it offers the same thing (keeping form in session by default). As for Spring MVC, I think this functionality is used for wizard subclasses.

                      This session-based approach in your case means that you have to control your "errors" lifecycle and ensure correct behaviour when session expires or user comes to the form from scratch unexpectedly. E.g. Struts or Spring MVC offer "magic" stuff like transaction tokens (which you have to carry through all of your redirects). Spring WebFlow offers more controlled solution (again by using tokens, but behind the scenes) by introducing "conversation scope".

                      The exact solution depends on your requirements, of course. I may only suggest looking at forum software (eg phpBB) or wizard forms on Amazon site: surprisingly, they don't use redirects and you get repost warning on every page (oops!).

                      Comment


                      • #12
                        Originally posted by nekoval View Post
                        Sorry, I don't see how you can safely use Back button in your solution. No matter how many steps back I'm doing, session state remains the same and I will get the same errors. This will not look like going back. And if I want to change something and see different submission result, I have to repost, which gives me a report warning.
                        Please, take a look at my code again... I'm temporarily storing the errors in the session in this case:

                        POST -> errors occur -> *STORE ERRORS TEMPORARILY IN SESSION* -> redirect to form view -> GET -> if errors are in session *REMOVE THEM FROM SESSION* -> add errors to binding -> display form (with errors)

                        The errors aren't sticking around in the session to show up in any pages the user goes back to (or reloads to).

                        Originally posted by nekoval View Post
                        From what I see in javadocs, it is not intended for being used in redirects. It could be used for preserving object identity across requests, for example, this might be important for ORM tool. If you know Struts, it offers the same thing (keeping form in session by default). As for Spring MVC, I think this functionality is used for wizard subclasses.
                        I don't see anything about not using sessionForm for redirects in the JavaDocs for setSessionForm. And in the case of the email form I mentioned - it's kind of a mini-wizard anyway - but I'm not using the WizardFormController because it's just staying on the same page.

                        Originally posted by nekoval View Post
                        This session-based approach in your case means that you have to control your "errors" lifecycle and ensure correct behaviour when session expires or user comes to the form from scratch unexpectedly. E.g. Struts or Spring MVC offer "magic" stuff like transaction tokens (which you have to carry through all of your redirects). Spring WebFlow offers more controlled solution (again by using tokens, but behind the scenes) by introducing "conversation scope".
                        If the session expires, there are going to be more problems than keeping track of the errors lifecycle (i.e. user isn't logged in, app state up to this point isn't in the session, etc.) If the user comes to the form from scratch, the initBindings() check won't find any errors in the session, and the form will be displayed as normal.

                        In my case, neither of these cases will even make it to the controller, as I have a HandlerInterceptor checking for the user being logged in.

                        The token thing could work too - but I would just have to change my code to look for it on the URL rather than in the session, so I don't think this adds any real value.

                        I haven't looked into WebFlow yet... I'll have to look into it more to see if it addresses this kind of thing.

                        Originally posted by nekoval View Post
                        The exact solution depends on your requirements, of course. I may only suggest looking at forum software (eg phpBB) or wizard forms on Amazon site: surprisingly, they don't use redirects and you get repost warning on every page (oops!).
                        But repost warnings are really just a side affect of the technology being used (HTTP Post) - not any kind of standard or requirement of HTTP/HTML...

                        Comment


                        • #13
                          Originally posted by NPruett View Post
                          if errors are in session *REMOVE THEM FROM SESSION* -> add errors to binding -> display form (with errors)
                          I'm not sure I've got your idea... If you reload a page then your errors instance is lost and you've got a clean form again and you've lost all of your data and errors...

                          If the session expires, there are going to be more problems than keeping track of the errors lifecycle (i.e. user isn't logged in, app state up to this point isn't in the session, etc.)
                          That's right, but user had lost all of his data. May not be a big problem though...

                          But repost warnings are really just a side affect of the technology being used (HTTP Post) - not any kind of standard or requirement of HTTP/HTML...
                          Well, if Amazon and Google can live with that, this is enough for me.

                          Comment

                          Working...
                          X