Spring Boot reloading page with dynamic URL in the Controller @PostMapping when @Valid constraints of input doesn't met, to show errors on webpage

Viewed 448

I'm currently working on a usermanagement system for a website with Java Spring Boot, Thymeleaf, and Salespoint Framework for a university project.

On pages where users can edit their infos e.g. email or password, I use a validation of the form in which the data is stored via the @Valid annotaion. If the BindingResult has errors it should return to the same page to show the user the inputs where something was wrong. I used this on the following page to create a new user and it just works fine:

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/createmember")
public String createMember(@ModelAttribute("creationForm CreationForm creationForm) {
    return "/createmember";
}

@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/createmember")
public String createMember(@Valid @ModelAttribute("creationForm") CreationForm creationForm, BindingResult bindingResult) {

    if (bindingResult.hasErrors()) {
        return "/createmember";
    }
    //function to create new member
    return "redirect:/memberlist";
}

The error message is implemented in the html page like this:

<td>
<input id="username" name="username" th:field="*{username}" required type="text" placeholder="Username">
<p th:if="${#fields.hasErrors('username')}" th:errors="*{username}"class="invalidinput">username errors</p>
</td>

The problem starts as soon as I want to use this for a page with a dynamic URL. In the following code, I want to change a users' password as a logged-in admin. For everything in the system to know which user currently is edited I use a dynamic URL password/{username}. The pure changing of the password also works fine as long as I don't check whether the password meets my validation requirements. It changes the users' password, if the newPassword and confirmationPasswordare the same, and if they are not, it doesn't change the password.

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("password/{username}")
public String password(@PathVariable String username, PasswordForm passwordForm) {
    passwordForm.setUsername(username);
    model.addAttribute("passwordForm", passwordForm);
    return "/password";
}

@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/password")
public String password(@Valid @ModelAttribute PasswordForm passwordForm, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "/password";
    }

    if (//checks if both passwords are equal) {
    //function to change password
        return "redirect:/memberlist";
    }
    return "redirect:/";
}

The HTML is equal to createMember.

The problem here is now that as soon as I give the form invalid inputs and submit the form, thymeleaf throws an error:

Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'passwordForm' available as a request attribute

And some other errors caused by this.

If I change the code to

if (bindinResult.hasErrors()) {
   return "/password/" + passwordForm.getUsername();
}

Thymeleaf can't find the template "/password/username".

However, if I change the code to:

if (bindinResult.hasErrors()) {
   return "redirect:/password/" + passwordForm.getUsername();
}

It reloads the page without problems, but everything is gone and it doesn't show any errors, so this is not an option.

I hope this is not too much, I searched a while for solutions on the web but there were none.

Does anyone have any idea what could be the problem?

1 Answers

After hours of not seeing the problem I just found out that I only forgot to set a @ModelAttribute name. Fixed code below is working fine.

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("password/{username}")
public String password(@PathVariable String username, @ModelAttribure("passwordForm") PasswordForm passwordForm) {
    passwordForm.setUsername(username);
    return "/password";
}

@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/password")
public String password(@Valid @ModelAttribute("passwordForm") PasswordForm passwordForm, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "/password";
    }

    if (//checks if both passwords are equal) {
    //function to change password
        return "redirect:/memberlist";
    }
    return "redirect:/";
}
Related