Archive for September, 2007

Introducción a Composite UI Application Block (CAB) V - UI Extension Sites

Descripción

La mayoría de las aplicaciones basadas en Windows tienen elementos comunes en la interfaz de usuario, como son los menús, las barras de estado, las barras de herramientas, etc. que poseen la funcionalidad necesaria para realizar gran variedad de acciones que pueden afectar a distintos sectores de la aplicación. Esto generalmente provoca acoplamiento entre los módulos que necesiten usar o modificar estos elementos. Composite UI Application Block permite que los UIElements puedan ser accedidos por todos los módulos de una manera desacoplada.

Antes de continuar voy a introducir el concepto de UIElement. Los UIElements son controles que están hosteados en el Shell los cuales presentan propiedades y funcionalidades requeridas por la mayoría de los módulos de la aplicación.

Durante el proceso de inicialización de la aplicación, cada módulo que se carga tiene la posibilidad de agregar elementos propios a los UIElements registrados. La Figura 1 muestra el proceso de la carga de un ítem a la barra de menú del shell durante la carga de un módulo.

UIExtensionSites

Figura 1

Proceso de carga de un item a la barra de menú durante la carga de un módulo.

El siguiente diagrama de clases muestra las relaciones entre las clases involucradas con el proceso mostrado en la Figura 1 y sus principales métodos.

Diag clases - UIElement

Figura 2

Clases involucradas con los UIElement y sus adapters.

Para poder entender mejor el diagrama anterior, la siguiente tabla muestra una breve descripción de las clases que lo componen.

Clase

Características

IUIElementAdapter Interfaz implementada por los objetos que van a proveer soporte para un determinado UIElement.
UIElementAdapter Es una clase genérica que implementa la interfaz IUIElementAdapter. Se usa como soporte para facilitar la creación de adapters, ya que trae implementación genérica para los métodos de la interfaz .
UIExtensionSite Esta clase permite que los UIElements sean extendidos por los todos los módulos. Es una especie de “gancho” donde los módulos pueden colgar elementos a un determinado elemento de la interfaz de usuario.
UIExtensionSiteCollection Representa una colección compartida por todos los módulos donde se guardan los IUIElementAdapter y UIExtensionSite para cada UIElement. La colección los identifica con un string como ID.
IUIElementAdapterFactory Provee la lógica necesaria para crear una instancia apropiada de un IUIElementAdapter para un determinado UIElement.
UIElementAdapterFactoryCatalog Clase que implementa la Interfaz IUIElementAdapterFactoryCatalog. La clase contiene un catálogo donde se guardan todas las factories de los adapters para cada UIElement soportado.

Tabla 1

Interfaces y Clases responsables de registrar y utilizar los UIElement.

La clase WorkItem posee la propiedad UIExtensionSites que expone una colección de tipo UIExtensionSitesCollection. El usuario puede registrar todos sus UIElements dentro de esa colección mediante el método RegisterSite. Este método posee dos sobrecargas. La primera versión del método es la siguiente:

