lunes, 22 de agosto de 2011

Archivos de Configuración - Crear secciones propias (3/3)

 

Introducción

En esta tercer parte se extenderá el ejemplo planteado en los artículos previos

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

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

A nivel de interfaz de usuario no ha sufrido cambios, pero si en funcionalidad, al ser esta mucho mas dinámica.

Entere los puntos tratados aquí se incluirán:

  • un modelo de configuración mas rico y extendido
  • instancia dinámica de librerías, las cuales no serán referenciadas al utilizarse

 

Analizando la estructura

Empecemos analizando como se dividieron los proyecto y como se interrelacionan.

En esta oportunidad las clases que definen la configuración están separadas del proyecto de presentación, pero unidas por medio de una referencia.

El proyecto de proveedores también esta en un proyecto separado, pero hay un detalle, el proyecto de UI no tiene referencia alguna a este. Esto es así porque se instanciara la librería se instanciara de forma dinámica.

Para poder hacer uso de la misma se copia la dll resultante usando un “Post Build Event”, para esto se accede por medio de las propiedades del proyecto de providers.

El post build copiara la dll del providers a la carpeta \bin\Debug del proyecto de UI, (donde estará el .exe), de esta forma al instanciarse dinámicamente la librería la encontrara sin problema, la carpeta local es uno de los sitios donde .net busca las instancias cuando necesita crearla.

El ejemplo se va a dividir en dos implementaciones, una en donde los proveedores no definan un interfaz común para poder se utilizados, y otra en donde la interacción es normalizada por una interfaz.

La definición de la configuración es común para ambas implementaciones, porque básicamente los datos son los mismos.

 

Definicion de la configuración

Los tag usados en esta oportunidad posee un mayor nivel de detalle comparado con los ejercicios de los artículos previos, en cuanto a funcionalidad he información que definen.

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

  <MediosPagoSection>
    <assembly file="MediosPagos.Providers.dll" />
    <MediosPago>
      <MedioPago id="1" descripcion="Efectivo">
        <provider type="MediosPagos.Providers.EfectivoProvider" method="CalcularDescuento" />
      </MedioPago>
      <MedioPago id="2" descripcion="Tarjeta Credito">
        <provider type="MediosPagos.Providers.TarjetaCreditoProvider" method="CalcularRecargo">
          <params>
            <param name="recargo" value="10" type="System.Int32" />
          </params>
        </provider>
      </MedioPago>
      <MedioPago id="3" descripcion="Tarjeta Debito">
        <provider type="MediosPagos.Providers.TarjetaDebitoProvider" method="CalcularRecargo" />
      </MedioPago>
      <MedioPago id="4" descripcion="Cheque">
        <provider type="MediosPagos.Providers.ChequeProvider" method="Calcular">
          <params>
            <param name="recargo" value="10" type="System.Int32" />
            <param name="cargofijo" value="5,4" type="System.Decimal" />
          </params>
        </provider>
      </MedioPago>
      <MedioPago id="5" descripcion="Transferencia Bancaria">
        <provider type="MediosPagos.Providers.TransferenciaBancariaProvider" method="Calcular" /> 
      </MedioPago>
    </MediosPago>
  </MediosPagoSection>
  
</configuration>

Al tener el xml de configuración un tags anidados con una profundidad mayor, es lógico que la interacción entre las clases también refleje esta complejidad.

Hay algunos tag a remarcar en cuanto a utilidad:

- el tag <assembly> definido dentro de la sección de medios de pago, es utilizado para conocer el nombre de la dll donde están los providers implementados, esta información será usada para crear la instancia de la librería

- tag <provider>, ahora tiene atributos que definen el type, indicando la clase donde se implementa ese provider dentro de la librería, y el method, que se invocara. Para la versión con interfaz este atributo no será necesario, porque el contrato será único, los metodos llevaran el mismo nombre y cantidad de parametros.

- tag <params>, proporciona información adicional y dinámica necesaria por la implementación de proveedor del calculo, esta es una colección por lo que podrá agregarse cualquier numero de tag ítems que sean necesarios

Implementación sin una interfaz común

Empecemos analizando la versión que no implementa una interfaz, en donde los nombres de los métodos puedes ser variados, al igual que el numero de parámetros.

