5

I have next working code in my SpringMVC controller:

@RequestMapping(value = "/register", method = RequestMethod.GET)
public void registerForm(Model model) {
    model.addAttribute("registerInfo", new UserRegistrationForm());
}

@RequestMapping(value = "/reg", method = RequestMethod.POST)
public String create(
        @Valid @ModelAttribute("registerInfo") UserRegistrationForm userRegistrationForm,
        BindingResult result) {

    if (result.hasErrors()) {
        return "register";
    }
    userService.addUser(userRegistrationForm);
    return "redirect:/";
}

In short create method try to validate UserRegistrationForm. If form has errors, it leaves user on the same page with filled form fields where error message will be shown.

Now I need to apply the same behaviour to another page, but here I have a problem:

@RequestMapping(value = "/buy/{buyId}", method = RequestMethod.GET)
public String buyGet(HttpServletRequest request, Model model, @PathVariable long buyId) {
    model.addAttribute("buyForm", new BuyForm());
    return "/buy";
}

@RequestMapping(value = "/buy/{buyId}", method = RequestMethod.POST)
public String buyPost(@PathVariable long buyId,
                          @Valid @ModelAttribute("buyForm") BuyForm buyForm,
                          BindingResult result) {

    if (result.hasErrors()) {
        return "/buy/" + buyId;
    }

    buyForm.setId(buyId);
    buyService.buy(buyForm);
    return "redirect:/show/" + buyId;
}

I faced with issue of dynamic url. Now if form has errors I should specify the same page template to stay on current page, but also I should pass buyId as a path variable. Where are a conflict in this two requirements. If I leave this code as is, I get an error (I'm using Thymeleaf as a template processor):

Error resolving template "/buy/3", template might not exist or might not be accessible by any of the configured Template Resolvers

I can write something like return "redirect:/buy/" + buyId, but in this case I lose all data and errors of form object.

What should I do to implement in buyPost method the same behaviour as in create method?

Maxim Kolesnikov
  • 5,075
  • 6
  • 38
  • 68
  • Have a look at http://stackoverflow.com/questions/18039064/spring-mvc-prg-passing-values-to-other-operation/18039981#18039981, you could use FlashAttributes to pass data to the redirected view. – Yugang Zhou Aug 09 '13 at 00:46
  • This solution is better than just redirect, because values of form's fields are saved. But in this case I loose my `BindingResult` for this form, so I still can't show validation errors to user after form submit. – Maxim Kolesnikov Aug 09 '13 at 11:06
  • 1
    You could pass BindingResult along with other data, see http://stackoverflow.com/questions/2543797/spring-redirect-after-post-even-with-validation-errors – Yugang Zhou Aug 09 '13 at 12:18
  • Thanks, now it's works. Previously I tried to pass `BindingResult` object using `FlashAttributes`, but I can't get it in my `buyGet` method through `@ModelAttribute` because `BindingResult` doesn't contains default constructor and can't be instantiated. So I passed this object through session, what's looks a little tricky. – Maxim Kolesnikov Aug 09 '13 at 19:09
  • Hippoom, you could format your comments as an answer and I'll accept it as a correct one. – Maxim Kolesnikov Aug 09 '13 at 19:18

3 Answers3

3

I tried the solution metioned in this post at this weekend, but it doesn't work for BindingResult.

The code below works but not perfect.

@ModelAttribute("command")
public PlaceOrderCommand command() {
    return new PlaceOrderCommand();
}

@RequestMapping(value = "/placeOrder", method = RequestMethod.GET)
public String placeOrder(
        @ModelAttribute("command") PlaceOrderCommand command,
        ModelMap modelMap) {
    modelMap.put(BindingResult.MODEL_KEY_PREFIX + "command",
            modelMap.get("errors"));
    return "placeOrder";
}

@RequestMapping(value = "/placeOrder", method = RequestMethod.POST)
public String placeOrder(
        @Valid @ModelAttribute("command") PlaceOrderCommand command,
        final BindingResult bindingResult, Model model,
        final RedirectAttributes redirectAttributes) {
    if (bindingResult.hasErrors()) {
        redirectAttributes.addFlashAttribute("errors", bindingResult);

        //it doesn't work when passing this          
        //redirectAttributes.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX + "command", bindingResult);

        redirectAttributes.addFlashAttribute("command", command);
        return "redirect:/booking/placeOrder";
    }
    ......
}
Community
  • 1
  • 1
Yugang Zhou
  • 7,123
  • 6
  • 32
  • 60
  • Passing `BindingResult` through `addFlashAttribute` looks like common issue http://stackoverflow.com/questions/15903238/passing-bindingresult-through-redirectionattributes Anyway work-around was found, so the question could be closed. Thanks. – Maxim Kolesnikov Aug 11 '13 at 17:34
  • Mentioned solution didn't work for me too until I changed register in "org.springframework.validation.BindingResult.register" to the modelAttribute's name. – borjab Oct 24 '14 at 16:31
2

*I'm using Hibernate Validator APIs to validate my beans. To preserve form data along with displaying error messages, you need to do these 3 things:

  1. Annotate your bean (eg. @NotEmpty, @Pattern, @Length, @Email etc.)

enter image description here

  1. Inside controller:

    @Controller public class RegistrationController {

    @Autowired
    private RegistrationService registrationService;
    
    @RequestMapping(value="register.htm", method=RequestMethod.GET, params="new")
    public String showRegistrationForm(Model model) {
        if (!model.containsAttribute("employee")) {
            model.addAttribute("employee", new Employee());
        }
        return "form/registration";
    }
    
    @RequestMapping(value="register.htm", method=RequestMethod.POST)
    public String register(@Valid @ModelAttribute("employee") Employee employee, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
            redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.employee", bindingResult);
            redirectAttributes.addFlashAttribute("employee", employee);
            return "redirect:register.htm?new";
        }
        registrationService.save(employee);
        return "workspace";
    }
    // ....
    

    }

  2. Update your view/jsp to hold error messages:

enter image description here

This article can surely be helpful.

masT
  • 804
  • 4
  • 14
  • 29
  • While this link may answer the question, it is better to include the essential parts of the answer [here](http://meta.stackexchange.com/a/8259) and provide the link for reference. Link-only answers can become invalid if the linked page changes. – bummi Nov 09 '13 at 19:10
  • 3
    The link is broken :( – shyam Mar 02 '16 at 06:22
0

You can change your POST implementation to this:

@RequestMapping(value = "/buy/{buyId}", method = RequestMethod.POST)
public String buyPost(@PathVariable long buyId,
                          @Valid @ModelAttribute("buyForm") BuyForm buyForm,
                          BindingResult result) {

    buyForm.setId(buyId); // important to do this also in the error case, otherwise, 
                          // if the validation fails multiple times it will not work.

    if (result.hasErrors()) {
        byForm.setId(buyId);
        return "/buy/{buyId}";
    }

    buyService.buy(buyForm);
    return "redirect:/show/{buyId}";
}

Optionally, you can also annotate the method with @PostMapping("/buy/{buyId}") if you use Spring 4.3 or higher.

Wim Deblauwe
  • 25,113
  • 20
  • 133
  • 211