Validazione del modulo con AngularJS e Spring MVC

1. Panoramica

La convalida non è mai così semplice come ci aspettiamo. E ovviamente la convalida dei valori inseriti da un utente in un'applicazione è molto importante per preservare l'integrità dei nostri dati.

Nel contesto di un'applicazione Web, l'input dei dati viene solitamente eseguito utilizzando moduli HTML e richiede la convalida sia lato client che lato server.

In questo tutorial, daremo uno sguardo all'implementazione della convalida lato client dell'input del modulo utilizzando AngularJS e della convalida lato server utilizzando il framework Spring MVC .

Questo articolo si concentra su Spring MVC. Il nostro articolo Validazione in Spring Boot descrive come eseguire le convalide in Spring Boot.

2. Dipendenze di Maven

Per iniziare, aggiungiamo le seguenti dipendenze:

 org.springframework spring-webmvc 4.3.7.RELEASE   org.hibernate hibernate-validator 5.4.0.Final   com.fasterxml.jackson.core jackson-databind 2.8.7 

Le ultime versioni di spring-webmvc, hibernate-validator e jackson-databind possono essere scaricate da Maven Central.

3. Convalida utilizzando Spring MVC

Un'applicazione non dovrebbe mai fare affidamento esclusivamente sulla convalida lato client, poiché questa può essere facilmente aggirata. Per impedire il salvataggio di valori errati o dannosi o causare un'esecuzione impropria della logica dell'applicazione, è importante convalidare i valori di input anche sul lato server.

Spring MVC offre supporto per la convalida lato server utilizzando le annotazioni della specifica di convalida Bean JSR 349 . Per questo esempio, useremo l'implementazione di riferimento della specifica, che è hibernate-validator .

3.1. Il modello di dati

Creiamo una classe utente che abbia proprietà annotate con annotazioni di convalida appropriate:

public class User { @NotNull @Email private String email; @NotNull @Size(min = 4, max = 15) private String password; @NotBlank private String name; @Min(18) @Digits(integer = 2, fraction = 0) private int age; // standard constructor, getters, setters }

Le annotazioni usate sopra appartengono alla specifica JSR 349 , ad eccezione di @Email e @NotBlank , che sono specifiche della libreria hibernate-validator .

3.2. Controller Spring MVC

Creiamo una classe controller che definisce un endpoint / user , che verrà utilizzato per salvare un nuovo oggetto User in un List .

Per abilitare la convalida dell'oggetto User ricevuto tramite i parametri di richiesta, la dichiarazione deve essere preceduta dall'annotazione @Valid e gli errori di convalida verranno conservati in un'istanza BindingResult .

Per determinare se l'oggetto contiene valori non validi, possiamo utilizzare il metodo hasErrors () di BindingResult .

Se hasErrors () restituisce true , possiamo restituire un array JSON contenente i messaggi di errore associati alle convalide che non sono state superate. Altrimenti, aggiungeremo l'oggetto all'elenco:

@PostMapping(value = "/user") @ResponseBody public ResponseEntity saveUser(@Valid User user, BindingResult result, Model model) { if (result.hasErrors()) { List errors = result.getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.toList()); return new ResponseEntity(errors, HttpStatus.OK); } else { if (users.stream().anyMatch(it -> user.getEmail().equals(it.getEmail()))) { return new ResponseEntity( Collections.singletonList("Email already exists!"), HttpStatus.CONFLICT); } else { users.add(user); return new ResponseEntity(HttpStatus.CREATED); } } }

Come puoi vedere, la convalida lato server aggiunge il vantaggio di avere la possibilità di eseguire controlli aggiuntivi che non sono possibili sul lato client.

Nel nostro caso, possiamo verificare se esiste già un utente con la stessa email e, se è così, restituire uno stato di 409 CONFLITTO.

Abbiamo anche bisogno di definire il nostro elenco di utenti e inizializzarlo con alcuni valori:

private List users = Arrays.asList( new User("[email protected]", "pass", "Ana", 20), new User("[email protected]", "pass", "Bob", 30), new User("[email protected]", "pass", "John", 40), new User("[email protected]", "pass", "Mary", 30));

Aggiungiamo anche una mappatura per recuperare l'elenco di utenti come oggetto JSON:

@GetMapping(value = "/users") @ResponseBody public List getUsers() { return users; }

L'ultimo elemento di cui abbiamo bisogno nel nostro controller Spring MVC è una mappatura per restituire la pagina principale della nostra applicazione:

@GetMapping("/userPage") public String getUserProfilePage() { return "user"; }

Daremo uno sguardo più dettagliato alla pagina user.html nella sezione AngularJS.

3.3. Configurazione Spring MVC

Aggiungiamo una configurazione MVC di base alla nostra applicazione:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.baeldung.springmvcforms") class ApplicationConfiguration implements WebMvcConfigurer { @Override public void configureDefaultServletHandling( DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Bean public InternalResourceViewResolver htmlViewResolver() { InternalResourceViewResolver bean = new InternalResourceViewResolver(); bean.setPrefix("/WEB-INF/html/"); bean.setSuffix(".html"); return bean; } }

3.4. Inizializzazione dell'applicazione

Creiamo una classe che implementa l' interfaccia WebApplicationInitializer per eseguire la nostra applicazione:

public class WebInitializer implements WebApplicationInitializer { public void onStartup(ServletContext container) throws ServletException { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(ApplicationConfiguration.class); ctx.setServletContext(container); container.addListener(new ContextLoaderListener(ctx)); ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx)); servlet.setLoadOnStartup(1); servlet.addMapping("/"); } }

3.5. Verifica della convalida di Spring Mvc tramite Curl

