# Лекция 9

## Тема: Получение данных формы. Валидация данных, Bean Validation API. Загрузка файлов на сервер.

## 6. Валидация данных формы

Создадим проект с индексной страницей, на которой расположена форма добавления нового студента.

{% tabs %}
{% tab title="Скриншот" %}
![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-M3Dgqxe2cyx-XlERagd%2F-M3Ds1jk9x8RlI2rZLXX%2Fimage.png?alt=media\&token=fecbcc68-16e9-4ee5-b460-46f15b6365fb)
{% endtab %}

{% tab title="Код формы" %}
{% code title="index.html" %}
```markup
<form enctype="multipart/form-data" action="#" th:action="@{/}" th:object="${student}" method="post" class="needs-validation">
    <div class="row">
        <div class="col-md-12">
            <h4 class="mb-3">Поля для заполнения</h4>
            <div class="row">
                <div class="col-md-6 mb-3">
                    <label for="lastName">Фамилия</label>
                    <input th:field="*{lastName}" type="text" class="form-control" id="lastName">
                </div>
                <div class="col-md-6 mb-3">
                    <label for="firstName">Имя</label>
                    <input th:field="*{firstName}" type="text" class="form-control" id="firstName">
                </div>
            </div>

            <div class="row">
                <div class="col-md-6 mb-3">
                    <label for="email">Электронная почта</label>
                    <input th:field="*{email}" type="text" class="form-control" id="email">
                </div>
            </div>

            <div class="row">
                <div class="col-md-6 mb-3">
                    <label for="age">Возраст</label>
                    <input th:field="*{age}" type="number" class="form-control" id="age">
                </div>
            </div>

            <hr class="mb-4">
            <button class="btn btn-primary btn-lg btn-block" type="submit" value="Submit">Добавить студента</button>
        </div>
    </div>
</form>
```
{% endcode %}
{% endtab %}
{% endtabs %}

Изначально, форма не имеет средств валидации, то есть мы не можем отследить корректность заполнения формы.

Spring предоставляет несколько инструментов для реализации валидации формы, воспользуемся библиотекой **Bean Validation API**

{% hint style="info" %}
Информацию по поводу использования библиотеки можно найти [здесь](https://download.oracle.com/otndocs/jcp/bean_validation-2_0_0-final-spec/) (см. раздел 8 мануала)
{% endhint %}

Добавим библиотеку в список зависимостей в файле **pom.xml**

{% code title="pom.xml" %}
```java
<?xml version="1.0" encoding="UTF-8"?>
<project ...>

    ...

    <dependencies>
    
    ...

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
    </dependencies>

...

</project>
```
{% endcode %}

Будем использовать механизм встроенных ограничений. Алгоритм использования встроенных ограничений следующий - с помощью аннотаций необходимо указать над полем класса-сущности требуемые параметры валидации и другие параметры. В нашем случае, сущностью выступает класс **Student**. Добавим необходимые ограничения для полей сущности.

{% code title="Student.java" %}
```java
public class Student {

    // Имя должно быть длиной от 2 до 50 символов
    @Size(min = 2, max= 50, message = "First name should be from 2 to 50 characters")
    private String firstName;
    
    // Фамилия должна быть длиной от 2 до 50 символов
    @Size(min = 2, max= 50, message = "Last name should be from 2 to 50 characters")
    private String lastName;
    
    // Возраст должен быть целым числом от 13 до 65
    @Range(min = 13, max = 65, message = "Student age should be from 13 to 65 years")
    private int age;
    
    // Для валидации электронной почты используем регулярное выражение
    @Pattern(regexp = "^[\\\\w!#$%&’*+/=?`{|}~^-]+(?:\\\\.[\\\\w!#$%&’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\\\.)+[a-zA-Z]{2,6}$",
            message = "Invalid email format")
    private String email;
    
    ...
}
```
{% endcode %}

Как мы видим, все достаточно просто и наглядно.

Далее, нам необходимо модифицировать контроллеры и реализовать следующий функционал:

* указать, что объект типа Student должен пройти валидацию;
* получить результаты валидации объекта;
* если объект не прошел валидацию - не добавлять объект в хранилище, выдать сообщение об ошибке в консоль.

Нам необходимо модифицировать метод контроллера, который обрабатывает данные формы. Указываем аннотацию **@Valid**, которая говорит о том, что полученный объект необходимо подвергнуть валидации. Далее указываем аргумент типа **BindingResult**, который хранит информацию о результате валидации. С помощью метода **hasErrors()** получаем результат валидации объекта.

```java
    @GetMapping("/")
    public String addStudent(Model model) {
        model.addAttribute("student", new Student());
        return "index";
    }

    @PostMapping("/")
    public String processAddStudentForm(@Valid Student student, BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            System.out.println("Validation has been failed!");
            return "index";
        }

        System.out.println(student);

        list.add(student);
        return "redirect:/";
    }
```

При попытке отправить пустую форму, получаем сообщение в консоли

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-M3Dgqxe2cyx-XlERagd%2F-M3DzREiNJleGPDGU138%2Fimage.png?alt=media\&token=76650433-899a-43f5-a426-7aa8a76e8a30)

Последний шаг - необходимо предоставить пользователю информацию о том, что то или иное поле формы не прошло валидацию.

Самый простой способ проинформировать пользователь - показать сообщение об ошибке около поля, которое не прошло валидацию. Чтобы реализовать данный функционал, перейдем в шаблон **index.html**.

Рассмотрим поле "Фамилия". Сообщение об ошибке мы разместим снизу поля. Добавим соответствующий элемент **\<small>** в HTML-макет.

{% code title="index.html" %}
```markup
<div class="col-md-6 mb-3">
    <label for="lastName">Фамилия</label>
    <input th:field="*{lastName}" type="text" class="form-control" id="lastName">
    <small class="text-danger" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"/>
