La semana pasada estaba leyendo algunos artículos sobre el uso de WCF, el marco de la entidad y cómo transportar entidades a través del límite del servicio. Uno de los artículos que encontré incluyó un proyecto de demostración que hizo uso del patrón del presentador de vistas al modelo (MVP).
Habiendo explorado el proyecto de demostración, pensé que sería interesante escribir un artículo sobre este patrón. Su motor de búsqueda favorito le proporcionará felizmente una gran cantidad de enlaces a otros artículos que explican este patrón a fondo.
Para este artículo, decidí suministrar una implementación concreta y centrarme menos en la teoría detrás del patrón.
Vamos a rodar ...
Por supuesto, se requiere un poco de teoría, así que saquémosla fuera del camino.
Como puede deducir del nombre, el patrón MVP consiste en tres partes distintas: el modelo, la vista y el presentador. Cada una de estas partes juega su propio papel en el establecimiento de una separación de las preocupaciones entre la presentación, el negocio y la capa de acceso a datos.
El modelo es responsable de manejar el acceso a los datos, el presentador se comunica con el modelo y pasa datos desde y hacia él. La vista recibe datos del presentador y devuelve los datos, nunca se comunica directamente con el modelo. El presentador es el intermediario para la vista y el modelo.
Figura 1 - Interacción del patrón MVP

La imagen de arriba representa la vista como implementación de una interfaz. La capa de presentación, ya sea un ASP.NET, Winforms o WPF, debe implementar una o más interfaces de vista. El presentador a su vez se comunica con la implementación de la vista a través de esta interfaz, no sabe nada sobre la implementación real en sí.
Esto proporciona un acoplamiento suelto y evita que su código dependa de la tecnología utilizada para la capa de presentación. La idea es que debería poder reemplazar esta capa sin que afecte la lógica de acceso comercial y de datos.
Todo esto podría parecer un poco vago, pero las cosas deberían aclararse una vez que pasemos a los siguientes puntos que proporcionan una implementación real de este patrón. Para el ejemplo, se utilizará una aplicación ASP.NET.
El lugar más lógico para comenzar es el modelo. Dado que es responsable de manejar el acceso y el almacenamiento de datos, primero debemos establecer un almacén de datos físicos. Para esto, utilicé SQL Server 2005 Express, creé una nueva base de datos llamada Southwind y agregué una tabla titulada Cliente. La tabla tiene 3 campos, a saber:
Eso es todo para la base de datos. Enciendamos Visual Studio 2008 y creemos una nueva solución en blanco llamada Avpptern. A continuación, agregue una biblioteca de clase titulada "Base de datos".
Normalmente le daría al proyecto un nombre que sigue el patrón "Company.Product.library", pero por simplicidad, mantengamos breve y simple. También elimine el archivo class1.cs autogenerado después de que el proyecto se haya agregado a la solución.
Creemos un modelo a partir de la base de datos utilizando el marco de entidad (EF). Así que asegúrese de estar utilizando Visual Studio 2008 y tenga instalado Service Pack 1 para Visual Studio y .NET Framework 3.5. Puede descargar los paquetes de servicio aquí.
Agregue un modelo de datos de Entity Framework a la biblioteca de clases seleccionando Agregar, nuevo elemento, modelo de datos de entidad ADO.NET desde el menú contextual del proyecto en el explorador de soluciones. Visual Studio ahora mostrará el asistente del modelo de datos de la entidad. Nombra el modelo Southwind y deja que Visual Studio genere el modelo para ti. Cuando se les preguntó qué objetos de base de datos desea incluir en su modelo, simplemente seleccione la tabla del cliente desde el nodo de tablas.
Observación : si no está familiarizado con la generación de modelos de datos con el marco de la entidad, sugiero este video de capacitación de Alex James. Le muestra cómo construir un modelo de datos de entidad simple desde cero.
La Figura 2 muestra el modelo de datos de entidad resultante. El modelo no puede ser mucho más simple que esto. Esto se hace a propósito para mantener las cosas lo más simples posible y mantener el enfoque en el patrón MVP.
Figura 2 - Modelo de datos de entidad

Asegúrese de cambiar el nombre de su entidad y entidad a un nombre apropiado después de que se haya generado el modelo de datos. La regla general es usar un solo sustantivo para el tipo de entidad y un plural para los conjuntos de entidades. Entonces, en este caso, nuestra EntityType debe ser nombrada Cliente y los clientes de EntitySet. El nombre de EntityType ya está bien, ya que lo hereda de la tabla del cliente, por lo que simplemente seleccione el cliente EntityType y ajuste su propiedad de nombre de conjunto de entidad.
Figura 3 - Nombre del conjunto de entidades

El marco de la entidad generará automáticamente una clase parcial para la tabla del cliente. Puede optar por extender esta clase de cliente parcial si lo desea. Para evitar que sus adiciones personalizadas se borren al regenerar el modelo, coloque este código en un archivo de clase separado. Esto es simular para trabajar con conjuntos de datos fuertemente tipados. Sin embargo, para el artículo esto no es necesario.
Figura 4 - Explorador de soluciones

