Validazione dei form con Spring MVC
Proseguiamo lo sviluppo della nostra fantastica SuperRubricaTelefonica!
Dopo aver affrontato il tema della gestione dei form con Spring MVC, è il momento di scoprire come validare i dati inviati al Controller.
Mettiamoci subito all’opera!
Validazione lato server
Allo stato attuale, la nostra applicazione non prevede un controllo sui dati immessi tramite form. Ovviamente, però, questo è un comportamento inammissibile se stiamo lavorando ad un progetto reale.
Benché in un’applicazione web che si rispetti la validazione dei dati avviene preventivamente lato client, il server non può ignorare la possibilità di ricevere dati “sporchi”. Valori non ammessi potrebbero essere inviati dall’utente per disattenzione o, in alcuni casi, con intento malevolo.
L’annotazione @Valid
Spring utilizza l’annotazione @Valid
, definita dalla specifica Java EE, per identificare quali oggetti di input devono essere validati.
È importante notare che la validazione per i parametri annotati con @Valid
sarà abilitata solo se all’interno del classpath è presente un validation provider. Se stiamo utilizzando Spring Boot basterà includere la dipendenza spring-boot-starter-web
, oppure,come nel nostro caso, spring-boot-starter-thymeleaf
per importare implicitamente anche Hibernate Validator, che si occuperà di processare le nostre regole di validazione.
Piccola nota La dipendenza hibernate-validator
è in effetti inclusa nello starter package spring-boot-starter-web
. Il pacchetto spring-boot-starter-thymeleaf
, che implementa l’integrazione con Thymeleaf, si basa su spring-boot-starter-web
, di conseguenza include tutte le dipendenze di quest’ultimo.
Riprendiamo dunque il nostro ContactController
e aggiungiamo qualche elemento al metodo submitNewContact
:
@PostMapping("/new")
public String submitNewContact(@Valid @ModelAttribute ContactForm contactForm
, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "contact-form";
}
ContactDetails details = new ContactDetails();
details.setFirstName(contactForm.getFirstName());
details.setLastName(contactForm.getLastName());
details.setPhone(contactForm.getPhone());
details.setEmail(contactForm.getEmail());
model.addAttribute("contact", details);
return "contact-details";
}
Le due novità sono:
- la già citata annotazione
@Valid
, che ci serve a contrassegnarecontactForm
come oggetto da validare; - il secondo argomento del metodo, di tipo
BindingResult
, che rappresenta l’esito della validazione.
L’oggetto bindingResult
conterrà la lista degli eventuali errori riscontrati nei campi di contactForm
. Come potete notare, l’esito della validazione è verificabile invocando il metodo hasErrors
.
Nel caso in cui la validazione non andasse a buon fine l’handler rimanderà alla pagina di partenza, in cui verranno evidenziati gli errori rilevati.
Piccola nota IMPORTANTE: ricordate di dichiarare sempre il parametro di tipo BindingResult
immediatamente dopo l’oggetto da validare. Se non doveste rispettare questo ordine, Spring potrebbe scatenare errori inaspettati e spesso fuorvianti. Io vi ho avvisati… :-P
Specifichiamo le regole di validazione
Una volta predisposto il Controller, è necessario spiegare a Spring come un certo oggetto deve essere validato.
Il nostro command object (o backing object), contactForm
, dovrà fornire a Spring indicazioni su quali valori sono ammessi per i suoi campi. Anche in questo caso le Java annotation ci tornano molto utili:
public class ContactForm {
@NotEmpty
@Size(max=50)
private String firstName;
@NotEmpty
@Size(max=100)
private String lastName;
@NotEmpty
@Size(max=20)
private String phone;
@NotEmpty
@Email
@Size(max=100)
private String email;
// getters + setters
}
Ho riportato un estratto della classe ContactForm
in cui, oltre ad aver aggiunto il campo email
, ho annotato tutti i campi da validare.
Hibernate Validator e Java mettono a disposizione diverse annotazioni per specificare vincoli sui valori ammessi dai campi di una classe. Nella classe ContactForm
ho utilizzato:
@NotEmpty
, per marcare i campi obbligatori;@Size
per indicare vincoli sulla lunghezza delle stringhe;@Email
per verificare il formato dell’indirizzo email inserito.
Perfetto! A questo dobbiamo visualizzare gli errori…
Visualizzare gli errori
Per far sì che gli errori di validazione vengano visualizzati all’interno della pagina, la nostra contact-form
necessita di un’aggiustatina.
Per comprendere meglio le modifiche da effettuare, per il momento concentriamoci sul campo firstName
:
<div th:classappend="${#fields.hasErrors('firstName')}? has-error">
<label>Nome</label> <input th:field="*{firstName}" type="text"/>
<div th:if="${#fields.hasErrors('firstName')}" class="form-error">
<span th:errors="*{firstName}" class="text-danger">
</div>
</div>
L’espressione #fields.hasErrors()
ci consente di verificare se per un determinato campo esistono errori di validazione.
Come vedete, nel mio caso utilizzo l’espressione nell’attributo th:classappend
per assegnare in maniera condizionale la classe di Bootstrap has-error
, utile per evidenziare i campi con valore errato. La stessa espressione la utilizzo inoltre, tramite th:if
, per visualizzare i messaggi d’errore, quando necessario.
Il risultato finale è pressappoco questo:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Nuovo contatto</title>
</head>
<body>
<form th:action="@{/contacts/new}" th:object="${contactForm}"
method="post">
<h2>Nuovo contatto</h2>
<div class="form-group"
th:classappend="${#fields.hasErrors('firstName')}? has-error">
<label class="control-label">Nome</label> <input
th:field="*{firstName}" type="text" class="form-control" />
<div th:if="${#fields.hasErrors('firstName')}" class="form-error">
<span th:errors="*{firstName}" class="text-danger">
</div>
</div>
<div class="form-group"
th:classappend="${#fields.hasErrors('lastName')}? has-error">
<label class="control-label">Cognome</label> <input
th:field="*{lastName}" type="text" class="form-control" />
<div th:if="${#fields.hasErrors('lastName')}" class="form-error">
<span th:errors="*{lastName}" class="text-danger">
</div>
</div>
<div class="form-group"
th:classappend="${#fields.hasErrors('phone')}? has-error">
<label class="control-label">Telefono</label> <input
th:field="*{phone}" type="text" class="form-control" />
<div th:if="${#fields.hasErrors('phone')}" class="form-error">
<span th:errors="*{phone}" class="text-danger">
</div>
</div>
<div class="form-group"
th:classappend="${#fields.hasErrors('email')}? has-error">
<label class="control-label">Email</label> <input th:field="*{email}"
type="text" class="form-control" />
<div th:if="${#fields.hasErrors('email')}" class="form-error">
<span th:errors="*{email}" class="text-danger">
</div>
</div>
<div class="form-group">
<input class="btn btn-primary" type="submit" value="Salva" />
</div>
</form>
</body>
</html>
Bene, vediamo il tutto in azione!
Se proviamo ad inserire un nuovo contatto senza valorizzare nessuno dei campi, otterremo qualcosa del genere:
Noterete che i messaggi di default sono in inglese, il che non ci piace molto (senza offesa per gli inglesi…) Per personalizzare e tradurre i messaggi in italiano dobbiamo fare un altro piccolo passo.
Nella cartella /resources
andremo a creare due file: il primo, vuoto, si chiamerà messages.properties
e conterrà i messaggi nella lingua di default, ovvero l’inglese; il secondo, quello che interessa a noi, lo chiameremo invece messages_it.properties
. In quest’ultimo possiamo definire i messaggi in italiano, ad esempio:
NotEmpty=Campo obbligatorio
Email=Inserire un indirizzo email corretto
Size=Inserire massimo {1} caratteri
A questo punto, lanciando nuovamente l’applicazione, dovreste vedere i messaggi tradotti nella nostra lingua.
(Appendice) Validazione lato client
Come già accennato all’inizio di questo post, è sempre buona norma effettuare la validazione dell’input prima dell’invio dei dati al server (quindi lato client).
Questo approccio, oltre a migliorare l’esperienza d’uso da parte dell’utente, evita di appesantire il server con un ulteriore carico di lavoro che può essere evitato facilmente.
Generalmente, per implementare la validazione lato client è sufficiente una piccola dose di JavaScript.
In questo breve addendum, però, ci accontentiamo di aggiungere ai nostri campi input
degli utili attributi che permettono di ottenere (nella maggior parte dei moderni browser) una rudimentale verifica dei dati immessi.
In particolare, aggiungendo l’attributo required
otterremo una verifica dei campi obbligatori, mentre sostituendo l’attributo type="text"
con type="email"
nel campo relativo all’indirizzo email avremo un controllo sul formato della stringa immessa.
Utilizzando Chrome, ad esempio, visualizzeremo qualcosa del genere:
In conclusione
La validazione dei dati inviati al server è un aspetto fondamentale di ogni web application, ma a volte sottovalutato.
Spring permette di definire regole di verifica dei dati immessi tramite form, mentre l’integrazione tra Spring MVC e Thymeleaf ci consente d’implementare nelle nostre View i meccanismi di notifica degli errori rilevati. Niente male!
Anche per oggi è tutto, statemi bene e alla prossima! ;-)
David