How do I pass ModelAttribute between Controllers?

th3an0maly picture th3an0maly · Sep 17, 2012 · Viewed 13.2k times · Source

I'm trying to create a Home page with 2 functionalities:

  1. Login
  2. Sign Up

I'm mapping each request to a different controller and trying to get the result back to my home.jsp. But I'm having trouble passing only certain specific ModelAttribute around, between Controllers. More specifically, i cant get the changed I make to the ModelMap and BindingResult in one controller to be reflected in others.

I'm sure there's something basically wrong with what I'm doing. Please help.

There are 2 forms in my home.jsp. One for Login:

<form:form name="loginForm" modelAttribute="loginUser" action="login" method="post">

    Email: <form:input name="loginEmail" id="loginEmail" value="" path="email"/>
    <form:errors path="email" cssClass="error" />
    <br/>

    password: <form:password name="loginPassword" Id="loginPassword" value="" path="password" />
    <form:errors path="password" />
    <br/>

    <input type="submit" id="id_login" value="Login">

</form:form>

and the other one for Sign Up:

<form:form name="SignUpForm" modelAttribute="signUpUser" action="signup" method="post">

    Full Name: <form:input name="name" id="name" value=""   path="name"/>
    <form:errors path="name" cssClass="error" />
    <br/>

    Email: <form:input name="signupEmail" id="signupEmail" value="" path="email"/>
    <form:errors path="email" cssClass="error" />
    <br/>

    password: <form:password name="signUpPassword" Id="signUpPassword" value="" path="password" />
    <form:errors path="password" />

    <input type="submit" id="id_signUp" value="Sign Up">

</form:form>

I have 3 controllers: HomeController.java:

@RequestMapping("/home")
public String showHome(ModelMap model,
        @ModelAttribute("loginUser") User loginUser,
        BindingResult loginResult,
        @ModelAttribute("signUpUser") User signUpUser,
        BindingResult signUpResult) {
    return "home";
}

AuthenticationController.java:

@RequestMapping("/login")
public String login(@ModelAttribute("loginUser") User user,
        BindingResult result, ModelMap model, HttpServletRequest request,
        HttpServletResponse response) {

    loginFormValidator.validate(user, result);

    if(Errors in `result`)
        return "forward:/home";

    // Authentication Logic
    request.getSession().setAttribute("s_user_obj", some_variable);
    return "forward:/home";

}

and ProfileController.java:

@RequestMapping("/signup")
public String signUpUser(@ModelAttribute("signUpUser") User user,
        BindingResult result, ModelMap model) {
// Do stuff related to Sign Up
// Forward to home.jsp
    }

When the request is forwarded to /home, I'm getting same values in both loginUser and signUpUser. Worse, there are no errors i.e. the result variable is not reflecting the ones in the previous controllers.

I'm sure there's a better way of doing this, this is a newbie's attempt at it. Please advice.

Answer

Biju Kunjummen picture Biju Kunjummen · Sep 17, 2012

The issue that I see here is the way @ModelAttribute("loginUser") and @ModelAttribute("signUpUser") is interpreted for methods by Spring - here is some reference - http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method-args

The arguments are retrieved from the model(and instantiated if necessary), but the problem part is that they are repopulated based on the request parameters, which would end up populating both your attributes the same way. The only workaround that I can think of is to explicitly retrieve the model attributes yourself in your method:

@RequestMapping("/home")
public String showHome(ModelMap model) {
    model.get("loginUser");...
    model.get("signUpUser");...
    return "home";
}

Update

Based on your comments, let me recommend an alternate flow and a more explicit flow:

Have a method which sets both your loginUser and signUpUser model attributes this way:

private void populateAttributes(Model model, User loginUser, User signupUser){
    if (loginUser==null) loginUser = new User();
    if (singupUser==null) singupUser = new User();
    model.addAttribute("loginUser", loginUser);
    model.addAttribute("signupUser", signupUser);
}

Now in your login flow:

@RequestMapping("/login")
public String login(User user,
    BindingResult result, Model model, HttpServletRequest request,
    HttpServletResponse response) {

loginFormValidator.validate(user, result);

if(Errors in `result`)
    populateAttributes(user, null);
    return "home";

// Authentication Logic
request.getSession().setAttribute("s_user_obj", some_variable);
populateAttributes(model, user, null);
return "home";

}

Similar flow in your signup:

@RequestMapping("/signup")
public String signUpUser(User user,
    BindingResult result, Model model) {
    populateAttributes(model, null, user);
    return "home"
}

and showHome:

@RequestMapping("/home")
public String showHome(Model model,) {
    populateAttributes(model, null, null);
    return "home";
}