Ahora que el modelo está en su lugar, agregemos una capa comercial además de donde podemos definir nuestra lógica comercial personalizada. Por simplicidad, mantendré la cantidad de código limitada en esta capa.
Tenga en cuenta que estas capas solo imponen una separación lógica (capa N), no física (N-Tier). Todas las capas residen en la misma máquina, aunque ciertamente puede optar por desembolsarlas en múltiples máquinas / niveles y crear una aplicación verdaderamente distribuida o de nivel N.
Para configurar la capa de negocios, agregue una nueva biblioteca de clase a la solución y llame a ella. SIGUIEN RENUMAR EL ARCHIVO DE CLASE.CS DEFABLE A Customermanager.cs. También agregue referencias a la base de datos de la biblioteca de clases creada anteriormente y al ensamblaje System.Data.Entity.
El Listado 1 muestra la clase CustomERMANAGER que contiene cierta lógica de negocios para trabajar con la entidad del cliente del Modelo de datos de entidad (EDM). El código se explica por sí mismo.
Listado 1 - CustomerManager Clase
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
}
}Figura 5 - Solución actualizada

Observación : Configurar una aplicación de nivel N incluiría la introducción de una capa de servicio en la que llama la capa de presentación. La capa de servicio luego utiliza los objetos comerciales que se encuentran en la capa de negocios. Ya no existe una asociación directa entre la presentación y la capa comercial, la capa de servicio actúa como intermedia. En un artículo futuro abordaré esto mostrando cómo transportar entidades EF a través del límite del servicio.
La capa de negocios solo contiene un método útil, a saber, "List GetCustomers ()". El presentador en el patrón MVP llamará a este método en el objeto comercial Customermanager para entregar los datos a la vista.
La aplicación de ejemplo solo muestra una lista de clientes que usan el patrón MVP. Esto podría ser un poco excesivo, pero se mantiene tan simple como posiblemente por diseño. El objetivo principal de este "¡Hola mundo!" El tipo de aplicación es transmitir la idea de cómo implementar este patrón. La funcionalidad real ofrecida por la aplicación no es tan importante.
La implementación de la vista real (página ASPX, WinForms, WPF ... etc.) necesitará implementar una interfaz de vista. La implementación de la vista debe crear una instancia del presentador y pasar como un parámetro en su constructor. El constructor del presentador tiene un parámetro que es el tipo de interfaz de vista.
El Listado 2 enumera la interfaz IView que implementaremos en breve en una página ASP.NET ASPX. Tiene un evento llamado PrepareView. El evento PrepareView utiliza un delegado cuya firma especifica que no devuelve nada y no toma parámetros.
La vista solo debe elevar este tipo de "eventos vacíos" para significar al presentador que se debe realizar alguna acción. La acción en este caso significa que el presentador debe actualizar la lista de clientes que mantiene la implementación de la vista. El presentador puede acceder a esta lista de clientes a través de la propiedad de clientes ilistes que se declara como parte de la interfaz.
Listado 2 - Ver interfaz
public delegate void VoidEventHandler ( ) ;
public interface IView
{
event VoidEventHandler PrepareView ;
IList < Customer > Customers { set ; }
}Para agregar la interfaz de vista a su proyecto, primero agregue un nuevo proyecto de biblioteca de clases a la solución llamada presentación. A continuación, agregue una nueva interfaz y copie y pegue el código que se muestra en la lista anterior. La biblioteca de código de presentación también contendrá los presentadores y las interfaces que implementan.
Separo las interfaces y presentadores de la vista en una biblioteca de código separada para que pueda compartirlas fácilmente entre múltiples "marcos de vista" como ASP.NET, WinForms, WPF ... etc. La Figura 6 muestra cómo he elegido organizar esta biblioteca de códigos.
Figura 6 - Solución actualizada

