41

I am trying to implement RESTful urls in my Spring MVC application. All is well except for handling form submissions. I need to redirect either back to the original form or to a "success" page.

@Controller
@RequestMapping("/form")
public class MyController {

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm() {
        // do my stuff
        return "myform";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String processForm(ModelMap model) {            
        // process form data

        model.addAttribute("notification", "Successfully did it!");
        return "redirect:/form";
    }
}

However as I read in the Spring documentation, if you redirect any parameters will be put into the url. And that doesn't work for me. What would be the most graceful way around this?

Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
  • What redirect URL are you trying to achieve? – skaffman Mar 23 '10 at 14:29
  • Doesn't really matter, I just need to redirect after processing the post data to a page and notify the user that what they submitted was successfully processed. Seems that this is a common request http://jira.springframework.org/browse/SPR-1294. But might not get fixed for a while. –  Mar 23 '10 at 14:51
  • It doesn't actually look like either of the fixes described in the two related tickets will solve your particular problem: one of them is about controlling which model attributes are exposed as querystrings (but will still expose some) and the other is about adding attributes as part of the url path (which is nicely restful for IDs, say, but still doesn't give you a place to carry a success message). – Jacob Mattison Mar 23 '10 at 14:56

7 Answers7

37

I had the same problem. solved it like this:

return new ModelAndView("redirect:/user/list?success=true");

And then my controller method look like this:

public ModelMap list(@RequestParam(required=false) boolean success) {
    ModelMap mm = new ModelMap();
    mm.put(SEARCH_MODEL_KEY, campaignService.listAllCampaigns());
    if(success)
        mm.put("successMessageKey", "campaign.form.msg.success");
    return mm;
}

Works perfectly unless you want to send simple data, not collections let's say. Then you'd have to use session I guess.

Mat
  • 2,378
  • 3
  • 26
  • 35
13

This problem is caused (as others have stated) by model attributes being persisted into the query string - this is usually undesirable and is at risk of creating security holes as well as ridiculous query strings. My usual solution is to never use Strings for redirects in Spring MVC, instead use a RedirectView which can be configured not to expose model attributes (see: http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/web/servlet/view/RedirectView.html)

RedirectView(String url, boolean contextRelative, boolean http10Compatible, boolean exposeModelAttributes)

So I tend to have a util method which does a 'safe redirect' like:

public static RedirectView safeRedirect(String url) {
    RedirectView rv = new RedirectView(url);
    rv.setExposeModelAttributes(false);
    return rv;
}

The other option is to use bean configuration XML:

<bean id="myBean" class="org.springframework.web.servlet.view.RedirectView">
   <property name="exposeModelAttributes" value="false" />
   <property name="url" value="/myRedirect"/>
</bean>

Again, you could abstract this into its own class to avoid repetition (e.g. SafeRedirectView).


A note about 'clearing the model' - this is not the same as 'not exposing the model' in all circumstances. One site I worked on had a lot of filters which added things to the model, this meant that clearing the model before redirecting would not prevent a long query string. I would also suggest that 'not exposing model attributes' is a more semantic approach than 'clearing the model before redirecting'.

MatCarey
  • 2,409
  • 1
  • 16
  • 12
8

You can have processForm() return a View object instead, and have it return the concrete type RedirectView which has a parameter for setExposeModelAttributes().

When you return a view name prefixed with "redirect:", Spring MVC transforms this to a RedirectView object anyway, it just does so with setExposeModelAttributes to true (which I think is an odd value to default to).

matt b
  • 138,234
  • 66
  • 282
  • 345
  • If the success message isn't exposed as a querystring, how does it get to the redirected view? – Jacob Mattison Mar 23 '10 at 14:36
  • How do you want it to get to the success page? If you want to do a redirect, then it seems like you either need to pass it along as a request parameter or you can instead set it in the session and remove it at the next page. But it has to be passed someway. – matt b Mar 23 '10 at 14:53
  • Well I can return a ModelAndView with the "/form" as the construct argument. However, this results in the page being a POST request. So that then if they were to refresh the page, it would resend the post request. Not exactly what I would want happening. Thus the redirect. But it does seem that redirect doesn't support this yet. It is set to be modified in 3.1. –  Mar 23 '10 at 14:57
7

http://jira.springframework.org/browse/SPR-6464 provided me with what I needed to get things working until Spring MVC offers the functionality (potentially in the 3.0.2 release). Although I simply implemented the classes they have temporarily and added the filter to my web application context. Works great!

4

If you don't want the success message in the URL, then one option is to put it in the session (in the processForm method) and then check for it (and remove it) in the setupForm method.

Edited to add: if this is a pain to do manually, then you could write a subclass of RedirectView that adds a "message" attribute and wraps the process of inserting it into the session. Not sure, though, if there's an easy way to wrap getting the message back out of the session...

Honestly, I don't think there's an easy answer -- the nature of an HTTP redirect is that state isn't carried over; if you want to maintain state anyway, you're stuck with the various usual ways to maintain state in web applications: the session, a cookie, a querystring...

Jacob Mattison
  • 50,258
  • 9
  • 107
  • 126
  • Certainly possible, but since my application is much larger than just the one, that becomes quite a pain having to do that for every single form. –  Mar 23 '10 at 14:44
  • Struts seems to offer something called ActionMessages. I am not entirely sure what it is. Also, with Ruby On Rails, they offer a flash[:notification]. Which is essentially what I am trying to achieve. The flash message is stored until the next action (resulting redirect or what have you). It seems http://www.jroller.com/raible/entry/migrating_from_struts_to_spring attempted to tackle this. His way seems interesting. –  Mar 23 '10 at 15:20
  • Well, it looks to me like his approach is basically wrapping the session and putting the message in the session, along the lines of what I had in mind. But he gives actual code, so certainly more useful. – Jacob Mattison Mar 23 '10 at 15:29
3

Hey you can just do one simple thing instead of using model to send parameter use HttpServletRequest object and do this

HttpServletRequest request;
request.setAttribute("param", "value")

now your parametrs will not be shown in your url header hope it works :)

Sudhanshu Gaur
  • 7,486
  • 9
  • 47
  • 94
3
@RequestMapping(path="/apps/add", method=RequestMethod.POST)
public String addApps(String appUrl, Model model, final RedirectAttributes redirectAttrs) {
    if (!validate(appUrl)) {
       redirectAttrs.addFlashAttribute("error", "Validation failed");
    }
    return "redirect:/apps/add"
} 

@RequestMapping(path="/apps/add", method=RequestMethod.GET)
public String addAppss(Model model) {
    String error = model.asMap().get("error");
}
Anton
  • 5,831
  • 3
  • 35
  • 45