kazbeel
kazbeel

Reputation: 1436

Spring - Model attributes after BindingResult with errors

In the following piece of code I just want to create a new user and link it to the selected groups.

Everything works fine when the user and group are valid. The problem comes when the bindingresult has errors. The controller detects such error (all fine so far) and returns the same view (I want to keep the data entered by the user) but the list of groups is empty (I have discovered that, after showing again the view, userform.groups is null).

Has anyone a clue about what the problem could be?

UserForm

@Component
public class UserForm {
    @Valid
    private User user;
    @Valid
    private Collection<Group> allGroups;

    // Setters and getters 
}

UserController

@Controller
public class UserController {

    @Autowired
    UserGroupService userGroupService;

    @Autowired
    BCryptPasswordEncoder passwordEncoder;

    @InitBinder
    public void initBinder (WebDataBinder binder) {
        binder.registerCustomEditor(Set.class, "userform.user.groups", new GroupListEditor(userGroupService));
    }

    @RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.GET)
    public ModelAndView createUsetGet () {

        ModelAndView mav = new ModelAndView("/admin/users/CreateUser");

        UserForm userForm = new UserForm();
        userForm.setUser(new User());
        userForm.setGroups(userGroupService.getAllEnabledGroups());
        mav.addObject("userform", userForm);

        return mav;
    }

    @RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.POST)
    public String createUserPost (@Valid @ModelAttribute("userform") UserForm userForm, BindingResult result) {

        if (result.hasErrors() == true) {
            return "/admin/users/CreateUser";
        }
        userForm.getUser().setPassword(passwordEncoder.encode(userForm.getUser().getPassword()));
        userGroupService.saveUser(userForm.getUser());

        return "redirect:/admin/users/ViewUsers";
    }
}

CreateUser.jsp (Only piece regarding the groups)

<form:form modelAttribute="userform" method="post">
    Username:
    <form:input path="user.loginName"/>

    <!-- More fields -->

    <form:select path="user.groups" multiple="true">
        <form:options items="${userform.groups}" itemValue="id" itemLabel="name" />
    </form:select>

    <button type="submit">Create</button>
</form:form>

Any help is appreciated!

Upvotes: 0

Views: 6085

Answers (2)

M. Deinum
M. Deinum

Reputation: 124461

The object gets recreated and values are bound to the resulting object. Which means no group objects.

Also those shouldn't be in the object at all. To solve use a @ModelAttribute annotated method, which will be invoked for each request handling method and create an object and fill the list of groups.

@ModelAttribute
public void init(Model model) {
    UserForm userForm = new UserForm();
    userForm.setUser(new User());
    model.addAttribute("userform", userForm);
    model.addAtrribute("groups", userGroupService.getAllEnabledGroups());
}

@RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.GET)
public String createUsetGet () {
    return "/admin/users/CreateUser";
}

@RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.POST)
public String createUserPost (@Valid @ModelAttribute("userform") UserForm userForm, BindingResult result) {

    if (result.hasErrors() == true) {
        return "/admin/users/CreateUser";
    }
    userForm.getUser().setPassword(passwordEncoder.encode(userForm.getUser().getPassword()));
    userGroupService.saveUser(userForm.getUser());

    return "redirect:/admin/users/ViewUsers";
}

Ofcourse your jsp has to change slightly also.

<form:select path="user.groups" multiple="true">
    <form:options items="${groups}" itemValue="id" itemLabel="name" />
</form:select>

There is one drawback of using this approach now the userGroupService.getAllEnabledGroups() is called for each incoming request. This might not be needed. You could store those in the session using the @SessionAttributes annotation on the class.

@Controller
@SessionAttributes("groups")
public class UserController {

    @Autowired
    UserGroupService userGroupService;

    @Autowired
    BCryptPasswordEncoder passwordEncoder;

    @InitBinder
    public void initBinder (WebDataBinder binder) {
        binder.registerCustomEditor(Set.class, "userform.user.groups", new GroupListEditor(userGroupService));
    }

    @ModelAttribute("groups")
    public List<Group> groups() {
        return userGroupService.getAllEnabledGroups();
    }

    @ModelAttribute("userform")
    public UserForm userform() {
        UserForm userForm = new UserForm();
        userForm.setUser(new User());
        return userForm;
    }

    @RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.GET)
    public String createUsetGet () {
        return "/admin/users/CreateUser";
    }

    @RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.POST)
    public String createUserPost (@Valid @ModelAttribute("userform") UserForm userForm, BindingResult result, SessionStatus status) {

        if (result.hasErrors() == true) {
            return "/admin/users/CreateUser";
        }
        userForm.getUser().setPassword(passwordEncoder.encode(userForm.getUser().getPassword()));
        userGroupService.saveUser(userForm.getUser());
        status.setComplete();
        return "redirect:/admin/users/ViewUsers";
    }
}

You will then need, on success, to tell the SessionStatus that you are finished. If you don't do this your session might pollute.

Upvotes: 2

Pusker Gy&#246;rgy
Pusker Gy&#246;rgy

Reputation: 398

It's because the information about the validation errors is lost after redirect. You can solve this using RedirectAttributes. Check this tutorial.

Upvotes: 1

Related Questions