La semaine dernière, je lisais quelques articles sur l'utilisation de WCF, le cadre d'entité et comment transporter les entités à travers la frontière du service. L'un des articles que j'ai rencontrés comprenait un projet de démonstration qui utilisait le modèle de présentateur de vue du modèle (MVP).
Après avoir exploré le projet de démonstration, j'ai pensé qu'il pourrait être intéressant d'écrire un article sur ce modèle. Votre moteur de recherche préféré vous fournira volontiers une pléthore de liens vers d'autres articles qui expliquent soigneusement ce modèle.
Pour cet article, j'ai décidé de fournir une implémentation concrète et de me concentrer moins sur la théorie derrière le modèle.
Rollons ...
Bien sûr, un peu de théorie est nécessaire, alors éloignez-le du chemin.
Comme vous pouvez déduire du nom, le modèle MVP se compose de trois parties distinctes: le modèle, la vue et le présentateur. Chacune de ces parties joue son propre rôle dans l'établissement d'une séparation des préoccupations entre la présentation, l'entreprise et la couche d'accès aux données.
Le modèle est responsable de la gestion de l'accès aux données, le présentateur communique avec le modèle et transmet les données de et vers elle. La vue reçoit des données du présentateur et transmet les données, elle ne communique jamais directement avec le modèle. Le présentateur est le go-between pour la vue et le modèle.
Figure 1 - Interaction du motif MVP

L'image ci-dessus décrit la vue comme implémentation d'une interface. La couche de présentation que ce soit un ASP.net, des applications WinForms ou WPF doivent implémenter une ou plusieurs interfaces de vue. Le présentateur communique à son tour avec l'implémentation de la vue via cette interface, il ne sait rien de l'implémentation réelle elle-même.
Cela fournit un couplage lâche et empêche votre code de dépendre de la technologie utilisée pour la couche de présentation. L'idée est que vous devriez être en mesure de remplacer cette couche sans affecter votre logique d'entreprise et d'accès aux données.
Tout cela peut sembler un peu vague, mais les choses devraient s'éclaircir une fois que nous passons aux points suivants qui fournissent une implémentation réelle de ce modèle. Pour l'exemple, une application ASP.NET sera utilisée.
L'endroit le plus logique pour commencer est le modèle. Étant donné qu'il est responsable de la gestion de l'accès et du stockage des données, nous devons d'abord établir un magasin de données physique. Pour cela, j'ai utilisé SQL Server 2005 Express, créé une nouvelle base de données appelée Southwind et ajouté une table intitulée Client. La table a 3 champs, à savoir:
C'est tout pour la base de données. Lançons Visual Studio 2008 et créons une nouvelle solution vierge nommée Avppattren. Ajoutez ensuite une bibliothèque de classe intitulée "base de données".
Normalement, je donnerais au projet un nom qui suit le modèle "Company.Product.Library", mais pour la simplicité, gardons-le court et simple. Supprimez également le fichier Class1.cs de Class1.cs en autoGee après que le projet a été ajouté à la solution.
Créons un modèle à partir de la base de données à l'aide du Framework Entity (EF). Assurez-vous donc que vous utilisez Visual Studio 2008 et faites installer le Pack 1 pour Visual Studio et le .NET Framework 3.5. Vous pouvez télécharger les packs de services ici.
Ajoutez un modèle de données d'entité Framework à la bibliothèque de classe en sélectionnant ADD, nouvel élément, modèle de données d'entité ADO.NET dans le menu contextuel du projet dans l'explorateur de solutions. Visual Studio montrera désormais l'assistant du modèle de données d'entité. Nommez le modèle Southwind et laissez Visual Studio générer le modèle pour vous. Lorsqu'on vous a demandé quels objets de base de données vous souhaitez inclure dans votre modèle, sélectionnez simplement le tableau client dans le nœud des tables.
Remarque : Si vous n'êtes pas familier avec la génération de modèles de données avec le cadre d'entité, je suggère fortement cette vidéo de formation d'Alex James. Il vous montre comment construire un modèle de données d'entité simple à partir de zéro.
La figure 2 montre le modèle de données d'entité résultant. Le modèle ne peut pas devenir beaucoup plus simple que cela. Cela se fait à des fins pour garder les choses aussi simples que possible et pour garder l'accent sur le modèle MVP.
Figure 2 - Modèle de données d'entité

