【问题标题】:Spring Thymeleaf form POST not sending updated collection model attributeSpring Thymeleaf 表单 POST 未发送更新的集合模型属性
【发布时间】:2021-10-22 03:33:07
【问题描述】:

我有一个服务器端呈现网页的 Spring Boot 应用程序。应用程序的其中一种形式无法正常工作。表单将正确呈现页面,并将值绑定到输入。当一个值发生变化时,表单提交时控制器收到的值是原来的。

我有一个CardDTO 对象,它具有以下内容:

@Data
@NoArgsConstructor
public class CardDTO {
    private int cardId;
    // Other fields
    private List<CustomValueDTO> customValues;
}

作为card 属性添加到模型中。

表格如下所示:

<form method="post" action="#" th:action="${#httpServletRequest.requestURI}" th:object="${card}">
    <div class="row" th:if="${editType == 'notes'}">
        <div class="col-12 text-right">
            <button class="btn btn-primary ml-4" type="submit" th:text="#{app.savebtn.label}">SAVE</button>
        </div>
    </div>
    <div th:switch="${editType}">
        <!-- other cases -->
        <div th:case="'notes'" th:insert="card-form-section-notes :: notes-form"></div>
    </div>
</form>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<body>
    <div th:fragment="notes-form" id="notes-edit-form" class="pr-3">
        <div class="row text-left" th:each="i: *{#numbers.sequence(0, customValues.size() - 1)}" th:if="*{customValues}">
            <div th:switch="*{customValues[__${i}__].type.name()}">
                <div th:case="LETTER" class="form-group md-form">
                    <textarea rows="1" type="text"
                        class="form-control custom-textarea"
                        th:field="*{customValues[__${i}__].value}" placeholder="Enter letters only"
                        th:errorclass="invalid"></textarea>
                    <label th:for="*{customValues[__${i}__].value}" th:text="*{customValues[__${i}__].label}"></label>
                    <small class="text-danger" th:if="${#fields.hasErrors('customValues[__${i}__].value')}"
                        th:errors="*{customValues[__${i}__].value}">Error</small>
                </div>

                <!-- more inputs -->
            </div>
        </div>
    </div>
</body>
</html>

控制器方法看起来像:


@Controller
@SessionAttributes({"card"})
public class CardCreateEditController {

    @PostMapping(value = {"/cards/{cardUUID}/{updateType}/edit","/locations/{locationUUID}/cards/{cardUUID}/{updateType}/edit"})
    public String submitChildCardEdit(@AuthenticationPrincipal OidcUser currentUser, @PathVariable String updateType, @PathVariable String cardUUID, @PathVariable Optional<String> locationUUID, @ModelAttribute("card") @Valid final CardDTO editCardDTO, final BindingResult bindingResult, final Model model, final SessionStatus status) {
    // logic here
    }
}

所以问题是当表单更新时,与它一起发送的@ModelAttribute("card") @Valid final CardDTO editCardDTO 参数仍然具有页面加载时editCardDTO.customValues(i) 中的原始值。

在 Chrome 中查看网络请求表明它正在提交带有更新值的表单,以及这些表单参数:

customValues[0].value: database1234
customValues[1].value: test
...

我已尝试将 CustomValueDTO 中的所有字段添加为隐藏输入,这些字段也作为表单参数提交,但仍未更改。

这与我拥有的另一种形式几乎完全相同,而且效果很好。我似乎无法弄清楚为什么这个没有。

更新 - 带有完整的 HTML:

card-edit-form.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<body>

    <div th:fragment="card-form-content">

        <div class="card">

            <!-- Card body -->
            <div class="card-body">

                <p class="h4 mb-3" th:text="#{${'myCompany.header.edit.'+editType}}">EDIT</p>
                <form method="post" action="#" th:action="${#httpServletRequest.requestURI}" th:object="${card}"
                    autocomplete="off" enctype="application/x-www-form-urlencoded">
                    <div class="row" th:if="${editType == 'notes'}">
                        <div class="col-12 text-right">
                            <a th:if="${locationObj==null}" href="#"
                                th:href="@{/cards/{cardUUID}/cancel(cardUUID=${{cardUUID}})}"
                                th:text="#{myCompany.cancelbtn.label}" tabindex="-1">CANCEL</a>
                            <a th:if="${locationObj!=null}" href="#"
                                th:href="@{/locations/{locationUUID}/cards/{cardUUID}/cancel(cardUUID=${{cardUUID}},locationUUID=${{locationObj.locationUUID}})}"
                                th:text="#{myCompany.cancelbtn.label}" tabindex="-1">CANCEL</a>
                            <button class="btn btn-primary ml-4" type="submit" th:text="#{myCompany.savebtn.label}">SAVE</button>
                        </div>
                    </div>
                    <h6 class="mb-4" th:if="${#messages.msgOrNull('myCompany.subheader.edit.'+editType)!=null}"
                        th:text="#{${'myCompany.subheader.edit.'+editType}}">SUBTITLE</h6>
                    <hr>
                    <div th:switch="${editType}">
                        <div th:case="'ico'" th:insert="card-form-section-ico :: ico-form"></div>
                        <div th:case="'info'" th:insert="card-form-section-info :: info-form"></div>
                        <!--/* <div th:case="'address'" th:insert="card-form-section-address :: address-form"></div> */-->
                        <div th:case="'health'" th:insert="card-form-section-health :: health-form"></div>
                        <div th:case="'resideswith'" th:insert="card-form-section-resideswith :: resideswith-form">
                        </div>
                        <div th:case="'notes'" th:insert="card-form-section-notes :: notes-form"></div>
                    </div>

                    <div th:if="${editType != 'notes'}">
                        <div class="row mt-5 align-bottom">
                            <div class="col-12 text-right">
                                <a th:if="${locationObj==null}" href="#"
                                    th:href="@{/cards/{cardUUID}/cancel(cardUUID=${{cardUUID}})}"
                                    th:text="#{myCompany.cancelbtn.label}" tabindex="-1">CANCEL</a>
                                <a th:if="${locationObj!=null}" href="#"
                                    th:href="@{/locations/{locationUUID}/cards/{cardUUID}/cancel(cardUUID=${{cardUUID}},locationUUID=${{locationObj.locationUUID}})}"
                                    th:text="#{myCompany.cancelbtn.label}" tabindex="-1">CANCEL</a>
                                <button class="btn btn-primary ml-4" type="submit"
                                    th:text="#{myCompany.savebtn.label}">SAVE</button>
                            </div>
                        </div>
                    </div>

                </form>

            </div> <!-- card body -->
        </div> <!-- card -->

<div th:if="${editType != 'notes'}">
        <div class="mt-4 text-center">
            <small><a href="#" th:text="#{myCompany.learnmore.part1}" data-toggle="modal"
                    data-target="#whyWeAskModal">LEARN MORE</a> <span th:text="#{myCompany.learnmore.part2}">ABOUT WHY WE
                    ASK FOR THIS INFO.</span></small>
        </div>
        </div>
        <div th:insert="components/card-why-we-ask-for-info :: why-we-ask-modal"></div>

    </div> <!-- fragment -->

</body>

</html>

card-form-section-notes.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<body>
    <div th:fragment="notes-form" id="notes-edit-form" class="pr-3">
        <h4 class="mt-3" th:if="*{customValues == null}" th:text="#{myCompany.section.cards.customfields.edit.noneexistmsg}">NONE EXIST YET.</h4>
        <div class="row text-left" th:each="customValue, itemStat : *{customValues}" th:if="*{customValues}">
            <div th:switch="*{customValues[__${itemStat.index}__].type.name()}">
<!--                <input type="hidden" th:field="*{customValues[__${i}__].id}">-->
<!--                <input type="hidden" th:field="*{customValues[__${i}__].uuid}">-->
<!--                <input type="hidden" th:field="*{customValues[__${i}__].type}">-->
<!--                <input type="hidden" th:field="*{customValues[__${i}__].label}">-->
<!--                <input type="hidden" th:field="*{customValues[__${i}__].customField}">-->

                <div th:case="LETTER" class="form-group md-form">
                    <textarea rows="1" type="text"
                        class="form-control custom-textarea"
                        th:field="*{customValues[__${itemStat.index}__].value}" placeholder="Enter letters only"
                        th:errorclass="invalid"></textarea>
<!--                    <label th:for="*{customValues[__${itemStat.index}__].value}" th:text="*{customValues[__${itemStat.index}__].label}"></label>-->
<!--                    <small class="text-danger" th:if="${#fields.hasErrors('customValues[__${itemStat.index}__].value')}"-->
<!--                        th:errors="*{customValues[__${itemStat.index}__].value}">Error</small>-->
                </div>

<!--                <div th:case="NUMBER" class="md-form form-group">-->
<!--                    <input type="number" th:id="'customValue' + ${i}" th:field="*{customValues[__${i}__].value}"-->
<!--                        class="form-control" th:errorclass="invalid" placeholder="Enter numbers only">-->
<!--                    <label th:for="'customValue' + ${i}" th:text="*{customValues[__${i}__].label}">NAME</label>-->
<!--                    <small class="text-danger" th:if="${#fields.hasErrors('*{customValues[__${i}__].value}')}"-->
<!--                        th:errors="*{customValues[__${i}__].value}">Error</small>-->
<!--                </div>-->

<!--                <div th:case="YESNO" class="form-group md-form yes-no-input">-->
<!--                    <span class="label-multi" th:text="*{customValues[__${i}__].label}">YES/NO</span>-->
<!--                    <div class="yes-no-check pb-2">-->
<!--                        <div class="form-check form-check-inline" th:each="b : ${allBooleanTypes}">-->
<!--                            <input class="form-check-input" type="radio"-->
<!--                                   th:field="*{customValues[__${i}__].value}" th:value="${b}" />-->
<!--                            <label class="form-check-label" th:for="*{customValues[__${i}__].value}"-->
<!--                                   th:text="#{${b==true ? 'myCompany.yes' : 'myCompany.no'}}">Wireframe</label>-->
<!--                        </div>-->
<!--                        <small class="text-danger" th:if="${#fields.hasErrors('*{customValues[__${i}__].value}')}"-->
<!--                               th:errors="*{customValues[__${i}__].value}">Error</small>-->
<!--                    </div>-->
<!--                </div>-->

