TDD, Test Driven Development, es una técnica que establece que primero se escriben las pruebas y luego el código del aplicativo.

Estas pruebas se efectúan de acuerdo a lo que se espera que haga el aplicativo, es por eso que algunos popes de esta disciplina dicen que practicándola intensivamente podemos prescindir de la documentación ya que el código de las pruebas es en si mismo documentación acerca del aplicativo.

En otras palabras las pruebas especifican lo que se espera de la aplicación a desarrollar.

Esta práctica es de amplia difusión en las Metodologías Ágiles, con esto se garantiza código de calidad desde el inicio del proceso de desarrollo y proporciona un buen elemento para el establecimiento de métricas.

El Ciclo TDD se compone de los siguientes pasos:

  • Escribir un test, aún de algo que todavía no hayamos codificado,
  • Compilar, va a dar error, ya que todavía no hemos codificado lo que estamos probando,
  • Escribir la mínima cantidad de código para que compile, aunque sea la cabecera del método o función a probar,
  • Compilar, ahora no da error, estamos en condiciones de ejecutar nuestro Test,
  • Ejecutar el test, falla, ya que habíamos escrito la mínima cantidad de código que garantice la compilación, pero no la ejecución del Test,
  • Escribir la mínima cantidad de código para que el Test no falle,
  • Ejecutar el test, esta vez no falla,
  • Refactorizar, este es un tema aparte, hay variadas técnicas de refactorización y muchos buenos libros escritos, más adelante veremos someramente alguna de esas técnicas,
  • Recomenzar escribiendo un nuevo Test.

Es habitual encontrar TDD asociado a estas tres palabras en sus respectivos colores:

Red, Green, Refactor

Estos son términos y colores asociados a la familia de herramientas de Test Unitarios xUnit, dela que usaremos NUnit, se pueden descargar la última versión desde http://www.nunit.org, también van a encontrar documentación.

Red, está asociado a la ejecución fallida de uno o más Tests.

Green, es el color que se ve en la IDE de NUnit al ejecutar exitosamente uno o más Test.

Refactor, lo podemos ver cuando estamos refactorizando nuestro proyecto de Testing.

Es muy importante que las modificaciones se hagan en pequeños pasos, esto hace a la estabilidad del código probado a lo largo del proceso de desarrollo. Una buena estrategia de Testing evita los grandes objetos, los Test integrales, la interdependencia entre las pruebas.

Una buena estructura TDD tiende a que el equipo de desarrollo esté más y mejor comunicado. Como se elimina la probabilidad de romper funcionalidad existente al liberar código nuevo, se eliminan temores a “papelones”.

La Barra Verde que nos muestra NUnit cuando los Test son exitosos se convierten en un factor motivacional positivo.

Por su propia naturaleza, TDD elimina los problemas derivados de las contínuas postergaciones del Debugging.

¿Y?… ¿cómo se hace?…

Te pido que me esperes a que termine el próximo post. 🙂

Anuncios

Otra vez acá.

Basado en el ejemplo del post anterior, voy a introducir modificaciones al modelo original con la idea de ver que tan fácil es hacerlo y regenerar código.

Partimos entonces de la solución generada que pueden descargar desde acá.

Supongamos que ahora nos piden que incluyamos un campo Telefono para la tabla Sucursal.

Conviene agregar algunos registros en las tablas para facilitar la prueba.En primer lugar abrimos SQL Server Management Studio, buscamos en la base de datos Prueba y en la tabla Sucursal agregamos un campo Telefono de tipo VARCHAR(50), grabamos los cambios.

Luego abrimos nuestro modelo con el Modeler y pulsamos Refresh. Al desplegar Sucursal veremos lo siguiente:

Ahora hacemos click sobre Quick generation, al finalizar abrimos la solución con Visual Studio y ejecutamos.Podremos ver que la grilla ya contiene la columna Telefono y si cargaron algunos registros correctamente en las tablas, podrán ver los valores.

Seguimos pensando en otro tipo de modificación, por ejemplo una Asociación, supongamos que nos piden poder calificar las Sucursales por Provincia. Esto nos obliga a crear una tabla Provincia con un campo ID (PK, autonumérico e INT) y Descripcion (VARCHAR(50)). Para vincularla a la tabla Sucursal, agregamos en esta última un campo IDProvincia (FK e INT) luego establecemos la relación entre la PK de Provincia con la FK de Sucursal.

Hacemos un Refresh desde el Modeler,

Desplegamos Sucursal, chequeamos ProvinciaString y quitamos el check de ProvinciaEntity, click en Save model,

Ahora un Quick Generation y volvemos a la solución.

Veremos que se han producido una serie de erroes.

Para solucionarlo iremos primero al proyecto PruebaEntities, en la carpeta Objects agregamos la clase ProvinciaObject.vb y en la carpeta Object/Auto la clase ProvinciaObject.Auto.vb.

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a ProvinciaGateway.vb y en Gateways/Auto la clase ProvinciaGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase ProvinciaMapper.vb y en Mappers/Auto la clase ProvinciaMapper.Auto.vb

Si ejecutamos la solución, vamos a ver la columna Provincia incorporada a la grilla.

Supongamos que nos indican que cada Sucursal está asociada a un Depósito y cada una tiene uno propio. Podemos suponer esto como una Agregación, una relación uno a uno.

Importante: Tengamos en cuenta que para que una relación quede establecida como uno a uno en una base de datos, la PK y la FK deben ser exactamente iguales, mismos campos, mismos tipos, mismos nombres.

En nuestro ejemplo la PK de Sucursal la forman los campos ID e IDCliente (podría haber sido ID solamente) entonces vamos crear una tabla Deposito con estos dos campos un ID (PK, autonumérico, INT)y un campo Descripcion (VARCHAR(50)).

Luego debemos crear una relación uno a uno entre Sucursal y Deposito.

Volvemos al Modeler y hacemos un Refresh. Desplegamos Sucursal y vemos un nuevo elemento llamado DepositoAggregation, si intentamos chequearlo vamos a ver que en pocos segundos pierde el check. Esto sucede porque todavía no definimos su dependencia. Para eso hacemos click sobre el elemento Deposito, en la propiedad GenerateAsChildOf seleccionamos Sucursal. Ahora si podemos chequear sobre el elemento DepositoAggregation de Sucursal.

Ahora vamos a mirar el elemento Cliente, lo desplegamos y seleccionamos SucursalCollection, si vemos su propiedad GenerateAsType vale ObjectList, esto significa que la entidad Cliente, trae una colección de objetos. Como antes les conté, los objetos mapean uno a uno con las tablas de la base de datos, esto quiere decir que la agregación que recién incorporamos no es accesible desde la entidad Cliente.

Para que esto suceda hay que fijar la propiedad GenerateAsType de SucursalCollection con valor EntityList en la entidad Cliente.

Volvemos a hacer un Quick generation y abrimos la solución.

Vamos al proyecto PruebaEntities, en la carpeta Objects agregamos la clase DepositoObject.vb y en la carpeta Object/Auto la clase DepositoObject.Auto.vb.  En la carpeta Entities agregamos la clase Deposito.vb y en la carpeta Entities/Auto la clase Deposito.Auto.vb

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a DepositoGateway.vb y en Gateways/Auto la clase DepositoGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase DepositoMapper.vb y en Mappers/Auto la clase DepositoMapper.Auto.vb

Si todo esto fue bien todavía nos va a quedar un error en la lista:

Value of type ‘PruebaRules.Entities.SucursalList’ cannot be converted to ‘PruebaRules.Objects.SucursalObjectList’.

Esto es en el método CargarGrillaSucursal del fromulario Form1. La linea que muestra el error es la siguiente:

Dim mSucursalList As SucursalObjectList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Esto es poruqe a la collección de Sucursales contenida en la entidad Cliente la cambiamos de ObjectList a EntityList. Corregida queda:

Dim mSucursalList As SucursalList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Cambiamos los tipos SucursalObjectList por tipos SucursalList.

Ahora tenemos un error en la siguiente linea:

Dim mSucursalListView As SucursalObjectListView = New SucursalObjectListView(mSucursalList)

Corregida queda:

Dim mSucursalListView As SucursalListView = New SucursalListView(mSucursalList)

Y esto es todo. Cuando lo ejecutemos en la grilla no vamos a ver automáticamente los campos de la agregación, pero van a notar que la agregacion ya es accesible.

Para terminar con esto incorporamos una Composición. Por ejemplo, nos cuentan que vamos a tener una lista de Contactos por cada Sucursal. Ya nos estamos imaginando una tabla Contacto con ID (PK, autonumérico, INT), IDSucursal (FK, INT), NombreApellido (VARCHAR(50)) y antes de establecer la relación entre Sucursal y Contacto, creamos un índice IsUnique sobre la columna ID

Ahora si podemos establecer la relación entre Contacto y Sucursal:

Volvemos una vez más al Modeler y hacemos Refresh.

Desplegamos Sucursal y vemos el nuevo elemento llamado ContactoCollection.

Antes de chequear ContactoCollection, debemos ir a la entidad Contacto a la propiedad GenerateAsChildOf la fijamos en el valor Sucursal. Ahora si, en la entidad Sucursal, chequemos ContactoCollection

Última vuelta al Modeler, Refresh y Quick generation, volvemos a la solución.

Vamos al proyecto PruebaEntities, en la carpeta Objects agregamos la clase ContactoObject.vb y en la carpeta Object/Auto la clase ContactoObject.Auto.vb.

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a ContactoGateway.vb y en Gateways/Auto la clase ContactoGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase ContactoMapper.vb y en Mappers/Auto la clase ContactoMapper.Auto.vb

Una vez hecho esto podemos acceder a un Contacto a través de la entidad Cliente, por ejemplo:

Dim mCliente As Cliente = ClienteMapper.Instance.GetOne(1)

mCliente.SucursalCollection.Item(2).ContactoCollection.Item(3).NombreApellido.Trim()

En la primera linea recuperamos el Cliente con ID igual a 1, en la segunda linea obtenemos el Nombre y Apellido del cuarto Contacto de la tercera Sucursal.

Repasando, con estos cuatro ejemplos que les presento vimos que las tablas pueden relacionarse de tres formas distintas en Cooperator, estas son:

  • Asociación, en el Modeler se ve como xxxxEntity, representa una relación que califica a la entidad base, en nuestro ejemplo, Sucursal Provincia.
  • Composición, en el Modeler se ve como xxxxCollection, es una relación 1 a n, hay que definir que la entidad colección sea definida como hija de la entidad base por medio de la propiedad GenerateAsChildOf.
  • Agregación, en el Modeler se ve como xxxxAggregate, es una relación 1 a 1 (las PK y FK deben se exactamente iguales), hay que definir que la entidad agregación sea definida como hija de la entidad base por medio de la propiedad GenerateAsChildOf.

Ýa en el estribo, tengamos en cuenta que, eL Mapper graba la Entidad con todas sus Composiciones y Agregaciones, no así con sus Asociaciones. Si el ID de la Asociación, no existe el motor SQL devuelve un error de integridad.

Acá les dejo el script de la base de datos resultante, el archivo Cooperator y el código generado para que jueguen.