Assurez-vous de renommer vos entités et vos entités et vos entités à un nom approprié une fois le modèle de données généré. La règle de base consiste à utiliser un seul nom pour le type d'entité et un pluriel pour les ensembles d'entité. Donc, dans ce cas, notre TityType doit être nommé client et les clients entités. Le nom de l'EtityType est déjà OK car il l'hérite de la table du client, alors sélectionnez simplement le Client EntityType et ajustez sa propriété Nom Set Name.
Figure 3 - Nom de l'ensemble des entités

Le cadre d'entité générera automatiquement une classe partielle pour la table client. Vous pouvez choisir d'étendre cette classe client partielle si vous le souhaitez. Pour éviter que vos ajouts personnalisés ne soient effacés lors de la régénération du modèle, mettez ce code dans un fichier de classe distinct. Ceci est similaire à travailler avec des ensembles de données fortement dactylographiés. Pour l'article, cela n'est cependant pas nécessaire.
Figure 4 - Explorateur de solution

Maintenant que le modèle est en place, ajoutons une couche commerciale dessus où nous pouvons définir notre logique commerciale personnalisée. Par souci de simplicité, je garderai la quantité de code limitée dans cette couche.
Notez que ces couches n'appliquent qu'une séparation logique (n-couche), pas une séparation physique (n-niveau). Les couches résident toutes sur la même machine, bien que vous puissiez certainement choisir de les divertir sur plusieurs machines / niveaux et de créer une application véritablement distribuée ou n-niveau.
Pour configurer la couche commerciale, ajoutez une nouvelle bibliothèque de classe à la solution et appelez l'industrie informatique. Renommez ensuite le fichier class.cs par défaut sur CustomerManager.cs. Ajoutez également des références à la base de données de bibliothèque de classe créée précédemment et à l'assemblage System.Data.entity.
Listing 1 affiche la classe CustomerManager qui contient une logique métier pour travailler avec l'entité client à partir du modèle de données d'entité (EDM). Le code est à peu près explicite.
Listing 1 - Classe CustomerManager
using System . Collections . Generic ;
using System . Linq ;
using Database ;
namespace Business
{
public class CustomerManager
{
private readonly SouthwindEntities context ;
#region Constructor(s)
public CustomerManager ( )
{
context = new SouthwindEntities ( ) ;
}
#endregion
#region Methods
// Retrieve a generic list of Customer entities.
// This method will return all the customers found in the Customer table.
public List < Customer > GetCustomers ( )
{
var q = from c in context . Customers
select c ;
return q . ToList ( ) ;
}
#endregion
}
}Figure 5 - Solution mise à jour

Remarque : La configuration d'une application de niveau N comprendrait l'introduction d'une couche de service dans laquelle la couche de présentation appelle. La couche de service utilise ensuite les objets commerciaux trouvés dans la couche commerciale. Il n'y a plus d'association directe entre la présentation et la couche commerciale, la couche de service agit comme intermédiaire. Dans un futur article, je vais y parler en montrant comment transporter des entités EF à travers la frontière du service.
La couche commerciale ne contient qu'une seule méthode utile, à savoir "List getCustomers ()". Le présentateur du modèle MVP appellera cette méthode sur l'objet commercial CustomerManager afin de livrer les données à la vue.
L'exemple d'application affiche uniquement une liste de clients utilisant le modèle MVP. Cela peut être un peu exagéré, mais il est resté aussi simple que possible par le design. L'objectif principal de ce "Hello World!" Le type d'application est de faire passer l'idée de la façon de mettre en œuvre ce modèle. La fonctionnalité réelle offerte par l'application n'est pas si importante.
L'implémentation de vue réelle (page ASPX, WinForms, WPF ... etc.) Devra implémenter une interface de vue. L'implémentation de la vue doit créer une instance du présentateur et se passer comme paramètre dans son constructeur. Le constructeur du présentateur a un paramètre qui est le type de l'interface de vue.
Listing 2 répertorie l'interface iView que nous implémenterons sous peu dans une page ASP.NET ASPX. Il a un événement nommé Préparer. L'événement PrepreyView utilise un délégué dont la signature spécifie qu'il ne renvoie rien et ne prend aucun paramètre.
La vue ne doit que soulever ce type de "événements vides" pour signifier au présentateur qu'une action doit être effectuée. L'action dans ce cas signifie que le présentateur doit actualiser la liste des clients que l'implémentation de la vue maintient. Le présentateur peut accéder à cette liste de clients via la propriété des clients ILIST qui est déclarée dans le cadre de l'interface.
Listing 2 - Afficher l'interface
public delegate void VoidEventHandler ( ) ;
public interface IView
{
event VoidEventHandler PrepareView ;
IList < Customer > Customers { set ; }
}Pour ajouter l'interface de vue à votre projet, ajoutez d'abord un nouveau projet de bibliothèque de classe à la solution appelée présentation. Ajoutez ensuite une nouvelle interface et copiez et collez le code indiqué dans la liste ci-dessus. La bibliothèque de code de présentation contiendra également les présentateurs et les interfaces qu'ils implémentent.
Je sépare les interfaces et les présentateurs de vue dans une bibliothèque de code séparée afin que vous puissiez facilement les partager entre plusieurs "frameworks de vue" tels que ASP.NET, WinForms, WPF ... etc. La figure 6 montre comment j'ai choisi d'organiser cette bibliothèque de code.
Figure 6 - Solution mise à jour

