478 votes

Qu'est-ce que le ViewModel en MVC ?

Je suis nouveau dans ASP.NET MVC. J'ai un problème à comprendre l'objectif d'un ViewModel.

Qu'est-ce qu'un ViewModel et pourquoi avons-nous besoin d'un ViewModel pour une application ASP.NET MVC?

Si j'obtiens un bon exemple de son fonctionnement et une explication, ce serait mieux.

4 votes

Cette publication est ce que vous cherchez - "Qu'est-ce qu'un ViewModel ASP.NET MVC?"

6 votes

Cet article semble génial : rachelappel.com/…

0 votes

685voto

Brendan Vogt Points 5000

Un view model représente les données que vous souhaitez afficher sur votre vue/page, que ce soit utilisé pour du texte statique ou pour des valeurs d'entrée (comme des zones de texte et des listes déroulantes) qui peuvent être ajoutées à la base de données (ou modifiées). C'est quelque chose de différent que votre domain model. C'est un modèle pour la vue.

Disons que vous avez une classe Employee qui représente votre modèle de domaine d'employé et qui contient les propriétés suivantes (identifiant unique, prénom, nom de famille et date de création) :

public class Employee : IEntity
{
     public int Id { get; set; }

     public string FirstName { get; set; }

     public string LastName { get; set; }

     public DateTime DateCreated { get; set; }
}

Les view models diffèrent des domain models en ce sens que les view models contiennent uniquement les données (représentées par des propriétés) que vous souhaitez utiliser sur votre vue. Par exemple, disons que vous souhaitez ajouter un nouvel enregistrement employé, votre view model pourrait ressembler à ceci :

public class CreateEmployeeViewModel
{
     public string FirstName { get; set; }

     public string LastName { get; set; }
}

Comme vous pouvez le voir, il ne contient que deux des propriétés. Ces deux propriétés se trouvent également dans le modèle de domaine d'employé. Pourquoi me direz-vous ? Id pourrait ne pas être défini à partir de la vue, il pourrait être automatiquement généré par la table Employee. Et DateCreated pourrait également être défini dans la procédure stockée ou dans la couche de service de votre application. Ainsi, Id et DateCreated ne sont pas nécessaires dans le view model. Vous pouvez vouloir afficher ces deux propriétés lorsque vous consultez les détails d'un employé (un employé qui a déjà été enregistré) en tant que texte statique.

Lors du chargement de la vue/page, la méthode d'action de création dans votre contrôleur d'employé créera une instance de ce view model, remplira les champs si nécessaire, puis passera ce view model à la vue/page :

public class EmployeeController : Controller
{
     private readonly IEmployeeService employeeService;

     public EmployeeController(IEmployeeService employeeService)
     {
          this.employeeService = employeeService;
     }

     public ActionResult Create()
     {
          CreateEmployeeViewModel model = new CreateEmployeeViewModel();

          return View(model);
     }

     public ActionResult Create(CreateEmployeeViewModel model)
     {
          // Faire ce qui doit être fait avant d'ajouter l'employé à la base de données
     }
}

Votre vue/page pourrait ressembler à ceci (en supposant que vous utilisez ASP.NET MVC et le moteur de vue Razor) :

@model MyProject.Web.ViewModels.CreateEmployeeViewModel

          Prénom:
          @Html.TextBoxFor(m => m.FirstName, new { maxlength = "50", size = "50" })
              @Html.ValidationMessageFor(m => m.FirstName)

          Nom:
          @Html.TextBoxFor(m => m.LastName, new { maxlength = "50", size = "50" })
              @Html.ValidationMessageFor(m => m.LastName)

La validation serait donc effectuée uniquement sur FirstName et LastName. En utilisant FluentValidation, vous pourriez avoir une validation comme ceci :

public class CreateEmployeeViewModelValidator : AbstractValidator
{
     public CreateEmployeeViewModelValidator()
     {
          RuleFor(m => m.FirstName)
               .NotEmpty()
               .WithMessage("Prénom requis")
               .Length(1, 50)
               .WithMessage("Le prénom ne doit pas dépasser 50 caractères");

          RuleFor(m => m.LastName)
               .NotEmpty()
               .WithMessage("Nom requis")
               .Length(1, 50)
               .WithMessage("Le nom ne doit pas dépasser 50 caractères");
     }
}

Et avec les Data Annotations, cela pourrait ressembler à ceci :

public class CreateEmployeeViewModel : ViewModelBase
{
    [Display(Name = "Prénom")]
    [Required(ErrorMessage = "Prénom requis")]
    public string FirstName { get; set; }

    [Display(Name = "Nom")]
    [Required(ErrorMessage = "Nom requis")]
    public string LastName { get; set; }
}

La chose essentielle à retenir est que le view model représente uniquement les données que vous souhaitez utiliser, rien d'autre. Vous pouvez imaginer tout le code et la validation inutiles si vous avez un modèle de domaine avec 30 propriétés et que vous voulez uniquement mettre à jour une seule valeur. Dans ce scénario, vous n'auriez que cette seule valeur/propriété dans le view model et pas toutes les propriétés qui se trouvent dans l'objet de domaine.

Un view model pourrait ne pas seulement avoir des données d'une seule table de base de données. Il peut combiner des données d'une autre table. Prenons mon exemple ci-dessus concernant l'ajout d'un nouvel enregistrement employé. En plus d'ajouter simplement les prénoms et noms, vous voudriez peut-être aussi ajouter le département de l'employé. Cette liste de départements viendra de votre table Departments. Ainsi, vous avez maintenant des données des tables Employees et Departments dans un seul view model. Vous devrez alors simplement ajouter les deux propriétés suivantes à votre view model et le peupler de données :

public int DepartmentId { get; set; }

public IEnumerable Departments { get; set; }

Lors de la modification des données d'un employé (un employé qui a déjà été ajouté à la base de données), cela ne différerait pas beaucoup de mon exemple ci-dessus. Créez un view model, appelez-le par exemple EditEmployeeViewModel. N'avoir que les données que vous souhaitez modifier dans ce view model, comme le prénom et le nom. Modifiez les données et cliquez sur le bouton de soumission. Je ne me préoccuperais pas trop du champ Id car la valeur Id sera probablement dans l'URL, par exemple :

http://www.votreSiteWeb.com/Employé/Modifier/3

Prenez cet Id et transmettez-le à votre couche de repository, avec vos valeurs de prénom et nom.

Lors de la suppression d'un enregistrement, je suis généralement le même chemin qu'avec le view model d'édition. J'aurais également une URL, par exemple :

http://www.votreSiteWeb.com/Employé/Supprimer/3

Lorsque la vue se charge pour la première fois, je récupérerais les données de l'employé depuis la base de données en utilisant l'Id de 3. Je afficherais simplement du texte statique sur ma vue/page pour que l'utilisateur puisse voir quel employé est en cours de suppression. Lorsque l'utilisateur clique sur le bouton Supprimer, j'utiliserais simplement la valeur Id de 3 et la passerais à ma couche de repository. Vous avez seulement besoin de l'Id pour supprimer un enregistrement de la table.

Autre point, vous n'avez pas vraiment besoin d'un view model pour chaque action. S'il s'agit de données simples, il serait bon d'utiliser uniquement EmployeeViewModel. S'il s'agit de vues/pages complexes et qu'elles diffèrent les unes des autres, je vous suggère d'utiliser des view models séparés pour chacune.

J'espère que cela éclaircira toute confusion que vous aviez concernant les view models et les domain models.

0 votes

Je suis encore confus - y a-t-il jamais eu, ou est-il sensé, de créer plusieurs modèles de vue correspondant à un seul modèle? Et si vous vouliez afficher soit l'identifiant soit la date de création d'un employé dans votre exemple?

6 votes

@Kenny: Alors montre-le :) Ce que j'essayais de dire, c'est que disons que vous avez un modèle de domaine avec 50 propriétés et que votre vue n'a besoin d'afficher que 5, il n'est donc pas utile d'envoyer les 50 propriétés juste pour en afficher 5.

7 votes

