jueves, 8 de abril de 2010

Aplicando el patrón Presentation Model como un estilo de programación en SharePoint Parte I (Estableciendo las bases)

Este patrón de presentación es una variación del famoso patrón MVP o Model View Presenter que ya ha sido descontinuado por el científico Martin Fowler.

En este artículo presentaré mas bien este patrón como un un estilo de programación mas que el simple argumento de hacer el código que este de una forma tal desacoplado que pueda probarlo o “testearlo” . Veo mas disfrutable el hecho de tener un código limpio, más fácil de mantener y adaptar a los cambios que es una constante en el desarrollo de software dejando optativo el escribir código para realizar las pruebas ya que no es el primordial incentivo.

Aclaraciones

El ejemplo utilizado esta orientado específicamente al desarrollo de elementos Web para SharePoint, aunque puede los conceptos y el estilo a otros entornos será una tarea que tendrá que realizar por si mismo.

No pretendo ser purista en mi exposición sino mas bien práctico y sistemático aplicando este patrón se adquiere una mecánica de trabajo como una línea de ensamblado donde ya esta predefinido el orden en que se encajan cada una de las partes.

Justificación:

Por qué se definieron los patrones de presentación debido a la complejidad que presenta el diseño de una pantalla y la lógica que requiere. Los dejo con un extracto de Martin Fowler sobre el problema que involucra el desarrollo de una interfaz de usuario:

  • Diseño de la pantalla: definiendo la localización de los controles en la pantalla y su jerarquía.
  • Lógica de la UI: Comportamiento que no es fácilmente programado dentro de los mismos controles.

Aplicando alguno de los patrones de presentación nos permite escribir la lógica que es completamente independiente de las vistas utilizadas para mostrar.

LVPM

Bauticemos este estilo de programación como LVPM que abrevia las palabras: Layout View Presentation Model, que reflejan la mecánica de trabajo de la siguiente forma:

Vamos a empezar diseñando el Layout de nuestra UI que luego nos permitirá abstraer una Interfaz de la Vista de los eventos que deseamos controlar que el usuario interactuará por medio de los controles definidos en la UI(Que en este caso en especifico es un control de usuario de extensión ascx). Siguiendo el patrón de presentación acoplaremos un Presentador que encapsulará toda la lógica de la UI y será responsable del estado de la Data representado a través de un Modelo, el cual a su vez abstraerá y separará responsabilidades según el modelo que hayamos definido del dominio a través de los patrones de servicios (que encapsula la lógica del dominio) y el repositorio (que encapsula el proveedor de los datos).

Recomendaciones preliminares:

No a la primera tenemos un diseño limpio y ajustado, habrán oportunidades de refactorización para delegar mejor las responsabilidades especialmente sobre el modelo que muchas veces es confusa y necesita de un análisis mas profundo.

No se estrese, relájese y hágalo de una forma natural comprendiendo una idea central sobre este estilo de patrones y es que el Presentador es el que manda a la vista y le dice cuando y que debe de hacer.

Por otro lado las Entidades del dominio y solamente esta parte del Modelo son de conocimiento publico entre estos 3 sujetos es la forma entre ellos se transfieren el estado del modelo. Ya que el Presentador no conoce nada acerca de los controles de la UI mas que solamente su interfaz denominada Vista.

Por el otro lado el Presentador no conoce las reglas del negocio ni que proveedor de datos esta utilizando el modelo para persistir su estado solo conoce la interfaz del modelo que encapsula internamente su propia lógica.

Resumiendo los pasos a seguir y siendo muy específicos al tema del desarrollo en SharePoint debería hacer lo siguiente:

1. Creo un elemento Web en mi Proyecto WSP en VS 2008 a través de WSPBuilder u otra herramienta disponible para crear soluciones para SharePoint dentro de VS 2008.

2. Creo un Control de Usuario de extensión ascx.

<%@ Assembly Name="Infoware.Portal.ReunionesV3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=60191eb7564e8d8f" %>
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="EditarTareaUserControl.ascx.cs"
    Inherits="Infoware.Portal.ReunionesV3.EditarTareaUserControl" %>

