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]

16 comentarios:

  1. hola leandro... me has ayudado muchisimo con tus aportes en la web..

    estoy desarrollando un sistema de gestion comercial... y estoy trancado con un datagrid.... y como tu eres el experto en datagrd, para mi seria un honor, que me ayudaras. si recibes este pequeño comentario porfavor devuelve a mi correo, y asi coordinamos la manera de interactuar... gracias por inticipado.... edward arias...
    mi email es. couldcountry@gmail.com

    ResponderEliminar
  2. hola edward arias

    no te animas a tratar el tema por medio del foro ?

    digo de esta forma si por alguna razon me retraso en responder alguien mas estaria dispuesto a colaborar, seguramente yo participe, pero no te quedas con mi uncia opinion sobre el tema

    foro msdn

    saludos

    ResponderEliminar
  3. Tengo un sistema en c#. El sistema tiene que ser capaz de soportar 10 PCs más en red.Tiene que tener en cuenta que la información de cada pc debe actualizarse Instantáneamente y automáticamente, si un usuario de una pc modifica , da de alta o elimina algún registro.La base de datos debe residir en un equipo que a su vez actúa de cliente.Si dos usuarios o más al mismo tiempo desean tocar registros diferentes en una misma tabla, que puedan hacerlo.Si dos o más usuarios desean justo hacer operaciones sobre el mismo registro en la misma tabla que el sistema del mensaje pertinente.El sistema es un sistema de factura y nomina clientes, el sistema esta hecho pero me falta esto (ponerlo en red) hacerlo multi-usuario, creo que así se los llama a los sistemas que actúan en red.Pensé en trabajar con winsock pero me dijeron que para esto no es necesario.
    Además me dijeron que necesito al estar la aplicación en red, realizar n control de perfiles de usuarios.

    Actualmente la base de datos que uso es sql server 2008.


    atencion mi hermano tuttini..... !!


    Por favor ayuda

    Gracias

    ResponderEliminar
  4. hola leandro veo que eres un muy buen experto en la programacion , tengo un problema estoy empezando en esto y estoy desarrollando una aplicacion donde cuento con 6 listbox y quiero que la informacion que esta en ellas se guarde en mi base de datos access 2003 trabajo con visual basic te paso algo de lo que estoy intentando hacer en el codigo como ves quize pasar la informacion a textbox pero aun asi me sale error que no coiciden los criterios, ayudame por favor


    Sub GUARDAR()
    For i = 0 To LB1.Items.Count - 1
    PRUEBA1.Text &= LB1.Items(i) & vbCrLf
    Next
    For i = 0 To LB2.Items.Count - 1
    PRUEBA2.Text &= LB2.Items(i) & vbCrLf
    Next
    For i = 0 To LB3.Items.Count - 1
    PRUEBA3.Text &= LB3.Items(i) & vbCrLf
    Next
    For i = 0 To LB4.Items.Count - 1
    PRUEBA4.Text &= LB4.Items(i) & vbCrLf
    Next
    For i = 0 To LB5.Items.Count - 1
    PRUEBA5.Text &= LB5.Items(i) & vbCrLf
    Next
    For i = 0 To LB6.Items.Count - 1
    PRUEBA6.Text &= LB6.Items(i) & vbCrLf
    Next

    Dim CMD As New OleDb.OleDbCommand("INSERT INTO VENTAS VALUES ('" & NUMERO.Text & "','" & FECHA.Value & "','" & MONEDA.Text & "','" & TC.Text & "','" & MODALIDAD.Text & "','" & DIAS.Text & "','" & FV.Value & "','" & CLIENTE.Text & "','" & DIRECCION.Text & "','" & RUC.Text & "','" & VENDEDOR.Text & "','" & OBSERVACIONES.Text & "','" & PRUEBA1.Text & "','" & PRUEBA2.Text & "','" & PRUEBA3.Text & "','" & PRUEBA4.Text & "','" & PRUEBA5.Text & "','" & PRUEBA6.Text & "','" & SUBTOTAL.Text & "','" & IMP.Text & "','" & IGV.Text & "','" & OTRO.Text & "','" & OTROS.Text & "','" & TOTAL.Text & "','" & SON.Text & "','" & CAMBIO.Text & "','" & CONTROL.Text & "')", CN)
    CN.Open()
    CMD.ExecuteNonQuery()
    CN.Close()
    MsgBox("Registro Agregado")
    End Sub

    ResponderEliminar
  5. hola edward

    imagino esta respuesta fue solucionada en el foro

    http://social.msdn.microsoft.com/Forums/es-ES/vcses/thread/b007cb63-628a-45ef-9085-798bdb19b157

    saludos

    ResponderEliminar
  6. hola JuancaPeru

    algo que puedo ver como primer problema es que no usas parametros en la query que define

    como lo explico aqui

    ADO.NET – Parte 4 – Actualización Información Ms Access

    es muy importante como primer medida usar parametros en las queries y no concatenar el string

    saludos

    ResponderEliminar
  7. hola leandro... me has ayudado muchisimo con tus aportes en la web..

    Te comento, estoy desarrollando un sistema para una empresa, en el que se logean los usuarios y pueden acceder a su historial de cuentas, movimientos, etc... necesitaría saber como hacer para mantener abierta una sesión con una cuenta particular y que al cerrar sesión no se pueda acceder a ningún tipo de información?
    Desde ya muchas gracias....

    ResponderEliminar
  8. hola nakiotio

    por cerrar la session te refieres a que la persona hace un log off ?
    solo sale de la aplicacion

    o seria inhabilitar esa cuenta de usuario


    saludos

    ResponderEliminar
  9. Hola Leandro, muchas gracias por todas las respuesta oportunas. Tengo una preguntica, que aunque no tiene que ver con este post, me tiene quemando neuronas!!. Esta relacionado con herencia, quiero saber si existe alguna forma de tener acceso a las propiedades de la clase hijo desde la clase padre? Gracias de nuevo..

    ResponderEliminar
  10. leandro, mi problema es como mantener un session de login.
    La aplicacion que estoy realizando tiene un form de LOGEO y luego otros formularios q dependen de la persona q se logea....

    Como hago para mantener activa la cuenta (utilizando SESSION????), asi mismo me gustaria saber como hago para cuando el usuario quiera salir de su cuenta....

    desde ya muchas gracias!!!!

    ResponderEliminar
  11. hola Pierina Joplin

    en principio aplicando conceptos de POO, no puedes

    ahora si la clase padre puede acceder a uan instancia de un hijo, mediente uan relacion de asociacion, en ese caso podrias


    public class PersonaBase{

    private void Metodo1(){
    PersonaClass2 class2 =(PersonaClass2)this;

    }

    }

    public class PersonaClass1: PersonaBase {
    }

    public class PersonaClass2: PersonaBase {
    }

    igualmente no creo que esto sea muy bueno hacerlo
    porque recuerda que clases hijas puede haber miles, con eso estarias generando un acoplamiento no saludable para el modelo



    saludos

    ResponderEliminar
  12. Hola, tengo una duda: Quiero leer datos específicos de un archivo de texto y mostrarlos en un DataGridView.
    Por ejemplo:

    Servicio de Administración Tributaria

    Acuse de cancelación de CFDI

    Fecha y hora de solicitud: 07/02/2012 15:27:48

    Fecha y hora de cancelación: 07/02/2012 15:27:48

    RFC Emisor: ASDFG

    Folio Fiscal: Estado CFDI
    00FDA0DC-9BBF-E20A-CB6A-500448C93351 Cancelado


    Lo que quiero leer es la fecha y hora de cancelación
    (07/02/2012 15:27:48) y el folio fiscal (00FDA0DC-9BBF-E20A-CB6A-500448C93351) y mostrarlos en una columna diferente en mi DataGridView. Espero que puedas ayudarme


    ResponderEliminar
  13. hola Juan

    el tema que veo es que ese txt no tene mucha estructura para procesarse

    podrias usar el

    string[] lineas = File.ReadAllLines("ruta");

    foreach(string linea in lineas){

    string[] partes= linea.Split(':');
    if(parte[0]== "Fecha y hora de cancelación"){
    txtFecha.Text = parte[1];
    }

    if(parte[0]== "Folio Fiscal"){
    txtFolio.Text = parte[1];
    }

    }

    por supuesto es solo una idea, habria que probarla y refinarla un poco mas

    saludos

    ResponderEliminar
  14. Disculpa, pero no me quedó del todo claro. Este es mi código:

    private void button2_Click(object sender, EventArgs e)
    {
    if (textBox1.Text == "")
    {
    string caption = "ERROR";
    MessageBox.Show("No hay ningún elemento seleccionado", caption);
    }

    if (textBox1.Text != "")
    {
    string texto;
    int count = 3;
    string[] split = null;

    try
    {
    StreamReader sr = new StreamReader(openFileDialog1.FileName);
    while ((texto = sr.ReadLine()) != null)
    {
    split = texto.Split(new char[] {',', ';', '*'}, count);
    dataGridView1.Rows.Add(split[0], split[1], split[2]);
    split = null;
    }
    }
    catch (Exception ex)
    {
    MessageBox.Show("Error: " + ex.Message);
    }
    }

    Mi DataGridView consta de 3 columnas, y para poder mostrar los campos que quiero en cada columna, es necesario que copie, separado por comas, dichos campos al inicio de mi archivo de texto, para que me lea esa línea. Así:

    02/02/2012, 17:16:11, FEB2D7A5-D677-E788-F46A-F8B09FC8E90B

    Servicio de Administración Tributaria
    Acuse de cancelación de CFDI

    Fecha y hora de solicitud: 02/02/2012 17:16:11

    Fecha y hora de cancelación: 02/02/2012 17:16:11

    RFC Emisor: xxxxxxxxxxx

    Folio Fiscal: Estado CFDI
    FEB2D7A5-D677-E788-F46A-F8B09FC8E90B Cancelado

    Sello digital SAT : xxxxxxxxxxxxxxx

    Lo que no quiero es seguir haciendo eso, ya que son aproximadamente 3000 archivos, y darle ése formato a todos los archivos, sería demasiado tardado.
    De nuevo, gracias por tu tiempo.
    Saludos

    ResponderEliminar
  15. hola

    pero cual es el forma real del archivo? porque al principio tienes unso valores separados por coma en un sola linea
    pero despues tienes un dato por linea

    lo que tenenes que haces es ir leyendo las linea y procesar, no hay mucho secreto, en lugar de usar el StreamReader usa el File.ReadAllLinea() asi sera directo volcar el archivo en memoria y procesarlo

    saludos

    ResponderEliminar