@BrendanVogt - tu as bien expliqué cela, mais je ne comprends pas quel est le coût de "envoyer les 50 propriétés". Un autre code a déjà créé un objet Model, avec les 50 propriétés, et il ne semble pas utile de maintenir une autre classe juste pour ne pas envoyer 45 propriétés - en particulier si vous pourriez vouloir envoyer l'une de ces 45 propriétés à l'avenir.

143voto

LukLed Points 18010

View model est une classe qui représente le modèle de données utilisé dans une vue spécifique. Nous pourrions utiliser cette classe comme modèle pour une page de connexion:

public class LoginPageVM
{
    [Required(ErrorMessage = "Essayeriez-vous vraiment de vous connecter sans saisir votre nom d'utilisateur?")]
    [DisplayName("Nom d'utilisateur/adresse e-mail")]
    public string UserName { get; set; }
    [Required(ErrorMessage = "Veuillez entrer le mot de passe :)")]
    [DisplayName("Mot de passe")]
    public string Password { get; set; }
    [DisplayName("Rester connecté lorsque le navigateur est fermé")]
    public bool RememberMe { get; set; }
}

En utilisant ce modèle de vue, vous pouvez définir la vue (moteur de vue Razor):

@model CamelTrap.Models.ViewModels.LoginPageVM

@using (Html.BeginForm()) {
    @Html.EditorFor(m => m);

}

Et les actions:

[HttpGet]
public ActionResult LoginPage()
{
    return View();
}

[HttpPost]
public ActionResult LoginPage(LoginPageVM model)
{
    ...code pour connecter l'utilisateur à l'application...
    return View(model);
}

Ce qui produit ce résultat (l'écran est pris après avoir soumis le formulaire, avec des messages de validation):

Comme vous pouvez le voir, un modèle de vue a de nombreux rôles:

  • Les modèles de vue documentent une vue en ne contenant que des champs représentés dans la vue.
  • Les modèles de vue peuvent contenir des règles de validation spécifiques en utilisant des annotations de données ou IDataErrorInfo.
  • Le modèle de vue définit l'apparence d'une vue (pour les aides LabelFor, EditorFor, DisplayFor).
  • Les modèles de vue peuvent combiner des valeurs issues de différentes entités de base de données.
  • Vous pouvez spécifier facilement des modèles d'affichage pour les modèles de vue et les réutiliser à de nombreux endroits en utilisant les aides DisplayFor ou EditorFor.

Un autre exemple d'un modèle de vue et de sa récupération: Nous voulons afficher les données de base de l'utilisateur, ses privilèges et le nom des utilisateurs. Nous créons un modèle de vue spécial, qui ne contient que les champs requis. Nous récupérons les données de différentes entités de la base de données, mais la vue est seulement consciente de la classe de modèle de vue:

public class UserVM {
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public bool IsAdministrator { get; set; }
    public string MothersName { get; set; }
}

Récupération:

var user = db.userRepository.GetUser(id);

var model = new UserVM() {
   ID = user.ID,
   FirstName = user.FirstName,
   LastName = user.LastName,
   IsAdministrator = user.Proviledges.IsAdministrator,
   MothersName = user.Mother.FirstName + " " + user.Mother.LastName
}

0 votes

Je pense que user.Mother.FirstName + " " + user.Mother.LastName devrait être fait dans ViewModel End. Toute la logique doit être faite dans le ViewModel.

3 votes

@Chandana: Je crois que la simple concaténation peut être faite dans le modèle de vue. Il n'y a aucune raison d'exposer deux champs, s'ils sont censés être présentés ensemble.

96voto

Sam Points 2521

Edit : J'ai mis à jour cette réponse sur mon Blog :

http://www.samwheat.com/post/The-function-of-ViewModels-in-MVC-web-development

Ma réponse est un peu longue mais je pense qu'il est important de comparer les modèles de vue à d'autres types de modèles couramment utilisés pour comprendre pourquoi ils sont différents et pourquoi ils sont nécessaires.

Pour résumer, et pour répondre directement à la question qui est posée :

De manière générale, un modèle de vue est un objet qui contient toutes les propriétés et méthodes nécessaires au rendu d'une vue. Les propriétés du modèle de vue sont souvent liées à des objets de données tels que les clients et les commandes et, en outre, elles contiennent également des propriétés liées à la page ou à l'application elle-même, telles que le nom de l'utilisateur, le nom de l'application, etc. Les modèles de vue fournissent un objet pratique à transmettre à un moteur de rendu pour créer une page HTML. L'une des nombreuses raisons d'utiliser un modèle de vue est que les modèles de vue fournissent un moyen de tester unitairement certaines tâches de présentation telles que le traitement des entrées utilisateur, la validation des données, la récupération des données pour l'affichage, etc.

Voici une comparaison des modèles d'entités (a.ka. DTOs a.ka. modèles), des modèles de présentation et des modèles de vue.

Objets de transfert de données, alias "modèle".

Un objet de transfert de données (DTO) est une classe dont les propriétés correspondent à un schéma de table dans une base de données. Les DTO sont appelés ainsi en raison de leur utilisation courante pour le transfert de données vers et depuis un magasin de données.
Caractéristiques des DTO :

  • Sont des objets d'affaires - leur définition dépend des données de l'application.
  • Ils ne contiennent généralement que des propriétés - pas de code.
  • Principalement utilisé pour transporter des données vers et depuis une base de données.
  • Les propriétés correspondent exactement ou étroitement aux champs d'une table spécifique dans un magasin de données.

Les tables de la base de données sont généralement normalisées et les DTO le sont également. Leur utilité pour la présentation des données est donc limitée. Toutefois, pour certaines structures de données simples, ils sont souvent très utiles.

Voici deux exemples de ce à quoi peuvent ressembler les DTO :

public class Customer
{
    public int ID { get; set; }
    public string CustomerName { get; set; }
}

public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; }
    public DateTime OrderDate { get; set; }
    public Decimal OrderAmount { get; set; }
}