Para mas información sobre como envolver un control de usuario a un elemento web vea el siguiente enlace: http://jmhogua.blogspot.com/2010/02/comparando-procedimiento-de-creacion-de.html







3. Lo envuelvo en el elemento Web que hará de Wrapper de mi control de usuario.









 var userControlPath = "/_controltemplates/infoware/EditarTareaUserControl.ascx";
                try
                {
                    var editarTarea = Page.LoadControl(userControlPath);
                    Controls.Add(editarTarea);
                    base.CreateChildControls();
                }








4. Inicio diseñando el Layout de mi control de usuario incluyendo los eventos que deseo manejar.









image









5. Defino una Interfaz de la Vista que represente mi control de usuario e incluya cada evento de la UI que el usuario quiero que intervenga.









 public interface IEditarTareaVista
    {
        IList<Prioridad> ListadoPrioridad { set; }
        IList<Estado> ListadoEstado { set; }
        bool EsApoyo { get; set; }
        string URLReunion { get; set; }
        string URLOrigen { get; set; }
        string ObtenerParametro(string nombreLlave);
       
        string MensajeError { set; }
        string MensajeValidacionTitulo { set; }
        string MensajeValidacionAsignado { set; }
        string MensajeValidacionFechaInicio { set; }
        string MensajeValidacionFechaVencimiento { set; }
        string MensajeValidacionAvance { set; }
        bool HabilitarControles { set; }
        void RedireccionarPagina(string urlPagina);
        Tarea Tarea { get; set; }
        void HabilitarEdicionParcial();
        string Asignado { set; }
   }








6. Crearé una clase Presentador que estará acoplada al control de usuario para su instanciación.









La parte del presentador









image









La parte del control de usuario









image









7. Iniciaré acoplando cada evento del control de usuario con un método del presentador.









        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                _presentador.FirstLoad();
            _presentador.OnLoad();
        }
        protected void AceptarTop_OnClick(object sender, EventArgs e)
        {
            AceptarInformacion();
        }
        protected void AceptarBottom_OnClick(object sender, EventArgs e)
        {
            AceptarInformacion();
        }
        private void AceptarInformacion()
        {
            var nuevaTarea = ObtenerInfoCapuradaPorLosControles();
            _presentador.PersistirCambioTarea(nuevaTarea);
        }
        private Tarea ObtenerInfoCapuradaPorLosControles()
        {
            var tareaEncontrada = new Tarea();
            if (CapturaAsignado.Accounts.Count > 0)
                tareaEncontrada.Asignado = CapturaAsignado.Accounts[0].ToString();
            decimal avance;
            if (!string.IsNullOrEmpty(CapturaAvance.Text)
                && decimal.TryParse(CapturaAvance.Text, out avance))
                tareaEncontrada.Avance = avance;
            tareaEncontrada.ModificadoPor = SPContext.Current.Web.CurrentUser.ToString();
            tareaEncontrada.FechaModificacion = DateTime.Now;
            tareaEncontrada.Descripcion = CapturaDescripcion.Text;
            tareaEncontrada.EsApoyo = EsApoyo;
            tareaEncontrada.EstadoActual = new Estado { Nombre = CapturaEstado.SelectedValue };
            if (!CapturaFechaInicio.IsDateEmpty)
                tareaEncontrada.FechaInicio = CapturaFechaInicio.SelectedDate;
            if (!CapturaFechaVencimiento.IsDateEmpty)
                tareaEncontrada.FechaVencimiento = CapturaFechaVencimiento.SelectedDate;
            tareaEncontrada.PrioridadAsignada = new Prioridad { Nombre = CapturaPrioridad.SelectedValue };
            tareaEncontrada.ReunionAsignada = new Reunion { URL = URLReunion };
            tareaEncontrada.Titulo = CapturaTitulo.Text;
            tareaEncontrada.NuevoComentario = CapturaComentarios.Text;
            tareaEncontrada.Comentarios = CapturaComentariosAnteriores.Text;
            return tareaEncontrada;
        }








