About org.springframework.web.bind.annotation.ModelAttribute annotation of Spring Web MVC

RMAG news

Controller

Following is a Controller to handle POST requests of HTTP form.

@Controller
@RequestMapping("/manage")
public class PrivateController {

    @PostMapping("/new")
    public String saveCreateMessage(@Valid @ModelAttribute("message") MessageMvcDto message, BindingResult errors, Model model) {
        if (errors.hasErrors()) {
            model.addAttribute("isEdit", false);
            model.addAttribute("postHandler", "new");
            return "private/message";
        }

        MessageDto dto = new MessageDto();
        dto.setPublishDate(message.getPublishDate());
        dto.setRemoveDate(message.getRemoveDate());
        dto.setDescription(message.getDescription());
        dto.setOwner(this.getLoginName());
        this.messageService.save(dto);
        return "redirect:/manage";
    }

    // ...
}

Before executing saveCreateMessage() method, the framework will bind values sent by POST to MessageMvcDto instance, perform validation and build BindingResult instance.

Then execute saveCreateMessage() and pass MessageMvcDto, BindingResult instance as parameter. Assume that validation failed and has an error, a template string is returned.

Since parameter message is annotated with @ModelAttribute(“message”), the binded MessageMvcDto and BindingResult instance will write into Model as follows.

KeyValue
messageMessageMvcDto instance
org.springframework.validation.BindingResult.messageBindingResult instance

If annotated with @ModelAttribute (without name), will become the following.

KeyValue
messageMvcDtoMessageMvcDto instance
org.springframework.validation.BindingResult.messageMvcDtoBindingResult instance

Rendering

Values in the Model will become attributes of HttpServletRequest (but not in Session). WebEngineContext instance is built for Thymeleaf to lookup the value of expression.

Using the following template

<!-- ... -->
  <form th:action="@{/manage/__${postHandler}__}" method="post" th:object="${message}" th:classappend="${#fields.hasErrors()}?error" class="ui form">
    <fieldset>
      <legend th:text="#{message}"></legend>
      <div th:if="${#fields.hasErrors()}" th:classappend="${#fields.hasErrors()}?error" class="ui message">
        <ul class="list">
          <li th:each="error:${#fields.allErrors()}" th:text="${error}"></li>
        </ul>
      </div>
      <div class="field">
        <label th:text="#{message.publishDate}"></label>
        <div class="two fields">
          <div th:classappend="${#fields.hasErrors('publishDateDate')}?error" class="field">
            <input id="edPublishDateDate" type="date" th:field="*{publishDateDate}" />
          </div>
          <div th:classappend="${#fields.hasErrors('publishDateTime')}?error" class="field">
            <input id="edPublishDateTime" type="time" th:field="*{publishDateTime}" />
          </div>
        </div>
      </div>
      <!-- ... -->
      <button id="edSave" class="ui mini primary button">[[#{save}]] <i class="send icon"></i></button>
      <button id="edCancel" type="button" class="ui mini button" onclick="document.location='../manage'; return false;">[[#{cancel}]] <i class="window close icon"></i></button>
    </fieldset>
  </form>
<!-- ... -->

#fields expression can be used within form tag with th:object only.

When processing form tag, attribute th:object will save into HttpServletRequest attribute springBoundObjectExpression (i.e. “${message}”).

When processing #fields expression, first lookup HttpServletRequest attribute springBoundObjectExpression, then prepend the result with org.springframework.validation.BindingResult. (i.e. org.springframework.validation.BindingResult.message) and lookup HttpServletRequest attribute again. Afterward a BindingResult instance is found (BindingResult is the child interface of Errors) and a related method is called.

The reason why nothing happened on using #fields expression is not to set name on @ModelAttribute annotation.

Please follow and like us:
Pin Share