Modèles de présentation

Un modèle de présentation est un utilitaire classe qui est utilisée pour rendre les données sur un écran ou un rapport. Les modèles de présentation sont généralement utilisés pour modéliser des structures de données complexes composées de données provenant de plusieurs DTO. Les modèles de présentation représentent souvent une vue dénormalisée des données.

Caractéristiques des modèles de présentation :

  • Sont des objets d'affaires - leur définition dépend des données de l'application.
  • Contiennent surtout des propriétés. Le code se limite généralement à formater les données ou à les convertir vers ou depuis un DTO. Les modèles de présentation ne doivent pas contenir de logique métier.
  • présentent souvent une vue dénormalisée des données. En d'autres termes, ils combinent souvent les propriétés de plusieurs DTO.
  • contiennent souvent des propriétés d'un type de base différent de celui d'un DTO. Par exemple, les montants en dollars peuvent être représentés sous forme de chaînes de caractères, de sorte qu'ils peuvent contenir des virgules et un symbole monétaire.
  • Souvent définis par la manière dont ils sont utilisés ainsi que par les caractéristiques de leur objet. En d'autres termes, un simple DTO qui est utilisé comme modèle de base pour le rendu d'une grille est en fait également un modèle de présentation dans le contexte de cette grille.

Les modèles de présentation sont utilisés "au besoin" et "là où c'est nécessaire" (alors que les DTO sont généralement liés au schéma de la base de données). Un modèle de présentation peut être utilisé pour modéliser les données d'une page entière, d'une grille sur une page ou d'une liste déroulante sur une grille sur une page. Les modèles de présentation contiennent souvent des propriétés qui sont d'autres modèles de présentation. Les modèles de présentation sont souvent construits dans un but unique, par exemple pour rendre une grille spécifique sur une seule page.

Un exemple de modèle de présentation :

public class PresentationOrder
{
    public int OrderID { get; set; }
    public DateTime OrderDate { get; set; }
    public string PrettyDate { get { return OrderDate.ToShortDateString(); } }
    public string CustomerName { get; set; }
    public Decimal OrderAmount { get; set; }
    public string PrettyAmount { get { return string.Format("{0:C}", OrderAmount); } }
}

Voir les modèles

Un modèle de vue est similaire à un modèle de présentation dans la mesure où il s'agit d'une classe de soutien pour le rendu d'une vue. Cependant, il est très différent d'un modèle de présentation ou d'un DTO dans la façon dont il est construit. Les modèles de vue contiennent souvent les mêmes propriétés que les modèles de présentation et les DTO, c'est pourquoi ils sont souvent confondus l'un avec l'autre.

