SpringBoot + Thymeleaf: подтвердить адрес электронной почты

У меня есть этот объект:

@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NatalChartDataPayload {
    String langCode;

    @NotEmpty(message = "Email cannot be empty")
    @Email(message = "Invalid email format")
    String email;

    String city;
    String place;
}

Контроллер выглядит так:

@GetMapping({"/data"})
public String data (Model model) {
    model.addAttribute("months", Month.values());
    model.addAttribute("data",   NatalChartDataPayload.builder().build());
    return "natalChartData";
}

@PostMapping("/create")
public String createNatalChart ( 
    @Valid @ModelAttribute NatalChartDataPayload data,
    BindingResult result, 
    Model model
) {        
    if (result.hasErrors()) {
        return "natalChartData";
    }
        
    User user = new User();    
    user.setEmail(data.getEmail());
               
    if (data.getPlace() == null ||
          data.getPlace().isEmpty() ||
          data.getEmail() == null ||
          data.getEmail().isEmpty()) {
    return "redirect:/natalchart/data";
}

Однако, когда адрес электронной почты неверен, у меня возникает эта ошибка:

aused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "#fields.hasErrors('email')" (template: "natalChartData" - line 142, col 20)
    at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
    at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
    ... 101 more
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('email')" (template: "natalChartData" - line 142, col 20)
    at org.thymeleaf.spring6.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:292)
    at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166)
    at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:125)
    at org.thymeleaf.standard.processor.StandardIfTagProcessor.isVisible(StandardIfTagProcessor.java:59)
    at org.thymeleaf.standard.processor.AbstractStandardConditionalVisibilityTagProcessor.doProcess(AbstractStandardConditionalVisibilityTagProcessor.java:61)
    at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)
    at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95)
    at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314)
    at org.thymeleaf.engine.TemplateHandlerAdapterMarkupHandler.handleOpenElementEnd(TemplateHandlerAdapterMarkupHandler.java:304)
    at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler$InlineMarkupAdapterPreProcessorHandler.handleOpenElementEnd(InlinedOutputExpressionMarkupHandler.java:278)
    at org.thymeleaf.standard.inline.OutputExpressionInlinePreProcessorHandler.handleOpenElementEnd(OutputExpressionInlinePreProcessorHandler.java:186)
    at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler.handleOpenElementEnd(InlinedOutputExpressionMarkupHandler.java:124)
    at org.attoparser.HtmlElement.handleOpenElementEnd(HtmlElement.java:109)
    at org.attoparser.HtmlMarkupHandler.handleOpenElementEnd(HtmlMarkupHandler.java:297)
    at org.attoparser.MarkupEventProcessorHandler.handleOpenElementEnd(MarkupEventProcessorHandler.java:402)
    at org.attoparser.ParsingElementMarkupUtil.parseOpenElement(ParsingElementMarkupUtil.java:159)
    at org.attoparser.MarkupParser.parseBuffer(MarkupParser.java:710)
    at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:301)
    ... 103 more
Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'data' available as request attribute
    at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:153)
    at org.springframework.web.servlet.support.RequestContext.getBindStatus(RequestContext.java:926)
    at org.thymeleaf.spring6.context.webmvc.SpringWebMvcThymeleafRequestContext.getBindStatus(SpringWebMvcThymeleafRequestContext.java:232)
    at org.thymeleaf.spring6.util.FieldUtils.getBindStatusFromParsedExpression(FieldUtils.java:306)
    at org.thymeleaf.spring6.util.FieldUtils.getBindStatus(FieldUtils.java:253)
    at org.thymeleaf.spring6.util.FieldUtils.getBindStatus(FieldUtils.java:227)
    at org.thymeleaf.spring6.util.FieldUtils.checkErrors(FieldUtils.java:212)