Ojalá les sea útil   🙂

Para quien no conoce nada acerca de Cooperator Framework le recomiendo leer mi post anterior Introducción a Cooperator Framework.

Comencemos por descargar la herramienta, aquí encontramos el último release de Cooperator Modeler, al finalizar la descarga, descomprimimos el archivo y podremos ver el ejecutable de nombre CooperatorModeler.exe, con un doble click veremos lo su interfaz gráfica:

La prueba la vamos a realizar sobre SQL Server 2005, entonces vamos a abrir SQL Server Management Studio y creamos una base de datos de nombre Prueba, donde crearemos dos tablas, Cliente y Sucursal con una relación 1 a n.

La tabla Cliente tiene un campo ID (PK, INT y autonumérico) y RazonSocial (VARCHAR(50)).

La tabla Sucursal tiene un campo ID (PK, INT y autonumérico), IDCliente (FK, INT y autonumérico) y Denominacion (VARCHAR(50)).

Establecemos la relación entre la PK de Cliente y la FK de Sucursal.

Volvamos ahora al Modeler y hagamos un click sobre el botón New Model, cuando se abra el formulario hacemos click sobre el botón Get Databases, seleccionamos la nuestra y Ok:

Ahora veremos el Modelo:

Desplegamos las Entidades:

Vemos que Cliente tiene una colección de Sucursales, pero si la queremos incluir en el modelo es necesario chequearla.

Seleccionemos ahora Sucursal:

Podemos ver la propiedad GenerateAsChildOf con el valor Cliente, si esto no fuera así, antes no hubieramos podido seleccionar, en Cliente, la colección de Sucursales. También vemos que en Sucursal existe una ClienteEntity, si se chequea esto la entidad Sucursal incuirá la entidad Cliente (padre) con todas sus propiedades, incluirla o no es una desición de diseño.

El elemento ClienteString va a mostrar el campo de la tabla Cliente que nosotros determinemos, seleccionemos el elemento RazonSocial de la tabla Cliente, la propiedad GenerateAsDescriptionField vale True, esto define que RazonSocial es el campo que se mostrará cuando se invoque Sucursal.ClienteString.

Un punto muy importante que resuelve Cooperator es la aplicación del patrón LazyLoad y lo hace sin ningún esfuerzo extra de nuestra parte. Seleccionemos el elemento SucursalCollection de la entidad Cliente, vemos que la propiedad GenerateAsLazyLoad está en True, no hace falta nada más.

Supongamos que nuestro Modelo, por ahora termina acá, lo salvamos y hacemos un click sobre Code Generation

Hacemos un click sobre Generate SP, al finalizar otro click sobre Generate source code y cuando finalice seleccionamos una carpeta en la que querramos generar nuestra solución y luego un click sobre Generate Solution (para esta primera vez).

Modeler casi inmediatamente nos va a avisar que la solución se generó exitosamente y nos preguntará si queremos abrirla, le contestamos que si y vamos al Visual Studio que se está abriendo.

Abramos el formulario en modo diseño y agreguemos un ComboBox de nombre cboCliente y un DataGridView de nombre dtgSucursal.

Para hacer la prueba vamos a cargar a mano algunos registros en las tablas.

Ahora vamos a agregar código al formulario Form1, comenzando por los Imports:

Imports PruebaRules.Entities

Imports PruebaRules.Objects

Imports PruebaRules.Mappers

Imports PruebaRules.Views

 

En el evento Load del formulario disparamos CargarComboCliente

Private Sub CargarComboCliente()

Dim mClienteList As ClienteList = ClienteMapper.Instance.GetAll()

Dim mClienteListView As ClienteListView = New ClienteListView(mClienteList)

cboCliente.DisplayMember = “RazonSocial”

cboCliente.ValueMember = “ID”

Dim mCliente As New Cliente

mCliente.RazonSocial = “(Ninguno)”

mClienteListView.Add(mCliente)

mClienteListView.Sort(“RazonSocial”, True)

cboCliente.DataSource = mClienteListView

cboCliente.SelectedIndex = 0

End Sub

Los Clientes se recuperan en un objeto ClienteList a través de su correspondiente Mapper,  en la segunda linea asigno este ClienteList a otro objeto ClienteListView que me permite más versatilidad. La tercer y cuarta linea de código me permiten definir que valores se van a ver en el ComboBox y cuales voy a recuperar al seleccionar un Cliente por medio de las propiedades DisplayMember y ValueMember respectivamente. En las siguientes tres lineas creo un objeto Cliente, le asigno el valor (Ninguno) y lo agrego en el ComboBox. Luego ordeno el objeto de tipo ClienteListView por medio de un Sort, le asigno dicho ListView a la propiedad DataSource del ComboBox y finalmente me posiciono en el valor (Ninguno) del ComboBox.

En el SelectedIndexChanged del ComboBox de Clientes disparo CargarGrillaSucursal:

Private Sub CargarGrillaSucursal()

Dim mSucursalList As SucursalObjectList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Dim mSucursalListView As SucursalObjectListView = New SucursalObjectListView(mSucursalList)

dtgSucursal.DataSource = mSucursalListView

End Sub

Acá  usamos un objeto de tipo SucursalObjectList para obtener, por medio de su Mapper, la lista de Sucursales correspondiente al Cliente seleccionado en el ComboBox. Vemos que se usa un objeto de tipo SucursalObjectList y no uno de tipo SucursalList. Si vamos al Modeler y vemos el elemento SucursalCollection, tiene una propiedad de nombre GenerateAsType con valor ObjectList, si en esta propiedad seleccionamos el valor EntityList, podríamos haber usado un objeto de tipo SucursalList a cambio de SucursalObjectList.

A esta altura es necesario aclarar que un Object mapea uno a uno contra una tabla de la base de datos, mientras que en un objeto de tipo Entity no necesariamente es así.

Ahora F5 y a ver como anda.

No intenten hacer esto solos en sus casas.

Voy a subir el código de este post ya que va a ser la base de otros posteriores.

Prueba.coop es el archivo con que el Modeler persiste la información.

Script.sql, para crear la base de datos.

Prueba.zip la solución Visual Studio 2005

Espero les sirva y espero sus comentarios. 🙂

Este post es continuación de este anterior sobre Delegates. Si ya conocés el tema no te va a hacer falta empezar por él.

En .NET tenemos la posibilidad de publicar un evento, al cual uno o más objetos pueden suscribirse.

Cuando se produce el evento, el aplicativo informa a los objetos suscriptos, que en el entorno del evento ejecutarán el código que contengan en la definición de su clase.

Al producirse el evento se invoca un método definido por medio de un delegado

Este Delegate debe recibir dos parámetros de entrada, el primero de tipo Object que recibe el objeto que produce el evento, el segundo es algún objeto que derive de la clase EventArgs.

Vamos a ver un ejemplo con el que jamás podremos a llamar la atención de nadie que nos interese. 🙂

Desde este link pueden descargarlo.

Vamos a tener tres formularios, el primero de ellos tiene un TrackBar que cuando se modifica los otros dos formularios se “enteran” y muestran la modificación a través de un textBox.

El primer punto interesante es que no tengo que agregar nada de código en los formularios que se “enteran” del evento. En el ejemplo que les dejo, en VB.NET y C#, podemos ver que en los Form2 y Form3 sólo hay estructura para verse como formularios y para soportar un textBox.

Empiezo por la clase TrackBarChangeEventsArgs que encapsula el valor que toma el TrackBar cuando se modifica su estado.

[VB]

Imports System

 

”’ <summary>

”’ Encapsula el argumento del evento, en este caso es un entero

”’ que representa al valor del TrackBar.

”’ </summary>

”’ <remarks>La clase hereda de EventArgs.</remarks>

Class TrackBarChangeEventsArgs

    Inherits EventArgs

 

    Private mChange As Integer

 

    ”’ <summary>

    ”’ Constructor

    ”’ </summary>

    ”’ <param name=”pChange”>Valor del TrackBar.</param>

    ”’ <remarks></remarks>

    Public Sub New(ByVal pChange As Integer)

        MyBase.New()

        Me.mChange = pChange

    End Sub

 

    ”’ <summary>

    ”’ Publica el valor del argumento del evento, en este caso

    ”’ el valor del TrackBar.

    ”’ </summary>

    ”’ <value></value>

    ”’ <returns>Valor del TrackBar</returns>

    ”’ <remarks></remarks>

    Public ReadOnly Property Change() As Integer

        Get

            Return mChange

        End Get

    End Property

End Class

 

 