Caractéristiques des modèles de vue :

  • Sont la source unique de données utilisée pour rendre une page ou un écran. En général, cela signifie qu'un modèle de vue expose chaque propriété dont un contrôle de la page a besoin pour s'afficher correctement. Faire du modèle de vue la source unique de données pour la vue améliore considérablement sa capacité et sa valeur pour les tests unitaires.
  • Sont objets composites qui contiennent des propriétés constituées de données d'application ainsi que des propriétés utilisées par le code de l'application. Cette caractéristique est cruciale lors de la conception du modèle de vue en vue de sa réutilisation et est abordée dans les exemples ci-dessous.
  • Contiennent le code de l'application. Les modèles de vue contiennent généralement des méthodes qui sont appelées pendant le rendu et lorsque l'utilisateur interagit avec la page. Ce code concerne généralement la gestion des événements, l'animation, la visibilité des contrôles, le style, etc.
  • Contiennent du code qui appelle des services d'entreprise dans le but de récupérer des données ou de les envoyer à un serveur de base de données. Ce code est souvent placé par erreur dans un contrôleur. L'appel de services métier à partir d'un contrôleur limite généralement l'utilité du modèle de vue pour les tests unitaires. Pour être clair, les modèles de vue eux-mêmes ne doivent pas contenir de logique métier mais doivent appeler des services qui en contiennent.
  • contiennent souvent des propriétés qui sont d'autres modèles de vue pour d'autres pages ou écrans.
  • sont écrits "par page" ou "par écran". Un modèle de vue unique est généralement écrit pour chaque page ou écran d'une application.
  • Dérivent généralement d'une classe de base, car la plupart des pages et des écrans partagent des propriétés communes.

Composition du modèle de vue

Comme indiqué précédemment, les modèles de vue sont des objets composites dans la mesure où ils combinent des propriétés d'application et des propriétés de données métier sur un seul objet. Voici des exemples de propriétés d'application couramment utilisées sur les modèles de vue :

  • Propriétés qui sont utilisées pour afficher l'état de l'application, comme les messages d'erreur, le nom de l'utilisateur, le statut, etc.
  • Propriétés utilisées pour formater, afficher, styliser ou animer les contrôles.
  • Propriétés utilisées pour la liaison des données, telles que les objets de type liste et les propriétés qui contiennent des données intermédiaires saisies par l'utilisateur.

Les exemples suivants montrent pourquoi la nature composite des modèles de vue est importante et comment nous pouvons construire au mieux un modèle de vue efficace et réutilisable.

Supposons que nous soyons en train d'écrire une application web. L'une des exigences de la conception de l'application est que le titre de la page, le nom de l'utilisateur et le nom de l'application doivent être affichés sur chaque page. Si nous voulons créer une page pour afficher un objet d'ordre de présentation, nous pouvons modifier le modèle de présentation comme suit :

public class PresentationOrder
{
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }
    public int OrderID { get; set; }
    public DateTime OrderDate { get; set; }
    public string PrettyDate { get { return OrderDate.ToShortDateString(); } }
    public string CustomerName { get; set; }
    public Decimal OrderAmount { get; set; }
    public string PrettyAmount { get { return string.Format("{0:C}", OrderAmount); } }
}

Cette conception pourrait fonctionner mais que faire si nous voulons créer une page qui affichera une liste de commandes ? Les propriétés PageTitle, UserName et ApplicationName seront répétées et il sera difficile de travailler avec elles. De plus, que se passe-t-il si nous voulons définir une certaine logique au niveau de la page dans le constructeur de la classe ? Nous ne pouvons plus le faire si nous créons une instance pour chaque commande qui sera affichée.

Composition sur l'héritage

Voici comment nous pourrions remanier le modèle de présentation des commandes de manière à ce qu'il devienne un véritable modèle de vue et qu'il soit utile pour afficher un seul objet PresentationOrder ou une collection d'objets PresentationOrder :