вот шаблон:

 <form method = "post" th:action = "@{/natalchart/create}" th:object = "${data}">
          <div class = "row">
            <div class = "col-lg-8">
                <h4>Enter your birth details to create your natal chart</h4>

              <p>&nbsp;</p>
              <p>&nbsp;</p>

              <div th:if = "${#fields.hasErrors('email')}" th:errors = "*{email}">Email Error</div>

                <div>
                  <label for = "language">PDF Report Language</label>
                  <select th:field = "*{langCode}">
                    <option value = "en">English</option>
                    <option value = "es">Spanish</option>
                    <option value = "fr">French</option>
                    <option value = "pt">Portuguese</option>
                  </select><br><br>

                  <label for = "email">Email</label>
                  <input id = "email" name = "email" placeholder = "email" th:field = "*{email}"  /><br><br>

                  <!-- Label and select fields for day, month, and year of birth -->
                  <label for = "dayOfBirth">Date of Birth:</label>
                  <select id = "dayOfBirth" th:field = "*{dayOfBirth}">
                    <option th:each = "day : ${#numbers.sequence(1, 31)}" th:text = "${day}" th:value = "${day}"></option>
                  </select>
                  <select id = "monthOfBirth" th:field = "*{monthOfBirth}">
                    <option th:each = "month : ${months}" th:text = "${month}" th:value = "${month.getValue()}"></option>
                  </select>
                  <select id = "yearOfBirth" th:field = "*{yearOfBirth}">
                    <option th:each = "year : ${#numbers.sequence(1900, 2025)}" th:text = "${year}" th:value = "${year}"></option>
                  </select>
                  <br><br>
                  <!-- Label and select fields for hour and minute of birth -->
                  <label for = "hourOfBirth">Time of Birth:</label>
                  <select id = "hourOfBirth" th:field = "*{hourOfBirth}">
                    <option th:each = "hour : ${#numbers.sequence(0, 23)}" th:text = "${hour}" th:value = "${hour}"></option>
                  </select>
                  <select id = "minuteOfBirth" th:field = "*{minuteOfBirth}">
                    <option th:each = "minute : ${#numbers.sequence(0, 59)}" th:text = "${minute}" th:value = "${minute}"></option>
                  </select>
                  <br><br>
                  <label for = "hint">Place of birth (Select one of the list)</label>
                  <input id = "hint"  name = "hint"   th:field = "*{city}" /><br><br>
                  <input id = "place" name = "place" th:field = "*{place}" type = "hidden"/>
                </div>
          </div>
            <div class = "col-lg-4">
              <div class = "blog-sidbar">
                    <div class = "d-flex justify-content-between mt-20">
                      <button class = "fill-btn" data-text = "Send Message" type = "submit">create PDF Natal Chart Report</button>
                    </div>
              </div>
            </div>
        </div>
</form>

Как выглядит ваш HTML?

Wim Deblauwe 20.04.2024 19:51
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
1
150
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте использовать условный оператор в своем классе, чтобы оценить, следует ли использовать «класс ошибки».

Пример:

<p  th:if = "${#fields.hasErrors('email')}" th:class = "${#fields.hasErrors('email')}? error">Invalid Email</p>

<style>
    .error {
        color: red;
    }
</style>

Другой атрибут Thymeleaf th:errors дает нам возможность отображать все ошибки в указанном селекторе, скажем, по электронной почте:

<div>
    <label for = "email">Email</label> <input type = "text" th:field = "*{email}" />
    <p th:if = "${#fields.hasErrors('email')}" th:errorclass = "error" th:errors = "*{email}" />
</div>

Дополнительная ссылка:

Я пробовал с той же ошибкой

Nuñito Calzada 24.04.2024 13:41
Ответ принят как подходящий

Согласно последней причине в вашей трассировке ошибок:

Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'data' available as request attribute

проблема, с которой вы столкнулись, может быть вызвана отсутствием атрибута data в запросе.

По словам вашего контроллера, в случае ошибок проверки вы даете Spring указание снова перенаправить запрос на страницу вашего шаблона HTML, но не предоставляете обязательный атрибут data.

Вероятно, проблему можно решить разными способами, но попробуйте следующее, я думаю, все должно работать правильно:

@PostMapping("/create")
public String createNatalChart ( 
    @Valid @ModelAttribute NatalChartDataPayload data,
    BindingResult result, 
    Model model
) {        
    if (result.hasErrors()) {
        // Add data to the returned model. Probably you will need
        // to add the attribute months as well
        model.addAttribute("data", data);
        return "natalChartData";
    }
        
    User user = new User();    
    user.setEmail(data.getEmail());
               
    if (data.getPlace() == null ||
          data.getPlace().isEmpty() ||
          data.getEmail() == null ||
          data.getEmail().isEmpty()) {
    return "redirect:/natalchart/data";
}

Другие вопросы по теме