0
votes

I'm working on validating a form and showing inline errors for fields that don't pass validation using Thymeleaf. I'm using Spring Boot 1.5.8.

I have a User object that will be bound to the form. It has fields like: username and email. Here's the form declaration,

<form role="form" class="form" method="post"
    th:action="@{'/admin/user/new'}" th:object="${user}">

I set up an error controller like this because I wanted a catch all error page. I also created a template at src/main/resources/templates/error.html. This was because the security context was lost on the error page according to this github issue, although it indicates the issue should be fixed in my version. Without this controller the default error template is shown, but I have no access to the security context. With this controller in place and the error.html template any random error will be handled.

@Controller
public class ErrorController extends BasicErrorController {

    /**
     * This Bean declaration provides the Spring Security Context to 4xx responses. This is reported as
     * fixed in Spring Boot 2.0.0:
     *
     * https://github.com/spring-projects/spring-boot/issues/1048
     *
     * @param springSecurityFilterChain
     * @return
     */
    @Bean
    public FilterRegistrationBean getSpringSecurityFilterChainBindedToError(
                    @Qualifier("springSecurityFilterChain") Filter springSecurityFilterChain) {

            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(springSecurityFilterChain);
            registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
            return registration;
    }

    @Autowired
    public ErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes, new ErrorProperties());
    }

    @RequestMapping(value = "/error")
    public String error(Model model) {
        return "/error";
    }

    @Override
    public String getErrorPath() {
        return "/__dummyErrorPath";
    }

}

For this example I'm just trying to validate the email field and to show a message if validation fails next to this field. The class field looks like this on User,

@NotNull
@Email
private String email;

The Thymeleaf field looks like this,

<div th:fragment="email" class="form-group">
    <label>Email*</label>
    <input class="form-control" type="text" th:field="*{email}" th:required="required" />
    <span class="error" th:if="${#fields.hasErrors('email')}" th:errors="*{email}">...</span>
</div>

The new user form controller method looks like this,

@GetMapping("admin/user/new")
public String newUser(Model model, HttpServletRequest request) {
    model.addAttribute("user", new User());
    return "admin/user/new";
}

And the corresponding POST method looks like this,

@PostMapping("admin/user/new")
public String newUser(@Valid @ModelAttribute User user, final ModelMap model, BindingResult bindingResult) {
    Assert.notNull(user, "User must not be null");
    if (bindingResult.hasErrors()) {
        return "admin/user/new";
    }
    userService.save(user);
    model.clear();
    return "redirect:/admin/user/list";
}

In this POST method I'm checking to see if the BindingResult has errors. If it does I'm expecting the admin/user/new template to be rendered with the inline error message shown. However my catch all error template is being rendered. The URL still shows /admin/user/new and refreshing the page still shows the error template.

My main question is how can I use the catch all error page and still show inline errors when I need to if possible?

2

2 Answers

1
votes

I believe that the BindingResult needs to directly follow the Valid annotation'd parameter in the parameter list.

See Why does BindingResult have to follow @Valid?

I'd give that a go

0
votes

What about using this in html-view?

<div th:if="${param.error}"
     th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}">
</div>