</div>
```
{% endcode %}

Используем тег **th:if**. Если выражение внутри тега равно **true**, то элемент **\<small>** будет показан на экране, если **false** - будет скрыт.

Выражение `${fields.hasErrors('lastName)}` означает, есть ли ошибки валидации для поля `lastName`? Если ошибки есть - поле будет показано. Текст ошибки выводим с помощью атрибута **th:errors**.

Добавляем элементы для вывода ошибок для остальных полей формы. Проверяем результат

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-M3Dgqxe2cyx-XlERagd%2F-M3E823cTMhZI6gujfIC%2Fimage.png?alt=media\&token=0986c9f4-d395-41cf-b13c-0d80298704c2)

Ниже представлен листинг классов и файлов

{% tabs %}
{% tab title="index.html" %}
```markup
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <title>Добавление студента</title>
</head>
<body class="bg-light">
<div class="container">
    <div class="py-5 text-center">
        <h2>Добавление студента</h2>
        <p class="lead">Заполните поля и нажмите кнопку 'Добавить студента'</p>
    </div>

    <form enctype="multipart/form-data" action="#" th:action="@{/}" th:object="${student}" method="post">
        <div class="row">
            <div class="col-md-12">
                <h4 class="mb-3">Поля для заполнения</h4>
                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="lastName">Фамилия</label>
                        <input th:field="*{lastName}" type="text" class="form-control" id="lastName">
                        <small class="text-danger" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"/>
                    </div>
                    <div class="col-md-6 mb-3">
                        <label for="firstName">Имя</label>
                        <input th:field="*{firstName}" type="text" class="form-control" id="firstName">
                        <small class="text-danger" th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"/>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="email">Электронная почта</label>
                        <input th:field="*{email}" type="text" class="form-control" id="email">
                        <small class="text-danger" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"/>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="age">Возраст</label>
                        <input th:value="${student.age > 0} ? ${student.age} : ''" th:field="*{age}" type="number" class="form-control" id="age">
                        <small class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"/>
                    </div>
                </div>

                <hr class="mb-4">
                <button class="btn btn-primary btn-lg btn-block" type="submit" value="Submit">Добавить студента</button>
            </div>
        </div>
    </form>
</div>
</div>
</div>

<br/><br/><br/>


<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
        integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
        crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
        crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
        integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
        crossorigin="anonymous"></script>
</body>
</html>
```
{% endtab %}

{% tab title="Student.java" %}
```java
public class Student {

    // Имя должно быть длиной от 2 до 50 символов
    @Size(min = 2, max= 50, message = "First name should be from 2 to 50 characters")
    private String firstName;

    // Фамилия должна быть длиной от 2 до 50 символов
    @Size(min = 2, max= 50, message = "Last name should be from 2 to 50 characters")
    private String lastName;

    // Возраст должен быть целым числом от 13 до 65
    @Range(min = 13, max = 65, message = "Student age should be from 13 to 65 years")
    private int age;

    // Для валидации электронной почты используем регулярное выражение
    @Pattern(regexp = "^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$",
            message = "Invalid email format")
    private String email;

    public Student(String firstName, String lastName, int age, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.email = email;
    }

    public Student() {
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
```
{% endtab %}

{% tab title="StudentController.java" %}
```java
@Controller
public class StudentController {

    private List<Student> list = new ArrayList<>();

    @GetMapping("/")
    public String addStudent(Model model) {
        model.addAttribute("student", new Student());
        return "index";
    }

    @PostMapping("/")
    public String processAddStudentForm(@Valid Student student, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "index";
        }

        System.out.println(student);

        list.add(student);
        return "redirect:/";
    }
}
```
{% endtab %}
{% endtabs %}

## 7. Загрузка файлов на сервер

{% tabs %}
{% tab title="index.html" %}
```markup
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <title>Добавление студента</title>
</head>
<body class="bg-light">
<div class="container">
    <div class="py-5 text-center">
        <h2>Добавление студента</h2>
        <p class="lead">Заполните поля и нажмите кнопку 'Добавить студента'</p>
    </div>

    <form enctype="multipart/form-data" action="#" th:action="@{/}" th:object="${student}" method="post">
        <div class="row">
            <div class="col-md-12">
                <h4 class="mb-3">Поля для заполнения</h4>
                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="lastName">Фамилия</label>
                        <input th:field="*{lastName}" type="text" class="form-control" id="lastName">
                        <small class="text-danger" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"/>
                    </div>
                    <div class="col-md-6 mb-3">
                        <label for="firstName">Имя</label>
                        <input th:field="*{firstName}" type="text" class="form-control" id="firstName">
                        <small class="text-danger" th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"/>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="email">Электронная почта</label>
                        <input th:field="*{email}" type="text" class="form-control" id="email">
                        <small class="text-danger" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"/>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="age">Возраст</label>
                        <input th:value="${student.age > 0} ? ${student.age} : ''" th:field="*{age}" type="number" class="form-control" id="age">
                        <small class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"/>
                    </div>
                </div>

                <div class="form-group">
                    <label for="file">Choose file:</label>
                    <input type="file" name="file" class="form-control-file" id="file">
                </div>

                <hr class="mb-4">
                <button class="btn btn-primary btn-lg btn-block" type="submit" value="Submit">Добавить студента</button>
            </div>
        </div>
    </form>
</div>
</div>
</div>

<br/><br/><br/>

<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
        integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
        crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
        crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
        integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
        crossorigin="anonymous"></script>
</body>
</html>
```
{% endtab %}
{% endtabs %}
