Spring Boot和Thymeleaf在字段错误时返回400错误。

huangapple go评论75阅读模式
英文:

Spring Boot and Thymeleaf are returning a 400 error on field errors

问题

我正在使用Spring Boot 2.3.1和Thymeleaf创建一个简单的注册应用程序,以便用户可以创建并将“Member”模型保存到数据库中。我使用“javax.validation”框架确保字段不为空,在模型有效时它可以正常工作,但是如果模型无效并且缺少字段,则会将我从默认的“error.html”页面退出,而不是将用户带回注册表单,我可以在那里显示字段级别的错误(例如,“需要用户名”)。

该模型很简单,如下所示:

@Entity
@Table(name="member")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Email
    @NotEmpty
    private String username;

    @NotEmpty
    @Column(name="password", nullable = false)
    private String password;
   
    // ... 获取器和设置器
}

控制器如下所示:

@PostMapping("/register")
public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response,
                                       @ModelAttribute @Valid Member member, ModelMap model, BindingResult result){
   log.info("Attempting to register a user");

   // 一些与javax.validation无关的其他错误检查...

   memberService.save(member);

   return new ModelAndView("/success", model);
}

最后,(简化后的)HTML/Thymeleaf表单如下所示(register.html):

<form th:action="@{/register}" th:object="${member}" method="post">

    <input type="text" class="form-control" placeholder="Email" id="username" th:field="*{username}">

    <input type="password" placeholder="Enter your Password" id="password" class="form-control" th:field="*{password}">

    <input type="submit" class="btn btn-primary btn-block btn-login">
</form>

在提交带有“username”和“password”值的表单时,一切正常,但是如果我省略其中一个,比如“username”,我会得到以下“WARN”日志语句:

Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 4 errors
Field error in object 'member' on field 'username': rejected value []; codes [NotEmpty.member.username,NotEmpty.username,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [member.username,username]; arguments []; default message [username]]; default message [must not be empty]

但是然后服务器返回400错误并呈现“error.html”页面,而不是返回到表单,同样重要的是我的控制器中的registerMember()方法根本不被调用。它完全绕过了我的逻辑,因此我甚至无法使用BindingResult手动检查错误。

如果我在模型上省略了@Valid注释,如@ModelAttribute Member member,那么我的方法会被调用,但是不会使用任何javax.validation,并且BindingResult.getErrorCount()0,从而完全没有使用它的意义(我必须手动验证自己)。

您知道如何让@Valid发挥作用并返回到/register端点以显示字段级别的错误吗?

英文:

I'm using Spring Boot 2.3.1 and Thymeleaf for a simple registration app so a user can create and save a Member model to the database. I'm using the javax.validation framework to ensure fields are not empty and it works fine when the model is valid, but an invalid model with missing fields kicks me out with a 400 error code to my default error.html page, instead of taking the user back to the registration form where I can display field-level errors (e.g., "Username is required").

The model is simple and looks like this:

@Entity
@Table(name=&quot;member&quot;)
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Email
    @NotEmpty
    private String username;

    @NotEmpty
    @Column(name=&quot;password&quot;, nullable = false)
    private String password;
   
    // ... getters and setters

The controller looks like this:

@PostMapping(&quot;/register&quot;)
public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response,
                                       @ModelAttribute @Valid Member member, ModelMap model, BindingResult result){
   log.info(&quot;Attempting to register a user&quot;);

   // some other error checking not related to javax.validation ...

   memberService.save(member);

   return new ModelAndView(&quot;/success&quot;, model);
}

Finally, the (simplified) HTML/Thymeleaf form looks like this (register.html):

       &lt;form th:action=&quot;@{/register}&quot; th:object=&quot;${member}&quot; method=&quot;post&quot;&gt;

             &lt;input type=&quot;text&quot; class=&quot;form-control&quot; placeholder=&quot;Email&quot; id=&quot;username&quot; th:field=&quot;*{username}&quot;&gt;

             &lt;input type=&quot;password&quot; placeholder=&quot;Enter your Password&quot;  id=&quot;password&quot; class=&quot;form-control&quot; th:field=&quot;*{password}&quot;&gt;

             &lt;input type=&quot;submit&quot; class=&quot;btn btn-primary btn-block btn-login&quot;&gt;
        &lt;/form&gt;