<!--                <div th:case="PHONE" class="md-form form-group">-->
<!--                    <input type="text" th:id="'customValue' + ${i}" th:field="*{customValues[__${i}__].value}"-->
<!--                        class="form-control phone-text" th:errorclass="invalid" placeholder="###-###-####">-->
<!--                    <label th:for="'customValue' + ${i}" th:text="*{customValues[__${i}__].label}">NAME</label>-->
<!--                    <small class="text-danger" th:if="${#fields.hasErrors('*{customValues[__${i}__].value}')}"-->
<!--                        th:errors="*{customValues[__${i}__].value}">Error</small>-->
<!--                </div>-->

<!--                <div th:case="DATE" class="md-form form-group">-->
<!--                    <input type="text" th:id="'customValue' + ${i}" th:field="*{customValues[__${i}__].value}"-->
<!--                        class="form-control child_dob_input_date" th:errorclass="invalid" placeholder="YYYY-MM-DD">-->
<!--                    <label th:for="'customValue' + ${i}" th:text="*{customValues[__${i}__].label}">NAME</label>-->
<!--                    <small class="text-danger" th:if="${#fields.hasErrors('*{customValues[__${i}__].value}')}"-->
<!--                           th:errors="*{customValues[__${i}__].value}">Error</small>-->
<!--                </div>-->

<!--                <div th:case="YEARMONTH" class="md-form form-group">-->
<!--                    <input type="text" th:id="'customValue' + ${i}" th:field="*{customValues[__${i}__].value}"-->
<!--                        class="form-control child_dob_input_YM" th:errorclass="invalid" placeholder="YYYY-MM">-->
<!--                    <label th:for="'customValue' + ${i}" th:text="*{customValues[__${i}__].label}">NAME</label>-->
<!--                    <small class="text-danger" th:if="${#fields.hasErrors('*{customValues[__${i}__].value}')}"-->
<!--                           th:errors="*{customValues[__${i}__].value}">Error</small>-->
<!--                </div>-->

<!--                <div th:case="ANY" class="form-group md-form">-->
<!--                    <textarea rows="1" type="text" th:name="'customFields.customField' + ${i}"-->
<!--                        th:id="'customValue' + ${i}" class="form-control custom-textarea"-->
<!--                        th:field="*{customValues[__${i}__].value}" placeholder="Enter any letters"-->
<!--                        th:errorclass="invalid"></textarea>-->
<!--                    <label th:for="'customValue' + ${i}" th:text="*{customValues[__${i}__].label}">NAME</label>-->
<!--                    <small class="text-danger" th:if="${#fields.hasErrors('*{customValues[__${i}__].value}')}"-->
<!--                           th:errors="*{customValues[__${i}__].value}">Error</small>-->
<!--                </div>-->

            </div>
            <!--</div>-->
        </div>

    </div> <!-- fragment -->

</body>

</html>

【问题讨论】:

  • 你解决了吗?不是一些缓存吗?你在数据库中直接看到了吗?
  • 查看对象最终的 BindingResult bindingResult 是否有任何绑定错误: if (bindingResult.hasErrors()) { // 错误处理 }
  • 另外,请分享完整的 html,您在哪里使用 th:field="*{customValues}" 绑定 customValues 对象???
  • @RhadamezGindriHercilio 它不是任何缓存,因为我正在@Controller 上的@PostMapping 方法中进行调试,而@ModelAttribute("card") @Valid final CardDTO editCardDTO 上的值没有被更新。
  • @ZeeshanArif 没有任何绑定错误。 customValues 的绑定来自 th:object="${card}",因为 CardDTO 具有 customValues 成员。

标签: spring spring-boot spring-mvc thymeleaf


【解决方案1】:

问题可能出在 HTTP POST 请求上。特别是它的编码。
请看这个答案:https://stackoverflow.com/a/26503292/3461793

假设您的控制器上没有 @SessionAttributes 与“卡片”并混合您的对话 ID。

【讨论】:

  • 控制器注解@SessionAttributes({"card"})
  • 在这种情况下,您可能会在触发 submitChildCardEdit 方法之前为模型提供“卡片”。使用方法级别 @ModelAttribute 或类似 model.addAttribute("card", someCard); 的方法。否则你会遇到这样的异常消息:“org.springframework.web.HttpSessionRequiredException: Expected session attribute 'card'”。请看Session Attributes in Spring MVC
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-12
  • 1970-01-01
  • 1970-01-01
  • 2018-12-16
  • 1970-01-01
  • 2015-08-04
相关资源
最近更新 更多