N'oubliez pas d'ajouter des références à la base de données et aux projets commerciaux et à l'assemblage System.Data.entity.
Pour la dernière partie du modèle MVP, nous devons fournir un présentateur. La classe ClientsPresenter présentée dans Listing 3 prend une référence à une implémentation IView dans son constructeur. De cette façon, il peut communiquer avec le point de vue sans rien savoir sur l'implémentation réelle. C'est ce couplage lâche qui rend le motif MVP si adapté à différents "frameworks de vue".
Dans le constructeur également, tous les événements de l'interface de vue sont connectés à un gestionnaire d'événements. Dans ce cas, il n'y a qu'un seul événement. L'événement PrepreyView est connecté au gestionnaire d'événements View_prepareView. Cela appelle à son tour la méthode privée du présentateur getCustomers () qui renvoie une collection de clients "actualisée" et la affecte à la collection de clients entretenue par l'implémentation de la vue.
Listing 3 - CLIENTS PRESSENTER CLASS
public class CustomersPresenter : ICustomersPresenter
{
#region Fields
private readonly IView view ;
#endregion
#region Constructor(s)
public CustomersPresenter ( IView view )
{
// Save a reference to the view
this . view = view ;
// Hook up an event handler for the events of the view
view . PrepareView += view_PrepareView ;
}
#endregion
#region Private methods
private List < Customer > GetCustomers ( )
{
return new CustomerManager ( ) . GetCustomers ( ) ;
}
#endregion
#region ICustomersPresenter Members
public virtual void view_PrepareView ( )
{
view . Customers = GetCustomers ( ) ;
}
#endregion
}Le présentateur ci-dessus implémente également une interface. Dans l'exemple de code source, cette interface est laissée vide. Je ne l'ai mis là que à des fins d'illustration. Vous pouvez étoffer cette interface si vous le souhaitez. Vous pourriez en avoir besoin pour que votre cadre de test unitaire préféré se moque des présentateurs par exemple.
Ainsi, la page Affichage ou ASPX dans notre cas n'a besoin que de mettre en œuvre l'interface de vue et de déclencher l'événement Préparer Préparer afin de recevoir une liste de clients mis à jour du présentateur. La vue elle-même ne communique pas directement avec la base de données ou la couche commerciale. Le présentateur gère la communication avec la couche commerciale qui récupère les données en abordant le modèle (ou la couche d'accès aux données si vous voulez).
Pour terminer cet article, voyons comment tout cela se rassemble dans un projet de démonstration ASP.NET. Ajoutez un nouveau projet à l'aide du modèle de projet d'application Web ASP.NET à la solution. Ajoutez des références aux projets de présentation et de base de données et à l'assemblage System.Data.entity.
Ajoutez un GridView nommé "GridView1" et un bouton nommé "btnrefresh" à la page default.aspx. Ajoutez le code dans la liste 4 au code derrière la page.
Listing 4 - code default.aspx derrière
public partial class _Default : System . Web . UI . Page , IView
{
private CustomersPresenter presenter ;
protected override void OnInit ( EventArgs e )
{
presenter = new CustomersPresenter ( this ) ;
}
protected void Page_Load ( object sender , EventArgs e )
{
if ( ! IsPostBack )
{
PrepareView ( ) ;
}
}
protected void btnRefresh_Click ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
#region IView Members
public event VoidEventHandler PrepareView ;
public IList < Database . Customer > Customers
{
set
{
GridView1 . DataSource = value ;
GridView1 . DataBind ( ) ;
}
}
#endregion
}Tout le code derrière la page ASPX est d'implémenter l'interface IView, de créer un présentateur et de passer une implémentation IView, étant elle-même, dans son constructeur. Ensuite, tout ce qui reste est de déclencher l'événement Preprepview () de l'interface IView aux heures appropriées.
Pendant la création du présentateur, un gestionnaire d'événements est automatiquement attribué à cet événement, ce qui s'assure que lorsqu'il est déclenché, le présentateur sait comment mettre à jour la collection de clients entretenue par la page. La page elle-même ne sait rien de l'où vient ces données ou de la façon dont elle est récupérée. Le plus stupide, mieux c'est.
Lorsque vous consultez cette page dans un navigateur, c'est le résultat:
Figure 7 - Démo sur le site ASP.NET

