9

Spring 3 MVC Handle multiple form submit with a Controller.

I am developing JSP page with multiple forms. 1) Search Customer, 2) Search Product, 3) Print Something etc. I've a different form bind object tied to each form and my controller code looks similar to below

  @Controller
  @RequestMapping(value="/search.do")
  public class SearchController {

    @RequestMapping(method = RequestMethod.GET)
    public String pageLoad(ModelMap modelMap) {
      modelMap.addAttribute("productSearch", new ProductSearchCriteria());
        modelMap.addAttribute("customerSearch", new CustomerSearchCriteria());
        modelMap.addAttribute("print", new PrintForm());
    }

    @RequestMapping(method = RequestMethod.POST)
    public ModelAndView searchProducts(@ModelAttribute("productSearch") ProductSearchCriteria productSearchCriteria,
            BindingResult result, SessionStatus status) {
            //Do Product search
            return modelAndView;
    }

    @RequestMapping(method = RequestMethod.POST)
    public ModelAndView searchCustomers(@ModelAttribute("customerSearch") CustomerSearchCriteria customerSearchCriteria,
            BindingResult result, SessionStatus status) {
            //Do Customer search
            return modelAndView;
    }

    @RequestMapping(method = RequestMethod.POST)
    public ModelAndView printSomething(@ModelAttribute("print") PrintForm printForm,
            BindingResult result, SessionStatus status) {
            //Print something
            return modelAndView;
    }
  }

Above certainly doesn't work as I assumed it would. I get exception saying 'Request method 'POST' not supported'. If I have only one POST method inside above controller say searchProducts it works well. But it won't with more than one methods with POST. I also tried adding hidden parameter in JSP and changing method signatures similar to below only to get the same exception again.

  @RequestMapping(method = RequestMethod.POST, params="pageAction=searchProduct")
    public ModelAndView searchProducts(@ModelAttribute("productSearch") ProductSearchCriteria productSearchCriteria,
            BindingResult result, SessionStatus status) {
            //Do Product search
            return modelAndView;
   }

Can anyone please suggest correct way to achieve above? Also any reference to source material or further reading will be greatly appreciated. Thanks.

EDIT #1: The above approach with params="pageAction=searchProduct" works perfectly as far as you get your hidden parameter right in JSP (see comment below). In addition to that, answers by @Bozho and @Biju Kunjummen is also very helpful and a good (possibly better?) alternative to tackle multiple form submit.

4
  • 2
    Are you sure approach with hidden field doesn't work? Commented Jan 31, 2011 at 11:07
  • Yes indeed. I am baffled why it didn't work. I found similar question here with answer using same params="name=value" technique. Many people up voted and it must be the one way to nail this. Commented Jan 31, 2011 at 11:31
  • Very strange, it should work. Commented Jan 31, 2011 at 11:42
  • @axtavt Got it working. It was so painfully obvious an error that I wish to bury myself in shame. My hidden parameter was defined as <input type="hidden" id="action" value="eads"/> It should be <input type="hidden" name="action" value="eads"/>. The doubt you expressed made me to go over it again and again, until it finally hit me. I don't have enough privileges to up vote you but Really appreciate your help. Thanks. Commented Jan 31, 2011 at 15:14

4 Answers 4

12

Your mappings are not completely correct @TMan:

  1. The mappings in web.xml are to get Spring Dispatcher servlet to handle your request - eg. like in your case:

    <servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/META-INF/appServlet/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    

    appServlet *.do So now any request to URI's ending with .do will be handled by Spring's DispatcherServlet

  2. The controller can have a mapping, like in your case:

    @Controller @RequestMapping(value="/search.do")

But where you have gone wrong is in the RequestMapping for the controller :

@Controller
@RequestMapping(value="/search")

The .do at the end of RequestMapping should not be there, as it is purely for the Dispatcher servlet to be invoked, once that is invoked it will handle dispatching to the correct Controller method, so your call to /search.do will end up with the controller.

  1. Now, each method can be annotated with RequestMapping like in your case, with a RequestMethod atribute of RequestMapping, specifying whether to dispatch to the method in case of POST or GET:

    @RequestMapping(method = RequestMethod.GET)
    @RequestMapping(method = RequestMethod.POST)
    

So when there is a POST to the /search.do URI the appropriate method will be called.

In your case, there are multiple methods annotated with the RequestMethod.POST attribute, so the AnnotationMethodHandlerAdapter component of Spring simply doesn't know which method to dispatch to.

There are a couple of things that you can do:

  1. put a request mapping for each method, the way suggested by @Bozho: @RequestMapping(value="/customers" method=Request.POST) @RequestMapping(value="/products" method=Request.POST) so now your request URI's would be

    /search/customers.do /search/products.do and you should be doing POST to get the correct dispatch method.

  2. Get rid of method=Request.POST all together and depend on the @RequestMapping like above to find the correct method.

  3. You can pass an optional params attribute to RequestMapping, which again is a way to help Spring find your correct method to dispatch to:

    @RequestMapping(method=Request.POST, params="customers")

with customers parameters in the request or say products parameters in the request.

The simplest will be option 1 though.

EDIT 1: Adding a reference to a good Spring document - http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html

Sign up to request clarification or add additional context in comments.

4 Comments

Hmmm.. Thanks for very detailed and helpful explanation. It did clear up few confusions. Unfortunately I still face the same dilemma. I have one JSP called search.jsp mapped to URL > search.do. This page has three forms and depending on which form you submit you see the different result on this same page i.e. search.jsp > search.do. I learned from both @Bozho and your responses and tried submitting my forms to different URL e.g. search/customer (incorrect I think) and search/customer.do (should work?) but tomcat kept moaning 'requested resource not found'. I am stumped.
The 404 is not for the the controller, I am guessing this is for the view now, have you validated that the view is correctly configured - for eg. <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean>
Yes I have similar config as you have described, difference being my view resolver bean is using UrlBasedViewResolver class. Would that be an issue with this? I have resolved my problem with params="pageAction=searchProduct" approach but it would be very handy to figure out what I am doing wrong here with this popular url trick.
Nice point (3) in combination with hidden form field: I was looking for solution like that.
5

When you have multiple forms, why don't you map the three forms to different URLs?

@RequestMapping("/products")
public ModelAndView searchProducts(..)

@RequestMapping("/customers")
public ModelAndView searchCustomers(..)

And have your form actions be pointed at /search/products and /search/customers (no need of the .do)

3 Comments

Thanks. I have tried this before but tomcat kept complaining 'requested resource not available'. I am sure problem lies in web.xml where my servlet URL mapping is to only <url-pattern>*.do</url-pattern>. I am in the process of working out correct config for this. Any suggestion from top-of your head would be very handy. Cheers.
@TMan - ah, if you mapped the dispatcher servlet to *.do, then it won't work. Another way is to map it to /, so that everything is handled by spring.
Thanks Bozho, I have tried this approach but something isn't working for me. While I found the different working solution I am still investigating where I have gone wrong with this approach. I will update the question text if I get this trick working too. Cheers.
5

I've had the same problem: two forms (register and contact) on the same page with two submit buttons. Each form has its own validation by Spring.

I've managed to do this in one Controller:

@RequestMapping(value = "/contactOrRegister.do", method = RequestMethod.GET)
public void contactOrRegisterFormGet(@ModelAttribute("contactForm") ContactForm contactForm, @ModelAttribute("registerForm") RegisterForm registerForm) {
    //-----prepare forms for displaying here----
}

@RequestMapping(value = "/contact.do", method = RequestMethod.POST)
public String contactFormPost(@ModelAttribute("contactForm") ContactForm contactForm, BindingResult result, final Model model) {        
    contactValidator.validate(contactForm, result);
    if (result.hasErrors()) {
        model.addAttribute("registerForm", new RegisterForm());
        return "contactOrRegister";
    }
    //--- validation is passed, do submit for contact form here ---
}

@RequestMapping(value = "/register.do", method = RequestMethod.POST)
public String registerFormPost(@ModelAttribute("registerForm") RegisterForm registerForm, BindingResult result, final Model model) {        
    registerValidator.validate(registerForm, result);
    if (result.hasErrors()) {
        model.addAttribute("contactForm", new ContactForm());
        return "contactOrRegister";
    }
    //--- validation is passed, do submit for register form here ---
}

I need to create a new form (contact or register) and put it in model when validation is failed because "contactOrRegister" view needs both forms to display. So when "contact" form was submitted and has errors the "register" form content will be erased. This way fits for me.

The contactOrRegister.jsp contains both forms with different action:

 <form:form action="/register.do" modelAttribute="registerForm" method="POST">
    <!-- register form here -->
 </form:form>

 <form:form action="/contact.do" modelAttribute="contactForm" method="POST">
    <!-- contact form here -->
 </form:form>

Comments

2

You can write

@RequestMapping(method = {RequestMethod.GET,RequestMethod.POST})

when using multiple method on the same controller. This is useful when sometimes the user wants to submit the data using GET or when you use return "forward:url"; from another controller.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.