[C#]

public void RegisterSite(string siteName, IUIElementAdapter adapter)

[Visual Basic]

Public Sub RegisterSite(ByVal siteName As String, ByVal adapter As IUIElementAdapter)

Este método crea una instancia de la clase UIExtensionSite para el adapter pasado y lo agrega a la colección asociándolo con el ID recibido en el string siteName.

La otra versión del método RegisterSite es:

[C#]

public void RegisterSite(string siteName, object uiElement)

[Visual Basic]

Public Sub RegisterSite(ByVal siteName As String, ByVal uiElement As Object)

Este método se encarga de crear un nuevo IUIElementAdapter para el UIElement pasado utilizando una IUIElementAdapterFactory la cual tuvo que ser registrada anteriormente para ese tipo de UIElement en particular. Una vez echo esto invoca a la version anterior del método con el adapter recién creado.

Luego invocando al siguiente método de la colección UIExtensionSites del WorkItem:

[C#]

public UIExtensionSite this[string siteName]

[Visual Basic]

Default Public ReadOnly Property Item(ByVal siteName As String) As UIExtensionSite

Se accede, mediante el siteName, al UIExtensionSite encargado de manejar al IUIElementAdapter creado para el UIElement que se agregó.

Es muy importante notar que la colección UIExtensionSites no permite acceder directamente a los UIElements que se registraron sino que sólo se obtiene una referencia a un objeto de tipo UIExtensionSite el cual es el responsable de utilizar al adapter creado para manejar el objeto.

Registrar un UIElement

Generalmente los UIElements se registran en el método AfterShellCreated de la clase principal de la aplicación (la que hereda de FormShellApplication) como se muestra en el siguiente código.

[C#]

// MainMenuStrip expone una instancia de un MenuStrip.

// MainStatusStrip expone una instancia de un StatusStrip.

// MainToolbarStrip expone una instancia de un ToolStrip.

protected override void AfterShellCreated()

{

base.AfterShellCreated();

RootWorkItem.UIExtensionSites.RegisterSite(”MainMenu”, this.Shell.MainMenuStrip)

RootWorkItem.UIExtensionSites.RegisterSite(“MainStatus”, this.Shell.MainStatusStrip)

RootWorkItem.UIExtensionSites.RegisterSite(“MainToolbar”, this.Shell.MainToolbarStrip)

}

[Visual Basic]

‘ MainMenuStrip expone una instancia de un MenuStrip.

‘ MainStatusStrip expone una instancia de un StatusStrip.

‘ MainToolbarStrip expone una instancia de un ToolStrip.

Protected Overrides Sub AfterShellCreated()

MyBase.AfterShellCreated()

RootWorkItem.UIExtensionSites.RegisterSite(“MainMenu”, Me.Shell.MainMenuStrip)

RootWorkItem.UIExtensionSites.RegisterSite(“MainStatus”, Me.Shell.MainStatusStrip)

RootWorkItem.UIExtensionSites.RegisterSite(“MainToolbar”, Me.Shell.MainToolbarStrip)

End Sub

Es buena práctica utilizar constantes para guardar los ID de los UIElements. Estas constantes se deberían ubicar en clases accesibles por todos los módulos. Para ello se puede crear un proyecto donde se albergue todo lo que se considere de uso común entre los módulos. Esto permite un mayor desacople.

Nota: Smart Client Software Factory provee una estructura generada por la Guiance Package la cual establece un proyecto (Infrastructure.Interface) donde se definen todas las constantes y clases comunes que son usadas por todos los módulos. Además registra por defecto al menú principal, la barra de estado y la barra de herramientas dentro de la colección UIExtensionSites.

Utilizar los UIExtensionSite para agregar ítems al Shell

En general los ítems particulares de cada módulo se cargan en el método OnRunStarted del WorkItem del módulo como se muestra en el siguiente código de ejemplo.

[C#]

// En este ejemplo se agregará un menú con dos botones a la barra de menu

// del shell.

protected override void OnRunStarted()

{

ExtendMenu();

AddViews();

base.OnRunStarted();

}

private void ExtendMenu()

{

ToolStripMenuItem menu = new ToolStripMenuItem(”Menu del Modulo”);

ToolStripMenuItem button1 = new ToolStripMenuItem(”Button1″);

ToolStripMenuItem button2 = new ToolStripMenuItem(”Button2″);

menu.DropDownItems.Add(button1);

menu.DropDownItems.Add(button2);

UIExtensionSites["MainMenu"].Add(menu);

}

[Visual Basic]

‘ En este ejemplo se agregará un menú con dos botones a la barra de menu

‘ del shell.

Public Overrides Sub OnRunStarted()

ExtendMenu()

AddViews()

MyBase.OnRunStarted()

End Sub

Private Sub ExtendMenu()

Dim menu As ToolStripMenuItem

Dim button1 As ToolStripMenuItem

Dim button2 As ToolStripMenuItem

menu = New ToolStripMenuItem(”Menu del Modulo”)

button1 = New ToolStripMenuItem(”Button1″)

button2 = New ToolStripMenuItem(”Button2″)

menu.DropDownItems.Add(button1)

menu.DropDownItems.Add(button2)

UIExtensionSites(”MainMenu”).Add(menu)

End Sub

Si se remueve el nombre del módulo en el archivo ProfileCatalog.xml o el ModuleLoaderService verifica que el usuario actual no puede tener acceso a ese módulo, entonces no se cargará el módulo anterior y por lo tanto no aparecerán los ítems en la barra de menús. La aplicación podrá continuar normalmente.

Crear Adapters para UIElements

Cada tipo de UIElement requiere su propio adapter para que sea correctamente manejado. CAB provee la interfaz IUElementAdapter que define los miembros que debe soportar un adapter y además la template class UIElementAdapter la cual provee implementación genérica de los métodos de la interfaz.

CAB trae soporte para MenuStrips y ToolBarStrips (las clases ToolStrip, ToolStripItem, ToolStripItemCollection, MenuStrip y todas las clases derivadas de estas). Utiliza las clases ToolStripItemCollectionUIAdapter, ToolStripItemOwnerCollectionUIAdapter como adapters y la clase ToolStripUIAdapterFactory como factory para crear los adapters. Sin embargo CAB permite incluir soporte para clases customizadas si se construyen los adapters correspondientes. Para que tenga sentido crear un adapter para un determinado UIElement, este deberá poseer una colección de objetos que permita agregar y quitar elementos.

El siguiente ejemplo muestra como se implementa un adapter para la clase ListBox del assembly System.Windows.Forms.

[C#]

using Microsoft.Practices.CompositeUI.UIElements;

using System.Windows.Forms;

using Microsoft.Practices.CompositeUI.Utility;

public class ListBoxAdapter : IUIElementAdapter

{

private ListBox _listBox;

public ListBoxAdapter(ListBox listBox)

{

Guard.ArgumentNotNull(listBox, “listBox”);

_listBox = listBox;

}

#region IUIElementAdapter Members

public object Add(object uiElement)

{

if (_listBox != null)

{

if (!_listBox.Items.Contains(uiElement))

{

_listBox.Items.Add(uiElement);

}

}

return uiElement;

}

public void Remove(object uiElement)

{

if (_listBox != null)

{

if (_listBox.Items.Contains(uiElement))

{

_listBox.Items.Remove(uiElement);

}

}

}

#endregion

}

[Visual Basic]

Imports Microsoft.Practices.CompositeUI.UIElements

Imports System.Windows.Forms

Imports Microsoft.Practices.CompositeUI.Utility

Public Class ListBoxAdapter

Implements IUIElementAdapter

Private _listBox As ListBox = Nothing

Public Sub New(ByVal listBox As ListBox)

Guard.ArgumentNotNull(listBox, “listBox”)

_listBox = listBox

End Sub

Public Function Add(ByVal uiElement As Object) As Object Implements IUIElementAdapter.Add

If Not _listBox Is Nothing Then

If Not _listBox.Items.Contains(uiElement) Then

_listBox.Items.Add(uiElement)

End If

End If

Return uiElement

End Function

Public Sub Remove(ByVal uiElement As Object) Implements IUIElementAdapter.Remove

If Not _listBox Is Nothing Then

If _listBox.Items.Contains(uiElement) Then

_listBox.Items.Remove(uiElement)

End If

End If

End Sub

End Class

Como se puede ver la clase ListBoxAdapter implementa la interfaz IUIElementAdapter.

Con la clase implementada anteriormente ya se pueden registrar objetos de tipo ListBox en la colección UIExtensionSites del WorkItem de la siguiente forma:

[C#]

// La propiedad ListBox expone una instancia de la clase ListBox.

RootWorkItem.UIExtensionSites.RegisterSite(”ListBox”, new ListBoxAdapter (this.Shell.ListBox));

[Visual Basic]

‘ La propiedad ListBox expone una instancia de la clase ListBox.

RootWorkItem.UIExtensionSites.RegisterSite(”ListBox”, New ListBoxAdapter (Me.Shell.ListBox))

Si se prefiere utilizar la otra sobrecarga del método RegisterSite donde se pasa directamente el UIElement en el segundo parámetro, se deberá registrar una factory que se encargue de crear los adapters. El siguiente código muestra como implementarla continuando el ejemplo anterior.

[C#]

using Microsoft.Practices.CompositeUI.UIElements;

using System.Windows.Forms;

public class ListBoxAdapterFactory : IUIElementAdapterFactory

{

#region IUIElementAdapterFactory Members

public IUIElementAdapter GetAdapter(object uiElement)

{

if (Supports(uiElement))

{

return new ListBoxAdapter((ListBox)uiElement);

}

return null;

}

public bool Supports(object uiElement)

{

if (uiElement is ListBox)

return true;

return false;

}

#endregion

}

[Visual Basic]

Imports Microsoft.Practices.CompositeUI.UIElements

Imports System.Windows.Forms

Public Class ListBoxAdapterFactory

Implements IUIElementAdapterFactory

Public Function GetAdapter(ByVal uiElement As Object) As IUIElementAdapter Implements IUIElementAdapterFactory.GetAdapter

If Supports(uiElement) Then

Return New ListBoxAdapter(CType(uiElement, ListBox))

End If

Return Nothing

End Function

Public Function Supports(ByVal uiElement As Object) As Boolean Implements IUIElementAdapterFactory.Supports

If TypeOf uiElement Is ListBox Then

Return True

End If

Return False

End Function

End Class

Como se observa en el código anterior, la factory implementa la interfaz IUIElementAdapterFactory.

El método Support de la clase ListBoxAdapterFactory devuelve true si el objeto pasado como parámetro es soportado por la factory. En caso contrario devuelve false. Este método es utilizado por la clase UIElementAdapterFactoryCatalog en el método GetFactory. Dentro de ese método se recorre todo el catálogo de factories hasta encontrar una que soporte el objeto requerido.

En cuanto al método GetAdapter devuelve una instancia del adapter correcto para el tipo de objeto pasado como parámetro. Si el objeto no es soportado por la factory devuelve null/Nothing.

Por ultimo se deberá agregar la factory creada anteriormente al catálogo de fábricas del WorkItem (el catálogo está registrado como un servicio que implementa la interfaz IUIElementAdpterFactoryCatalog). Esto generalmente se realiza en el método AfterShellCreated de la clase principal de la aplicación (la que hereda de FormShellApplication) como se muestra en el siguiente código.

[C#]

protected override void AfterShellCreated()

{

base.AfterShellCreated();

IUIElementAdapterFactoryCatalog catalogFactory = RootWorkItem.Services.Get();

catalogFactory.RegisterFactory(new ListBoxAdapterFactory());

// TODO: Agregar los UIElements a la coleccion UIExtensionSites.

}

[Visual Basic]

Protected Overrides Sub AfterShellCreated()

MyBase.AfterShellCreated()

Dim catalogFactory As IUIElementAdapterFactoryCatalog = RootWorkItem.Services.Get(Of IUIElementAdapterFactoryCatalog)()

catalogFactory.RegisterFactory(New ListBoxAdapterFactory())

‘ TODO: Agregar los UIElements a la coleccion UIExtensionSites.

End Sub

En el siguiente post se explicarán los principales rasgos de los Commands.

Introducción a Composite UI Application Block (CAB) IV

Model-View-Presenter

En general los formularios siempre están formados por varios controles, manejan eventos y contienen lógica para responder a estos eventos. Si se escribe este código dentro de la clase del formulario, se hace demasiado compleja y difícil de probar. Una posible solución a este problema es separar las responsabilidades de mostrar los aspectos visuales y el manejo de los eventos en diferentes clases. Una clase representará a la vista, la cual controlará el formulario, y la otra, el presenter, se encargará del manejo de los eventos y de actualizar correctamente a la vista.

El patrón Model-View-Presenter (MVP) separa el modelo del dominio, la presentación y las acciones basadas en la interacción con el usuario en tres clases separadas. La vista le delega a su presenter toda la responsabilidad del manejo de los eventos del usuario. El presenter se encarga de actualizar el modelo cuando surge un evento en la vista, pero también es responsable de actualizar a la vista cuando el modelo le indica que ha cambiado. El modelo no conoce la existencia del presenter. Por lo tanto, si el modelo cambia por acción de algún otro componente que no sea el presenter, debe disparar un evento para que el presenter se entere.

A la hora de implementar este patrón, se identifican los siguientes componentes:

· IView: es la interfaz con la que el presenter se comunica con la vista.

· View: vista que implementa la interfaz IView y se encarga de manejar los aspectos visuales. Mantiene una referencia a su presenter al cual le delega la responsabilidad del manejo de los eventos.

· Presenter: contiene la lógica para responder a los eventos y manipula el estado de la vista mediante una referencia a la interfaz IView. El presenter utiliza el modelo para saber cómo responder a los eventos.

· Model: Esta compuesto por los objetos que conocen y manejan los datos dentro de la aplicación. Por ejemplo, pueden ser las clases que conforman el modelo del negocio (business entities).

La Figura1 ilustra la lógica del patrón Model-View-Presenter (MVP).

MVP(expandido)

Figura 1
Vista lógica del patrón MVP.

MVP aplicado en CAB

CAB provee una infraestructura que permite aplicar fácilmente el patrón MVP para mejorar el diseño de nuestras aplicaciones. Johnny Halife escribió hace un tiempo el siguiente post: How CAB and TDD helps doing better designs, donde explica cómo CAB permite mejorar nuestros diseños.

A continuación se mostrarán implementaciones de ejemplo de una vista con su presenter utilizando las herramientas proporcionas por Composite UI Application Block.

Vista

Gracias a la inyección de dependenicias (Dependency Injection) la vista puede inyectar una nueva instancia de su presenter cuando esta se crea. El siguiente código muestra una implementación de una vista usando el patrón MVP:

[C#]

[SmartPart]

public partial class View : UserControl, IView

{

private Presenter _presenter = null;

[CreateNew]

public Presenter Presenter

{

set

{

_presenter = value;

_presenter.View = this;

}

}

public View()

{

InitializeComponent();

}

protected override void OnLoad(EventArgs e)

{

_presenter.OnViewReady();

base.OnLoad(e);

}

#region IView Members

// TODO: Implementar los metodos de la interfaz IView.

#endregion

// TODO: Agregar metodos que controlen los controles de

// la vista.

}

[Visual Basic]

<SmartPart()> _

Partial Public Class View

Inherits UserControl

Implements IView

Private _presenter As Presenter = Nothing

_

Public WriteOnly Property Presenter() As Presenter

Set(ByVal value As Presenter)

_presenter = value

_presenter.View = Me

End Set

End Property

Public Sub New()

InitializeComponent()

End Sub

Protected Overrides Sub OnLoad(ByVal e As EventArgs)

_presenter.OnViewReady()

MyBase.OnLoad(e)

End Sub

‘ TODO: Implementar los metodos de la interfaz IView.

‘ TODO: Agregar metodos que controlen los controles de

‘ la vista.

End Class

Analizando el código anterior, lo primero que se observa es el atributo SmartPart sobre la definición de la clase. Este atributo es requerido para soportar la funcionalidad Inversión de Control (Inversion of Control - IoC) e Inyección de dependencias (Dependency Injection) provista por CAB.

Otro dato curioso es el operador partial en el encabezado de la clase. Ese operador indica que la clase está definida en más de un archivo.

También se observa el atributo CreateNew encima de la propiedad Presenter. Este atributo le indica al ObjectBuilder que cuando instancie la clase (mediante algún WorkItem) se encargue también de crear una nueva instancia de su presenter e inyectarla. Como se puede ver, la vista además vincula al presenter con ella misma mediante la propiedad View.

Por último se sobrescribe el método OnLoad() de la clase UserControl para poder avisarle al presenter cuando la vista se está cargando.

Presenter

CAB provee la clase Controller la cual puede ser usada como base para crear los presenters o controladores (si se usa el patrón MVC). Esta clase incluye una referencia al WorkItem padre y una propiedad para acceder a su estado (State). Sin embargo no es necesario usar la clase Controller para poder implementer a un presenter. A continuación se muestra una implementación de ejemplo de esta clase:

[C#]

public class Presenter

{

private IService _service;

private IView _view;

[ServiceDependency]

public IService Service

{

set { service = value; }

}

public IView View

{

get { return _view; }

set { _view = value; }

}

public override void OnViewReady()

{

// TODO: Agregar el codigo necesario para que la

// vista se cargue con datos validos.

}

// TODO: Agregar los metodos necesarios para manejar

// los eventos del usuario.

}

[Visual Basic]

Public Class Presenter

Private _service As IService

Private _view As IView

_

Public WriteOnly Property Service() As IService

Set(ByVal value As IService)

_service = value

End Set

End Property

Public Property View() As TView

Get

Return _view

End Get

Set(ByVal value As TView)

_view = value

End Set

End Property

Public Overrides Sub OnViewReady()

‘ TODO: Agregar el codigo necesario para que la

‘ vista se cargue con datos validos.

End Sub

‘ TODO: Agregar los metodos necesarios para manejar

‘ los eventos del usuario.

End Class

Como se vio anteriormente, el presenter posee una referencia a la vista mediante la interfaz IView. La encargada de cargar el valor en esta propiedad es la vista cuando ObjectBuilder le inyecta la nueva instancia de su presenter.

El presenter también es el responsable de acceder a los servicios necesarios y/o entidades del modelo de negocios (Business Entities). En el ejemplo anterior, cuando el ObjectBuilder cree una instancia de la clase Presenter, se encargará también de inyectar el servicio que implementa la interfaz IService (en los próximos post se profundizará más sobre este tema).

Dentro del método OnViewReady() se deberá colocar la lógica necesaria para cargar por primera vez los datos en la vista, en caso de que fueran necesarios, llamando a los métodos de la IView que se encargan de modificar cargar los datos que se muestran.

Además, es el presenter el que se encarga de publicar y/o suscribirse a todos los eventos necesarios. Para ello, CAB proporciona la herramienta EventBroker para facilitar esta tarea (este tema se explicará en más detalle en los próximos post).

Nota: Smar Client Software Factory trae una recipe llamada Add View (with presenter) la cual se encarga de crear las clases las clases con la estructura necesaria para implementar el patrón Model-View-Presenter. Además provee una clase abstracta y genérica llamada Presenter que usa como base para construir los presenter de cada vista.

TDD con MVP

Usando el patrón MVP se puede realizar fácilmente Test-Driven-Development (TDD). Por ejemplo se puede testear el presenter, independientemente de la implementación de la vista. Aislar al presenter en los tests es útil porque se puede enfocar toda la atención en probar sólo esta clase y no sus dependencias. Estas dependencias (como por ejemplo la vista y los servicios) son reemplazadas por implementaciones falsas (mock-implementations) lo cual es posible gracias a que el presenter tiene referencias sólo a interfaces y no a implementaciones concretas.

Para obtener una mejor visión de cómo aplicar TDD con el patrón MVP, recomiendo la lectura del post de Johnny Halife: How-To: Write MVP using TDD.

En el siguiente post se verá cómo utilizar UI Extension Sites y Commands.

Introducción a Composite UI Application Block (CAB) III

Módulos

Componentes de cada módulo

En una aplicación CAB un módulo es una unidad de desarrollo y despliegue. CAB provee la posibilidad de cargar los módulos teniendo en cuenta el tipo de usuario que utiliza la aplicación. Esto permite presentar una interfaz y comportamiento distinto dependiendo del rol del usuario.

Los módulos generalmente están compuestos por:

  • WorkItems: Cada módulo debe poseer uno o más WorkItems. Estas clases heredan de la clase WorkItem de CAB y se construyen a partir del RootWorkItem. Son usadas para construir y albergar a todos los objetos que el módulo necesite, agregar servicios y acceder a ellos. El WorkItem es el único que tiene acceso a todos los componentes del módulo.
  • SmartParts: Son las partes visuales que agregará el módulo a la aplicación. Se implementan mediante clases que heredan de UserControl y presentan el atributo SmartPart. Mantienen una referencia a su controlador o presenter.
  • Servicios: El módulo puede agregar sus propios servicios. Dependiendo en qué WorkItem se los agregue estos pueden estar disponibles para todos los módulos o sólo para el que los creó.
  • Business Entities: Son las clases propias del modelo de negocio que la aplicación utilizará para implementar su comportamiento.

ProfileCatalog

Composite UI Application Block provee un servicio para la carga de los módulos durante la etapa de inicialización de la aplicación. Utiliza un archivo XML como catálogo para determinar qué módulos se van cargar. Su nombre por defecto es ProfileCatalog.xml y debe estar ubicado en la carpeta principal de la aplicación (donde está el ejecutable). Dentro de este archivo se especifican todos los módulos de la aplicación y cuales son los usuarios que tendrán acceso a ellos. Si no se especifica ningún rol de usuario todos los módulos presentes en el archivo se cargarán.

A continuación se muestra el formato del archivo ProfileCatalog.xml.

XML

sdfasd

<?xml version=”1.0″ encoding=”utf-8″ ?>

<SolutionProfile xmlns=”http://schemas.microsoft.com/pag/cab-profile>

<Modules>

<ModuleInfo AssemblyFile=”Module1.dll”>

<Roles>

<Role Allow=”Users”/>

</Roles>

</ModuleInfo>

<ModuleInfo AssemblyFile=”Module2.dll”>

<Roles>

<Role Allow=”Administrators”/>

</Roles>

</ModuleInfo>

</Modules>

</SolutionProfile>

En el ejemplo anterior se especifican dos módulos y el rol del usuario necesario para que estos se carguen.

Dependencias

Durante el desarrollo de la aplicación puede darse el caso de tener módulos independientes y dependientes. Cuando un módulo posee una dependencia hacia otro módulo esta se debe indicar a CAB mediante el atributo ModuleDependency. Este atributo afecta la secuencia de carga asegurando que primero se cargarán todos los módulos de los cuales se depende.

La siguiente línea de código muestra como se implementa esto. Se la debe colocar en el archivo AssemblyInfo.cs/vb del módulo que tiene la dependencia (cambiar MyModule por el nombre del módulo del cual depende). Antes de hacer esto asegurarse que el proyecto tenga referencias a los assemblies Microsoft.Practices.CompositeUI.dll y Microsoft.Practices.ObjectBuilder.dll.

[C#]

[assembly: Microsoft.Practices.CompositeUI.ModuleDependency("MyModule")]

[Visual Basic]

<Assembly: Microsoft.Practices.CompositeUI.ModuleDependency(”MyModule”)>

Para que funcione el código anterior se debe agregar en el archivo AssemblyInfo.cs/vb del módulo del cual se depende la siguiente línea:

[C#]

[assembly: Microsoft.Practices.CompositeUI.Module("MyModule")]

[Visual Basic]

<Assembly: Microsoft.Practices.CompositeUI.Module(”MyModule“)>

Esto permite establecerle un nombre al módulo para que los otros lo puedan identificar.

Nota: En Smart Client Software Factory, se pueden especificar las dependencias entre los módulos directamente desde el Profile Catalog.

Proceso de carga

Cuando CAB carga un módulo usa reflection para saber si el assembly contiene una clase que implementa la interfaz IModule. En general en vez de implementar esa interfaz, se hereda de la clase abstracta ModuleInit (que ya implementa la interfaz anterior) y se sobrescriben los métodos Load() y AddServices().

En el método Load() generalmente se inicializa el WorkItem principal del módulo y se lo ejecuta llamando a su método Run(), y en el método AddServices() se agregan al RootWorkItem los servicios del módulo (esto se verá en más detalle en los próximos posts). El primero en ejecutarse es el método AddServices() y luego Load().

A continuación se muestra un ejemplo de cómo construir esta clase:

[C#]

using Microsoft.Practices.ObjectBuilder;

using Microsoft.Practices.CompositeUI;

// namespace donde se encuentran los servicios en el ejemplo.

using ExampleModule.Services;

namespace ExampleModule

{

public class ExampleModuleInit : ModuleInit

{

private WorkItem _rootWorkItem;

[InjectionConstructor]

public ExampleModuleInit([ServiceDependency] WorkItem rootWorkItem)

{

_rootWorkItem = rootWorkItem;

}

public override void AddServices()

{

base.AddServices();

_rootWorkItem.Services.AddNew();

}

public override void Load()

{

ExampleWorkItem workitem = _rootWorkItem.WorkItems.AddNew();

workitem.Run();

}

}

}

[Visual Basic]

Imports Microsoft.Practices.CompositeUI

Imports Microsoft.Practices.ObjectBuilder

‘ namespace donde se encuentran los servicios en el ejemplo.

Imports ExampleModule.Services

Public Class ExampleModuleInit

Inherits ModuleInit

Private _rootWorkItem As WorkItem

<InjectionConstructor()> _

Public Sub New(<ServiceDependency()> ByVal rootWorkItem As WorkItem)

_rootWorkItem = rootWorkItem

End Sub

Public Overrides Sub AddServices()

MyBase.AddServices()

_rootWorkItem.Services.AddNew(Of Service, IService)()

End Sub

Public Overrides Sub Load()

MyBase.Load()

Dim workItem As ExampleWorkItem = _rootWorkItem.WorkItems.AddNew(Of ExampleWorkItem)()

workItem.Run()

End Sub

End Class

La clase ExampleModuleInit posee una referencia al RootWorkItem la cual se inyecta en el constructor. El atributo InjectionConstructor le indica al ObjectBuilder que deberá usar ese constructor en el momento de instanciar la clase y el atributo ServiceDependency le informa que se debe inyectar una dependencia en el parámetro del constructor.

El método AddServices() agrega un servicio que implementa la interfaz IService al contenedor de servicios del RootWorkItem.

En el método Load() se crea una instancia de la clase ExampleWorkItem (que representa al WorkIem principal del módulo) y se ejecuta el método Run(). La clase ExampleWorkItem deberá sobrescribir el método OnRunStarted() para agregar el código que se encargará de crear las vistas del módulo y cargarlas en el Workspace correspondiente. A continuación se muestra un ejemplo de cómo implementar esta clase:

[C#]

using Microsoft.Practices.CompositeUI;

using Microsoft.Practices.CompositeUI.SmartParts;

// namespace donde se encuentran las vistas en el ejemplo.

using ExampleModule.Views;

namespace ExampleModule

{

public class ExampleWorkItem : WorkItem

{

protected override void OnRunStarted()

{

AddViews();

base.OnRunStarted();

}

private void AddViews()

{

IWorkspace workspace = Workspaces["MainWorkspace"];

workspace.Show(Items.AddNew<UserControl1>());

}

}

}

[Visual Basic]

Imports Microsoft.Practices.CompositeUI

Imports Microsoft.Practices.CompositeUI.SmartParts

‘ namespace donde se encuentran las vistas en el ejemplo.

Imports ExampleModule.Views

Public Class ExampleWorkItem

Inherits WorkItem

Protected Overrides Sub OnRunStarted()

AddViews()

MyBase.OnRunStarted()

End Sub

Public Sub AddViews()

Dim workspace As IWorkspace = Workspaces(”MainWorkspace”)

workspace.Show(Items.AddNew(Of UserControl1)())

End Sub

End Class

Al ejecutar el método Run() de la clase ExampleWorkItem, se invocará el método sobrescrito OnRunStarted(). Este método crea una vista de tipo UserControl1 y la muestra en el Workspace del shell identificado con el nombre “MainWorkspace”.

En el próximo post se explicará el patrón Model-View-Presenter (MVP) y se dará un ejemplo de cómo aplicarlo en CAB.

Introducción a Composite UI Application Block (CAB) II

Comienzo de la aplicación CAB

La base de toda aplicación CAB es la clase CABApplication. Posee el código para, entre otras cosas, agregar servicios globales, construir, inicializar y ejecutar el RootWorkItem, cargar todos los módulos especificados en el catálogo, comenzar la aplicación iniciando el loop de mensajes (eso dependerá del tipo de aplicación que sea) y por último ejecutar el código de dispose (cuando esta finaliza). Sobre esta clase se monta la jerarquía de clases que se muestra en la Figura 1.

457x375.aspx

Figura 1

Jerarquía de clases del Shell.



·         CabApplication: Esta clase, como se mencionó anteriormente, se encarga de administrar los pasos fundamentales de la inicialización de Composite UI Application Block. Es utilizada por todos los tipos de shell y sólo requiere como parámetro la clase que se usará como RootWorkItem.

·         CabShellApplication: Se usa en aplicaciones que necesitan un shell. Incorpora dos métodos virtuales BeforeShellCreated() y AfterShellCreated(). Estos métodos son llamados antes y después de la creación del shell con el RootWorkItem.

·         WindowsFormsApplication: Es usada en todos los shell que utilizan Windows Forms. Incorpora algunas tareas a la etapa de inicialización, como agregar Builder Strategies que manejará el ObjectBuilder y registrar un visualizador, entre otras cosas.

·         FormShellApplication: Sobrescribe el método Start() de CABApplication y llama al método Application.Run() (requerido por los Windows Forms) pasándole el shell. Esta clase es usada por aplicaciones que necesitan mostrar un formulario prinicipal.


·         ApplicationContextApplication: Esta clase se usa en aplicaciones de Windows que no necesitan mostrar un shell durante la etapa de inicialización. Además del RootWorkItem, esta clase recibe como parámetro una clase que herede de ApplicationContext. La clase ApplicationContext se encarga de controlar la aplicación principal y las circunstancias que causan la salida del loop de mensajes. Se usa en aplicaciones que se cargan como plug-ins en otros sistemas como por ejemplo los Office Add-in. También sobrescribe el método Start() de CABApplication y llama a Application.Run() pero le pasa el ApplicationContext antes mencionado.

Toda aplicación CAB comienza en el método Run() de la clase CABApplication. La Figura 2 ilustra las prinicipales tareas del flujo de inicialización.

 500x339.aspx

Figura 2

Inicialización de CAB.

  • Se crea el ObjectBuilder y se le agregan las Builder Strategies.
  • Se crea e inicializa el RootWorkItem. Esta instancia será usada desde todos los módulos para crear objetos y acceder a partes del shell (Workspace y UIElements).
  • Se agregan todos servicios al RootWorkItem
  • Se llama al servicio de autenticación (Authentication) el cual es responsable de verificar el rol del usuario en el dominio de la aplicación.
  • Se buscan y cargan los módulos especificados en el Catálogo en la secuencia correcta. Se busca una clase que implemente la interfaz IModule.
  • Se cargan los servicios propios de cada módulo y se llama al método Load() de la clase de entrada.
  • Se ejecuta el RootWorkItem invocando a su método Run().
  • Se invoca el método Start() encargado de mostrar el formulario principal. Este método retorna el control solo cuando la aplicación finaliza (FormShellApplication) o cuando el contexto en la que estaba finaliza (ApplicationContextApplication).

  • Al finalizar el método Start() se hace un dispose.

En el siguiente informe se explicará el proceso de carga de un módulo y cómo aplicar el patrón Model-View-Presenter (MVP) con Composite UI Application Block.

Introducción a Composite UI Application Block (CAB) I

Durante mi primera semana en Southworks estuve aprendiendo sobre Composite UI Appication Block. Leí mucha documentación y realice distintos tipos de ejercicios.

Al concluir mi etapa de entrenamiento en este block decidí realizar una guía que explique los conceptos básicos de CAB así como también sus principales componentes y la forma correcta de emplearlos.

La guía incluye una descripción general del block y de sus componentes. Este documento solo pretende ser una introducción a Composite UI Application Block. Para obtener una visión más profunda de su uso y funcionamiento leer la documentación oficial:

Descripción General

Composite UI Application Block permite construir aplicaciones de interfaz de usuario que corren sobre Windows. Este application block esta diseñado para soportar el desarrollo de aplicaciones clientes line of business.

El patrón de diseño Composite – uno de los patrones utilizados en el diseño este bloque - combina distintas interfaces de usuario simples para crear una más compleja. Estas piezas son integradas dentro del shell para formar la aplicación. La separación de la aplicación en partes más pequeñas permite que cada parte sea desarrollada y testeada por separado.

El principal objetivo de su diseño es proveer a los desarrolladores de aplicaciones soporte para desarrollar módulos independientes y desacoplados.

Componentes

Los principales compomentes que introduce Composite UI Application Block son:

  • SmartParts: son los elementos visuales de la aplicación. Estos controles Windows Form pueden ser completamente independientes de la aplicación que los contiene, lo que permite que sean desarrollados y probados por separado.
  • Workspace: son contenedores de SmartParts y se encargan de su manejo. Controlan como se muestran u ocultan de acuerdo al estilo del Workspace. Estos estilos son: WindowWorkspace, MdiWorkspace, TabWorkspace, DeckWorkspace y ZoneWorkspace.
  • WorkItems: contienen la lógica para manejar un determinado caso de uso (para inicializarlo y darle de baja). Es un contenedor de diversos componentes como ser controladores o presenters (Controllers), vistas (SmartParts) y estado (State).
  • Controllers: tienen el mismo rol que en el patrón Model-View-Controller (MVC). Son los que implementan la lógica de negocio por detrás de la vista. También pueden ser usados para crear un presenter y aplicar el patrón Model-View-Presenter (MVP).
  • Service: son clases de soporte que proveen funcionalidad a otros componentes. CAB incluye una infraestructura de servicios que se puede usar en la aplicación. Entre estos están: Catalog Reader service, Module Loader service, Authentication service y State Persistence service. También permite crear servicios propios mediante el uso del atributo Service.
  • Module: están compuestos por servicios, WorkItems, SmarParts, Controllers, entidades de negocio y por la clase que carga el módulo (ModuleInit) la cual es usada para inicializar y ejecutar los WorkItems.
  • UIElements: permite que algunos elementos del shell sean accedidos por todos los módulos como son la barra de menú o la barra de estado. Esto se logra registrando a los elementos como UIExtensionSite. Cada módulo puede agregar sus propios elementos gracias a esto.
  • Commands: las aplicaciones en general tienen más de un control que invoca el mismo método. CAB utiliza el concepto de comandos para permitir asociar un command handler con más de un UIElement y asociar un UIElement con más de un command handler.
  • Event Broker: maneja la publicación y suscripción de eventos entre SmartParts y otros componentes en forma desacoplada.

En los siguients post se profundizará sobre los principales conceptos de CAB.