Prima di implementare la sezione client AngularJS, possiamo testare la nostra API utilizzando cURL con il comando:

curl -i -X POST -H "Accept:application/json" "localhost:8080/spring-mvc-forms/user?email=aaa&password=12&age=12"

La risposta è un array contenente i messaggi di errore predefiniti:

[ "not a well-formed email address", "size must be between 4 and 15", "may not be empty", "must be greater than or equal to 18" ]

4. Convalida AngularJS

La convalida lato client è utile per creare una migliore esperienza utente, poiché fornisce all'utente informazioni su come inviare correttamente dati validi e consente loro di essere in grado di continuare a interagire con l'applicazione.

La libreria AngularJS ha un ottimo supporto per l'aggiunta di requisiti di convalida sui campi del modulo, la gestione dei messaggi di errore e lo stile dei moduli validi e non validi.

Per prima cosa, creiamo un modulo AngularJS che inietta il modulo ngMessages , che viene utilizzato per i messaggi di convalida:

var app = angular.module('app', ['ngMessages']);

Successivamente, creiamo un servizio e un controller AngularJS che utilizzeranno l'API costruita nella sezione precedente.

4.1. Il servizio AngularJS

Il nostro servizio avrà due metodi che chiamano i metodi del controller MVC: uno per salvare un utente e uno per recuperare l'elenco degli utenti:

app.service('UserService',['$http', function ($http) { this.saveUser = function saveUser(user){ return $http({ method: 'POST', url: 'user', params: {email:user.email, password:user.password, name:user.name, age:user.age}, headers: 'Accept:application/json' }); } this.getUsers = function getUsers(){ return $http({ method: 'GET', url: 'users', headers:'Accept:application/json' }).then( function(response){ return response.data; } ); } }]);

4.2. Il controller AngularJS

Il controller UserCtrl inietta UserService , chiama i metodi di servizio e gestisce i messaggi di risposta e di errore:

app.controller('UserCtrl', ['$scope','UserService', function ($scope,UserService) { $scope.submitted = false; $scope.getUsers = function() { UserService.getUsers().then(function(data) { $scope.users = data; }); } $scope.saveUser = function() { $scope.submitted = true; if ($scope.userForm.$valid) { UserService.saveUser($scope.user) .then (function success(response) { $scope.message = 'User added!'; $scope.errorMessage = ''; $scope.getUsers(); $scope.user = null; $scope.submitted = false; }, function error(response) { if (response.status == 409) { $scope.errorMessage = response.data.message; } else { $scope.errorMessage = 'Error adding user!'; } $scope.message = ''; }); } } $scope.getUsers(); }]);

Nell'esempio sopra possiamo vedere che il metodo del servizio viene chiamato solo se la proprietà $ valid di userForm è vera. Tuttavia, in questo caso, c'è il controllo aggiuntivo per le email duplicate, che può essere fatto solo sul server e viene gestito separatamente nella funzione error () .

Inoltre, nota che esiste una variabile inviata definita che ci dirà se il modulo è stato inviato o meno.

Inizialmente, questa variabile sarà falsa e, invocando il metodo saveUser () , diventerà vera . Se non vogliamo che i messaggi di convalida vengano visualizzati prima che l'utente invii il modulo, possiamo utilizzare la variabile inviata per impedirlo.

4.3. Form Using AngularJS Validation

In order to make use of the AngularJS library and our AngularJS module, we will need to add the scripts to our user.html page:

Then we can use our module and controller by setting the ng-app and ng-controller properties:

Let's create our HTML form:

 ... 

Note that we have to set the novalidate attribute on the form in order to prevent default HTML5 validation and replace it with our own.

The ng-class attribute adds the form-error CSS class dynamically to the form if the submitted variable has a value of true.

The ng-submit attribute defines the AngularJS controller function that will be called when the form in submitted. Using ng-submit instead of ng-click has the advantage that it also responds to submitting the form using the ENTER key.

Now let's add the four input fields for the User attributes:

Email:  Password:  Name:  Age: 

Each input field has a binding to a property of the user variable through the ng-model attribute.

For setting validation rules, we use the HTML5 required attribute and several AngularJS-specific attributes: ng-minglength, ng-maxlength, ng-min, and ng-trim.

For the email field, we also use the type attribute with a value of email for client-side email validation.

In order to add error messages corresponding to each field, AngularJS offers the ng-messages directive, which loops through an input's $errors object and displays messages based on each validation rule.

Let's add the directive for the email field right after the input definition:

Invalid email!

Email is required!

Similar error messages can be added for the other input fields.

We can control when the directive is displayed for the email field using the ng-show property with a boolean expression. In our example, we display the directive when the field has an invalid value, meaning the $invalid property is true, and the submitted variable is also true.

Only one error message will be displayed at a time for a field.

We can also add a check mark sign (represented by HEX code character ✓) after the input field in case the field is valid, depending on the $valid property:

AngularJS validation also offers support for styling using CSS classes such as ng-valid and ng-invalid or more specific ones like ng-invalid-required and ng-invalid-minlength.

Let's add the CSS property border-color:red for invalid inputs inside the form's form-error class:

.form-error input.ng-invalid { border-color:red; }

We can also show the error messages in red using a CSS class:

.error-messages { color:red; }

After putting everything together, let's see an example of how our client-side form validation will look when filled out with a mix of valid and invalid values:

5. Conclusion

In this tutorial, we've shown how we can combine client-side and server-side validation using AngularJS and Spring MVC.

Come sempre, il codice sorgente completo per gli esempi può essere trovato su GitHub.

Per visualizzare l'applicazione, accedere all'URL / userPage dopo averla eseguita.