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]
|