[C#]

using System;

 

/// <summary>

/// Encapsula el argumento del evento, en este caso es un entero

/// que representa al valor del TrackBar.

/// </summary>

/// <remarks>La clase hereda de EventArgs.</remarks>

class TrackBarChangeEventsArgs : EventArgs

{

    private int mChange;

 

    /// <summary>

    /// Constructor

    /// </summary>

    /// <param name=”pChange”>Valor del TrackBar.</param>

    /// <remarks></remarks>

    public TrackBarChangeEventsArgs(int pChange)

    {

        this.mChange = pChange;

    }

 

    /// <summary>

    /// Publica el valor del argumento del evento, en este caso

    /// el valor del TrackBar.

    /// </summary>

    /// <value></value>

    /// <returns>Valor del TrackBar</returns>

    /// <remarks></remarks>

    public int Change

    {

        get

        {

            return mChange;

        }

    }

}

 

Podemos ver que la clase deriva de EventArgs, tiene un constructor que recibe como parámetro el valor del TrackBar y publica al mismo mediante una propiedad Read Only.

Ahora veamos la clase encargada de publicar el evento, en el ejemplo es TrackBarAdministrador, en ella es donde se declara el Delegate de nombre TrackBarChangeEventHandler, que, como mencioné antes, recibe dos parámetros uno de tipo Object que contiene al objeto que disparó el evento y otro que deriva de EventArgs, esta ves es TrackBarChangeEventArgs.

A continuación se declara el evento OnTrackBarChangeHandler del tipo Delegate TrackBarChangeEventHandler.

La clase tiene un único método llamado ChangeTrackBar que acepta un sólo parámetro de tipo entero que es el valor del TrackBar. En este método se declara una variable llamada e del tipo de la clase TrackBarChangeEventsArgs (la que encapsula y publica el valor del TrackBar) y se le pasa el parámetro que se recibió, el entero que representa el valor del TrackBar. Ahora se dispara el evento OnTrackBarChangeHandler pasándole como parámetros el propio objeto (que es una instancia de la clase TrackBarAdministrador) y con esto queda publicado el evento.

Veamos la clase:

[VB]

Imports System

 

”’ <summary>

”’ Administra el evento de cambio de valor del TrackBar.

”’ </summary>

”’ <remarks></remarks>

Class TrackBarAdministrador

    ”’ <summary>

    ”’ Declaro un Delegate de nombre TrackBarChangeEventHandler. A

    ”’ Continuación declaro un evento del tipo del Delegate, que

    ”’ será el evento a propagar.

    ”’ </summary>

    ”’ <param name=”source”>Parámetro de entrada de tipo Object</param>

    ”’ <param name=”e”>Parámetro de entrada de tipo TrackBarChangeEventsArgs</param>

    ”’ <remarks>Esta es la clase que administra el evento a producirse al

    ”’ cambiar el valor del TrackBar</remarks>

    Public Delegate Sub TrackBarChangeEventHandler(ByVal source As Object, ByVal e As TrackBarChangeEventsArgs)

    Public Event OnTrackBarChangeHandler As TrackBarChangeEventHandler

 

    ”’ <summary>

    ”’ Método que recibe el valor a propagar.

    ”’ </summary>

    ”’ <param name=”pChange”>Valor del TrackBar a propagar.</param>

    ”’ <remarks></remarks>

    Public Sub ChangeTrackBar(ByVal pChange As Integer)

        Dim e As TrackBarChangeEventsArgs = New TrackBarChangeEventsArgs(pChange)

        RaiseEvent OnTrackBarChangeHandler(Me, e)

    End Sub

End Class

[C#]

using System;

 

/// <summary>

/// Administra el evento de cambio de valor del TrackBar.

/// </summary>

/// <remarks></remarks>

class TrackBarAdministrador

{

    public event TrackBarChangeEventHandler OnTrackBarChangeHandler;

 

    /// <summary>

    /// Método que recibe el valor a propagar.

    /// </summary>

    /// <param name=”pChange”>Valor del TrackBar a propagar.</param>

    /// <remarks></remarks>

    public void ChangeTrackBar(int pChange)

    {

        TrackBarChangeEventsArgs e = new TrackBarChangeEventsArgs(pChange);

        OnTrackBarChangeHandler(this, e);

    }

 

    /// <summary>

    /// Declaro un Delegate de nombre TrackBarChangeEventHandler. A

    /// Continuación declaro un evento del tipo del Delegate, que

    /// será el evento a propagar.

    /// </summary>

    /// <param name=”source”>Parámetro de entrada de tipo Object</param>

    /// <param name=”e”>Parámetro de entrada de tipo TrackBarChangeEventsArgs</param>

    /// <remarks>Esta es la clase que administra el evento a producirse al

    /// cambiar el valor del TrackBar</remarks>

    public delegate void TrackBarChangeEventHandler(object source, TrackBarChangeEventsArgs e);

}

 

Nos queda por ver la clase engargada de notificar la ocurrencia del evento que en este caso se llama TrackBarObservador.

Si bien la clase tiene este nombre, esto no significa que estemos aplicando el patrón Observer, para quien le interese este patrón acá hay un artículo descriptivo.

Esta clase tiene un constructor que acepta como parámetro un objeto del tipo de la clase TrackBarAdministrador, lo guarda en una variable local y vincula al evento OnTrackBarChangeHandler declarado en la clase TrackBarAdministrador al método OnTrackBarChange definido más adelante en esta misma clase. Este método es donde se realizan las suscripciones de los formularios Form2 y Form3 que serán notificados del cambio en el TrackBar.

[VB]

Imports System

 

”’ <summary>

”’ Informa a los formularios sobre el evento que administra TrackBarAdministrador.

”’ </summary>

”’ <remarks></remarks>

Class TrackBarObservador

    ”’ <summary>

    ”’ Variable local del tipo de la clase que administra el evento

    ”’ a producirse al cambiar el vaor del TrackBar.

    ”’ </summary>

    ”’ <remarks></remarks>

    Private mTrackBarAdministrador As TrackBarAdministrador

 

    ”’ <summary>

    ”’ Constructor

    ”’ </summary>

    ”’ <param name=”pTrackBarAdministrador”>Recibe una instancia de la clase

    ”’ que administra el evento a producirse al cambiar el valor del

    ”’ TrackBar</param>

    ”’ <remarks>Se agrega al método local OnTrackBarChange como manipulador

    ”’ del evento de tipo Delegate declarado en la clase TrackBarAdministrador.</remarks>

    Public Sub New(ByVal pTrackBarAdministrador As TrackBarAdministrador)

        ‘MyBase.New()

        Me.mTrackBarAdministrador = pTrackBarAdministrador

        AddHandler mTrackBarAdministrador.OnTrackBarChangeHandler, AddressOf Me.OnTrackBarChange

    End Sub

 

    ”’ <summary>

    ”’ Método que manipula al evento a producirse al variar el valor del TrackBar.

    ”’ </summary>

    ”’ <param name=”source”>Parámetro de entrada de tipo Object</param>

    ”’ <param name=”e”>Parámetro de entrada de tipo TrackBarChangeEventsArgs</param>

    ”’ <remarks>Es importante destacar que los parámetros de este método deben ser

    ”’ idénticos a los de la firma del evento de tipo Delegate declarado en la clase

    ”’ TrackBarAdministrador.</remarks>

    Private Sub OnTrackBarChange(ByVal source As Object, ByVal e As TrackBarChangeEventsArgs)

        Form2.txtValor.Text = e.Change

        Form3.txtValor.Text = e.Change

    End Sub

End Class

 

[C#]

using System;

 

/// <summary>

/// Informa a los formularios sobre el evento que administra TrackBarAdministrador.

/// </summary>

/// <remarks></remarks>

class TrackBarObservador

{

    Form2 miForm2 = new Form2();

    Form3 miForm3 = new Form3();

 

    /// <summary>

    /// Variable local del tipo de la clase que administra el evento

    /// a producirse al cambiar el vaor del TrackBar.

    /// </summary>

    /// <remarks></remarks>

    private TrackBarAdministrador mTrackBarAdministrador;

 

    /// <summary>

    /// Constructor

    /// </summary>

    /// <param name=”pTrackBarAdministrador”>Recibe una instancia de la clase

    /// que administra el evento a producirse al cambiar el valor del

    /// TrackBar</param>

    /// <remarks>Se agrega al método local OnTrackBarChange como manipulador

    /// del evento de tipo Delegate declarado en la clase TrackBarAdministrador.</remarks>

    public TrackBarObservador(TrackBarAdministrador pTrackBarAdministrador)

    {

        this.miForm2.Show();

        this.miForm3.Show();

        this.mTrackBarAdministrador = pTrackBarAdministrador;

        mTrackBarAdministrador.OnTrackBarChangeHandler += new TrackBarAdministrador.TrackBarChangeEventHandler(this.OnTrackBarChange);

    }

 

    /// <summary>

    /// Método que manipula al evento a producirse al variar el valor del TrackBar.

    /// </summary>

    /// <param name=”source”>Parámetro de entrada de tipo Object</param>

    /// <param name=”e”>Parámetro de entrada de tipo TrackBarChangeEventsArgs</param>

    /// <remarks>Es importante destacar que los parámetros de este método deben ser

    /// idénticos a los de la firma del evento de tipo Delegate declarado en la clase

    /// TrackBarAdministrador.</remarks>

    private void OnTrackBarChange(object source, TrackBarChangeEventsArgs e)

    {

        this.miForm2.txtValor.Text = e.Change.ToString();

        this.miForm3.txtValor.Text = e.Change.ToString();

    }

}

Pasamos ahora al Form1 que es el que controla la aplicación. Este formulario en su componente visual contiene un TrackBar. En él se declaran una variable local de tipo de la clase TrackBarAdministrador, otra más del tipo de la clase TrackBarObservador y se le pasa la variable de tipo TrackBarAdministrador, recién declarada, como parámetro.

En el evento Scroll del TrackBar, es el que se dispara al cambiar manualmente su valor, ejecutamos el método ChangeTrackBar declarado en la clase TrackBarAdministrador que es el encargado de disparar el evento OnTrackBarChangeHandler y se cierra el círculo.

[VB]

”’ <summary>

”’ Desde este formulario se controlan los valores de Título y

”’ TextBox de los otros dos.

”’ </summary>

”’ <remarks></remarks>

Public Class Form1

    ”’ <summary>

    ”’ Declaro una variable local del tipo TrackBarAdministrador. Luego otra del

    ”’ tipo TrackBarObservador, a la que se le pasa TrackBarAdministrador como

    ”’ parámetro de entrada.

    ”’ </summary>

    ”’ <remarks></remarks>

    Private mTrackBarAdministrador As TrackBarAdministrador = New TrackBarAdministrador

    Private mTrackBarObservador As TrackBarObservador = New TrackBarObservador(mTrackBarAdministrador)

 

    ”’ <summary>

    ”’ Muestro los formularios que se recibirán al evento desde la clase

    ”’ TrackBarObservador.

    ”’ </summary>

    ”’ <param name=”sender”></param>

    ”’ <param name=”e”></param>

    ”’ <remarks></remarks>

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Form2.Show()

        Form3.Show()

    End Sub

 

    ”’ <summary>

    ”’ Al modificarse el valor del TrackBar se dispara el evento ChangeTrackBar

    ”’ declarado en TrackBarAdministrador.

    ”’ </summary>

    ”’ <param name=”sender”></param>

    ”’ <param name=”e”></param>

    ”’ <remarks></remarks>

    Private Sub TrackBar_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TrackBar.Scroll

        mTrackBarAdministrador.ChangeTrackBar(sender.Value)

    End Sub

End Class

[C#]

using System;

using System.Windows.Forms;

 

/// <summary>

/// Desde este formulario se controlan los valores de Título y

/// TextBox de los otros dos.

/// </summary>

public partial class Form1 : Form

{

    public Form1()

    {

        InitializeComponent();

    }

 

    /// <summary>

    /// Declaro una variable local del tipo TrackBarAdministrador. Luego otra del

    /// tipo TrackBarObservador, a la que se le pasa TrackBarAdministrador como

    /// parámetro de entrada.

    /// </summary>

    static TrackBarAdministrador mTrackBarAdministrador = new TrackBarAdministrador();

    TrackBarObservador mTrackBarObservador = new TrackBarObservador(mTrackBarAdministrador);

 

    /// <summary>

    /// Muestro los formularios que se recibirán al evento desde la clase

    /// TrackBarObservador.

    /// </summary>

    /// <param name=”sender”></param>

    /// <param name=”e”></param>

    /// <remarks></remarks>

    private void Form1_Load(object sender, System.EventArgs e)

    {

        Application.Run(new Form2());

        Application.Run(new Form3());

    }

 

    /// <summary>

    /// Al modificarse el valor del TrackBar se dispara el evento ChangeTrackBar

    /// declarado en TrackBarAdministrador.

    /// </summary>

    /// <param name=”sender”></param>

    /// <param name=”e”></param>

    /// <remarks></remarks>

    private void trackBar_Scroll(object sender, System.EventArgs e)

    {

        mTrackBarAdministrador.ChangeTrackBar(((System.Windows.Forms.TrackBar)(sender)).Value);

    }

}

No publico el código de los otros dos formularios porque no contienen más código que el necesario para ser formularios y contener un textBox de nombre txtValor, pueden verificarlo en los ejemplos que les dejo acá para bajar y probar.

Con la idea de haber sido útil, me despido hasta la próxima. 🙂

Una práctica fundamental en la Generacón Automática de Código es la Preservación del Código No Generado. En otras palabras que entre un ciclo de generación y el siguiente no perdamos el código que hayamos escrito manualmente.

Para esto AjGenesis prevee un mecanismo muy sencillo y seguro.

Este se basa en pequeñas modificaciones a los archivos .ajg y los Templates dónde exista código que se quiera preservar.

Para ejemplificar vamos a seguir con nuestro ejemplo del post anterior sobre AjGenesis, el ignoto SyP.

Comenzamos con el archivo BuildVbNet2.ajg. Como ya vimos en estos archivos se define cómo aplicar los Templates, precisamente con los comandos AjBasic TransformerManager.Transform. En los TransformerManager.Transform que generan archivos que contienen código que hayamos generado manualmente y no queremos perder, tendremos que agregar un parámetro en su invocación. Este parámetro es un string que define la palabra clave que el AjGenesis entenderá que delimita los bloques de código que se pretenden preservar, en el ejemplo este argumento será PRESERVE, pero podría ser cualquier otro String.

Nosotros vamos a suponer que en el SyP vamos a necesitar preservar código en todos sus archivos .vb

Tomo cómo ejemplo el primer TransformerManager.Transform que genera un archivo .vb que encuentro en BuildVbNet2.ajg:

TransformerManager.Transform(“Templates/VbNet2/HomeAspxVb.tpl”,”${Project.BuildDir}/Src/${Project.Name}.WebClient/Default.aspx.vb”,Environment)

y quedará:

TransformerManager.Transform(“Templates/VbNet2/HomeAspxVb.tpl”,”${Project.BuildDir}/Src/${Project.Name}.WebClient/Default.aspx.vb”,Environment,”PRESERVE”)

SI el parámetro existe y luego no se usa no es problema.

En cada archivo donde se quiera preservar código se debera definir en que lugar quedará alojado, esto se realiza ubicando dos Comentarios PRESERVE,

‘ PRESERVE

……….

‘ PRESERVE

todo el código contenido entre estos dos Comentarios se preservará a través de las sucesivas generaciones.

A continuación de la palabra clave se puede escibir lo que se necesite para hacer claro el código. En este ejemplo defino dos bloques de preservación de código, uno al comienzo y otro al final del archivo. Ambos bloques quedan definidos con los siguientes dos pares de Comentarios:

‘ PRESERVE 1

‘ PRESERVE

y

‘ PRESERVE 2

‘ PRESERVE

 El 1 y el 2 son capricosos, podría haber escrito cualquier otra cosa que los diferencie.

En el ejemplo, por una razón de claridad en el código, prefiero usarlo así.

‘ PRESERVE 1 comment start

‘ PRESERVE 1 comment end

y

‘ PRESERVE 2 comment start

‘ PRESERVE 2 comment end

Lo que trae problemas es no diferenciar los bloques, como en el siguiente ejemplo;

‘ PRESERVE

‘ PRESERVE

y

‘ PRESERVE

‘ PRESERVE

Ahora, si estás trabajando con un sólo bloque de código, no hay problema en que se use así:

‘ PRESERVE

‘ PRESERVE

pero tiene que ser un sólo bloque a preservar.

Para que esto quede establecido, tengo que agregar estas lineas en cada Template dónde quiera que se apliquen y este es todo el trabajo.

Es importante destacar que si se define un bloque PRESERVE en un archivo cualquiera, pero no se define en su correspondiente Template este bloque no se preservará y se perderá con sus Comentarios PRESERVE incluidos.

El código de ejemplo pueden descargarlo desde acá SyPP.zip, ahora a generar escribir y volvera regenerar sin caer en ataque de pánico.

Espero sus comentarios. 🙂

Cooperator es otro Generador de Código, con otras características.

El proyecto está pensado y desarrollado fundamentalmente por Daniel Calvin y Eugenio Serrano.

Si bien Cooperator está en evolución, ya hay mucha gente que está desarrollando software de alta calidad con esta herramienta y entre ellos me cuento.

Hay muy buen material de este proyecto, les recomiendo algunos links con los que se puede avanzar mucho:

Con esto hay para entretenerse un buen rato. Voy a volver sobre este proyecto ya que va a ser una de las vertientes principales de mi blog.

Un afectuoso saludo a la buena gente de Cooperator Framework. 🙂

Después de las pruebas que fuí haciendo con NAnt, jugando con AjGenesis, encontré la forma de automatizar algunas tareas que me parece bueno compartir acá.

Quiero agradecer a Ángel “Java” López y a Claudio Meschini que en más de una ocasión me asistieron con sus generosos aportes, en el marco del Grupo Google CodeGeneration donde nos encontramos un grupo de profesionales con similares inquietudes. Este es un grupo abierto y están todos invitados a participar.

Para quien desconoce NAnt recomiendo la lectura de Introducción a NAnt.

Estos son algunos ejemplos de tareas que pueden ejecutarse dentro de un archivo .build.

  • Creación de un directorio virtual en Internet Information Server.

    <target name=”CreateVirtualDirectory”>
        <mkiisdir
            dirpath=”c:/inetpub/wwwroot/Prueba”
            vdirname=”Prueba”
            failonerror=”false”
        />
    </target>

Crea un directorio virtual de nombre Prueba en el directorio c:/inetpub/wwwroot/Prueba.

  • Armar un proyecto de tipo Class Library.

    <target name=”BuildCore”>
        <vbc target=”library” output=”C>/bin/Core.dll” rootnamespace=”Core” >
            <imports>
                <import namespace=”System” />
                <import namespace=”System.Data” />
                <import namespace=”System.Xml” />
                <import namespace=”Microsoft.Practices.EnterpriseLibrary.Common” />
                <import namespace=”Microsoft.Practices.EnterpriseLibrary.Data” />
            </imports>
            <references>
                <include name=”System.dll” />
                <include name=”System.Data.dll” />
                <include name=”System.Xml.dll” />
                <include name=”c:/Resources/Microsoft.Practices.EnterpriseLibrary.Common.dll” />
                <include name=”c:/Resources/Microsoft.Practices.EnterpriseLibrary.Data.dll” />
            </references>
            <sources>
                <include name=”c:/Repositories/Core/*.vb” />
            </sources>
            <resources prefix=”Core” dynamicprefix=”true”>
                <include name=”c:/Repositories/Core/*.resx” />
            </resources>
        </vbc>
    </target>

Crea un proyecto de tipo Class Library, crea referencias a System.dll, System.Data, System.Xml, Microsoft.Practices.EnterpriseLibrary.Common y Microsoft.Practices.EnterpriseLibrary.Data, en el caso de estas dos últimas, además indica que las busque en c:/Resources. Además incluye en el proyecto todos los archivos con extensiones .vb y .resx que encuentre en la carpeta c:/Repositories/Core.

  • Cambio de carpeta.

<cd dir=”Resources” />

Cambia la carpeta activa a la carpeta Resources, relativa a la carpeta activa actual.

  • Ejecutar conjuntos de tareas alternativas.

<choose>
    <when Carpeta=”c:/Repositories/Core”>
        …
    </when>
    <when Carpeta=”c:/Repositories/Core2″>
        …
    </when>
    <otherwise>
        <fail>Se produjo un error.</fail>
    </otherwise>
</choose>

Si existe la carpeta c:/Repositories/Core ejecuta un conjunto de tareas, análogamente para la carpeta c:/Repositories/Core2, si no existe ninguna se muestra un mensaje.

  • Registrar una dll.

<comregister file=”miDll.dll” />

  • Eliminar un directorio virtual de Internet Information Server.

<deliisdir vdirname=”WebClient” />

  • Leer de un archivo .ini.

<iniread property=”miVariable” filename=”Setup.ini” section=”Inicial” key=”Nombre” default=”Carlos”/>

Esta línea lee de un archivo llamado Setup.ini de la section Inicial, la key Nombre, si no la encuentra asume el valor Carlos y lo guarda en la variable miVariable. El archivo Setup.ini se ve de la siguiente manera:

[Inicial]
Nombre=Carlos

  • Escribir un valor en un archivo .ini.

<iniwrite filename=”Setup.ini” section=”Inicial” key=”Nombre” value=”Carlos”/>

Para el mismo archivo Setup.ini del caso anterior, en este ejemplo graba el valor Carlos en la key Nombre de la section Inicial.

Además se pueden ejecutar expresiones SQL, comandos svn, crear un setup Windows Installer, ejecutar comandos de Source Safe, validar archivos XML con XSD o XML Schemas, compilar programas hechos en Visual Basic 6 y mucho más.

Existen funciones para determinar cuantos archivos hay en una carpeta, definir si un archivo existe dentro de un conjunto de archivos, obtener una lista de archivos como un string delimitado, obtener el estado de un determinado servicio, su nombre, su friendly name y sigue la lista…

Si hacen un recorrido por los links que recomiendo en la nota Introducción a NAnt van a poder apreciar la magnitud de este proyecto que no deja de crecer.

A esta altura se les debe estar ocurriendo una pila de aplicaciones con NAnt, cada uno en su especialidad puede comenzar a automatizar tareas repetitivas con un grado de elasticidad alto.

Espero que se beneficien con esto y se animen a dejar un comentario. 🙂

Para quien no tenga experiencia en desarrollar software con AjGenesis, recomiendo leer Introducción a NAnt, Cómo generar código con AjGenesis sirviéndonos de NAnt – Parte I y Cómo generar código con AjGenesis sirviéndonos de NAnt – Parte II.

El código utilizado en esta nota puede descargarse desde acá.

Partiendo de ejemplos similares que usé en las notas anteriores, comenzamos con Natalia Requejo, estudiante avanzada de ingeniería electrónica, y avezada desarrolladora de software, a generar código.

Al ir diseñando el modelo de datos nos encontramos con la necesidad de referenciar a una misma tabla, más de una vez, desde otra única tabla. Por ejemplo, supongamos una Entidad Cliente, que se relaciona con Contacto, pero por cada Cliente podría tener varios Contactos, pero especializados, uno para Pagos y otro para Recepción. Planteamos el Modelo con los siguientes dos archivos XML:

<?xml version=”1.0″ encoding=”ISO-8859-1″ standalone=”yes”?>
<Entity>
    <Name>Cliente</Name>
    <Description>Cliente</Description>
    <SetName>Clientes</SetName>
    <Descriptor>Cliente</Descriptor>
    <SetDescriptor>Clientes</SetDescriptor>
    <SqlTable>Cliente</SqlTable>
    <Properties>
        <Property>
            <Name>Id</Name>
            <Description>Id</Description>
            <Type>Id</Type>
            <SqlType>int</SqlType>
        </Property>
        <Property>
            <Name>RazonSocial</Name>
            <Description>Razón Social</Description>
            <Type>Text</Type>
            <SqlType>varchar(64)</SqlType>
        </Property>
        <Property>
            <Name>IDContactoPago</Name>
            <Description>Contacto de Pago</Description>
            <Type>IdRef</Type>
            <SqlType>int</SqlType>
            <Reference>Contacto</Reference>
        </Property>
        <Property>
            <Name>IDContactoRecepcion</Name>
            <Description>Contacto de Recepción</Description>
            <Type>IdRef</Type>
            <SqlType>int</SqlType>
            <Reference>Contacto</Reference>
        </Property>
    </Properties>
</Entity>

y

<?xml version=”1.0″ encoding=”ISO-8859-1″ standalone=”yes”?>
<Entity>
    <Name>Contacto</Name>
    <Description>Contacto</Description>
    <SetName>Contactos</SetName>
    <Descriptor>Contacto</Descriptor>
    <SetDescriptor>Contactos</SetDescriptor>
    <SqlTable>Contacto</SqlTable>
    <Properties>
        <Property>
            <Name>Id</Name>
            <Description>Id</Description>
            <Type>Id</Type>
            <SqlType>int</SqlType>
        </Property>
        <Property>
            <Name>Nombre</Name>
            <Description>Nombre</Description>
            <Type>Text</Type>
            <SqlType>varchar(64)</SqlType>
        </Property>
        <Property>
            <Name>Apellido</Name>
            <Description>Apellido</Description>
            <Type>Text</Type>
            <SqlType>varchar(64)</SqlType>
        </Property>
    </Properties>
</Entity>

Al generar vimos que el actual esquema no contempla este caso. Hay nombres que se repiten y artefactos que se sobreescriben…

El tema de la nota es justamente cómo, modificando unas pocas línes de sólo cuatro Templates y agregando un nodo a un archivo XML, pudimos resolver muy rápidamente la situación.

Como primera medida agregamos un Nodo nuevo al archivo XML Cliente:

<?xml version=”1.0″ encoding=”ISO-8859-1″ standalone=”yes”?>
<Entity>
    <Name>Cliente</Name>
    <Description>Cliente</Description>
    <SetName>Clientes</SetName>
    <Descriptor>Cliente</Descriptor>
    <SetDescriptor>Clientes</SetDescriptor>
    <SqlTable>Cliente</SqlTable>
    <Properties>
        <Property>
            <Name>Id</Name>
            <Description>Id</Description>
            <Type>Id</Type>
            <SqlType>int</SqlType>
        </Property>
        <Property>
            <Name>RazonSocial</Name>
            <Description>Razón Social</Description>
            <Type>Text</Type>
            <SqlType>varchar(64)</SqlType>
        </Property>
        <Property>
            <Name>IDContactoPago</Name>
            <Description>Contacto de Pago</Description>
            <Type>IdRef</Type>
            <SqlType>int</SqlType>
            <Reference>Contacto</Reference>
            <ReferenceClasification>Pago</ReferenceClasification>
         </Property>
        <Property>
            <Name>IDContactoRecepcion</Name>
            <Description>Contacto de Recepción</Description>
            <Type>IdRef</Type>
            <SqlType>int</SqlType>
            <Reference>Contacto</Reference>
            <ReferenceClasification>Recepcion</ReferenceClasification>
        </Property>
    </Properties>
</Entity>

Las modificaciones están resaltadas en verde con negrita. Se trata del Nodo ReferenceClasification, que voy a usar en los Templates, justamente como un clasificador de estas dos referencias, inicialmente iguales.

Vamos ahora a los Templates. Tomo cómo base los mismos del ejemplo Cómo generar código con AjGenesis sirviéndonos de NAnt – Parte II.

Las Plantillas a modificar son cuatro, EntityFormWebVb.tpl, EntityCodeWebVb.tpl, EntityFormCodeWebVb.tpl y EntityWebVb.tpl

En el caso de EntityFormWebVb.tpl:

<#
message    “Generating Form for Entity ${Entity.Name}…”
include “Templates/VbNet2/VbFunctions.tpl”
include “Templates/EntityFunctions.tpl”
EntitySqlProperties    = SqlProperties(Entity)
EntityNoIdSqlProperties    = SqlNoIdProperties(Entity)
EntityIdProperty = IdProperty(Entity)
#>
<%@ Page Language=”vb” MasterPageFile=”~/MasterPages/MainMasterPage.master” AutoEventWireup=”false” CodeFile=”${Entity.Name}Update.aspx.vb” Inherits=”${WebPage.Prefix}${Entity.Name}UpdatePage” Title=”${Entity.Descriptor}”%>
<asp:Content ID=”Content1″ ContentPlaceHolderID=”MainContentPlaceHolder” Runat=”Server”>
            <br />
            <div class=”Normal”>
                <a href=”${Entity.SetName}.aspx” mce_href=”${Entity.SetName}.aspx”>${Entity.SetDescriptor}</a>
<%
    if IdEntity>0 then
%>
                <a href=”${Entity.Name}.aspx?Id=<%=IdEntity%>” mce_href=”${Entity.Name}.aspx?Id=<%=IdEntity%>”>${Entity.Descriptor}</a>      
<%
    end if
%>
            </div>
            <asp:Label id=”lblMensaje” runat=”server” CssClass=”Error” Visible=”False”></asp:Label>
            <asp:ValidationSummary id=”ValidationSummary1″ runat=”server” CssClass=”Error” DisplayMode=”List”></asp:ValidationSummary>
            <br />
            <table class=”DataTable” id=”tblDatos” cellspacing=”1″ cellpadding=”1″ width=”80%” border=”1″>
<#
for each Property in Entity.Properties where Property.Type <> “Id”
    if Property.Reference then
        RefDescriptorProperty = Property.Reference.DescriptorProperty
        RefIdProperty = IdProperty(Property.Reference)
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:DropDownList id=”ddl${Property.Reference.SetName}${Property.ReferenceClasification}” runat=”server” DataTextField=”${RefDescriptorProperty.Name}” DataValueField=”${RefIdProperty.Name}” DataSource=”<%# ${Property.Reference.SetName}${Property.ReferenceClasification} %>”>
                        </asp:DropDownList>
                    </td>
                </tr>
<#
    else
        if Property.Enumeration then
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:DropDownList id=”ddl${Property.Name}” runat=”server” DataTextField=”Description” DataValueField=”Id” DataSource=”<%# ${Property.Enumeration.Name}List %>”>
                        </asp:DropDownList>
                    </td>
                </tr>
<#
        else
#>          
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:TextBox id=”txt${Property.Name}” runat=”server” Text=”<%# Entity.${Property.Name} %>”>
                        </asp:TextBox></td>
                </tr>
<#              
        end if
    end if
end for
#>
            </table>
            <asp:Button id=”btnAccept” runat=”server” Text=”Accept”></asp:Button>
            <br />          
</asp:Content>

Nuevamente las modificaciones se encuentran en verde con negrita.

Verán que la modificación es simplemente insertar una variable que contenga el valor del nuevo Nodo ReferenceClasification del archivo XML, de esa forma logramos diferenciar las dos referencias.

Veamos que se modifica en los otros tres Templates:

EntityCodeWebVb.tpl

<#
include “Templates/VbNet2/Prologue.tpl”
#>
Imports ${Project.Name}.Services
Imports ${Project.Name}.Entities
Public Partial Class ${WebPage.Prefix}${Entity.Name}Page
    Inherits System.Web.UI.Page
    Public Entity As ${Entity.Name}
    Public Property IdEntity() As Integer
        Get
            Return DirectCast(ViewState(“IdEntity”), Integer)
        End Get
        Set(ByVal Value As Integer)
            ViewState(“IdEntity”) = Value
        End Set
    End Property
<#
    for each Property in Entity.Properties where Property.Reference
#>
    Public Entity${Property.Reference.Name}${Property.ReferenceClasification} as ${Property.Reference.Name}
<#
    end for
#>
<#
    for each Property in Entity.Properties where Property.Enumeration
#>
    Public ReadOnly Property ${Property.Name}Description as String
        Get
            return Enumerations.Translate(Enumerations.${Property.Enumeration.Name}List, Entity.${Property.Name})
        End Get
    End Property
<#
    end for
#>
    Protected Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
        ‘Put user code to initialize the page here
        If Not IsPostBack Then
            IdEntity = CInt(Request(“Id”))
            Entity = ${Entity.Name}Service.GetById(IdEntity)
<#
    for each Property in Entity.Properties where Property.Reference
#>
            Entity${Property.Reference.Name}${Property.ReferenceClasification} = ${Property.Reference.Name}Service.GetById(Entity.${Property.Name})
<#
    end for
    for each Relation in Entity.Relations where Relation.RelationType=”Referenced”
#>
            ‘gvwData${Relation.Entity.SetName}.DataSource = ${Relation.Entity.Name}Service.GetBy${Entity.Name}Ex(IdEntity)
<#
    end for
#>
            DataBind()
        End If
    End Sub
End Class

Como en el Template anterior, con insertar la misma variable en dos líneas alcanza.

EntityFormCodeWebVb.tpl

<#
message    “Generating Form Code Behind for Entity ${Entity.Name}…”
include “Templates/VbNet2/VbFunctions.tpl”
include “Templates/EntityFunctions.tpl”
EntitySqlProperties    = SqlProperties(Entity)
EntityNoIdSqlProperties    = SqlNoIdProperties(Entity)
EntityIdProperty = IdProperty(Entity)
include “Templates/VbNet2/Prologue.tpl”
#>
Imports System
Imports System.Data
Imports ${Project.Name}.Services
Imports ${Project.Name}.Entities
Public Partial Class ${WebPage.Prefix}${Entity.Name}UpdatePage
    Inherits System.Web.UI.Page
    Public Entity As ${Entity.Name}
    Public Property IdEntity() As Integer
        Get
            Return DirectCast(ViewState(“IdEntity”), Integer)
        End Get
        Set(ByVal Value As Integer)
            ViewState(“IdEntity”) = Value
        End Set
    End Property
<#
    for each Property in Entity.Properties where Property.Reference
#>
    Public ${Property.Reference.SetName}${Property.ReferenceClasification} as DataView
<#
    end for
#>
<#
    for each Property in Entity.Properties where Property.Enumeration
#>
    Public ReadOnly Property ${Property.Enumeration.Name}List as IList
        Get
            return Enumerations.${Property.Enumeration.Name}List
        End Get
    End Property
<#
    end for
#>
    Protected Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
        ‘Put user code to initialize the page here
        If Not IsPostBack Then
            If Request(“Id”) Is Nothing then
                IdEntity = 0
                Entity = new ${Entity.Name}
            else
                IdEntity = CInt(Request(“Id”))
                Entity = ${Entity.Name}Service.GetById(IdEntity)
            End If
<#
    for each Property in Entity.Properties where Property.Reference
#>
            ${Property.Reference.SetName}${Property.ReferenceClasification} = Get${Property.Reference.SetName}${Property.ReferenceClasification}
<#
    end for
#>
                DataBind()
            if IdEntity>0 then
<#
    for each Property in Entity.Properties where Property.Reference
#>
                ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue = Entity.${Property.Name}
<#
    end for
#>
<#
    for each Property in Entity.Properties where Property.Enumeration
#>
                ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue = Entity.${Property.Name}
<#
    end for
#>
            else
<#
    for each Property in Entity.Properties where Property.Reference
#>
                if not Request(“${Property.Name}”) is nothing then
                    ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue = CInt(Request(“${Property.Name}”))
                end if
<#
    end for
#>
            end if
        End If
    End Sub
    Private Function FormValidate() As Boolean
        Return True
    End Function
<#
    for each Property in Entity.Properties where Property.Reference
#>
    Private Function Get${Property.Reference.SetName}${Property.ReferenceClasification}() As IList
        Dim ds As DataSet
        ds = ${Property.Reference.Name}Service.GetList
        Dim dr As DataRow
        dr = ds.Tables(0).NewRow
<#
        if not Property.Required then
#>
        dr(“Id”) = 0
        dr(“${Property.Reference.DescriptorProperty.Name}”) = “”
        ds.Tables(0).Rows.Add(dr)
<#
        end if
#>
        Dim dw As New DataView(ds.Tables(0))
        dw.Sort = “${Property.Reference.DescriptorProperty.Name}”
        Return dw
    End Function
<#
    end for
#>
    Private Sub Update()
        if IdEntity>0 then
            Entity = ${Entity.Name}Service.GetById(IdEntity)
        else
            Entity = New ${Entity.Name}()
        end if
<#
for each Property in EntityNoIdSqlProperties
    if Property.Reference then
#>
        Entity.${Property.Name} = ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue
<#
    else
        if Property.Enumeration then
#>
        Entity.${Property.Name} = ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue
<#
        else
#>      
        Entity.${Property.Name} = txt${Property.Name}.Text
<#
        end if
    end if
end for
#>      
        If IdEntity = 0 Then
            ${Entity.Name}Service.Insert(Entity)
        Else
            ${Entity.Name}Service.Update(Entity)
        End If
    End Sub
    Private Sub btnAccept_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAccept.Click
        If Not IsValid Then
            Return
        End If
        Try
            If FormValidate() Then
                Update()
                If IdEntity = 0 Then
                    Server.Transfer(“${Entity.SetName}.aspx”)
                Else
                    Server.Transfer(“${Entity.Name}.aspx?Id=” & IdEntity)
                End If
            End If
        Catch Ex As Exception
            lblMensaje.Visible = True
            lblMensaje.Text = Ex.Message
        End Try
    End Sub
End Class

Acá tuvimos que modificar más líneas, pero en todos los casos se trata de insertar el valor del Nodo ReferenceClasification

EntityWebVb.tpl

<#
message    “Generating Page for Entity ${Entity.Name}…”
include “Templates/VbNet2/VbFunctions.tpl”
include “Templates/EntityFunctions.tpl”
EntitySqlProperties    = SqlProperties(Entity)
EntityNoIdSqlProperties    = SqlNoIdProperties(Entity)
EntityIdProperty = IdProperty(Entity)
#>
<%@ Page Language=”VB” MasterPageFile=”~/MasterPages/MainMasterPage.master” AutoEventWireup=”false” CodeFile=”${Entity.Name}.aspx.vb” Inherits=”${WebPage.Prefix}${Entity.Name}Page” Title=”${Entity.Descriptor}”%>
<%@ Register src=”~/Controls/Subtitle.ascx” mce_src=”~/Controls/Subtitle.ascx” TagName=”Subtitle” TagPrefix=”uc1″ %>
<asp:Content ID=”Content1″ ContentPlaceHolderID=”MainContentPlaceHolder” Runat=”Server”>
            <br />
            <div class=”Normal”>
                <a href=”${Entity.SetName}.aspx” mce_href=”${Entity.SetName}.aspx”>${Entity.SetDescriptor}</a>
                <a href=”${Entity.Name}Update.aspx?Id=<%=IdEntity%>” mce_href=”${Entity.Name}Update.aspx?Id=<%=IdEntity%>”>Update</a>
                <a href=”${Entity.Name}Delete.aspx?Id=<%=IdEntity%>” mce_href=”${Entity.Name}Delete.aspx?Id=<%=IdEntity%>”>Delete</a>
            </div>
            <br />
            <table class=”DataTable” id=”tblDatos” cellspacing=”1″ cellpadding=”1″ width=”80%” border=”1″>
<#          
for each Property in Entity.Properties
    if Property.Reference then
        RefDescriptorProperty = Property.Reference.DescriptorProperty
        RefIdProperty = IdProperty(Property.Reference)
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:HyperLink id=”lnk${Property.Name}” runat=”server” Text=”<%# Entity${Property.Reference.Name}${Property.ReferenceClasification}.${RefDescriptorProperty.Name} %>” NavigateUrl='<%# “${Property.Reference.Name}.aspx?Id=” & Entity${Property.Reference.Name}${Property.ReferenceClasification}.Id %>’>
                        </asp:HyperLink>
                    </td>
                </tr>
<#
    else
        if Property.Enumeration then
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:Label id=”lbl${Property.Name}” runat=”server” Text=”<%# ${Property.Name}Description %>”>
                        </asp:Label></td>
                </tr>
<#
        else
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:Label id=”lbl${Property.Name}” runat=”server” Text=”<%# Entity.${Property.Name} %>”>
                        </asp:Label></td>
                </tr>
<#
        end if
    end if      
end for
#>
            </table>
</asp:Content>

En este último Template hay que insertar esta variable en una única línea.

Estas son todas las modificaciones que hay que realizar para extender el generador de forma tal que resulva estas relaciones. Con esto queda demostrado que AjGenesis presenta un modelo de generación de código fácilmente extendible.

Para quien se inicia en el mundo de estos Templates, una forma de sencilla de entenderlos, es leer paralelamente el Template y el Artefacto que genera, de esa forma se facilita la comprensión de este lenguaje de scripts que nos propone Ángel “Java” López.

Cuando tuve esto funcionando le mostré a Ángel la idea y me respondió:

……Podrias poner, en algun momento, para completar el modelo, algo como:    if Property.Reference and not Property.ReferenceClassification and Property.Name.BeginsWith(“Id”) then
        Property.ReferenceClassification = Property.Name.Substring(….)
    end if……

 

 

Para el que guste, se puede seguir por allí.

Ángel suele hacer hincapié en esta interesante idea de Completar el Modelo

Si desean pueden bajarse el código de ejemplo desde acá, hacer pruebas y espero quieran dejar sus comentarios. 🙂

Sigo en la misma linea de la nota anterior.

Ahora la idea es generar una aplicación web completa en VB.NET para Visual Studio 2005, trabajando con SQL Server 2005.

Esta vez subo un conjunto de archivos somo ejemplo para que quien quiera haga sus pruebas.

Los archivos pueden bajarse desde este link SyP.zip 

Pero para esto recomiendo tener funcionando el HelloWorld del ejemplo anterior, de esta forma ya tenemos garantizado el funcionamiento de AjGenesis con NAnt correctamente.

AjGenesis, el generador de código de Ángel “Java” López, permite, entre otras cosas, definir el lenguaje en el que se quiere trabajar, Ángel tiene ejemplos publicados en VB.NET, C#, PHP, Java, en cuanto a bases de datos hay para MySQL, SQL Server 2000, y SQL Server 2005, cualquiera que quiera extender este modelo sólo debe escribir el Template y agregarlo en la Tarea que corresponda.

Como si fuera poco Ángel publica ejemplos con Modelos en Domain Driven Design, usando NHibernate, por separado y combinados también.

En esta versión de AjGenesis, Ángel, agregó un archivo .xml de configuración, en nuestro caso de nombre VbNet2.xml, en el que se define el Dialecto de Programación, el Dialecto de Base de Datos y valores de la conexión a la misma.

Los Dialectos definen el juego de Templates que se van a utilizar, en nuestro ejemplo serán los correspondientes a VB.NET para Visual Studio 2005 y SQL Server 2005.

Los Templates o Plantillas son archivos con extensión .tpl y las Tareas o Tasks son archivos .ajg, ambos archivos de texto.

Los Templates son la base del código que se autogenerará a partir de AjGenesis, los Templates “leen” el Modelo, la estructura de datos, de un conjunto de archivos .xml.

En estos archivos .xml se definen los nombres de las Entidades y campos, los tipos, longitudes y las relaciones entre Entidades.

Existe un archivo .xml que define al conjunto de las Entidades que componen al Modelo, en nuestro ejemplo será Project.xml, luego por cada Entidad tendremos un archivo .xml.

Como escribí antes, los Templates “leen” el Modelo de estos archivos .xml, los Templates se aplican al ejecutar las Tareas.

Por último para ejecutar las Tareas necesitamos NAnt, en un archivo .build se orquestan las Tareas y por línea de comando se ejecuta el archivo NAnt.El ejemplo que voy a desarrollar, como escribí antes, se continúa con el HelloWorld de la nota anterior, es decir, voy a seguir usando la misma estructura de directorios, a quién le agrade podrá usar la misma, sino el modelo permite cualquier otra, pero hay que tener particular cuidado de corregir todas las referencias necesarias.

El ejemplo que voy a tomar es el la aplicación AjSaaS que Ángel “Java” López publica en AjGenesisExamples3.

Pueden bajarse el conjunto de archivos desde acá: SyP.zip, en mi ejemplo yo lo ubico en la carpeta SyP, debajo de la misma veremos el siguiente árbol:

Carpeta Raiz

En la carpeta SyP, nuestra raiz, encontramos el archivo NAnt, en este caso, AjSaaSVbNet2.build.

En Libraries encontraremos las dlls que necesitaremos, este ejemplo usa AjFramework para acceder a datos, entonces encontraremos una carpeta AjFramework y dentro de ella AjFramework.Core.dll y AjFramework.Data.dll. Para quien quiera conocer el AjFramework, otro desarrollo de Ángel “Java” López lo puede encontrar acá, AjFramework, con código fuente disponible.

En Projects se almacenan los Modelos, un Modelo por carpeta, nosotros vamos a ver una carpeta con nombre AjSaaS, que contiene Project.xml, en el que se definen las Entidades intervinientes, veamos: 

<?xml version=”1.0″ encoding=”ISO-8859-1″ standalone=”yes”?>

<Project>

            <Name>AjSaaS</Name>

            <Description>A sample Software as a Service application</Description>

            <Company>ajlopez.com</Company>

            <CompanyName>ajlopez</CompanyName>

            <SystemName>AjSaaS</SystemName>

            <Language>En</Language>

            <Prefix>AjSaaS</Prefix>

            <OpenSource>true</OpenSource>

            <Model>

                        <Entities>

                                    <Entity Source=”Entities/Tenant.xml”/>

                                    <Entity Source=”Entities/Provider.xml”/>

                                    <Entity Source=”Entities/User.xml”/>

                                    <Entity Source=”Entities/Application.xml”/>

                                    <Entity Source=”Entities/Role.xml”/>

                                    <Entity Source=”Entities/UserRole.xml”/>

                                    <Entity Source=”Entities/TenantApplication.xml”/>

                                    <Entity Source=”Entities/Entity.xml”/>

                                    <Entity Source=”Entities/EntityProperty.xml”/>

                                    <Entity Source=”Entities/EntityApplication.xml”/>

                                    <Entity Source=”Entities/Contact.xml”/>

                                    <Entity Source=”Entities/ToDoItem.xml”/>

                        </Entities>

             </Model>

</Project>

 

 

En la carpeta Projects también encontramos otras dos carpetas más, Entities y Technologies. En Entities tenemos un archivo .xml por cada Entidad que compone al Modelo, por ejemplo Role.xml: 

 

<?xml version=”1.0″ encoding=”ISO-8859-1″ standalone=”yes”?>

<Entity>

            <Name>Role</Name>

            <Description>Role</Description>

            <SetName>Roles</SetName>

            <Descriptor>Role</Descriptor>

            <SetDescriptor>Roles</SetDescriptor>

            <SqlTable>roles</SqlTable>

            <Properties>

                        <Property>

                                    <Name>Id</Name>

                                    <Description>Id</Description>

                                    <Type>Id</Type>

                                    <SqlType>int</SqlType>

                        </Property>

                        <Property>

                                    <Name>RoleName</Name>

                                    <Description>Role Name</Description>

                                    <Type>Text</Type>

                                    <SqlType>varchar(40)</SqlType>

                                    <Required>true</Required>

                        </Property>

                        <Property>

                                    <Name>Description</Name>

                                    <Description>Description</Description>

                                    <Type>Text</Type>

                                    <SqlType>varchar(200)</SqlType>

                                    <Required>true</Required>

                        </Property>

                        <Property>

                                    <Name>IdApplication</Name>

                                    <Description>Application</Description>

                                    <Type>IdRef</Type>

                                    <SqlType>int</SqlType>

                                    <Reference>Application</Reference>

                        </Property>

            </Properties>

</Entity>

 

 

 

Los primeros nodos son respectivamente para definir Nombre, Descripción, Nombre de Conjunto, Descriptor, Descriptor de Conjunto, y nombre de tabla de base de datos, esto es definir para una determinada Entidad cómo se la conocerá y describirá en el código que se autogenere acerca de ella.

Después está Properties, una colección de nodos Property, que definen los campos de la tabla de base de datos y las propiedades de la Entidad relacionada, todas las Property tienen nodos Name, Description, Type y SqlType.

Type puede valer:

Id: Clave Primaria de la tabla de base de datos.

Int: Cualquier entero.

Date: para fechas.

Decimal: para decimales.

Text: Cualquier tipo de cadena.

IdRef: para definir relaciones con otras Entidades y Foreign Keys en las relaciones de tablas de bases de datos.

 

Si Type vale IdRef, entonces hay que agregar el nodo Reference, para referenciar a la tabla con que se quiere vincular.

El nodo SqlType define el tipo SQL Server.

Con esto tenemos bien entendido el Modelo, ahora vamos a la carpeta Technologies, en ella encontraremos el archivo VbNet2.xml: 

<Technology>

            <Programming>

                        <Dialect>VbNet2</Dialect>

            </Programming>

            <Database>

                        <Dialect>MsSql2005</Dialect>

                        <Name>AjSaaS</Name>

                        <Username>sa</Username>

                        <Prefix>AjSaaS_</Prefix>

                        <Trusted_Connection>yes</Trusted_Connection>

                        <Host>SPICA\SQLEXPRESS</Host>

            </Database>

</Technology> 

 

 

Podemos leer claramente cómo se definen los Dialectos y los valores de conexión a base de datos, como escribí más arriba.

Ya estamos en condiciones de ver el archivo NAnt, AjSaaSVbNet2.build, acá su contenido: 

 

<?xml version=”1.0″?>

 <project name=”AjSaaS VB.NET 2.0 Web Site” default=”04-Build”>

     <property name=”project.name” value=”AjSaaS”/>

     <property name=”technology” value=”VbNet2″/>

     <property name=”src.dir” value=”.”/>

     <property name=”templates.dir” value=”${src.dir}/Templates”/>

     <property name=”tasks.dir” value=”${src.dir}/Tasks”/>

     <property name=”ajgenesis.dir” value=”C:.5″/>

     <property name=”nanttasks.dir” value=”${ajgenesis.dir}/bin”/>

     <property name=”website.dir” value=”${src.dir}/Build/${project.name}/${technology}/${project.name}/src/${project.name}.WebClient”/>

     <property name=”project.dir” value=”${src.dir}/Projects/${project.name}”/>

     <property name=”lib.dir” value=”${src.dir}/Libraries”/>

     <property name=”build.dir” value=”${src.dir}/Build/${project.name}/${technology}”/>

 

     <target name=”00-Clean” description=”Cleans Build Directory”>

         <delete dir=”${build.dir}” if=”${directory::exists(build.dir)}” />

     </target>

     <target name=”01-LoadTasks” description=”Loads AjGenesis Tasks” >

         <loadtasks assembly=”${nanttasks.dir}/AjGenesis.NAnt.dll” />

     </target>

     <target name=”02-Init” depends=”01-LoadTasks” description=”Init the AjGenesis model, Create build directory”>

         <mkdir dir=”${build.dir}”/>

         <loadmodel model=”${project.dir}/Project.xml”/>

         <loadmodel model=”${project.dir}/Technologies/${technology}.xml”/>

        <executetask task=”${tasks.dir}/BuildProject2.ajg”/>

        <executetask task=”${tasks.dir}/BuildTechnology.ajg”/>

    </target>

    <target name=”03-BuildSQL” depends=”02-Init”>

        <executetask task=”${tasks.dir}/BuildSql.ajg”/>

    </target>

    <target name=”04-Build” depends=”02-Init”>

        <executetask task=”${tasks.dir}/BuildProg.ajg”/>

        <copy todir=”${build.dir}/Src/Libraries”>

            <fileset basedir=”${lib.dir}/AjFramework”>

                <include name=”**/*”/>

            </fileset>

        </copy>

    </target>

    <target name=”05-DeploySQL” depends=”03-BuildSQL”>

        <exec program=”${build.dir}/Sql/CreateDatabase.bat”/>

    </target>

</project

 

 

 

Las primeras líneas del archivo ya están explicadas en la nota anterior, veremos algunas pequeñas diferencias como la definición de la carpeta de Technologies.

En la Target 02-Init podemos ver cómo se carga el Modelo y las Technologies a utilizar, luego se ejecutan las Tasks BuildProject2.ajg y BuildTechnologies.ajg.

Las Targets que más nos interesan son:

 

 

03-BuildSql, crea un script que creará todas las tablas, stored procedures, claves primarias y foráneas y se establecen las relaciones

04-Build, genera la solución con sus proyectos.

05-DeploySql, ejecuta 03-BuildSql y a continuación crea un archivo de lotes de nombre CreateDatabase.bat que ejecuta el script creado en la Targer 03-BuildSql y lo ejecuta. De esta forma se crea la base de datos automáticamente.

Las Tareas se ejecutan de la siguiente forma, abrimos una ventana de línea de comando y nos ubicamos en la carpeta que contiene al archivo NAnt AjSaaSVbNet2.build.

Si tipeamos NAnt y <Enter> se ejecutará la Target por default que es 04-Build, como se define en la segunda linea del archivo: 

 

 

<project name=”AjSaaS VB.NET 2.0 Web Site” default=”04-Build”> 

 

 

Si quisiéramos ejecutar una Target que no sea la definida como default, por ejemplo, 03-BuildSql, tipeamos lo siguiente: 

 

 

NAnt 03-BuildSql 

En nuestro caso ejecutando las Targets 04-Build y 05-DeploySql estaría todo hecho.

Para ejecutar dos Tareas con NAnt tipeamos lo siguiente: 

 

 

NAnt 04-Build 05-DeploySql 

La salida de esto es una larga lista de mensajes que al final incluye el feliz BUILD SUCCEEDED, sino algo falló. Hay que revisar las definiciones y reintentarlo. NAnt tiene que haber creado unas carpetas debajo de SyP con la siguiente estructura: 

Carpeta Build

 

En SyP\Build\AjSaaS\VbNet2\Sql están CreateDatabase.bat y Database.sql, que son respectivamente el encargado de ejecutar el script contra la base de datos y el script en si mismo.

En SyP\Build\AjSaaS\VbNet2\src está el archivo de la solución con todo lo que lo compone.

La solución se genera de tal modo que haciendo un doble click sobre el archivo .sln, para abrirlo con Visual Studio 2005 y con un simple F5 en ese ambiente, ya ejecuta sin ningún error ni ajuste adicional.

Como dice Ángel “Java” Lópezesto es ver la luz

Ahora sólo tienen que defiir un Modelo propio, crear su propio Project.xml y los archivos .xml de las Entidades y ya están en condiciones de generar una nueva aplicación.

Bienvenidos. )

Hay situaciones en programación que no son de resolución trivial.

Supongamos que tenemos instanciado un objeto servidor y necesitamos conocer algún cambio de estado en el mismo.

Podríamos solucionarlo con un Timer, pero como solución es excesivamente costosa e impráctica. Hay quien sencillamente programa una función en el objeto servidor que avisa al cliente, un horror, desde el punto de vista de la orientación a objetos, impide la reutilización de código, etc.

Para este tipo de situaciones existen salidas más elegantes y efectivas que es lo que vamos a ver en esta nota.

Los delegados son algo similar a los punteros a funciones en C++, pero como corren sobre el Framework de .NET se trata de código administrado (code managed) y de tipo seguro.

Se usan para implementar métodos de devolución de llamada, callbacks, que son fundamentales para lograr:programar procesamiento asíncrono o para insertar código de cliente, entre las instancias que produce el servidor, para no usar timers donde no sea necesario.

Los métodos de devolución de llamada se usan en procesamiento asíncrono porque se desconoce el tiempo que va a tardar en ejecutarse el método que se invoca y que potencialmente podría ser largo.

En este caso vamos a ver como se usan delegados para insertar código de cliente, entre las devoluciones de llamada que produce el servidor.

Para entender esto, vamos a pensar en una aplicación que toma mediciones que pueden tardar en completarse, para el ejemplo no nos importa la medición, por eso la vamos a implementar como una medición simulada en una clase de nombre Measures. Tendremos además otra clase, Manager, que administra las mediciones y una aplicación de consola de nombre App.

Veamos primero la clase Measure: 

[C#]

using System; class Measure{

     /// <summary>

     /// Variable protegida que encapsula el valor de la propiedad Name.

    /// </summary>

     /// <remarks></remarks>

     protected string Name;

     /// <summary>

     /// Constructor de la clase que implementa una Medición simulada.

     /// </summary>

     /// <param name=”name”>Nombre concreto de la Medición.</param>

     /// <remarks>Es una forma de diferenciar las distintas instacias

    /// que se produzcan de esta clase.</remarks>

     public Measure(string name)

    {

        this.name = name;

    }

    /// <summary>

    /// Propiedad Name.

    /// </summary>

    /// <value></value>

    /// <returns>Nombre (Name) de la instancia.</returns>

    /// <remarks>Las últimas dos lineas del get simulan una demora en la

    /// toma de la medición, pongo un beep para que sea más perceptible la

    /// demora entre callbacks. Esto para poder apreciar mejor que se está

    /// haciedo un callback por cada medición, de otra forma el tipo de output de

    /// consola no da idea de cómo se ejecuta el código.</remarks>

    public string name

    {

        get

        {

            for (int i = 0; i <= 100000000; i++) { }            System.Media.SystemSounds.Beep.Play();            return this.Name;        }

        set

        {

            this.Name = value;

        }

    }

} 

[VB]

Imports System 

Public Class Measure

    ”’ <summary>

    ”’ Variable protegida que encapsula el valor de la propiedad Name.

    ”’ </summary>

    ”’ <remarks></remarks>

    Protected strName As String

    ”’ <summary>

    ”’ Constructor de la clase que implementa una Medición simulada.

    ”’ </summary>

    ”’ <param name=”name”>Nombre concreto de la Medición.</param>

    ”’ <remarks>Es una forma de diferenciar las distintas instacias

    ”’ que se produzcan de esta clase.</remarks>

    Public Sub New(ByVal name As String)

        MyBase.New()

        Me.name = name

    End Sub

    ”’ <summary>

    ”’ Propiedad Name.

    ”’ </summary>

    ”’ <value></value>

    ”’ <returns>Nombre (Name) de la instancia.</returns>

    ”’ <remarks>Las últimas tres lineas del Get simulan una demora en la

    ”’ toma de la medición, pongo un beep para que sea más perceptible la

    ”’ demora entre callbacks. Esto para poder apreciar mejor que se está

    ”’ haciedo un callback por cada medición, de otra forma el tipo de output de

    ”’ consola no da idea de cómo se ejecuta el código.</remarks>

    Public Property name() As String

        Get

            Dim i As Int64            For i = 0 To 100000000 : Next            Beep()            Return strName

        End Get

        Set(ByVal value As String)

            strName = value

        End Set

    End Property

End Class 

 

Seguimos ahora con la clase Manager: 

[C#]

using System; 

class Manager{

    /// <summary>

    /// Es una constante donde se define la cantidad de

    /// mediciones simuladas que se harán.

    /// </summary>

    /// <remarks></remarks>

    int kMeasure = 4;

    /// <summary>

    /// Arreglo de Measures que soporta las mediciones.

    /// </summary>

    /// <remarks></remarks>

    static Measure[] Measures;

    /// <summary>

    /// Definición de la función Delegada

    /// </summary>

    /// <param name=”pMeasure”>Recibe un objeto Measure como entrada</param>

    /// <remarks>Esta es la función sobre la que se implementa el método

    /// de devolución de llamada (callback) al nombre de esta función se

    /// lo postfija con Callback por convención con la idea de facilitar

    /// la lectura.</remarks>

    public delegate void EnumMeasuresCallback(Measure measure);

    /// <summary>

    /// Agrega mediciones simuladas.

    /// </summary>

    /// <remarks>Se agregan tantas como hayan definido en kMeasure.</remarks>

    public void AddMeasures()

    {

        Measures = new Measure[kMeasure];

        for (System.Int64 i = 0; i < kMeasure; i++)

        {

            Measures[i] = new Measure(“Measure “ + (i + 1));

        }

    }

    /// <summary>

    /// Enumera mediciones, es la función que se invocará desde la aplicación.

    /// </summary>

    /// <param name=”callback”>Función delegada.</param>

    /// <remarks>Aceptando una función delegada como parámetro de entrada

    /// es la forma en que en que queda establecido efectivamente el callback.</remarks>

    public static void EnumMeasures(EnumMeasuresCallback callback)

    {

        foreach (Measure measure in Measures)

        {

            callback(measure);

        }

    }

}  

[VB]

Imports System 

Public Class Manager

    ”’ <summary>

    ”’ Es una constante donde se define la cantidad de

    ”’ mediciones simuladas que se harán.

    ”’ </summary>

    ”’ <remarks></remarks>

    Private Const kMeasure As Integer = 4

    ”’ <summary>

    ”’ Arreglo de Measures que soporta las mediciones.

    ”’ </summary>

    ”’ <remarks></remarks>

    Private Shared Measures(kMeasure) As Measure

    ”’ <summary>

    ”’ Definición de la función Delegada

    ”’ </summary>

    ”’ <param name=”pMeasure”>Recibe un objeto Measure como entrada</param>

    ”’ <remarks>Esta es la función sobre la que se implementa el método

    ”’ de devolución de llamada (callback) al nombre de esta función se

    ”’ lo postfija con Callback por convención con la idea de facilitar

    ”’ la lectura.</remarks>

    Public Delegate Sub EnumMeasuresCallback(ByVal pMeasure As Measure)

    ”’ <summary>

    ”’ Agrega mediciones simuladas.

    ”’ </summary>

    ”’ <remarks>Se agregan tantas como hayan definido en kMeasure.</remarks>

    Public Sub AddMeasures()

        Dim i As Integer

        For i = 0 To kMeasure

            Measures(i) = New Measure((“Measure “ + CStr(i + 1)))

        Next i

    End Sub

    ”’ <summary>

    ”’ Enumera mediciones, es la función que se invocará desde la aplicación.

    ”’ </summary>

    ”’ <param name=”callback”>Función delegada.</param>

    ”’ <remarks>Aceptando una función delegada como parámetro de entrada

    ”’ es la forma en que en que queda establecido efectivamente el callback.</remarks>

    Public Shared Sub EnumMeasures(ByVal callback As EnumMeasuresCallback)

        For Each mMeasure As Measure In Measures

            callback(mMeasure)

        Next

    End Sub

End Class 

 

Las lineas más destacables de Manager son  

[C#]

public delegate void EnumMeasuresCallback(Measure measure);

[VB]

Public Delegate Sub EnumMeasuresCallback(ByVal pMeasure As Measure)

Esta es la definición de la función sobre la que se implementará el callback 

[C#]

    public static void EnumMeasures(EnumMeasuresCallback callback)

    {

        foreach (Measure measure in Measures)

        {

            callback(measure);

        }

    }

[VB]

    Public Shared Sub EnumMeasures(ByVal callback As EnumMeasuresCallback)

        For Each mMeasure As Measure In Measures

            callback(mMeasure)

        Next

    End Sub

Esta es la función que se invocará desde la aplicación (que todavía no vimos), su característica más saliente es que recibe una función delegada como parámetro de entrada. 

Por último la aplicación de consola: 

[C#]

using System;

/// <summary>

/// Esta es la aplicación desde dónde compruebo el funcionamiento de los callbacks.

/// </summary>

/// <remarks>Esta primera línea de código

/// Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

/// En esta definición es dónde efectivamente relaciono la función delegada

/// con una función de la aplicación. En otras palabras, delego la función

/// EnumMeasuresCallback (definida en Manager) en MeasuresCallback (definida

/// localmente). En esta misma linea estoy definiendo la varibal miCallback,

/// de tipo Delegate, implícito en Manager.EnumMeasuresCallback.</remarks>

class App{

    public static Manager.EnumMeasuresCallback miCallback = new Manager.EnumMeasuresCallback(MeasuresCallback);

    /// <summary>

    /// Acá se inserta el código que se quiere ejecutar por cada callback.

    /// </summary>

    /// <param name=”pMeasure”></param>

    /// <remarks></remarks>

    public static void MeasuresCallback(Measure measure)

    {

        Console.WriteLine(“Callback “ + measure.name);

    }

    /// <summary>

    /// Rutina principal de la aplicación

    /// </summary>

    /// <remarks>Con mgr.AddMeasures(), agrego las mediciones simuladas.

    /// En la invocación

    /// Manager.EnumMeasures(miCallback)

    /// está la llamada a la función que implementa el callback.

    /// En la clase Manager podrá verse que es la función

    /// que recibe un Delegate como parámetro de entrada.

    /// Es así que le paso el delegado que definí más arriba

    /// con nombre miCallback.

    /// </remarks>

    public static void Main()

    {

        Manager mgr = new Manager();

        Console.WriteLine(“Agrego mediciones”);

        mgr.AddMeasures();

        Console.WriteLine(“Entra”);

        Manager.EnumMeasures(miCallback);

        Console.WriteLine(“Sale”);

        Console.ReadLine();

    }

} 

[VB]

Imports System 

”’ <summary>

”’ Esta es la aplicación desde dónde compruebo el funcionamiento de los callbacks.

”’ </summary>

”’ <remarks>Esta primera línea de código

”’ Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

”’ En esta definición es dónde efectivamente relaciono la función delegada

”’ con una función de la aplicación. En otras palabras, delego la función

”’ EnumMeasuresCallback (definida en Manager) en MeasuresCallback (definida

”’ localmente). En esta misma linea estoy definiendo la varibal miCallback,

”’ de tipo Delegate, implícito en Manager.EnumMeasuresCallback.</remarks>

Class App

    Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

    ”’ <summary>

    ”’ Acá se inserta el código que se quiere ejecutar por cada callback.

    ”’ </summary>

    ”’ <param name=”pMeasure”></param>

    ”’ <remarks></remarks>

    Public Shared Sub MeasuresCallback(ByVal pMeasure As Measure)

        Console.WriteLine((“Callback “ + pMeasure.name))

    End Sub

    ”’ <summary>

    ”’ Rutina principal de la aplicación

    ”’ </summary>

    ”’ <remarks>Con mgr.AddMeasures(), agrego las mediciones simuladas.

    ”’ En la invocación

    ”’ Manager.EnumMeasures(miCallback)

    ”’ está la llamada a la función que implementa el callback.

    ”’ En la clase Manager podrá verse que es la función

    ”’ que recibe un Delegate como parámetro de entrada.

    ”’ Es así que le paso el delegado que definí más arriba

    ”’ con nombre miCallback.

    ”’ </remarks>

    Public Shared Sub Main()

        Dim mgr As Manager = New Manager

        Console.WriteLine(“Agrego Mediciones”)

        mgr.AddMeasures()

        Console.WriteLine(“Entra”)

        Manager.EnumMeasures(miCallback)

        Console.WriteLine(“Sale”)

        Console.ReadLine()

    End Sub

End Class 

 

Lo fundamental en la aplicación son las siguientes líneas de código: 

[C#]

public static Manager.EnumMeasuresCallback miCallback = new Manager.EnumMeasuresCallback(MeasuresCallback);

[VB]

Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

Acá se declara el delegado (miCallback) que se pasará como parámetro de entrada a la función EnumMeasures que implementa el callback en Manager. Es importante notar que la función de devolución de llamada recibe como parámetro de entrada la dirección de la función delegada 

[C#]

Manager.EnumMeasures(miCallback);

[VB]

Manager.EnumMeasures(miCallback)

En estas líneas estamos invocando a la función que implementa el callback, para eso se le pasa cómo parámetro el delegado definico al comienzo.  

[C#]

    public static void MeasuresCallback(Measure measure)

    {

        Console.WriteLine(“Callback “ + measure.name);

    }

[VB]

    Public Shared Sub MeasuresCallback(ByVal pMeasure As Measure)

        Console.WriteLine((“Callback “ + pMeasure.name))

    End Sub

Esta es la función delegada. En otras palabras en ella se delega el Delegate, como se define en la declaración de miCallback. 

Este es sólo un ejemplo que usa Delegates, hay mucho más sobre este tema.

La solución con los proyectos de ejemplo se pueden descargar de Delegates.zip.

Para quien quiera profundizar es recomendable cualquier libro, en especial el libro de Tom Archer que conocí gracias a la desinteresada recomendación de Daniel Calvin. 

ISBN: 84-481-3246-7

Autor: Tom Archer (http://blogs.msdn.com/tomarcher/)

Título: C# a fondo (http://www.cuspide.com/isbn/8448132467)
Editorial: Mc Graw Hill. 

 

Les mando un saludo. 🙂

Asesoramiento a Organizaciones Públicas y Privadas.

Cursos a grupos ya constituidos.

Charlas de orientación.

Metodologías Ágiles.

Startups.

Visitas

  • 139.836 hits

Mejor calificado

del.icio.us

Anuncios