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

10 comentarios:

  1. Hola, Tengo un problema muy feo quisiera me puedas ayudar. nose donde mandarte las imagenes para que tengas una idea mas clara.

    Ena lista de actividades, y las cuales tienen requisistos, y en otras tablas SegActividades y SegRequisitos se va guardando el avance de dichas acciones, pero nose como hacer para que me copie lo de las tablas a las otras, y me copie los requisitos...... agrr es en gorroso.

    ResponderEliminar
  2. hola Susana

    para subir las imagenes podrias publicarlas en skydrive

    la verdad no compredi del todo el problema, veo que quieres copiar el contenido de una entidad en ota, pero esto no se si necesitas resolverlo a nivel de codigo en la pantalla, o es a nivel de base de datos


    me pregunto no sera un tema que el foro podria tratar en detalle, digo me parece un medio donde se puede plantear codigo y publicar imagenes
    foro c#

    saludos

    ResponderEliminar
  3. Buenos dias.
    Camarada Leandro me poadría orientar con lo siguiente:
    Tengo un grupo de empresas a los le doy Soporte Técnico y algunas me quedan mal en el pago.
    ¿ transcurridos n dias como bloquear una computadora?, que no se pueda usar mas. ¿Habrán funciones que me permitan realizar estos bloqueos o interferencias?
    Gracias por su atención.

    ResponderEliminar
  4. hola MarlonVillamizar

    la verdad no trabajo en infraestructura, soy desarrollador

    si quieres bloquear el uso de una aplciacion que desarrollas hay tecnicas para definir licencias en el uso de tu desarrollo

    ahora si solo es una pc, no veo etico impedir el uso de la misma, digo uan cosa es que una herramienta no pueda suarla y otra es inutilizar una pc completa

    no se si cuando no te pagan aun tienes acceso a la red, porque si es asi de forma remota podrias hacer estragos sin que se enteren, con una tool como esta

    http://www.dameware.com/Home.aspx

    quizas deberias consultar en los foros de TechNet

    saludos

    ResponderEliminar
  5. Buenos días. Bueno Alejandro. gracias por tu respuesta

    ResponderEliminar
  6. Hola Leandro, nuevamente yo por aquí, en esta oportunidad quisiera una sugerencia de navegación. Estoy llamando una misma página desde dos sitios diferentes, la cosa es que necesito saber quien fue la pagina que hizo la llamada inicial. La complicación es que esa pagina, es un index que hace referencia a otros links, actualmente estoy usando una variable para saber de donde vino la llamada, pero la tengo en todos los actions, y tengo q mantenerla entre los request, alguna idea? No estoy segura de como funciona la variable de session, pero tengo la idea de que esta pudiera confundir la pagina q hizo la llamada, si el mismo usuario solicita una opción diferente durante la misma session?. Muchas gracias..

    ResponderEliminar
  7. hola Pierina Joplin

    no has evaluado usar las

    ASP ServerVariables

    puntualmente la HTTP_REFERER


    saludos

    ResponderEliminar
  8. Buenas Tardes Leandro.

    Intento desarrollar un sistema en c# dividido en modulos solo lo he hecho en c++.
    No se si tambien se puede realizar en c#.

    Lo que intento es lo siguiente:

    EJEMPLO
    Sistema (EXE EJECUTABLE) Encargado de ejecutar las DLLs

    ALTA ( ALTA.DLL )
    BAJA ( BAJA.DLL)
    CAMBIOS( CAMBIOS.DLL )

    C:/SISTEMA/DLL <-- EMJEMPLO DE RUTA
    NOTA ESTAS DLL ME GUSTARIA PONERLAS EN UNA CARPETA ESPECIFICA DONDE LAS CARGUE DIRECTAMENTE EL SISTEMA

    Asi pienso que al darle manteminieto solo modifico un modulo.

    Me podrias ayudar si se puede realizar esto en c#

    ResponderEliminar
    Respuestas
    1. hola
      cargar dll dinamicas desde una carpeta se puede lograr pero no es tan modular si tienes que programarlo
      quizas debas darle una mirada a Managed Extensibility Framework (MEF)
      Con estas librerias podras extender la logica en modulos que se inyectan
      Nota: para ver el codigo correctamente cambia en la url donde dice es-es por en-us
      saludos

      Eliminar
  9. Saludos Leandro, muy buena tu explicación del uso de los archivos de configuración. Tengo una consulta, estoy desarrollando una aplicación que comparte información con otra, y necesito acceder a la información del archivo de configuración de esta. ¿De que manera puedo hacerlo?

    ResponderEliminar