No olvide agregar referencias a la base de datos y los proyectos comerciales y el ensamblaje System.Data.Entity.
Para la parte final del patrón MVP necesitamos proporcionar un presentador. La clase de presentes de clientes que se muestra en el Listado 3 toma una referencia a una implementación IVIEW en su constructor. De esta manera, puede comunicarse con la opinión sin saber nada sobre la implementación real. Es este acoplamiento suelto el que hace que el patrón MVP sea tan adecuado para diferentes "marcos de vista".
También en el constructor, todos los eventos de la interfaz View están conectados a un controlador de eventos. En este caso solo hay un evento. El evento PrepareView está conectado al controlador de eventos View_PrepareView. Esto a su vez llama al método privado del presentador GetCustomers () que devuelve una colección de clientes "renovados" y la asigna a la colección de clientes mantenida por la implementación de la vista.
Listado 3 - Clase de presentación de clientes
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
}El presentador anterior también implementa una interfaz. En el código fuente de muestra, esta interfaz se deja vacía. Solo lo he puesto allí para fines ilustrativos. Puede desarrollar esta interfaz si lo desea. Es posible que lo necesite para su marco de prueba unitario favorito para burlarse de los presentadores, por ejemplo.
Por lo tanto, la página Ver o ASPX en nuestro caso solo necesita implementar la interfaz de vista y activar el evento PrepareView para recibir una lista de clientes actualizados del presentador. La vista en sí no se comunica directamente con la base de datos o la capa de negocios. El presentador maneja la comunicación con la capa comercial que recupera los datos abordando el modelo (o la capa de acceso de datos si lo desea).
Para terminar este artículo, veamos cómo esto se une en un proyecto de demostración ASP.NET. Agregue un nuevo proyecto utilizando la plantilla del proyecto ASP.NET Web Application a la solución. Agregue referencias a los proyectos de presentación y base de datos y el ensamblaje System.Data.Entity.
Agregue una GridView llamada "GridView1" y un botón llamado "BTNRefresh" a la página predeterminada.aspx. Agregue el código en el Listado 4 al código detrás de la página.
Listado 4 - código predeterminado.aspx detrás
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
}Todo el código detrás de la página ASPX es implementar la interfaz IVIEW, crear un presentador y pasar una implementación IVIEW, siendo misma, en su constructor. Entonces, todo lo que queda es activar el evento PrepareView () de la interfaz IView en los momentos apropiados.
Durante la creación del presentador, un controlador de eventos se asigna automáticamente a este evento, lo que se asegura de que cuando se active, el presentador sabe cómo actualizar la colección de clientes mantenida por la página. La página en sí no sabe nada de dónde provienen estos datos o cómo se recupera. Cuanto más tonto sea una vista, mejor.
Al ver esta página en un navegador, este es el resultado:
Figura 7 - Demo del sitio web de ASP.NET

Observación : no olvide agregar la conexión de conexión requerida por el marco Entity al archivo de configuración web.config. Puede encontrar la conexión de conexión en el archivo app.config del proyecto de biblioteca de clases de base de datos. Se insertó allí automáticamente cuando Visual Studio generó el modelo de datos de entidad.
Por supuesto, las cadenas de conexión suministradas en el código fuente de muestra no funcionarán en su computadora, ya que se construyeron con una base de datos local mía. Así que asegúrese de ajustarlos en consecuencia.
Figura 8 - Solución actualizada

Como el paso final de este artículo, creamos una vista utilizando una aplicación de formularios de Windows solo para mostrarle cuán flexible es realmente el patrón MVP. Los pasos para hacer esto son casi idénticos al ejemplo anterior de crear un sitio web ASP.NET. Agregue una nueva aplicación de formularios de Windows a su solución y agregue referencias a la base de datos y el proyecto de presentación y el ensamblaje System.Data.Entity.
A continuación, agregue una vista datagridview y un control de botones al formulario. El código para el formulario se muestra en la lista a continuación. Es casi idéntico a la de la página predeterminada.aspx. También no olvide agregar la cadena de conexión de Entity Framework al archivo de configuración App.Config.
Listado 5 - Código Form1.CS
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, finalmente hemos terminado. Tenga en cuenta que aunque el ejemplo ASP.NET y WinForms son casi idénticos en el código, este podría no ser el caso en aplicaciones más complejas. Los tipos de interacciones en estos dos tipos de interfaces de usuarios son muy diferentes y la creación de un presentador que puede usar en todas las situaciones podría no ser tan claro.
Figura 9 - Demostración de aplicaciones de Windows Forms

Figura 10 - Solución actualizada

Para este artículo, la interpretación clásica del patrón MVP se utilizó y demostró implementando una solución ASP.NET. El patrón de MVP original se considera "retirado" desde que Martin Fowler anunció SO. El patrón se puede dividir en dos campos ahora, siendo:
Lea este artículo de Microsoft Patterns & Practices para obtener más información.
Escribí este artículo para proporcionar una implementación concreta del patrón MVP mientras lo estaba explorando en ese momento. Sin embargo, puede ser aconsejable mantenerse alejado de él y esperar hasta que Microsoft libere el marco ASP.NET MVC. Para aquellos que deseen imitar un marco de controlador de visión modelo (MVC) ahora sugiero leer este artículo de Microsoft Patterns & Practices.
Observación : El patrón MVP es un derivado del patrón del controlador de visión del modelo (MVC). Al momento de escribir esto, Microsoft está actualmente ocupado desarrollando el marco ASP.NET MVC. Al mapear la arquitectura para un nuevo proyecto de sitio web, sugiero que consulte este marco.
La principal diferencia entre los patrones MVP y MVC se puede identificar a quién es responsable de manejar la entrada del usuario, como los eventos de teclado y MoUE. En el patrón MVP, la GUI en sí es responsable y necesita delegarlos al presentador a través de eventos. En el patrón MVC, el controlador es responsable de manejar estos eventos.