Con respecto a los parámetros, hay que mencionar una regla definida, el importe es fijo he ira siempre en primer lugar.

 

string mediopago = Convert.ToString(cmbMediosPago.SelectedValue);
ProviderElement provider = Config.Instance().GetProviderById(mediopago);

Assembly _assembly = Assembly.LoadFrom(Config.Instance().MediosPago.assembly.file);
object instance = _assembly.CreateInstance(provider.type);

//
// defino los parametros
//
List<object> param = new List<object>();
param.Add(importe);

foreach (ParamElement paramItem in provider.Params)
{
    param.Add(Convert.ChangeType(paramItem.value, Type.GetType(paramItem.type)));
}

//
// invoco al metodo
//
object result = instance.GetType().InvokeMember(provider.method, BindingFlags.InvokeMethod, null, instance, param.ToArray());


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

El código para realizar la invocación después de todo no parece ser tan complejo, no al menos como uno se lo imaginaba cuando observar el archivo de configuración.

Primeramente, se crea la instancia, pero como primer paso se carga el Assembly, es por eso que se requiere el nombre de la dll.

A continuación se defina la lista de parámetros en el mismo orden como se define en la implementación del método, colocando el importe en primer lugar, y a continuación los parámetros dinámicos.

Por ultimo se invoca el método, para lo cual se ha usado el InvokeMember(), porque de este solo se tiene el nombre devuelto por la configuración, pudiendo ser distinto en cada implementación.

 