Remarque : n'oubliez pas d'ajouter le ConnectionSring requis par le Framework Entity au fichier de configuration Web.config. Vous pouvez trouver le ConnectionString dans le fichier app.config du projet de bibliothèque de classe de base de données. Il y a été inséré automatiquement lorsque Visual Studio a généré le modèle de données d'entité.
Bien sûr, les chaînes de connexion fournies dans l'exemple de code source ne fonctionneront pas sur votre ordinateur car elles ont été construites dans une de mes bases de données locales. Assurez-vous donc de les ajuster en conséquence.
Figure 8 - Solution mise à jour

Comme la dernière étape de cet article, créons une vue à l'aide d'une application Windows Forms juste pour vous montrer à quel point le modèle MVP est flexible. Les étapes pour ce faire sont presque identiques à l'exemple précédent de créer un site Web ASP.NET. Ajoutez une nouvelle application Windows Forms à votre solution et ajoutez des références à la base de données et au projet de présentation et à l'assemblage System.Data.entity.
Ajoutez ensuite un contrôle DataGridView et Button au formulaire. Le code du formulaire s'affiche dans la liste ci-dessous. Il est presque identique à celui de la page par défaut.aspx. N'oubliez pas non plus d'ajouter la chaîne de connexion Framework Entity Framework au fichier de configuration app.config.
Listing 5 - Form1.cs Code
using System ;
using System . Windows . Forms ;
using Presentation . Presenters ;
using Presentation . ViewInterfaces ;
namespace WindowsFormsApplication
{
public partial class Form1 : Form , IView
{
private CustomersPresenter presenter ;
public Form1 ( )
{
InitializeComponent ( ) ;
presenter = new CustomersPresenter ( this ) ;
}
private void Form1_Load ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
private void btnRefresh_Click ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
#region IView Members
public event VoidEventHandler PrepareView ;
public System . Collections . Generic . IList < Database . Customer > Customers
{
set
{
dataGridView1 . DataSource = value ;
}
}
#endregion
}
}Voila, nous avons enfin fini. Notez que bien que l'exemple ASP.NET et WinForms soit presque identique dans le code, cela pourrait ne pas être le cas dans des applications plus complexes. Les types d'interactions dans ces deux types d'interfaces utilisateur sont très différents et proposer un présentateur que vous pouvez utiliser dans toutes les situations pourrait ne pas être une coupe claire.
Figure 9 - Demo d'application des formulaires Windows

Figure 10 - Solution mise à jour

Pour cet article, l'interprétation classique du motif MVP a été utilisée et démontrée en la mettant en œuvre une solution ASP.NET. Le modèle MVP d'origine est considéré comme "retraité" depuis que Martin Fowler l'a annoncé. Le motif peut être divisé en deux camps maintenant, étant:
Lisez cet article Microsoft Patterns & Practices pour plus d'informations.
J'ai écrit cet article pour fournir une implémentation concrète du modèle MVP pendant que je l'explorais à l'époque. Cependant, il peut être sage de rester à l'écart et d'attendre que Microsoft publie le cadre MVC ASP.NET. Pour ceux qui souhaitent imiter un framework Model-View-Controller (MVC) maintenant, je suggère de lire cet article de Microsoft Patterns & Practices.
Remarque : Le modèle MVP est une dérivée du modèle de contrôleur de vue du modèle (MVC). Au moment d'écrire ces lignes, Microsoft est actuellement occupé à développer le framework ASP.NET MVC. Lors de la cartographie de l'architecture d'un nouveau projet de site Web, je suggère de consulter ce cadre.
La principale différence entre les modèles MVP et MVC peut être identifiée à qui est responsable de la gestion de l'entrée de l'utilisateur tel que le clavier et les événements Moue. Dans le modèle MVP, l'interface graphique elle-même est responsable et doit les déléguer au présentateur par le biais d'événements. Dans le modèle MVC, le contrôleur est responsable de la gestion de ces événements.