Thymeleaf - Nie działa walidacja

0

Cześć,
Mam formularz który ma dane pracownika a następnie dane adresowe. Dane adresowe to lista składająca się z dwóch adresów (stały i korespondencyjny).
Wywołując submita na pustym formularzu powinienem dostać błąd że pole musi być wypełnione tymczasem otrzymuję komunikat:

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Tue May 19 15:31:29 CEST 2020
There was an unexpected error (type=Internal Server Error, status=500).
Exception evaluating SpringEL expression: "addresses" (template: "new_employee_form" - line 57, col 30)

a w konsoli po walidacji pracownika otrzymuję błąd:

2020-05-19 15:31:27.737  INFO 15244 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
employee Nazwisko nie może zawierać cyfr i znaków specjalnych.
employee Obywatelstwo musi mieć między 3, a 30 znaków
employee Nazwa stanowiska nie może zawierać cyfr i znaków specjalnych.
employee Nazwisko musi mieć między 3, a 30 znaków.
employee Nazwa stanowiska musi mieć  między 5, a 40 znaków.
employee Imię musi mieć między 3, a 25 znaków.
employee Pole musi być wypełnione
employee Imię nie może zawierać znaków specjalnych.
employee Obywatelstwo nie może zawierać znaków specjalnych.
2020-05-19 15:31:29.614 ERROR 15244 --- [nio-8080-exec-2] org.thymeleaf.TemplateEngine             : [THYMELEAF][http-nio-8080-exec-2] Exception processing template "new_employee_form": Exception evaluating SpringEL expression: "addresses" (template: "new_employee_form" - line 57, col 30)

org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "addresses" (template: "new_employee_form" - line 57, col 30)

oraz dalej:

Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'addresses' cannot be found on null

Kawałek widoku formularza do linijki 57

                       <div th:each="address, stat : *{addresses}">
                            <span th:switch="*{addresses[__${stat.index}__].type}">
                                <div style = "text-align: center;" th:case="P" ><h5>Adres stały</h5></div>
                                <div style = "text-align: center;" th:case="C" ><h5>Adres korespondencyjny</h5></div>
                            </span>
                            <div class="form-group">
                                <input type="hidden" class="form-control" th:field="*{addresses[__${stat.index}__].type}"/>
                            </div>
                            <div class="form-group">
                                <input type="text" class="form-control" th:field="*{addresses[__${stat.index}__].street}"/>
                                <label class="control-label">Ulica</label>
                                <div class="text-danger"><p th:if="${#fields.hasErrors('addresses[__${stat.index}__].street')}" th:errors="*{addresses[__${stat.index}__].street}"/></div>
                            </div>

Wycinek klasy address:

    @NotNull
    @Size(min=3, max=40, message = "Nazwa ulicy musi mieć między 3 a 40 znaków")
    @Pattern(regexp = "[a-zA-Ząśćężźłóń0-9 ]+", message = "Nazwa ulicy nie może zawierać znaków specjalnych.")
    public String street;

Gdy miałem formularz samego pracownika, wysyłka pustego skutkowała pojawieniem się zdefiniowanych errorów na widoku, natomiast teraz kiedy dodałem listę z adresami, zamiast otrzymywać komunikaty na widoku jak powyżej, otrzymuję info że lista address jest niewypełniona.
Jak poprawnie obsłużyć formularz adresów aby jego pola mogły być walidowane?

0

Debug wskazuje że błąd dostaję dokładnie wtedy kiedy w @RequestMapping (POST) w wyniku błędów na formularzu, wracam do niego z powrotem. Miejsce to w poniższym kodzie zawarłem pomiędzy gwiazdkami.
Controller:

Pierwsze wywołanie formularza (przed POST)

    @GetMapping("/new")
    public String showCreateFormForEmployeeAndAddresses(Model model) {

        if(addressService.getCheck()== false) {
            for (int i = 1; i <= 2; i++) {
                addressService.newAddress(new Address());
            }
        }
//        System.out.println(addressService.getAddresses());
        model.addAttribute("employee", new Employee()).addAttribute("form", addressService);
        return "new_employee_form";
    }

POST na pustym formularzu i ponowne jego otwarcie = błąd.

    @RequestMapping(value = "/employees", method = RequestMethod.POST)
    public String saveEmployeeAndAddress(@ModelAttribute @Valid Employee employee,
                                         BindingResult bindingResultEmployee,
                                         @ModelAttribute @Valid AddressRepository addresses,
                                         BindingResult bindingResultAddressRepository)  {

        if(bindingResultEmployee.hasErrors() || bindingResultAddressRepository.hasErrors()) {
                **return "new_employee_form";**

        } else{
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Runnable runnableEmployee = () -> employeeService.saveEmployeeToDB(employee);
            List<Address> addressesFromForm = addresses.getAddresses();
            Runnable runnableAddress = () -> addressService.saveAddressToDB(addressesFromForm);

            executor.submit(runnableEmployee);
            executor.submit(runnableAddress);

            return "redirect:/employees";
        }
    }

Nie wiem dlaczego błąd wskazuje na null, skoro widać iż do templata wracam z listą identyczną jak w momencie otwarcia formularza po raz pierwszy.
Przechwytywanie19052020.PNG

Dodam że na uzupełnionych danych (bez błędów na formularzu), wszystko działa poprawnie.

0