public class PresentationOrderVM
{
    // Application properties
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }

    // Business properties
    public PresentationOrder Order { get; set; }
}

public class PresentationOrderVM
{
    // Application properties
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }

    // Business properties
    public List<PresentationOrder> Orders { get; set; }
}

En regardant les deux classes ci-dessus, nous pouvons voir qu'une façon de penser à un modèle de vue est qu'il s'agit d'un modèle de présentation qui contient un autre modèle de présentation comme propriété. Le modèle de présentation de niveau supérieur (c'est-à-dire le modèle de vue) contient des propriétés qui sont pertinentes pour la page ou l'application, tandis que le modèle de présentation (propriété) contient des propriétés qui sont pertinentes pour les données de l'application.

Nous pouvons pousser notre conception un peu plus loin et créer une classe de modèle de vue de base qui peut être utilisée non seulement pour PresentationOrders mais aussi pour toute autre classe :

public class BaseViewModel
{
    // Application properties
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }
}

Maintenant nous pouvons simplifier notre PresentationOrderVM comme ceci :

public class PresentationOrderVM : BaseViewModel
{
    // Business properties
    public PresentationOrder Order { get; set; }
}

public class PresentationOrderVM : BaseViewModel
{
    // Business properties
    public List<PresentationOrder> Orders { get; set; }
}

Nous pouvons rendre notre BaseViewModel encore plus réutilisable en le rendant générique :

public class BaseViewModel<T>
{
    // Application properties
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }

    // Business property
    public T BusinessObject { get; set; }
}

Maintenant, nos mises en œuvre se font sans effort :

public class PresentationOrderVM : BaseViewModel<PresentationOrder>
{
    // done!
}

public class PresentationOrderVM : BaseViewModel<List<PresentationOrder>>
{
    // done!
}

2 votes

Sam Merci!! cela m'a vraiment aidé à comprendre pleinement l'entité multidimensionnelle qu'est un: View-Model. Je suis un étudiant universitaire en train d'apprendre l'architecture MVC, et cela a clarifié bon nombre des fonctionnalités capables qui sont exposées au développeur. Si je le pouvais, je mettrais une étoile à côté de votre réponse.

1 votes

@Sam 'Les modèles de vue contiennent souvent les mêmes propriétés que les modèles de présentation et les DTO et c'est pourquoi ils sont souvent confondus les uns avec les autres.' Est-ce que cela signifie qu'ils sont couramment utilisés à la place des modèles de présentation, ou sont-ils censés contenir les modèles de présentation/dtos ?

2 votes

@AlexanderDerck Ils sont utilisés à des fins différentes. Ils sont souvent confondus l'un pour l'autre (par erreur). Non, vous n'utiliserez généralement pas un modèle de présentation à la place d'un modèle de vue. Bien plus courant est que le VM "contient" le modèle de présentation c'est-à-dire MonViewModel\

27voto

halfacreSal Points 35

Je n'ai pas lu tous les messages mais chaque réponse semble manquer d'un concept qui m'a vraiment aidé à "comprendre"...

Si un Modèle est semblable à une table de base de données, alors un ViewModel est semblable à une vue de base de données - Une vue retourne généralement de petites quantités de données à partir d'une table ou des ensembles de données complexes à partir de plusieurs tables (jointures).

Je me retrouve à utiliser des ViewModels pour transmettre les informations à une vue/formulaire, puis transférer ces données dans un Modèle valide lorsque le formulaire est renvoyé au contrôleur - également très pratique pour stocker des Listes (IEnumerable).

22voto

fozylet Points 664

Si vous avez des propriétés spécifiques à la vue, et non liées à la base de données / au service / au magasin de données, il est bon d'utiliser des ViewModels. Disons que vous voulez laisser une case à cocher sélectionnée en fonction d'un champ de la base de données (ou deux) mais que le champ de la base de données lui-même n'est pas booléen. Bien qu'il soit possible de créer ces propriétés dans le modèle lui-même et de les masquer de la liaison aux données, vous ne voulez peut-être pas encombrer le modèle en fonction du nombre de ces champs et transactions.

S'il y a trop peu de données spécifiques à la vue et / ou de transformations, vous pouvez utiliser le modèle lui-même.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X