6

I am working on a web application that involves the user filling out a multi-step form that spans several pages. The form has tabbed navigation across the top (these links do not submit the current page) and a next button at the bottom (which does submit). I am considering several strategies for handling form submission/validation:

  1. one action method and view per form page. When you hit next, it submits the form to the action method for the next page. If there are validation errors, you are redirected back to the previous page:

    • URL's are descriptive and can be copy-pasted
    • Only redirects in the error case
    • Since the redirect does not have the form data, we lose context about the submission which makes it hard to display certain error messages
    • The same validation logic works for redirecting the user if they try to visit a step in the flow that they aren't ready for yet
  2. one action method and view per form page. When you hit next, it submits the form to the current page action. If there are validation errors, the same view is returned. Otherwise, we redirect to the next page action:

    • URL's are descriptive and can be copy-pasted
    • Redirects are very common (not sure if this is bad)
    • When displaying validation errors, we are in the same request as the form submission so we have full access to the invalid input
    • Have to pass additional context if we want the ability to, for example, add a "Previous" button which also submits
  3. one action method for ALL pages. URL's contain additional context about the step being submitted (e.g. MyController/MyAction/{step}). The controller message selects which view page to return depending on validation and the current step.

    • URL's are not descriptive (e. g. if I submit step 1 to go to step 2, then the URL the user sees will be the same regardless of whether page 1 (invalid) or page 2 is returned
    • No redirects
    • When displaying validation errors, we are in the same request as the form submission so we have full access to the invalid input
  4. A different method I haven't listed here

I have tried to enumerate what I see as some of the pros and cons of each method, but I would be interested to know:

  • What are other pros and cons of these methods? Are mine correct? Could some of the cons I've listed be designed around?
  • Is there a standard approach to this problem which I should be using? If so, why is it the standard approach?
tereško
  • 58,060
  • 25
  • 98
  • 150
ChaseMedallion
  • 20,860
  • 17
  • 88
  • 152
  • It sounds like you need to embrace the notion of actions handling requests and returning (in most cases) views. Having an action per form page allows you to have a view model specific to the data on that page. The action can either return the same view if the model is invalid or the view for the next page. No redirect is needed. You can use `hidden` inputs in your `form` to pass context along from page to page. – HABO Sep 09 '12 at 18:08
  • @HABO: but if I don't redirect and the user submits something invalid on page 1, then won't they still see the page 2 url even though the view returned is the invalid view for page 1? – ChaseMedallion Sep 09 '12 at 22:52
  • Your action selects the appropriate view to return: page 1 or page 2. How it decides is up to you. The browser displays whatever it gets, but doesn't need to be asked to ask for a different page via a redirect. – HABO Sep 09 '12 at 23:47

1 Answers1

2

I would highly recommend option 2 with a minor modification. You may want to think about also creating one view model per action/view as well. If you have one model that spans all the pages, validation will occur across ALL properties, meaning that even though the user can only edit part of the model on each screen, they could get validation warnings for properties they can't see. We did this recently in a project and it worked beautifully. You have to do some data manipulation in the back-end to combine everything back together, but it was worth it in the end.

As you said, your URLs would be deep-linkable, which means users can Copy/Paste, and more importantly, they can add the page as a favorite in their browser, allowing them to come back to the same place very easily. In my opinion this makes option 3 obsolete.

You will also benefit from the fact that all of your logic for navigation is occurring in one place. You'll have to store the state of the "wizard" on the client (which page you're currently on) so that your controller knows what to do on submit. You'll want to analyze the state of the wizard and make a decision for where the user needs to go next. If you go with option 1, you won't know where you "came from" and server-validation errors will be difficult to display to the client. This is a beautiful example of the POST - REDIRECT - GET pattern. Each page would have 2 actions, a GET that takes simple ids, and a POST which takes more complex models. Post the server, figure out where to go next, redirect to a GET.

Lastly, consider your previous button simply linking directly to the previous step, instead of submitting the form. Otherwise, the user could potentially get stuck on an invalid step. This happened to us and again, worked very nicely.

Hopefully this was helpful. Good luck!

Adam
  • 1,202
  • 11
  • 25
  • 1
    You mention the POST - REDIRECT - GET pattern. Is there any risk of the frequent redirects making the page transitions feel slower (esp. since we load portions of the model from the database on each request)? – ChaseMedallion Dec 05 '12 at 18:43
  • Absolutely. It's something you'd have to consider. POST - REDIRECT - GET works very well because of the stateless nature of the web. However, if performance is a concern (slow queries, etc), then you may want to consider either another storage solution such as a No-SQL provider, or storing objects in session. – Adam Dec 07 '12 at 15:55
  • 2
    With one model per view, how does step n know that step 1 and step 2 were completed correctly? For example, assume someone gets to step three and then leaves the site/app. Presumably their progress is stored in session... but let's assume they stay away for long enough for the session to decay. They return directly to their bookmark to page 3. It sounds to me like the 'index' action for each step is going to have to pull every previous step from session back into the appropriate model and re-validate. It seems to me that this would get very expensive on step 20. – Mir Dec 26 '12 at 17:17
  • It can definitely get expensive if you're not careful; however, one could argue that you'd want it to validate all the steps to make sure they haven't passed an invalid step somehow. For instance, if you are on Step 10 and step 2 is invalid, maybe you don't want them to be on step 10? The workaround of course would be to simply validate the entire state of the object on the final step right before you "publish" or whatever you're doing. In our implementation we use a lot of Lazy's to help improve performance. That way we only actually hit the DB when we're on the actual step. – Adam Jan 18 '13 at 15:52