views:

2549

answers:

3

Using Spring MVC 3.0.0.RELEASE, I have the following Controller:

@Controller
@RequestMapping("/addIntake.htm")
public class AddIntakeController{

  private final Collection<String> users;

  public AddIntakeController(){
    users = new ArrayList<String>();
    users.add("user1");
    users.add("user2");
    // ...
    users.add("userN");
  }

  @ModelAttribute("users")
  public Collection<String> getUsers(){
    return this.users;
  }

  @RequestMapping(method=RequestMethod.GET)
  public String setupForm(ModelMap model){

    // Set up command object
    Intake intake = new Intake();
    intake.setIntakeDate(new Date());
    model.addAttribute("intake", intake);

    return "addIntake";
  }

  @RequestMapping(method=RequestMethod.POST)
  public String addIntake(@ModelAttribute("intake")Intake intake, BindingResult result){

    // Validate Intake command object and persist to database
    // ...

    String caseNumber = assignIntakeACaseNumber();

    return "redirect:intakeDetails.htm?caseNumber=" + caseNumber;

  }

}

The Controller reads Intake information from a command object populated from an HTML form, validates the command object, persists the information to the database, and returns a case number.

Everything works great, except for when I redirect to the intakeDetails.htm page, I get a URL that looks like this:

http://localhost:8080/project/intakeDetails.htm?caseNumber=1&amp;users=user1&amp;users=user2&amp;users=user3&amp;users=user4...

How do I prevent the user Collection from showing up in the URL?

+1  A: 

There are no good ways to solve this problem (i.e. without creating custom components, without excessive amounts of explicit xml configuration and without manual instantiation of RedirectView).

You can either instantiate RedirectView manually via its 4-argument constructor, or declare the following bean in your context (near other view resolvers):

public class RedirectViewResolver implements ViewResolver, Ordered {
    // Have a highest priority by default
    private int order = Integer.MIN_VALUE; 

    // Uses this prefix to avoid interference with the default behaviour
    public static final String REDIRECT_URL_PREFIX = "redirectWithoutModel:";     

    public View resolveViewName(String viewName, Locale arg1) throws Exception {
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            return new RedirectView(redirectUrl, true, true, false);
        }
        return null;
    }

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}
axtavt
A: 

Don't use @ModelAttribute. Store the users in the ModelMap explicitly. You're doing as much with the command object anyway.

@RequestMapping(method=RequestMethod.GET)
    public String setupForm(ModelMap model){

        // Set up command object
        Intake intake = new Intake();
        intake.setIntakeDate(new Date());
        model.addAttribute("intake", intake);

        model.addAttribute("users", users);

        return "addIntake";
    }

The disadvantage to this is if a validation error takes place in addIntake(). If you want to simply return the logical name of the form, you must also remember to repopulate the model with the users, otherwise the form won't be setup correctly.

Christian
+1  A: 

The @ModelAttribute method annotation is intended to be used for exposing reference data to the view layer. I can't say for sure in your case, but I wouldn't say that a collection of users qualified as reference data. I suggest that you pass this information through to the model explicitly in your @RequestMapping-annotated handler methods.

If you still want to use @ModelAttribute, there's a blog entry here that discusses the redirect problem.

But all the previous examples have a common issue, as all @ModelAttribute methods are run before the handler is executed, if the handler returns a redirect the model data will be added to the url as a query string. This should be avoided at all costs as it could expose some secrets on how you have put together your application.

His suggested solution (see part 4 of the blog) is to use a HandlerInterceptorAdapter to make the common reference data visible to the view. Since reference data shouldn't be tightly coupled to the controllers, this shouldn't pose a problem, design-wise.

skaffman
Wow, architect buzzword bingo done in one word: HandlerInterceptorAdapter!
Robert Grant