英文:
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="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;
// ... getters and setters
The controller looks like this:
@PostMapping("/register")
public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response,
@ModelAttribute @Valid Member member, ModelMap model, BindingResult result){
log.info("Attempting to register a user");
// some other error checking not related to javax.validation ...
memberService.save(member);
return new ModelAndView("/success", model);
}
Finally, the (simplified) HTML/Thymeleaf form looks like this (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>
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 '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]
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.
- 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 "register";
}
- You can controller advice for handling exceptions
@RestControllerAdvice
@EnableWebMvc
public class ExceptionAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Response handleInvaldException (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
}
}
答案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.
<input type="text" class="form-control" placeholder="Email" id="username" th:field="*{username}" required>
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论