8. Definiré la lógica dentro del presentador para cada notificación que haya invocado en el paso 7.









        public void FirstLoad()
        {
            _vista.ListadoPrioridad = _prioridadRepo.ObtenerListado();
            _vista.ListadoEstado = _estadoRepo.ObtenerListado();
            var valorTareaID = _vista.ObtenerParametro(_configuracionRepo.ParametroTareaID());
            _vista.URLOrigen = _vista.ObtenerParametro(_configuracionRepo.ParametroURLOrigen());
            if (!string.IsNullOrEmpty(valorTareaID))
            {
                Tarea tareaEncontrada = _tareaServicio.ObtenerPorID(ObtenerTareaID(valorTareaID));
                if (!string.IsNullOrEmpty(tareaEncontrada.Comentarios))
                    tareaEncontrada.Comentarios = tareaEncontrada.Comentarios.Replace("", "<br>").Replace("[", "<span style='color:grey'>[").Replace("]", "]</span>");
                _vista.Tarea = tareaEncontrada;
            }
        }
        public void OnLoad()
        {
            string urlReunion = null;
            if (_vista.Tarea != null && _vista.Tarea.ReunionAsignada != null)
                urlReunion = _vista.Tarea.ReunionAsignada.URL;
            SPUser usuarioActual = SPContext.Current.Web.CurrentUser;
            if (_vista.Tarea == null
                 (!_usuarioRepo.EsAdministrador(urlReunion, usuarioActual.LoginName)
                && !_miembroRepo.EsOrganizador(urlReunion, usuarioActual.LoginName)
                )
                )
            {
                if (_vista.Tarea != null && usuarioActual.LoginName == _vista.Tarea.Asignado)
                    _vista.HabilitarEdicionParcial();
                else
                    _vista.HabilitarControles = false;
                /// Esto es porque al deshabilitar el control se pierde el valor
                _vista.Asignado = _vista.Tarea != null ? _vista.Tarea.Asignado : "";
            }
        }








9. Si necesito presentar data sin importarme como la vista guarde el estado de la misma crearé en la interfaz de la vista una propiedad set y dejaré libertad al control de usuario como enlaza los datos al control ya que mi presentador no conoce nada sobre los controles definidos en mi control de usuario.

















image









image









Bueno amigos, en la segunda parte veremos el Modelo como interactúa con el Presentador y las Entidades viajan a través de los 3 sujetos Vista, Presentador y Modelo.









Los dejo con una ultima imagen parcial de la solución y referencias útiles para entender el patrón.









image Para efectos de simplicidad en el deployment no cree un proyecto por cada capa pero es tan sencillo con este diseño como crear los proyectos tipo librería y mover las clases a cada proyecto respectivo, si tenemos problema de referencia circular con los proyectos muy probablemente no delegamos correctamente las responsabilidades y/o no agrupamos el código debidamente en los proyectos.









Por el tipo de proyecto que es para crear un proyecto por cada capa en mi experiencia es necesario llevar un riguroso control de las versiones de los ensamblados para evitar que otros elementos web que utilizan los mismos ensamblados dejen de funcionar por el deployment de los nuevos lo cual se traduce en buen chapín “nos sale mas caro el caldo que los frijoles “, por lo que todo esta en un solo proyecto tipo solución de SharePoint para evitar estas inconveniencias en un ambiente productivo al momento de hacer el deployment de la solución.






En resumen vimos los conceptos y los beneficios que nos trae la aplicación de este patrón como un forma de hacer las cosas buscando una mecánica sistemática que nos ponga en una línea de producción imaginaria y obtengamos los beneficios de un código limpio que desacopla por responsabilidades los componentes haciendo mas fácil su mantenimiento, adaptación al cambio y finalmente porque no decirlo, “testeable “.






Hasta la próxima parte, Code4Fun!






Manolo Herrera









Artículos recomendados:













Arquitectura de la Interfaz de Usuario por Martin Fowler









Presentation Model por Martin Fowler









Passive View por Martin Fowler

No hay comentarios.: