domingo, 31 de julio de 2011

Archivos de Configuración – Creando secciones propias (2/3)

Introducción

Continuando con el planteo en la primer artículo

Archivos de Configuración - Una introducción (1/3)

avanzaremos en complejidad agregando funcionalidad que permita extender la configuración a nuestro capricho, modelando así los tags de la estructura xml que se considere adecuada para representar la información de configuración que requiere nuestra aplicación.

Continuando con la idea del artículo anterior y los medios de pago, imaginemos que dado un importe hay que aplicarle un determinado recargo (o descuento) según el medio de pago seleccionado, pero esto debería poder configurarse, porque se prevé que puede aparecer otros medios de pago en el futuro.

La interfaz es muy simple, se selecciona un medio de pago y se ingresa un importe, el botón “calcular” invocara al proveedor definido para aplicar la operación devolviendo el resultado que se muestra.

Definición de la configuración

Definiremos la configuración con la cual nos basaremos en el ejemplo

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="MediosPagoSection" type="WinformsConfigSeccionesPropias.Configuration.MediosPagoConfigurationSection, WinformsConfigSeccionesPropias" />
  </configSections>

  <MediosPagoSection>
    <MediosPago>
      <MedioPago id="1" descripcion="Efectivo" provider="WinformsConfigSeccionesPropias.EfectivoProvider" />
      <MedioPago id="2" descripcion="Tarjeta Credito" provider="WinformsConfigSeccionesPropias.TarjetaCreditoProvider"/>
      <MedioPago id="3" descripcion="Tarjeta Debito" provider="WinformsConfigSeccionesPropias.TarjetaDebitoProvider"/>
      <MedioPago id="4" descripcion="Cheque" provider="WinformsConfigSeccionesPropias.ChequeProvider"/>
      <MedioPago id="5" descripcion="Transferencia Bancaria" provider="WinformsConfigSeccionesPropias.TransferenciaBancariaProvider"/>
    </MediosPago>
  </MediosPagoSection>
  
</configuration>

Un primer punto a remarcar es la definición del una sección de configuración de nombre “MediosPagoSection”, la cual se asocia a una clase diseñada para poder interpretar la región de configuración que necesitamos.

La sección define una colección de medios de pago, en donde cada ítem cuenta con un “id”, “descripción” y el mas importante el “provider”, que no es nada mas que el nombre completo de la clase que implementa el calculo para ese medio de pago.

Definición de las clase de configuración

En el siguiente diagrama

Se define la estructura de clases utilizadas para poder mapear los tag del .config con clases que permitan manipular esta información.

La clase de nombre “Config” no seria en realidad parte de la implementación necesaria para interpretar los tag de configuración, sino mas bien es un adicional que aplica el patrón singleton para proporcionar un único acceso y directo a la información de configuración, pero su inclusión no es obligatoria.

public class Config
{
    private static Config _config;

    private Config()
    {
        this.MediosPago = (MediosPagoConfigurationSection)ConfigurationManager.GetSection("MediosPagoSection");
    }

    public static Config Instance()
    {
        if (_config == null)
            _config = new Config();

        return _config;
    }

    public MediosPagoConfigurationSection MediosPago { get; private set; }

}

El resto de los archivos si son parte del mapeo y requieren unirse uno con otro para armar la estructura.

La clase “MediosPagoConfigurationSection” que también se habrá observado en el tag del app.config, define el punto de entrada. Esta contiene una colección de ítems, es por eso que se asocia a “MediosPagoCollection”.

public class MediosPagoConfigurationSection : ConfigurationSection
{

    [ConfigurationProperty("MediosPago")]
    public MediosPagoCollection MedioPagoItems
    {
        get { return ((MediosPagoCollection)(base["MediosPago"])); }
    }
    
}

La clase que representa la colección define como se trabaja con un ítem, en esta se puede definir la propiedad “this”, para que busque tanto por índice, así como por la key definida en el elemento.

[ConfigurationCollection(typeof(MedioPagoElement), AddItemName = "MedioPago")]
public class MediosPagoCollection : ConfigurationElementCollection
{

    protected override ConfigurationElement CreateNewElement()
    {
        return new MedioPagoElement();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((MedioPagoElement)(element)).Id;
    }


    public MedioPagoElement this[int index]
    {
        get { return (MedioPagoElement)BaseGet(index); }
    }

    public MedioPagoElement this[string id]
    {
        get { return (MedioPagoElement)BaseGet(id); }
    }

}

Cada elemento representado por la clase “MedioPagoElement” solo tiene el mapeo a las propiedades del tag.

public class MedioPagoElement : ConfigurationElement
{

    [ConfigurationProperty("id", DefaultValue = "", IsKey = true, IsRequired = true)]
    public string Id
    {
        get { return ((string)(base["id"])); }
        set { base["id"] = value; }
    }

    [ConfigurationProperty("descripcion", DefaultValue = "", IsKey = false, IsRequired = true)]
    public string descripcion
    {
        get { return ((string)(base["descripcion"])); }
        set { base["descripcion"] = value; }
    }

    [ConfigurationProperty("provider", DefaultValue = "", IsKey = false, IsRequired = true)]
    public string provider
    {
        get { return ((string)(base["provider"])); }
        set { base["provider"] = value; }
    }

}

Definición de las clase de calculo de pago

En cada medio de pago se definición una clase especifica para realizar el calculo, para una mayor comodidad todas estas clases implementan una interfaz común, lo cual hace simple la instanciación.

interface ICalculoImpuesto
{
    decimal Calcular(decimal importe);
}

Cada clase concreta implementa la interfaz y aplica el calculo.

public class ChequeProvider : ICalculoImpuesto
   {

       /// <summary>
       /// El cheque recarga un 10%
       /// </summary>
       /// <param name="importe"></param>
       /// <returns></returns>
       public decimal Calcular(decimal importe)
       {
           return importe + (importe * (decimal)0.10);
       }

   }


public class EfectivoProvider : ICalculoImpuesto
   {

       /// <summary>
       /// En Efectivo se descuenta un 10%
       /// </summary>
       /// <param name="importe"></param>
       /// <returns></returns>
       public decimal Calcular(decimal importe)
       {
           return importe - (importe * (decimal)0.10);
       }

   }

public class TarjetaCreditoProvider : ICalculoImpuesto
   {

       /// <summary>
       /// La tarjeta de Credito recarga un 10%
       /// </summary>
       /// <param name="importe"></param>
       /// <returns></returns>
       public decimal Calcular(decimal importe)
       {
           return importe + (importe * (decimal)0.10);
       }

   }

public class TarjetaDebitoProvider : ICalculoImpuesto
   {

       /// <summary>
       /// La tarjeta de Debito recarga un 5%
       /// </summary>
       /// <param name="importe"></param>
       /// <returns></returns>
       public decimal Calcular(decimal importe)
       {
           return importe + (importe * (decimal)0.05);
       }

   }


public class TransferenciaBancariaProvider : ICalculoImpuesto
   {

       /// <summary>
       /// El Trsnaferencia Bancaria no afecta al importe
       /// </summary>
       /// <param name="importe"></param>
       /// <returns></returns>
       public decimal Calcular(decimal importe)
       {
           return importe;
       }

   }

 

Aplicación de todo lo definido

Bien, ahora llego el momento de poner manos a la obra y hacer uso de todo lo configurado en los pasos anteriores.

Empezaremos por cargar el combo de medios de pago, tomando la información de esta nueva estructura.

private void Form2_Load(object sender, EventArgs e)
{

    var result = (from config in Config.Instance().MediosPago.MedioPagoItems.Cast<MedioPagoElement>()
                  select new
                  {
                      key = config.Id,
                      value = config.descripcion
                  }).ToList();

    cmbMediosPago.DisplayMember = "value";
    cmbMediosPago.ValueMember = "key";
    cmbMediosPago.DataSource = result;

    cmbMediosPago.SelectedIndex = -1;

}

Como se observa no ha cambiado mucho con respecto al artículo anterior, solo que esta vez se cuenta con la ayuda de

Config.Instance().MediosPago.MedioPagoItems

el cual nos abstrae de la operación de carga de config en las clases.

 

El próximo punto involucra al calculo de impuesto.

private void btnCalcular_Click(object sender, EventArgs e)
{
    errProvider.Clear();

    if (cmbMediosPago.SelectedIndex == -1)
    {
        errProvider.SetError(cmbMediosPago, "Debe seleccionar un medio de pago");
        return;
    }

    decimal importe = 0;

    if (!decimal.TryParse(txtImporte.Text, out importe))
    {
        errProvider.SetError(txtImporte, "El importe ingresado es invalido");
        return;
    }


    string mediopago = Convert.ToString(cmbMediosPago.SelectedValue);
    string provider = Config.Instance().MediosPago.MedioPagoItems[mediopago].provider;


    ICalculoImpuesto calculo = (ICalculoImpuesto)Activator.CreateInstance(Type.GetType(provider));

    txtTotal.Text = string.Format("{0:N2}", calculo.Calcular(importe));

}

Como líneas a destacara se podría mencionar la 19, en donde se accede por medio de la key para recuperar el proveedor que se debe invocar, en este punto si es importante recuperar la información de la configuración ya que el control ComboBox no nos proporciona esta data, solo nos brinda la key.

La línea 22, tiene de interesante el uso de la clase “Activator” para crear la instancia basada en el nombre completo de la clase (namespace + nombre clase).

La línea 24, al contar con una interfaz común solo se invoca al método de la instancia creada y eso es todo lo que se necesita.

 

Código de ejemplo

[C#]
[VB.NET]

Archivos de Configuración - Una introducción (1/3)

 

Introducción


Es muy común tener que definir parámetros en las aplicaciones que se desarrollan proporcionando cierta configuración que si bien se podría considerar casi estática (porque no cambia constantemente), si sea necesario prever la posibilidad de adaptación.

Un ejemplo muy claro de estos es la cadena de conexión a la base de datos, por lo general una vez instalada la aplicación no cambia, pero cuando se esta en la etapa de implementación seguramente sea necesario su adaptación al entorno.

Por lo general se busca que sea un lugar que impacte lo menos posible en el desarrollo, algunos tienden a crear una clase y colocar constantes o variables readonly dentro del propio código, pero el problema con esto es que requieren recompilar por completo el desarrollo, además de tener que actualizar cada cliente por un simple cambio de configuración.

También se busca un lugar estándar y conocido, algunos usan la registry de windows para definir configuración, quizás era una opción se algo mas viable cuando se contaba con Win XP, pero con Sistemas Operativos como ser Vista o Win 7 esto cambio bastante, el modelo de seguridad que estos imponen aplica restricciones a estas acciones, no haciendo tan cómoda la escritura en este sitio.

Entonces porque no usar la propuesta que hace .net al respecto, si se trata de los archivos de configuración, entre las ventajas que este presenta se pueden encontrar:

  • una lectura simple, ya que se basa en xml
  • fácil acceso y modificación (se puede editar con el notepad), por lo general este archivo se encuentra junto a la aplicación por lo que la seguridad debería permitir la escritura en esta carpeta

Entre los temas que se trataran en este articulo

1- Agregar un archivo de configuración a nuestro proyecto

2- Definir una sección key-value

2a- usando la sección appSettings

2b- definiendo una sección propia

 

1- Agregar un archivo de configuración a nuestro proyecto


Si bien es una acción sencilla, si recién se esta introduciendo en el desarrollo, puede que no resulte tan intuitivo encontrar los pasos para agregar un archivo de configuración.

Estando sobre el proyecto en el “Solution Explorer”, se acciona el menú que aparece con el boton derecho del mouse, seleccionado el menu Add –> New Item…

image

en el recuadro se seleccionara la opción:

image

Esto agregar un archivo de nombre App.config, que en una primer instancia tendrá solo un tag.

Empecemos por una tarea simple, la lectura de una cadena de conexión.

Para lograr esta tarea se necesitara la ayuda de una clase en particular, me refiero al 

ConfigurationManager

con esta clase tendremos acceso a los tag de información definidos, pero para hacer uso de la misma se requiere hacer referencia a la librería System.Configuration, los pasos para esta acción serian representados en la siguiente imagen:

image

Solo queda agregar el código que tomaría la información del archivo de configuración, usándolo luego con los objetos de ado.net para conectarse

image

En el punto 1 se define el “using” a la librería referenciada en el paso anterior (definiendo así su namespace), mientras que en el 2 se hace uso de la clase ConfigurationManager para tomar la cadena de conexión.

Por supuesto en el archivo de configuración se debió agregar la key que se define en el .config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="default" connectionString="Data Source=LecturaConnectionString\TestDb.sdf;Persist Security Info=False;"/>
  </connectionStrings>
</configuration>

2- Definir una sección key-value


Definir información del tipo key-value en el archivo de configuración puede realizarse de varias formas

  • usando la sección appsetting
  • por medio de una seccion custom creada por uno

 

2a- Usando AppSetting

Esta es la forma más simple y directa de definir valores simples en la configuración, solo basta con ingresar en la sección <appSettings> el tag “add” con cada item que se requiera.

En el ejemplo se observará la sección definida como:

<appSettings>
  <add key="1" value="Efectivo" />
  <add key="2" value="Tarjeta Credito" />
  <add key="3" value="Tarjeta Debito" />
  <add key="4" value="Cheque" />
  <add key="5" value="Transferencia Bancaria" />
</appSettings>

En el código, en el Form2, se trabaja con esta información con al ayuda de linq para cargar un combo

private void Form2_Load(object sender, EventArgs e)
{


    var result = (from configKey in ConfigurationManager.AppSettings.Keys.Cast<string>()
                  let configValue = ConfigurationManager.AppSettings[configKey]
                  select new
                  {
                      key = configKey,
                      value = configValue
                  }).ToList();

    
    cmbMediosPago.DisplayMember = "value";
    cmbMediosPago.ValueMember = "key";
    cmbMediosPago.DataSource = result;
    
}

Acceder a un ítem en concreto es tan simple como usar

ConfigurationManager.AppSettings[key]

 

private void cmbMediosPago_SelectionChangeCommitted(object sender, EventArgs e)
{
    string key = Convert.ToString(cmbMediosPago.SelectedValue);
    string value = ConfigurationManager.AppSettings[key];

    lblSeleccion.Text = string.Format("Se ha seleccionado\n Key:{0} \n Value:{1}", key, value);
}

2b- Definiendo una sección propia

El uso de la sección <appSettings> en algunas circunstancia puede resultar demasiado genérico y no brindar un lugar que sea claramente identificable para el negocio que se esta programando, esta sección al ser tan común se puede llenar rápidamente de items key-value no relacionados, haciendo difícil el mantenimiento.

Pero existe una alternativa a este problema y consiste en crear una sección propia para conserva estos pares key-value de forma personalizada, otorgando una visibilidad con sentido para la aplicación

En el código del Form3 se implementa la solución usando una sección definida por uno mismo, la cual aplica el mismo concepto key-value.

<configuration>
  <configSections>
    <section name="MediosPago" type="System.Configuration.DictionarySectionHandler" />
  </configSections>
  
  
  <MediosPago>
    <add key="1" value="Efectivo" />
    <add key="2" value="Tarjeta Credito" />
    <add key="3" value="Tarjeta Debito" />
    <add key="4" value="Cheque" />
    <add key="5" value="Transferencia Bancaria" />
  </MediosPago>
  
</configuration>

Para recuperar la información y listarla

private void Form3_Load(object sender, EventArgs e)
{

    var result = (from config in ((Hashtable)ConfigurationManager.GetSection("MediosPago")).Cast<DictionaryEntry>()
                 select new 
                 {
                     key = config.Key,
                     value = config.Value
                 }).ToList();

    cmbMediosPago.DisplayMember = "value";
    cmbMediosPago.ValueMember = "key";
    cmbMediosPago.DataSource = result;

    cmbMediosPago.SelectedIndex = -1;

}

Obtener un ítem basándonos en la key requiere recuperar la sección completa para luego si acceder al valor

private void cmbMediosPago_SelectionChangeCommitted(object sender, EventArgs e)
{
    string key = Convert.ToString(cmbMediosPago.SelectedValue);
    string value = ((Hashtable)ConfigurationManager.GetSection("MediosPago"))[key].ToString();

    lblSeleccion.Text = string.Format("Se ha seleccionado\n Key:{0} \n Value:{1}", key, value);
}

Por supuesto tanto en estos ejemplo hacer uso de las clase de configuración para tomar el valor del ítem seleccionado, no tiene un sentido practico, ya que el propio combobox proporciona ambos datos key y value (usando el SelectedValue y SelectedText), solo se realiza con fines ilustrativos para poder aplicar los conceptos de programación con el archivo de configuración.

 

Código de ejemplo


[C#] 
[VB.NET] 

domingo, 17 de julio de 2011

[Winforms] Singleton - Pasar datos entre formularios

 

Introducción


En mas de una oportunidad surgió el hecho de comunicar formulario y pasar información entre los mismos, cuando estos se invocan uno a otra la solución es relativamente simple, pero que sucede cuando las distintas invocaciones deben capturar y conservar los datos en cada paso que realizan.

Es aquí donde entra en juego un patrón bien simple de aplicar pero no por eso menos potente, hago referencia al singleton.

Para el presente artículo platearemos un escenario nada complicado, simplemente algunos forms donde se capturan datos números, pero esta información deberá ser usada en la ultima pantalla, la cual realiza el calculo con los datos recolectados en los pasos previos.

image

 

1- Singleton,  Pasar datos entre formularios


Implementar el patrón es muy simple, solo se deben aplicar ciertas reglas que abrigan a tomar la misma instancia.

public class RecolectarDatos
{
    private static RecolectarDatos datos;

    private RecolectarDatos()
    {
    }

    public static RecolectarDatos Instance()
    {
        if (datos == null)
        {
            datos = new RecolectarDatos();
        }

        return datos;
    }

    public int Dato1 { get; set; }
    public int Dato2 { get; set; }
    public int Dato3 { get; set; }

    public int Dato4 { get; set; }
    public int Dato5 { get; set; }
}

El primer punto es definir una instancia static del propia clase singleton (línea 3)

Segundo, definir el constructor de la clase como privado (línea 5), de esta forma no se podrán crear instancia de la clase, salvo desde dentro de la propia clase.

Tercero, definir un método o propiedad static, la cual será el punto central por donde se podrá recuperar una instancia de la clase (línea 9), en este punto es donde se valida y crea la instancia de la variable static definida mas arriba, es por esto que siempre se retorna esta misma instancia.

El resto del código pueden ser método, propiedades, otras instancias de clases, o sea se puede aplicar cualquier concepto de POO sin problemas, porque el patron singleton devuelve una única instancia, pero esta tiene comportamiento como cualquier otro objeto.

 

2 – Eventos, Informar de cambios entre formulario


Al llegar al ultimo formulario era necesario informar al primero que las operaciones se realizaron correctamente, pero entre estos formulario no hay visibilidad alguna, no se aplico ninguna pasamano de instancias entre los formularios, o algo que los comunique.

Es por eso que la subscripción a un evento genérico resulta ideal para avisar de una determinada acción sin ser necesario pasar instancias.

 

public static class CompleteEvents
{
    public delegate void CompleteHandler(CompleteEventArgs args);
    public static event CompleteHandler Complete;

    public static void RaiseEvent(int calculo)
    {
        if(Complete != null)
            Complete(new CompleteEventArgs(calculo));
    }
}

public class CompleteEventArgs : EventArgs
{
    public CompleteEventArgs(int calculo)
    {
        this.Calculo = calculo;
    }

    public int Calculo { get; set; }
}

El formulario principal se adjunta al evento definido en la clase, mientras que en el ultimo formulario es invocado el método RaiseEvent() que lanzara la acción.

Adicionalmente se define un argumento del evento para informar el resultado del calculo, igualmente en este caso por aplicar singleton, quizás no era necesario, ya que se podría haber tomado directamente los valores de cada dato recolectado y realizar nuevamente el calculo.

 

Código del ejemplo


[C#] 
[VB.NET] 

sábado, 2 de julio de 2011

[WinForm] Listar Archivos del Directorio seleccionado

 

Introducción

En el presente artículo se implementa un ejemplo sencillo de como listar directorios con su contenido, incluyendo además el icono asociado a la extensión del archivo.

Algunas pruebas

Si bien durante las primeras pruebas se hizo uso de la funcionalidad

Icon.ExtractAssociatedIcon

para obtener la imagen relacionada con la extensión del archivo, note que no siempre se recuperaba el icono correcto, es por eso que investigando algo mas en detalle encontré que por medio de las api se puede realizar esto mismo.

La implementación de esta se puede hallar en el archivo ExtractIcon.cs

Si bien en el código solo he dejado una de estas alternativas

private void LoadFileList(string path)
        {
            string[] files = Directory.GetFiles(path);

            lvFiles.Items.Clear();

            foreach (var item in files)
            {
                string extension = Path.GetExtension(item);

                if (!imlSmall.Images.ContainsKey(extension))
                {
                    Icon iconSmall = FileExplorer.ExtractIconClass.GetIcon(item, true);
                    imlSmall.Images.Add(extension, iconSmall);
                    Icon iconLarge = FileExplorer.ExtractIconClass.GetIcon(item, false);
                    imlLarge.Images.Add(extension, iconLarge);
                }

                ListViewItem listviewItem = new ListViewItem(Path.GetFileName(item), extension);
                lvFiles.Items.Add(listviewItem);

            }
        }

podrían cambiarse las líneas 13 a 16 para hacer uso del

Icon.ExtractAssociatedIcon()

y comprobar por uno mismo la diferencia en el icono que se obtiene.

 

Ejemplo de código

Debo remarcar que la implementación que se encuentre en la clase ExtractIcon.cs, no es de mi autoría, sino que use de guía ejemplo como ser

Extracting Icons from Files

ExtractIconClass.cs

Building a Better ExtractAssociatedIcon

 

[C#]
[VB.NET]