[C#]
 

Implementación con interfaz Común

Si bien esta debería ser una implementación correcta del ejemplo, la anterior tenia su objetivo practico, que apuntaba a demostrar como configurar he invocar cuando todo es dinámico, pero en un desarrollo real siempre hay que tratar de llevar a la estandarización, y las interfaces permiten que esto sea simple.

Para que esto funcione fue necesario adaptar algo mas los proyecto, es por eso que ahora hay uno nuevo que actúa de intermediario entre la UI y la implementación de la proveedores, apunto al proyecto de interfaz, el cual es referenciado por los otros dos, como se observa en la imagen:

El proyecto de interfaz define el contrato entre las partes:

public interface ICalculo
{
    decimal Calcular(decimal importe);
}

Los proveedores los respetan y cumplen:

public class ChequeProvider : ICalculo
{

    /// <summary>
    /// El cheque recarga un porcentaje configurable
    /// Ademas se cobrara un cargo fijo de administracion
    /// </summary>
    /// <param name="importe"></param>
    /// <returns></returns>
    public decimal Calcular(decimal importe)
    {
        //
        // se obtiene los parametros de la configuracion
        //
        ProviderElement provider = Config.Instance().GetProviderById("4"); //Cheque
        int recargo = Convert.ToInt32(provider.Params["recargo"].value);
        decimal cargofijo = Convert.ToDecimal(provider.Params["cargofijo"].value);

        //
        // se realiza el calculo del importe final
        //
        return importe + (importe * ((decimal)recargo / 100)) + cargofijo;
    }

}
public class TarjetaCreditoProvider : ICalculo 
{

    /// <summary>
    /// La tarjeta de Credito recarga configurable por sistema
    /// </summary>
    /// <param name="importe"></param>
    /// <returns></returns>
    public decimal Calcular(decimal importe)
    {
        ProviderElement provider = Config.Instance().GetProviderById("2"); //Tarjeta Credito
        int recargo = Convert.ToInt32(provider.Params["recargo"].value);

        return importe + (importe * ((decimal)recargo / 100));
    }

}

Ahora los métodos solo reciben el importe por parámetro, si estos quieren un valor configurable ellos mismos acceden a los datos por el mismo lugar que lo hace la UI, y toman el valor directamente.

La invocación al método ahora es bien simple, porque se tiene una interfaz:

string mediopago = Convert.ToString(cmbMediosPago.SelectedValue);
ProviderElement provider = Config.Instance().GetProviderById(mediopago);

Assembly _assembly = Assembly.LoadFrom(Config.Instance().MediosPago.assembly.file);
ICalculo instance = (ICalculo)_assembly.CreateInstance(provider.type);

//
// invoco al metodo
//
decimal result = instance.Calcular(importe);

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

 

[C#]
 

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]

jueves, 23 de junio de 2011

[GridView] Eventos de controles contenidos en el GridView (1/2)

 

Introducción

En ciertas ocasiones puede ser necesario trabajar directamente con los eventos de los controles contenidos en el propio GridView, pero el problema planteado es como trabajar con el control que ejecuta la acción y al mismo tiempo poder acceder a la información de la fila del gridview que contiene a dicho control.

El articulo demuestra como conseguirlo de dos formas distintas:

- una implicara extender el control que se quiera utilizar

- la otra simplemente accederá al objeto que contiene al control que lanza el evento

En los ejemplos se hará uso de un control radiobutton como parte de gridview y será este quien ejecute el evento con el cual se necesitara trabajar.

 

Extendiendo el control

Para poder implementar este camino se necesitara definir una propiedad que será útil cuando se trabaje con el evento del control.

Esta propiedad no se dispone de forma estándar en el control, sino que deberá crearse un control que extienda del original.

public class CustomRadioButtonList : RadioButtonList 
    {
        //
        // La creacion del custom control es justamente para agregar esta propiedad 
        // que por defecto no posee el RadioButtonList
        //
        [DefaultValue("")]
        public string CommandArgument
        {
            get
            {
                string s = ViewState["CommandArgument"] as string;
                return s == null ? String.Empty : s;
            }
            set
            {
                ViewState["CommandArgument"] = value;
            }
        }

    }

Es importante mencionar que el uso de este nuevo control requiere de su declaración en la pagina

<%@ Register Assembly="GridViewEventosControles" Namespace="GridViewEventosControles"
    TagPrefix="cc1" %>

El siguiente paso sea definir del control que se ha creado dentro de la pagina.

<asp:TemplateField HeaderText="Respuesta">

    <ItemTemplate>
        <cc1:CustomRadioButtonList ID="rblRespuesta" runat="server" 
            CommandArgument='<%#Container.DataItemIndex %>' 
            onselectedindexchanged="rblRespuesta_SelectedIndexChanged" AutoPostBack="True">
        </cc1:CustomRadioButtonList>
    </ItemTemplate>

</asp:TemplateField>

La línea donde se usa el Container.DataItemIndex permite asigna el índice de cada row a la propiedad, para que pueda ser tomada desde el código al ejecutarse el evento.

protected void rblRespuesta_SelectedIndexChanged(object sender, EventArgs e)
{
    CustomRadioButtonList radiolist = sender as CustomRadioButtonList;

    if (radiolist == null)
        return;

    //
    // Se toma el index de la row del gridview, el cual fue asociado en el CommandArgument
    //
    int rowindex = Convert.ToInt32(radiolist.CommandArgument);
    
    //
    // Sabiendo el index de la row con que se debe trabajar se pued recuperat el id
    //
    int idpregunta = Convert.ToInt32(GridView1.DataKeys[rowindex].Value);

    bool seleccion = Convert.ToBoolean(Convert.ToInt32(radiolist.SelectedValue));

    PreguntasManager.Update(idpregunta, seleccion);

}

[C#]
[C# Skydrive]

 

Accediendo al objeto contenedor

Para esta implementación no se requiere el uso de ningún control adiciona, simplemente se hará uso de una propiedad que permite recuperar el contenedor del control.

Específicamente se hace referencia a NamingContainer

La definición del control en el grid será de forma normal

<asp:TemplateField HeaderText="Respuesta">

    <ItemTemplate>
        <asp:RadioButtonList ID="rblRespuesta" runat="server" 
            onselectedindexchanged="rblRespuesta_SelectedIndexChanged" AutoPostBack="True">
        </asp:RadioButtonList>
    </ItemTemplate>

</asp:TemplateField>

Pero el acceso al row que contiene el control es donde esta el truco.

protected void rblRespuesta_SelectedIndexChanged(object sender, EventArgs e)
{
    RadioButtonList radiolist = sender as RadioButtonList;

    if (radiolist == null)
        return;

    //
    // Se recupera la row del GridView que contien el control
    //
    GridViewRow row = radiolist.NamingContainer as GridViewRow;
    
    //
    // Sabiendo el index de la row con que se debe trabajar se puede recuperar el id
    //
    int idpregunta = Convert.ToInt32(GridView1.DataKeys[row.RowIndex].Value);

    bool seleccion = Convert.ToBoolean(Convert.ToInt32(radiolist.SelectedValue));

    PreguntasManager.Update(idpregunta, seleccion);

}

Es allí donde se usa la propiedad para obtener el row donde esta contenido el control que lanzo el evento, y el el index será utilizado para obtener el identificador de la entidad, en este caso de la pregunta a la cual se da la respuesta.

 

[C#]
[C# SkyDrive]

lunes, 20 de junio de 2011

[GridView] Aplicar color en las filas

 

Introducción

Este artículo plantea como aplicar color a las filas del gridview basándose en la definición de cierta regla previamente establecida.

En el ejemplo se tiene una lista de productos, la cual por “error” uno fue duplicado, la idea es poder detectar en que registros del grid se presenta este problema y remarcarlo con un color diferente para un fácil reconocimiento.

Se hará uso de linq trabajando junto a un dataset para poder aplicar la verificación de cada ítem en memoria.

El resultado obtenido será el visualizado en la imagen siguiente:

Determina color GridView

Para aplicar la lógica de colores en cada fila del grid será usado el evento RowDataBound, el mismo se lanza por cada row creada en el grid, es por eso que validando la información de la fila contra el origen de datos será posible determinar si la información esta repetida

 

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        string desc = e.Row.Cells[0].Text;

        int cant = (from item in ((DataTable)Session["datos"]).AsEnumerable()
                      where item.Field<string>("Descripcion") == desc
                      select item).Count();

        if (cant > 1)
        {
            e.Row.BackColor = Color.LightCoral;
        }
        
    }
}

 

[C#]
[C# SkyDrive]

[Winforms] Control global de Errores – Implementar Log

 

Introducción

He notado en varias oportunidades que en ciertas circunstancias surgen errores no controlador que son difíciles de rastrear

Estos es consecuencia de un descuido, al no agregar correctamente en todos los puntos importantes el try..catch, o simplemente por la lógica de la aplicación por se grande implicaría un gran esfuerzo poner en cada evento el control de errores.

Es por eso que definir un control global a nivel de aplicación podría ayudar, atrapando el problema y registrando que sucedió y donde, esta información es muy preciada cuando se esta perdido y no se sabe que produce el problema, mas cuando se esta en el entorno de producción y no se cuenta con una herramienta de debug

En este artículo se trataran los siguientes temas:

  1. Control global de errores
  2. Log usando System.IO.Log

 

1- Control Global Errores

El implementar la lógica para controlar globalmente los errores no es nada difícil, el truco esta en adjuntarse a un evento, concretamente:

Application.ThreadException

Un buen lugar para realizar esta asignación del evento es el archivos Program.cs, dentro del método Main()

[C#]

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

    Application.Run(new Form1());
}

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
    MessageBox.Show(e.Exception.Message);
}

Para lograr esto mismo en vb.net requiere de algunos paso adicionales, ya que vb.net no brinda un acceso transparente al método Main, ni permite que este sea asignado como inicio de la aplicación, pero en este articulo

Winforms, realizar tareas antes de inicializar aplicación

explico como destrabar esta situación para poder así implementarlo como lo harían en c#, definiendo el Main() como inicio de la aplicación.

[VB.NET]

<STAThread()> _
Friend Shared Sub Main()

    Application.EnableVisualStyles()
    Application.SetCompatibleTextRenderingDefault(False)

    AddHandler Application.ThreadException, AddressOf Application_ThreadException

    Application.Run(New Form1())

End Sub

Private Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs)

    MessageBox.Show(e.Exception.Message)

End Sub

El código en ambos lenguajes preserva el mismo concepto, pero la forma en como se adjunta el evento difiera bastante.

Entonces, ahora si sucediera algo como lo reflejado en la imagen

imagen1

O sea, si se ingresara caracteres en el calculo, el no atrapar el error en un bloque try..catch, haría que la aplicación finalice de forma brusca cerrándose, pero al tener definido el evento ThreadException, entraría en acción tomando el error y mostrando el mensaje.

Con estos simples paso ya tenemos el control global de errores implementado.

 

2- Log usando System.IO.Log

Si bien en una primer instancia hacer uso de un mensaje podría ayudar a detectar el problema en la aplicación, este podría evolucionar en un log a un archivo para dejar tracking de lo sucedido, en este caso no solo se pondría el mensaje del problema, sino que además se podría agregar el StackTrace para poder analizar que métodos se fueron ejecutando hasta causar el fallo.

En este caso el log a un archivos será muy simple

[C#]

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            using(FileRecordSequence record = new FileRecordSequence("application.log", FileAccess.Write))
            {

                string message = string.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, 
                                                                                    e.Exception.Message, 
                                                                                    e.Exception.StackTrace);

                record.Append(CreateData(message), SequenceNumber.Invalid, 
                                                            SequenceNumber.Invalid, 
                                                            RecordAppendOptions.ForceFlush);
            }
        }


        private static IList<ArraySegment<byte>> CreateData(string str)
        {
            Encoding enc = Encoding.Unicode;

            byte[] array = enc.GetBytes(str);

            ArraySegment<byte>[] segments = new ArraySegment<byte>[1];
            segments[0] = new ArraySegment<byte>(array);

            return Array.AsReadOnly<ArraySegment<byte>>(segments);
        }

[VB.NET]

Private Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs)

    Using record As New FileRecordSequence("application.log", FileAccess.Write)

        Dim message As String = String.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, e.Exception.Message, e.Exception.StackTrace)

        record.Append(CreateData(message), SequenceNumber.Invalid, SequenceNumber.Invalid, RecordAppendOptions.ForceFlush)
    End Using

End Sub


Private Shared Function CreateData(ByVal str As String) As IList(Of ArraySegment(Of Byte))

    Dim enc As Encoding = Encoding.Unicode

    Dim _array As Byte() = enc.GetBytes(str)

    Dim segments As ArraySegment(Of Byte)() = New ArraySegment(Of Byte)(0) {}
    segments(0) = New ArraySegment(Of Byte)(_array)

    Return Array.AsReadOnly(Of ArraySegment(Of Byte))(segments)

End Function

Ahora el evento global de errores tiene bastante mas código, en donde se arma un mensaje bastante mas útil, el cual será enviado a la funcionalidad de log para registrar el suceso.

Para recuperar el archivo, solo deben ir a la ubicación donde esta el .exe, en este caso debería esta en la carpeta \bin\Debug del proyecto.

Lo mas probable es que el archivo no se legible a simple vista porque este sistema de log trabaja con el concepto de entradas de registros, es por eso que se confecciono un formulario muy simple para poder recuperar la información de forma visual.

[C#] 
[VB.NET] 

domingo, 12 de junio de 2011

[ASP.NET][GridView] - Como seleccionar una fila

 

Introducción


He visto en reiteradas oportunidad que una operación simple como es el caso de operar con una fila de un GridView se puede transformarse en algo complejo, mas que nada motivado por la distintas formas que hay para realizar esta acción.

En este articulo veremos las algunas formas de lograrlo y como difieren las técnicas que se puede aplicarse.

Temas que se tratar:

1- Seleccionar una Row

a- Definiendo un CommandField

b- Usando un ImageButton y CommandName

c- Usando el evento RowCommand

2-Uso de DataKeyNames y DataKeys

   a- DataKeyNames con campos Múltiples

Para todos los casos planteados partiremos del mismo gridview, el cual se ira modificando para agregarle opciones y ver los distintos temas.

 

1- Seleccionar una Row


Existen varias formas de realizar una misma tarea, pero veremos aquí las dos mas simples y directas que se suelen encontrar cuando se necesita seleccionar un registro en el control gridview.

1a- Definiendo un CommandField

Iremos realizando los paso de forma visual así se comprende como proceder, remarcando luego como impacta esto en el html del grid

El primer paso será editar las columnas del GridView hasta visualizar el cuadro con las opciones de CommandField disponibles.

imagen1

imagen2

Para este caso en particular se agregara solo la opción de selección. Mediante las flechas laterales se puede posicionar el comando. También se dispone de distintos tipos de representación visual, como ser un Link, Button o Image.

imagen3

Para este caso usaremos un comando del tipo Image, por lo tanto se deberá definir la propiedad “SelectImageUrl”. Si se define del tipo Link y se quiere cambiar el texto, se usaría la propiedad “SelectText”.

El próximo paso será el de definir el evento de selección, para esto solo marcamos el gridview, y yendo a sus propiedades se podrá activar el evento SelectedIndexChanged

imagen4

El html resultante debería tener resaltadas las siguiente características

 imagen5

Con estos pasos ya estamos listos para capturar la acción de selección del gridview.

 

1b - Usando un ImageButton y CommandName

En esta alternativa se hará uso de un TemplateField, se procede de la misma forma del paso 1a, pero se agrega un item diferente

imagen6

Una vez que esta el témplate, se adapta modificando directamente en el html, incluyendo de esta forma el control ImageButton.

Es muy importante remarcar que el ImageButton deberá tener la propiedad CommandName=”Select” para que esta ejecute el evento SelectedIndexChanged

imagen7

A nivel de código de la pagina se encontrara la definición del evento

imagen8

Hay que aclarar que en este caso se uso un ImageButton para corresponder con la acción del punto 1a, en donde se define una imagen, pero si se requiere de un link solo será cuestión de usar un LinkButton, definiendo en este el CommandName=”Select”, es justamente el CommandName quien define que evento será lanzado al presionarse.

 

1c- Usando el evento RowCommand

Seguramente a estas alturas se preguntaran que cantidad de formas de hacer lo mismo, asi es, y para completarlo una opción extra.

Resulta que al definir un ImageButton (o LinkButton) en un TemplateItem y usar el CommandName=”Select” se habilita un evento adicionar para poder capturar esta acción, si es que el SelectedIndexChanged no nos convence.

Se trata del evento RowCommand.

Hay un pequeño detalle con este evento y se trata de la definición del CommandArgument para determinar que fila lanza la acción.

imagen9

[C#]

protected void gvPerson_RowCommand(object sender, GridViewCommandEventArgs e)
{
    if (e.CommandName == "Select")
    {
        //
        // Se obtiene indice de la row seleccionada
        //
        int index = Convert.ToInt32(e.CommandArgument);
        
        //
        // Obtengo el id de la entidad que se esta editando
        // en este caso de la entidad Person
        //
        int id = Convert.ToInt32(gvPerson.DataKeys[index].Value); 

    }

}

[VB.NET]

Protected Sub gvPerson_RowCommand(sender As Object, e As GridViewCommandEventArgs)
	
        If e.CommandName = "Select" Then
		'
		' Se obtiene indice de la row seleccionada
		'
		Dim index As Integer = Convert.ToInt32(e.CommandArgument)

		'
		' Obtengo el id de la entidad que se esta editando
		' en este caso de la entidad Person
		'

		Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(index).Value)
	End If

End Sub

En el ejemplo de la pagina WebForm4.aspx, se podrá probar como ambos eventos, tanto el RowCommand y el SelectedIndexChanged, pueden definirse, aunque lo normal es utilizar solo uno de estos.

 

2 - Uso de DataKeyNames y DataKeys


Una de las mejores técnicas usada para detectar que entidad se esta editando o seleccionado es por medio de id o código que esta tenga asignada, pero como logar hacerlo sin mostrar el identificador al usuario en una columna ?, es justamente el trabajo de estas dos propiedades que se consigue resolver el problema.

Si se presta atención al html este contaba con al definición de esta propiedad de nombre DataKeyNames

imagen10

Solo debe definirse que campo del origen de datos identifica a la entidad que se esta trabajando.

Cuando se lance el evento solo será cuestión de tomar la row que ejecuta la acción, y de esta, por el índice recuperar el valor del id de la entidad, en este caso el PersonID.

[C#]

protected void gvPerson_SelectedIndexChanged(object sender, EventArgs e)
{
    //
    // Se obtiene la fila seleccionada del gridview
    //
    GridViewRow row = gvPerson.SelectedRow;

    //
    // Obtengo el id de la entidad que se esta editando
    // en este caso de la entidad Person
    //
    int id = Convert.ToInt32(gvPerson.DataKeys[row.RowIndex].Value);


}

[VB.NET]

Protected Sub gvPerson_SelectedIndexChanged(sender As Object, e As EventArgs)
	'
	' Se obtiene la fila seleccionada del gridview
	'
	Dim row As GridViewRow = gvPerson.SelectedRow

	'
	' Obtengo el id de la entidad que se esta editando
	' en este caso de la entidad Person
	'
	Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(row.RowIndex).Value)


End Sub

2a- DataKeyNames con campos Múltiples

En este ejemplo solo se utilizo un valor simple para identificar a la entidad, pero el CommandName puede definir mas de un campo de información.

Por ejemplo que sucede si se quiere enviar el PersonId y el Nombre, esto es tan solo un ejemplo para demostrar las funcionalidad, no tiene una aplicación práctica en este caso, ya que con solo el PersonID seria mas que suficiente.

Para definir el DataKeyNames en el grid es tan simple como separar los campos por una coma.

imagen11

y en el código solo se toma el valor de la propiedad Values

[C#]

protected void gvPerson_SelectedIndexChanged(object sender, EventArgs e)
{
    //
    // Se obtiene la fila seleccionada del gridview
    //
    GridViewRow row = gvPerson.SelectedRow;

    //
    // Obtengo el id y el nombre  de la entidad que se esta editando
    // en este caso de la entidad Person
    //
    int id = Convert.ToInt32(gvPerson.DataKeys[row.RowIndex].Values["PersonID"]);

    string nombre = Convert.ToString(gvPerson.DataKeys[row.RowIndex].Values["FirstName"]);


}

[VB.NET]

Protected Sub gvPerson_SelectedIndexChanged(sender As Object, e As EventArgs)
	'
	' Se obtiene la fila seleccionada del gridview
	'
	Dim row As GridViewRow = gvPerson.SelectedRow

	'
	' Obtengo el id y el nombre  de la entidad que se esta editando
	' en este caso de la entidad Person
	'
	Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(row.RowIndex).Values("PersonID"))

	Dim nombre As String = Convert.ToString(gvPerson.DataKeys(row.RowIndex).Values("FirstName"))


End Sub

Ejemplo Código


Para el ejemplo se hizo uso de Visual Studio 2008 con SP1, el service pack es útil para poder hacer uso de Entity Framework y poder crear el ADO.NET Entity Data Model haciendo simple el acceso a la db.

La base de datos es Sql Server 2008 Express R2, y se encuentra el mdf dentro de la carpeta App_Data, pero se podría adjuntar al servicio de Sql Server o hacer uso del script que se encuentra en el proyecto de DataAccess.

 

 

domingo, 8 de mayo de 2011

[ASP.NET] GridView – Edición Empleados


Introducción


En esta ocasión se implementara al versión web de un articulo previo:
[WinForms] Edición Empleados – Grabar imagen en base de datos

Los puntos que se trataran con detalle serán:
- selección de un fila en el gridview
- eliminar un registro del grid
- visualizar una imagen que se encuentra dentro de la base de datos en un control de Image

 pantalla



1 – Selección  de una fila en el GridView


Existen varias formas de lograr este objetivo, en el ejemplo del este articulo aplique solo una de ellas haciendo uso del CommandField para definir las acciones sobre el grid, para ello hice uso de la opción visual:


 image


esto desplegara el dialogo:


 image


es aquí donde se definen las columnas entre ellas el CommandField, al seleccionarlo mostrara las propiedades de configuración, las propiedades de la sección “Behavior” es donde uno selecciona que botones quiere visualizar, es por eso que en este caso la propiedad “ShowSelecteButton” esta en True.

Además es importante la sección “Appearance” en donde se define “ButtonType” del tipo imagen y el “SelectImageUrl”, con la url del botones que se ve en el grid.
Concluida la definición de columnas y botones de acción, se debe especificar una propiedad muy importante en el grid, se trata del “DataKeyNames”, esta debería llevar el nombre la propiedad (o columna) del origen de datos que se usara como identificación de la entidad que se edita, en este caso como son empleado, será su id, definiéndose: DataKeyNames="IdEmpleado"

Este paso es importante ya que en los eventos se podrá recuperar sobre que entidad se debe aplicar la acción.
El botón de selección lanzara el evento “SelectedIndexChanging”, es por eso que será necesario definirlo en el grid: onselectedindexchanging="gvEmpleados_SelectedIndexChanging"
mientras que en el código de la pagina:

protected void gvEmpleados_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    int idempleado = Convert.ToInt32(gvEmpleados.DataKeys[e.NewSelectedIndex].Value);

    Response.Redirect(string.Format("EditarEmpleado.aspx?id={0}", idempleado));

}

Se hace uso del DataKeys para tomar el valor definido por el DataKeyNames, estas dos propiedades trabajan bien relacionadas entre ellas.
Lo último paso que queda es redireccionar a la pagina de edición.



2- Eliminar un registro


La operación de eliminar es muy parecida a la selección, solo cambia el comando usado para esto.
Al igual que la selección hay que configurar el CommandField habilitando la propiedad “ShowDeleteButton” en true.
Esta acción lanzara el evento “RowDeleting”, para lo cual especificamos su definición en el grid mediante: onrowdeleting="gvEmpleados_RowDeleting"
y luego en el código:

protected void gvEmpleados_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    int idempleado = Convert.ToInt32(gvEmpleados.DataKeys[e.RowIndex].Value);

    EmpleadosDAL.Eliminar(idempleado);

    CargarGrid(); //luego de eliminar se recarga el grid
}

Aquí también se hace uso del DataKeys para recuperar el id de la entidad que lanzo la acción.


3- Visualizar imagen


Cuando la imagen se encuentra dentro de la base de datos es necesario usar un intermediario para poder asignarla al control Image, y se haga visible al usuario.
Además todo esto sin generar archivos temporales que permitan el acceso a la imagen.
En este caso es el handler quien nos brinda esta ayuda, si se observa el código del mismo se ve muy simple, se recupera la entidad del empleado, y a continuación si contiene una imagen asociada se poniéndola en el Response, sino hay imagen se envía una por defecto, la cual indicara que no esta disponible (líneas: 21-23).

public class HttpImageHandler : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        //
        // Se recupera la entidad empleado
        //
        int id = Convert.ToInt32(context.Request.Params["id"]);

        EmpleadoEntity empleado = EmpleadosDAL.ObtenerById(id);

        //
        // Arma el contexto que enviara la imagen en el response
        // se usa el nombre del empleado para el nombre del archivo que se envia
        //
        context.Response.Clear();
        context.Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", empleado.Nombre)); 
        context.Response.ContentType = "image/jpg";

        byte[] imagenEmpleado = empleado.Imagen;
        if (empleado.Imagen == null)
            imagenEmpleado = File.ReadAllBytes(context.Server.MapPath("Imagenes/NoDisponible.jpg"));

        //
        // Se escribe en el response la imagen asociada al empleado
        //
        context.Response.BinaryWrite(imagenEmpleado);
        context.Response.End();
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

Esto es tanto usado por el grid para recuperar las imágenes que lista, como así también por cualquier otro control individual que necesites mostrar la imagen del empleado.
Es por eso que en el webform “EditarEmpleado.aspx”, cuando se carga el empleado todos sus datos se asignan directo a los controles, menos el control Image que recibe una url al handler para que tome de allí la imagen que debe mostrar (línea: 18)

private void CargarEmpleado(int id)
{
    EmpleadoEntity empleado = EmpleadosDAL.ObtenerById(id);

    if (empleado == null)
    {
        imgEmpleado.ImageUrl = "Imagenes/NoDisponible.jpg";
        return;
    }

    lblIdEmpleado.Text = Convert.ToString(empleado.IdEmpleado);
    txtNombre.Text = empleado.Nombre;
    txtApellido.Text = empleado.Apellido;
    txtFechaNacimiento.Text = empleado.FechaNacimiento.ToShortDateString();
    
    AsignarEstudios(empleado);

    imgEmpleado.ImageUrl = string.Format("imagen.ashx?id={0}", id);
}


Código del artículo



El ejemplo fue desarrollado con Visual Studio 2008, y base de datos Sql Server 2008 R2 Express.
Dentro de la carpeta “script” del proyecto “DataAccess” encontrara un archivo .sql que define la estructura de datos.

[C#]
[VB.NET]