Contactez-nous

Gestion des formulaires (`@ModelAttribute`)

Apprenez à lier les données de formulaires HTML à des objets Java en utilisant @ModelAttribute dans Spring MVC pour simplifier le traitement des soumissions.

Introduction à la liaison de données de formulaire

Les formulaires HTML sont le moyen principal par lequel les utilisateurs interagissent et soumettent des données dans les applications web traditionnelles. Côté serveur, le framework doit fournir un moyen pratique de recevoir ces données soumises, de les valider et de les lier à des objets Java pour un traitement ultérieur dans la logique métier. Extraire manuellement chaque champ de formulaire des paramètres de la requête (HttpServletRequest.getParameter()) est fastidieux et source d'erreurs.

Spring MVC simplifie considérablement ce processus grâce à son mécanisme de liaison de données (data binding). L'annotation @ModelAttribute joue un rôle central dans cette fonctionnalité. Elle permet de lier automatiquement les données d'une requête entrante (généralement issues d'une soumission de formulaire) à un objet Java, appelé souvent "form-backing bean" ou "command object".

Cette annotation peut être utilisée de deux manières principales dans le contexte des formulaires : sur un paramètre de méthode de handler pour recevoir les données soumises, et sur une méthode pour ajouter des données de référence au modèle avant le rendu de la vue.

`@ModelAttribute` sur un paramètre de méthode : Lier les données soumises

C'est l'utilisation la plus fréquente de @ModelAttribute pour la gestion des formulaires. Lorsque vous l'appliquez à un paramètre d'une méthode de handler (typiquement une méthode annotée avec @PostMapping pour traiter la soumission d'un formulaire), vous demandez à Spring MVC de :

  1. Instancier un objet du type du paramètre (par exemple, UserForm).
  2. Récupérer les paramètres de la requête HTTP (qui correspondent généralement aux champs <input>, <select>, <textarea> du formulaire HTML soumis).
  3. Faire correspondre les noms des paramètres de la requête aux noms des propriétés de l'objet (en utilisant les setters ou l'accès direct aux champs).
  4. Effectuer une conversion de type si nécessaire (par exemple, convertir une chaîne "true" en un booléen, une chaîne "123" en un entier).
  5. Passer l'objet rempli (populé) en tant qu'argument à votre méthode de handler.

Par défaut, le nom sous lequel cet objet est ajouté au modèle (et donc accessible dans la vue, notamment en cas d'erreurs de validation) est le nom de la classe, décapitalisé (par exemple, UserForm devient userForm). Vous pouvez spécifier un nom différent en utilisant @ModelAttribute("customName").

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.validation.BindingResult; // Pour la validation
import javax.validation.Valid; // Pour déclencher la validation

@Controller
public class RegistrationController {

    // Affiche le formulaire vide
    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        // Important: Ajoute un objet UserForm vide au modèle pour la liaison initiale du formulaire
        model.addAttribute("userForm", new UserForm()); 
        return "registrationForm"; // Nom logique de la vue (ex: /templates/registrationForm.html)
    }

    // Traite la soumission du formulaire
    @PostMapping("/register")
    public String processRegistration(@Valid @ModelAttribute("userForm") UserForm userForm, 
                                      BindingResult bindingResult) {
        
        // Vérifier les erreurs de validation (sera vu plus en détail)
        if (bindingResult.hasErrors()) {
            return "registrationForm"; // Ré-affiche le formulaire avec les erreurs
        }
        
        // Logique métier si le formulaire est valide (ex: sauvegarder l'utilisateur)
        // userService.register(userForm);
        System.out.println("Utilisateur enregistré: " + userForm.getUsername());

        return "redirect:/registrationSuccess"; // Redirection après succès (Pattern Post-Redirect-Get)
    }
}

// Classe simple représentant les données du formulaire (Form-Backing Bean)
class UserForm {
    @NotNull @Size(min=3, max=20)
    private String username;
    @NotNull @Email
    private String email;
    // ... autres champs, getters et setters

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

Dans la vue HTML (par exemple, avec Thymeleaf), vous utiliseriez l'objet lié avec th:object et les champs avec th:field pour établir la correspondance :

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <form action="#" th:action="@{/register}" th:object="${userForm}" method="post">
        <div>
            <label>Username:</label>
            <input type="text" th:field="*{username}" />
            <!-- Affichage des erreurs de validation -->
            <span th:if="${#fields.hasErrors('username')}" th:errors="*{username}">Error</span>
        </div>
        <div>
            <label>Email:</label>
            <input type="email" th:field="*{email}" />
            <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Error</span>
        </div>
        <button type="submit">Register</button>
    </form>
</body>
</html>

`@ModelAttribute` sur une méthode : Fournir des données de référence

L'autre utilisation courante de @ModelAttribute est de l'appliquer directement à une méthode au sein d'un @Controller. Une telle méthode est invoquée avant chaque méthode de handler (@GetMapping, @PostMapping, etc.) du même contrôleur.

Le but principal de ces méthodes est de peupler le modèle avec des données communes ou des données de référence qui sont nécessaires pour afficher le formulaire ou la page. Par exemple, pour remplir les options d'une liste déroulante (<select>), des cases à cocher ou des boutons radio.

L'objet retourné par la méthode annotée avec @ModelAttribute est automatiquement ajouté au modèle. Le nom de l'attribut dans le modèle est déterminé soit par la valeur spécifiée dans l'annotation (@ModelAttribute("attributeName")), soit, si aucune valeur n'est spécifiée, par le nom de la méthode de retour décapitalisé (en inférant à partir du nom de la classe si le type de retour est void et qu'un paramètre Model est présent).

@Controller
public class ProductFormController {

    @Autowired
    private CategoryService categoryService;

    // Cette méthode est appelée avant showProductForm et processProductForm
    @ModelAttribute("allCategories") // Nom de l'attribut dans le modèle
    public List<Category> populateCategories() {
        System.out.println("Appel de populateCategories pour ajouter les catégories au modèle.");
        return categoryService.findAll(); // Récupère la liste des catégories
    }

    @GetMapping("/products/new")
    public String showProductForm(Model model) {
        model.addAttribute("productForm", new ProductForm());
        // Pas besoin d'ajouter 'allCategories' ici, c'est fait par la méthode @ModelAttribute
        return "productForm";
    }

    @PostMapping("/products/new")
    public String processProductForm(@ModelAttribute ProductForm productForm, BindingResult result) {
        if (result.hasErrors()) {
            // 'allCategories' est toujours disponible ici aussi pour ré-afficher le formulaire
            return "productForm";
        }
        // ... sauvegarde du produit
        return "redirect:/products";
    }
}

// Dans la vue productForm.html:
// <select th:field="*{categoryId}">
//   <option th:each="cat : ${allCategories}" th:value="${cat.id}" th:text="${cat.name}">Category</option>
// </select>

Utiliser @ModelAttribute sur une méthode est un moyen élégant d'éviter de répéter le code de récupération et d'ajout de données de référence dans chaque méthode de handler qui en a besoin.

Résumé du rôle de `@ModelAttribute`

En somme, @ModelAttribute est une annotation polyvalente et essentielle pour gérer les formulaires web avec Spring MVC :

  • Sur un paramètre de méthode : Elle lie les données de la requête entrante (soumission de formulaire) à un objet Java (data binding).
  • Sur une méthode : Elle permet d'ajouter des données de référence communes au modèle avant l'exécution des méthodes de handler, souvent pour peupler les options des éléments de formulaire.

Maîtriser @ModelAttribute est fondamental pour construire des formulaires interactifs de manière efficace et propre dans les applications web Spring, en réduisant le code répétitif et en facilitant l'intégration avec les mécanismes de validation et les technologies de vue.