On form submit with a value for username and password everything works, but if I omit either of those, like the username I get the following WARN log statement:

Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 4 errors
Field error in object &#39;member&#39; on field &#39;username&#39;: rejected value []; codes [NotEmpty.member.username,NotEmpty.username,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [member.username,username]; arguments []; default message [username]]; default message [must not be empty]

But then the server returns a 400 error and renders the error.html page instead of returning to the form, and equally important is my registerMember() method in the controller isn't called at all. It by passes my logic entirely so I can't even manually inspect the errors using BindingResult.

If I omit the @Valid annotation on my model like @ModelAttribute Member member then my method is invoked but none of the javax.validation is used and the BindingResult.getErrorCount() is 0, thus defeating the entire point of using it (I'd have to validate myself manually).

Any idea how I can let @Valid do its job and return back to the /register endpoint to display the field-level errors?

答案1

得分: 5

以下是您要翻译的内容:

您可以通过以下方式解决这个问题。

1)您需要将方法签名从

public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response, @ModelAttribute @Valid Member member, ModelMap model, BindingResult result)

更改为

public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response, @ModelAttribute @Valid Member member, BindingResult result, ModelMap model)

因为绑定结果应该立即应用于 @Valid 对象。

在此之后,您可以按如下所示处理错误并将其发送到所需的表单:

if (bindingResult.hasErrors()) {
    return "register";
}

2)您可以使用控制器建议来处理异常

@RestControllerAdvice
@EnableWebMvc
public class ExceptionAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Response handleInvalidException(MethodArgumentNotValidException ex, Response response) {    
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((err) -> {
            String fieldName = ((FieldError) err).getField();
            String errorMessage = err.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        response.setData(errors);
        // retr 这部分内容不完整,无法确定翻译
    }
}
英文:

You can solve this problem by following ways.

  1. You need to change the method signature from

public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response,@ModelAttribute @Valid Member member, ModelMap model, BindingResult result)

to this

public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response,@ModelAttribute @Valid Member member, BindingResult result, ModelMap model) 

Because binding result should be immediate to @Valid object.

After this you can errors and send it desired form as below

if (bindingResult.hasErrors()) {
     return &quot;register&quot;;
}
  1. You can controller advice for handling exceptions

@RestControllerAdvice
@EnableWebMvc

public class ExceptionAdvice {

@ExceptionHandler(MethodArgumentNotValidException.class)
public Response handleInvaldException (MethodArgumentNotValidException ex, Response response) {    
      Map &lt; String, String &gt; errors = new HashMap &lt; &gt; ();
        ex.getBindingResult().getAllErrors().forEach((err) - &gt; {
        String fieldName = ((FieldError) err).getField();
        String errorMessage = err.getDefaultMessage();
        errors.put(fieldName, errorMessage);
    });
    response.setData(errors);
   retr
}

}

答案2

得分: 1

在实际应用中,表单验证发生在前端。我强烈建议您阅读有关默认的HTML和JS表单验证的内容。例如,@NotNull 验证在您的HTML标签中只是 required 属性。该属性将不允许您在字段为空时提交表单。可以使用简单的JS或jQuery执行更复杂的验证。

<input type="text" class="form-control" placeholder="电子邮件" id="username" th:field="*{username}" required>

在后端,诸如 @NotNull 的注解用于重新检查您是否将不正确的数据保存到数据库中。但仅使用这些注解作为唯一的检查是不自然的。

英文:

In real applications, form validation occurs on the front-end side. I highly recommend you to read about default html and js form validation. For example, @NotNull validation is just required attribute at your html tag. This attribute will not allow you to submit the form while the field is empty. More complex validation can be performed using simple JS or jQuery.

&lt;input type=&quot;text&quot; class=&quot;form-control&quot; placeholder=&quot;Email&quot; id=&quot;username&quot; th:field=&quot;*{username}&quot; required&gt;

On the back-end side, annotations such as @NotNull are used for rechecking that you are not saving incorrect data to your db. But it is unnatural to use these annotations as the only check.

huangapple
  • 本文由 发表于 2020年7月27日 02:17:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/63103987.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定