Wyczytałem na stackoverflow że w przypadku takim jak mój muszę ponownie przekazać atrybuty do modelu manualnie. Zmodyfikowałem więc metodę:

    @RequestMapping(value = "/employees", method = RequestMethod.POST)
    public String saveEmployeeAndAddress(@ModelAttribute @Valid Employee employee,
                                        BindingResult bindingResultEmployee,
                                        @ModelAttribute @Valid AddressRepository addresses,
                                        BindingResult bindingResultAddressRepository, Model model)  {

        if(bindingResultEmployee.hasErrors() || bindingResultAddressRepository.hasErrors()) {

            model.addAttribute("employee", employee).addAttribute("form", addresses);
            return "new_employee_form";

        } else{
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Runnable runnableEmployee = () -> employeeService.saveEmployeeToDB(employee);
            List<Address> addressesFromForm = addresses.getAddresses();
            Runnable runnableAddress = () -> addressService.saveAddressToDB(addressesFromForm);

            executor.submit(runnableEmployee);
            executor.submit(runnableAddress);

            return "redirect:/employees";
        }
    }

dodając przed return

 model.addAttribute("employee", employee).addAttribute("form", addresses);

Pomogło ale nie do końca. Jak widać przekazuję zwrócony obiekt employee oraz listę adresów addresses ponownie do modelu.
Strona http ładuje się, nie ma błędów w konsoli, pola employee są walidowane na formularzu ale adresu już nie.
Nie rozumiem dlaczego skoro dodaję do modelu widoku tą samą listę adresów którą poprzednio zwróciłem.
Formularz wygląda teraz tak:
https://zapodaj.net/b7841cb1fdb73.png.html

0
Dazon napisał(a):

Wyczytałem na stackoverflow że w przypadku takim jak mój muszę ponownie przekazać atrybuty do modelu manualnie. Zmodyfikowałem więc metodę:

Warto zrozumieć dlaczego i na czym polega twój przypadek, bo to nie jest żaden corner case. Pisząc "return nazwaWidoku" zwracasz ... sam widok. Więc musisz dostarczyć wszystkie zależności, nie przechodzisz przez metodę z kontrolera która obsługuje http Get i która tworzy zależności dla tego widoku. Powinieneś zrobić redirect w taki sposób żeby wywołała się metoda obsługująca get bo teraz masz budowanie tego widoku w dwóch miejscach.

    @RequestMapping(value = "/employees", method = RequestMethod.POST)
    public String saveEmployeeAndAddress(@ModelAttribute @Valid Employee employee,
                                        BindingResult bindingResultEmployee,
                                        @ModelAttribute @Valid AddressRepository addresses,
                                        BindingResult bindingResultAddressRepository, Model model)  {

        if(bindingResultEmployee.hasErrors() || bindingResultAddressRepository.hasErrors()) {

            model.addAttribute("employee", employee).addAttribute("form", addresses);
            return "new_employee_form";

        } else{
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Runnable runnableEmployee = () -> employeeService.saveEmployeeToDB(employee);
            List<Address> addressesFromForm = addresses.getAddresses();
            Runnable runnableAddress = () -> addressService.saveAddressToDB(addressesFromForm);

            executor.submit(runnableEmployee);
            executor.submit(runnableAddress);

            return "redirect:/employees";
        }
    }

dodając przed return

 model.addAttribute("employee", employee).addAttribute("form", addresses);

Pomogło ale nie do końca. Jak widać przekazuję zwrócony obiekt employee oraz listę adresów addresses ponownie do modelu.

Nie zwracasz listy adresów tylko obiekt repository, który ma w sobie listę adresów , którą możesz dostać wywołując na nim jakąś metodę.

Strona http ładuje się, nie ma błędów w konsoli, pola employee są walidowane na formularzu ale adresu już nie.
Nie rozumiem dlaczego skoro dodaję do modelu widoku tą samą listę adresów którą poprzednio zwróciłem.
Formularz wygląda teraz tak:
https://zapodaj.net/b7841cb1fdb73.png.html

0

Cały czas siedzę nad powyższym problemem.
Obecnie wygląda to tak:
Po raz pierwszy otwierając formularz, wchodzę do niego z takimi danymi:

AddressService{addressRepository=AddressRepository{addresses=[Address{idAddress=null, idEmployee=null, type='P', street='null', flattNr=null, streetNumber='null', postalCode='null', city='null', country='null'}, Address{idAddress=null, idEmployee=null, type='C', street='null', flattNr=null, streetNumber='null', postalCode='null', city='null', country='null'}]}}

czyli mam listę o nazwie addresses gdzie nazwa ta odpowiada zadeklarowanej liście addresses w szablonie Thymeleaf.

Jeśli NIE wypełniając pól formularza wywołam na nim metodę POST, ponownie do formularza wchodzę z takimi danymi:

[Address{idAddress=null, idEmployee=null, type='P', street='null', flattNr=null, streetNumber='null', postalCode='null', city='null', country='null'}, Address{idAddress=null, idEmployee=null, type='C', street='null', flattNr=null, streetNumber='null', postalCode='null', city='null', country='null'}]

Czyli nie mam listy addresses której spodziewa się Thymeleaf tylko tablicę Address.

Obecnie moja metoda zwracająca listę adresów wygląda tak:

@Repository
public class AddressRepository {

    public List<Address> addresses = new ArrayList<>();

    public List<Address> getAddresses() {
        return addresses;
    }
}

Czy to ona stanowi prloblem?
Jak zwrócić listę adresów aby była identyczna jak przy pierwszym wejściu do formularza?

1 użytkowników online, w tym zalogowanych: 0, gości: 1