sábado, 6 de octubre de 2012

[ASP.NET] Web Service - Publicar en IIS Windows XP (2/3)

 

Introducción


Aunque actualmente seguro se cuanta con Win 7, o alguna versión Win 2008 en un servidor, no va a faltar la ocasión que ante alguna prueba simple solo se disponga de Windows XP para montar el server web, quizás para algún uso de red local resulta simple

Se da por hecho que el IIS esta instalado en Windows, lo cual se realiza desde “Add and Remove Programs” en la opción especial “Add and Remove Windows Components”

Algo que debe aclararse antes de empezar es que el IIS de Windows XP no se pueden crear sitios web a los cuales se le asignen un puerto, esto si se puede en Win 7 o alguna las versiones de Win Server

En el IIS de Win Xp solo se pueden crear directorios virtuales que estarán bajo el default en el puerto 80

Este artículo es la continuación de

[ASP.NET] Crear y Publicar - Web Service (1/3)

 

Definición del sitio web mediante Wizard


El primer paso como es de esperar será acceder al IIS, para lo cual se deberá ir al panel del control

SNAGHTML3fac085

Se accede el IIS, sonde podemos ver el sitio por defecto configurado en el puerto 80

image

Para crear el sitio que alojara el servicio solo es necesario acceder al Wizard que nos guiara

image

image

Se define un nombre que identifique el contenido

image

Se define la carpeta donde se alojara el deploy del desarrollo

image

Si se requiere se puede cambiar las opciones de acceso al sitio, se marcaria la opción “Browse” si se requiere permitir listas los archivos si es que no se define ninguna pagina en concreto, se marcaria “Write” en caso de permitir subir archivos al sitio por medio de http

image

image

La finalización dejaría el sitio como hijo del sitio Default, es importante validar que este el icono de la caja gris, lo cual indica que se creo correctamente

image

 

Alternativa creación sitio (sin Wizard)


La alternativa arranca con la creación de la carpeta del sitio que se quiere crear, pero en esta oportunidad debe realizarse dentro del wwwroot

image

Esto permite que al actualiza el IIS se observara la carpeta recientemente creada, esta claro que mientras este solo la carpeta con el icono simple no podrá alojarse un desarrollo que cuente con un web.config y dll independiente, porque como carpeta el root sigue siendo el default

image

el siguiente paso será acceder a las propiedades

image

La opción “Create” será la encargada de convertir esa simple carpeta en un directorio virtual que pueda alijar el desarrollo

image

image

Ahora si la carpeta ha cambiado y esta lista para aceptar la publicación

image

 

Publicar desarrollo


La publicación se podría ver como en dos partes, la primera consiste en obtener la compilación del proyecto web, para esta tarea se dispone la la opción de “Publish” del Visual Studio

image

Se presentara un cuando como el siguiente

image

donde se debería ingresar una carpeta local de la pc donde se ubicara la publicación

Se puede observar el log de la tarea para validar que no se produce ningún error

image

El resultado será la publicación en la carpeta

SNAGHTML4311aa7

Esto debería copiarse y pegarse en la carpeta definida en el IIS

image

Desde el IIS se observara el cambio

SNAGHTML433d10a

Y si se accede desde el browser se podra ver la pagina web

image

y el servicio también

image

 

Configuración


Seguramente se deba realizar en algún momento el cambio de configuración en el sitio

image

Si algo no funciona seguro seria bueno validar que la extensión este definida

image

O validar los tiempos de timeout del sitio

image

Si estamos en desarrollo poder depurar seria de ayuda

image

Pero creo que verificar la seguridad es un punto clave para el funcionamiento

image

El usuario utilizado para impersonar el sitio es el definido en la opción de anónimos, si es necesario acceder a una carpeta de red o ejecutar alguna aplicación con determinados privilegios será necesario validar que ese usuario pueda hacerlo

image

Validar si la aplicación desarrollada cuenta con alguna pagina por defecto  

image

sino se cuenta con una pagina dentro de la lista de default y tampoco se define una, al acceder al sitio este fallara porque no podrá encontrar que pagina cargar,  salvo que se tenga la opción de “Browse” marcada

Validar la versión del framework es muy importante

image

si de casualidad se instalo primero el framework de .net y luego se habilito el IIS quizás haga falta ejecutar el comando

aspnet_regiis –i

para así asociar los componente de .net con el IIS

ASP.NET IIS Registration Tool (Aspnet_regiis.exe)

[ASP.NET] Crear y Publicar - Web Service (1/3)

 

Introducción


Un problema que se presenta regularmente luego de terminado el desarrollo de un proyecto web esta relacionado con la publicación y puesta en marca en un ambiente productivo, la idea del articulo será la de marcar los pasos para la creación y puesta en producción de un servicio web, así como también una página aspx, como veremos tratar un servicio o una pagina requieren los mismo pasos.

Se podría considerarse un servicio web (asmx) como una paginas aspx solo que sin una interfaz visual que se desplegué al usuario, los servicio permitirán que otras aplicaciones recuperen información publicada por medio de un protocolo conocido como es el http

En esta primera parte se analizaran:

  • Crea un proyecto asp.net
  • Agregar un web service al desarrollo web
  • Consumir web service
  • Actualizar configuración servicio

El ejemplo en el cual se basara la demostración consiste solo una lista de cursos que se obtiene de una base de datos. Como la publicación de un servicio es idéntica a la de una pagina web aprovecharemos el articulo para contemplar ambos escenarios.

 

Crear proyecto web


El primer paso será la creación del proyecto web

clip_image002

Es importante usar la opción que sea un Web Application

SNAGHTML11bb456

La estructura del proyecto del ejemplo incluye una db en carpeta de datos

clip_image004

Al ejecutar la pagina que lista los cursos se visualizara en el browser el resultado

clip_image006

Como es un ejemplo simple solo se hizo uso de un gridview junto a la ayuda de Entity Framework para resolver el acceso a datos

Por supuesto hasta ahora se hace uso del Visual Studio Developer Server, el cual es muy útil cuando se esta desarrollando, pero solo en esta etapa luego debe publicarse en el IIS

SNAGHTML3c4c21a

image

El Visual Studio creara por nosotros un server virtual en el puerto indicado en la configuración

 

Crear Servicio web


El próximo paso involucra la creación del servicio web, para ello agregaremos un nuevo ítem al proyecto web creado en el paso anterior

clip_image008

 

image

En el servicio se define un webmethod que liste los cursos

 

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class SchoolService : System.Web.Services.WebService
{

    [WebMethod]
    public List<CurseDTO> GetCurseList()
    {
        using (SchoolEntities context = new SchoolEntities())
        {
            return (from curse in context.Course
                   select new CurseDTO()
                   {
                       CurseId = curse.CourseID,
                       Title = curse.Title,
                       Department = curse.Department.Name
                   }).ToList();
        }
    }
}

public class CurseDTO
{
    public int CurseId {get; set;}
    public string Title { get; set; }
    public string Department {get; set;}
}

Si queremos ver la pantalla de inicio del servicio para validar que este correcto, solo será cuestión de navegarlo en el browser

clip_image012

image

Y hasta se puede invocar desde allí mismo

image

 

Consumir servicio web


Para consumir un servicio desde una aplicación cliente se agrega una referencia que creara el proxy, el cual no es mas que código para facilitar la invocación al servicio

SNAGHTML110147f

En este caso como el servicio esta en la misma solución se descubre de forma directa, sino será necesario definir la url donde se publico el servicio

SNAGHTML111cec8

Si bien a simple vista pareciera que la referencia al servicio no hizo mucho

SNAGHTML3cdc8ed

Si se habilita la opción superior de la barra del solution explorer se podrá descubrir mucho

SNAGHTML3cf8fa5

La referencia al servicio crea código por nosotros, además de definir una sección en el app.config, en el cual se podrá cambiar la url sin necesidad de recompilar luego de hacer la implementación definitiva del servicio en el IIS

image

Consumir el servicio desde código solo implica crear la instancia del proxy para luego hacer uso del método definido

using School.UI.SchoolSvc;
.
.

CurseDTO[] curseList;
using (SchoolServiceSoapClient svc = new SchoolServiceSoapClient())
{
    curseList = svc.GetCurseList();
}

dgvCourse.DataSource = curseList;

Con esto alcanza para invocar al servicio que hostea el server virtual que crea el VS y cargar el grid con la misma lista que al principio mostramos en la pagina web

Algo que seguramente habrán notado en el código seguro se relaciona con la respuesta del web method, como puede ser que devuelve un array de entidades cuando el código del servicio deja bien claro que era una lista, bien eso se debe a como se configura la referencia, pero puede cambiarse si hace falta.

SNAGHTML3ecbfca

Y se define el tipo de dato para que sea una lista

SNAGHTML3efc7b6

Al recompilar el proyecto el código que genera la referencia al servicio también devuelve una lista.

Código


El ejemplo del código fue creado con Visual Studio 2008 y Sql Server Express 2008 R2

[C#]
 

domingo, 23 de septiembre de 2012

Como evitar el uso del switch (2/2)

 

Introducción


Ester artículo será una expansión y mejora del anterior

Como evitar el uso del switch (1/2)

Básicamente se plantearan dos cambios significativos

  • definir una clase base, donde poder definir código repetitivo
  • el combobox resuelva de forma directa la instancia de calculo

 

Definir una clase base


Una de de los principales problemas que presenta a simple vista es el código similar en cada implementación de las clases de calculo, en la mayoría de los casos el bloque

imagen1

es idéntico, salvando lo resaltado en círculos que varia en cada implementación, lo resaltado se trata justamente de las funciones de fecha que el plazo determina en cada caso

Ahora bien, como se podría evitar repetir código ?

Para poder lograrlo realizaremos varias modificaciones, la primer será cambiar la Interface por una clase abstract, esto nos permitirá definir una clase base que puede definir código reutilizable

 

public abstract class ResolverPlazo
{
    public abstract PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota);

    protected PazoPagoResult Calcular(DateTime fechainicio, 
                                    decimal monto, 
                                    decimal montocuota, 
                                    Func<DateTime, int, DateTime> formulafechaFin, 
                                    Func<DateTime, int, DateTime> formulafechaItem)
    {
        //
        //Se calculan la cantidad de cuotas
        //
        int cantcuotas = Convert.ToInt32(Math.Floor(monto / montocuota));

        //
        // Se define la entidad de respuesta
        //
        PazoPagoResult result = new PazoPagoResult()
        {
            FechaFin = formulafechaFin(fechainicio, cantcuotas)
        };

        //
        //crea la lista de Pagos
        //
        for (int cuota = 1; cantcuotas >= cuota; cuota++)
        {
            ItemPago item = new ItemPago()
            {
                Fecha = formulafechaItem(fechainicio, cuota),
                Monto = montocuota * cuota
            };

            result.ListaPagos.Add(item);
        }

        return result;
    }
}

Hay algunos puntos importantes por resaltar, el primero se define un método como abstract, cada clase concreta será responsabilidad desarrollarlo, pero además para ayudar se define un método adicional que actuaria como témplate para el calculo, define el código que detectamos como repetitivo y añade un punto de extensibilidad por medio del uso de Func<>

En los lugares donde se requiere especificidad que solo la clase concreta conoce se deja el lugar abierto para poder hacerlo, básicamente el Func<> es un delegado que recibe una fecha y un valor numérico, y espera como respuesta una fecha

Las clases concretas se ven ahora beneficiadas, reduciéndose notablemente el código

 

public class SinPlazoPago : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return new PazoPagoResult()
        {
            FechaFin = fechainicio,
            ListaPagos = null
        };
    }
}

public class UnSoloPago : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return new PazoPagoResult()
        {
            FechaFin = fechainicio,
            ListaPagos = new List<ItemPago>()
            {
                new ItemPago(){ Fecha = fechainicio, Monto = monto }
            }
        };
    }
}

public class Semanal : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return base.Calcular(fechainicio,
            monto,
            montocuota,
            (fecha, cuotas) => fecha.AddDays(7 * cuotas),
            (fecha, cuota) => fecha.AddDays(7 * cuota));

    }
}

public class Quincenal : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return base.Calcular(fechainicio,
                            monto,
                            montocuota,
                            (fecha, cuotas) => fecha.AddDays(15 * cuotas),
                            (fecha, cuota) => fecha.AddDays(15 * cuota));
    }
}

public class Mensual : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return base.Calcular(fechainicio,
                            monto,
                            montocuota,
                            (fecha, cuotas) => fecha.AddMonths(1 * cuotas),
                            (fecha, cuota) => fecha.AddMonths(1 * cuota));
    }
}

public class Bimestral : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return base.Calcular(fechainicio,
                monto,
                montocuota,
                (fecha, cuotas) => fecha.AddMonths(2 * cuotas),
                (fecha, cuota) => fecha.AddMonths(2 * cuota));
    }
}

public class Trimestral : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return base.Calcular(fechainicio,
                               monto,
                               montocuota,
                               (fecha, cuotas) => fecha.AddMonths(3 * cuotas),
                               (fecha, cuota) => fecha.AddMonths(3 * cuota));
    }
}

public class Semestral : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return base.Calcular(fechainicio,
                               monto,
                               montocuota,
                               (fecha, cuotas) => fecha.AddMonths(6 * cuotas),
                               (fecha, cuota) => fecha.AddMonths(6 * cuota));
    }
}

public class Anual : ResolverPlazo
{
    public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return base.Calcular(fechainicio,
                               monto,
                               montocuota,
                               (fecha, cuotas) => fecha.AddYears(1 * cuotas),
                               (fecha, cuota) => fecha.AddYears(1 * cuota));
    }
}

Solo las dos primeras clases (que no tienes lógica en la calculo de los plazos de pago) quedaron como se definen originalmente, mientras que el resto hace uso de la funcionalidad definida en la clase base, nótese como las expresiones lambda en los parámetros del delegado permite variar el calculo de la fecha en cada caso.

 

Resolver de forma directa la clase de calculo


Este caso quizás no aplique en todas los casos ya que se requiere de entorno en donde los controles puedan conservar las instancias de los ítems con que con cargados, en este caso por tratarse de un ejemplo con Windows Application puede aplicarse, pero si seria un entorno web no se podria ya que los controles no conservan estado, se debería seguir usando la implementación original.

Lo que se pretende es poder quitar el método que usa el Dictionary<> para definir que clase concreta resolverá el calculo, el primer cambio se realiza en la clase que se usara para cargar el combo

public class Plazo 
{
    public int Id { get; set; }
    public string Desc { get; set; }
    public ResolverPlazo Instancia { get; set; }
}

se agrega una propiedad que define la instancia de la clase que resuelve el calculo de plazos para ese ítem

El siguiente cambio impacta en la forma como se devuelve la lista de ítems, se define nueva la propiedad con la instancia concreta de cada implementación de calculo

 

public static class PlazosHelper
{

    public static List<Plazo> ObtenerPlazos()
    {
        return new List<Plazo>()
        {
            new Plazo() { Id=1, Desc="Un solo pago", Instancia = new UnSoloPago()  },
            new Plazo() { Id=2, Desc="Semanal", Instancia = new Semanal()  },
            new Plazo() { Id=3, Desc="Quincenal", Instancia = new Quincenal()  },
            new Plazo() { Id=4, Desc="Mensual", Instancia = new Mensual()  },
            new Plazo() { Id=5, Desc="Bimestral", Instancia = new Bimestral()  },
            new Plazo() { Id=6, Desc="Trimestral", Instancia = new Trimestral()  },
            new Plazo() { Id=7, Desc="Semestral", Instancia = new Semestral()  },
            new Plazo() { Id=8, Desc="Anual", Instancia = new Anual()  }
        };
    }

    public static List<Plazo> ObtenerPlazosConItemOpcional()
    {
        List<Plazo> plazos = ObtenerPlazos();
        plazos.Insert(0, new Plazo() { Id = 0, Desc = "<<<Seleccione>>>", Instancia = new SinPlazoPago() });
        return plazos;
    }

}

Solo queda cambiar la forma como se recupera el ítem del combo y se invoca el método de calculo

 

private void Calcular()
{
    errorProv.Clear();

    decimal monto = 0;
    if (!decimal.TryParse(txtMonto.Text, out monto))
    {
        errorProv.SetError(txtMonto, "Debe ingresar un valor numerico");
        return;
    }

    decimal montoporcuota = 0;
    if (!decimal.TryParse(txtMontoPorCuota.Text, out montoporcuota))
    {
        errorProv.SetError(txtMontoPorCuota, "Debe ingresar un valor numerico");
        return;
    }
   
    //
    // Recuperamos la instancia de la entidad bindeada al combo
    //
    ResolverPlazo calcularPlazo = ((Plazo)cmdPlazo.SelectedItem).Instancia;

    //
    // se invoca de forma directa la operacion de calcular
    //
    PazoPagoResult result = calcularPlazo.Calcular(dtpFechaInicio.Value,
                                                        monto,
                                                        montoporcuota);

    txtResultado.Text = result.FechaFin.ToShortDateString();

    dgvListaPagos.DataSource = result.ListaPagos;

}

La clave esta en estas dos líneas

//
// Recuperamos la instancia de la entidad bindeada al combo
//
ResolverPlazo calcularPlazo = ((Plazo)cmdPlazo.SelectedItem).Instancia;

//
// se invoca de forma directa la operacion de calcular
//
PazoPagoResult result = calcularPlazo.Calcular(dtpFechaInicio.Value,
                                                    monto,
                                                    montoporcuota);

Como cada ítem del combo permite recuperar la clase con que fue creado, se puede castear al tipo concreto para así recuperar la instancia de la clase que posee la lógica de calculo de las cuotas, con eso se evita usa un método adicional que defina un diccionario de correspondencias entre un id y su instancia

Ya no se requiere el SelectedValue, porque no se trabaja con ningún id o código, sino que se accede directo a la instancia de la clase concreta, acortando las interacciones

 

Código


[C#]
 

sábado, 22 de septiembre de 2012

Como evitar el uso del switch (1/2)

 

Introducción


Suele suceder que muchas veces se tiene una lista de opciones y por cada una cierta lógica por ejecutar, para resolver el problema lo primero que se viene a la mente es usar un switch (c#), o un Select Case (vb.net), pero no se evalúa que existen algunas técnicas para evitar esto usando el poder de POO (Programación Orientada a Objetos)

Imaginamos una aplicación que debe calcular las cuotas basado en ciertos parámetros, siendo el plazo de cada pago el factor que afecta la formula

imagen1

Lo que primero viene a la mente seria realizar un switch por cada plazo de pago y allí realizar el calculo en cada caso, pero como evitarlo, bien ese será el objetivo del articulo.

 

Cargar combo opciones


El primer paso será la carga del combo, tarea que se realizara haciendo uso de clases y listas, es por eso que asignaremos

private void frmPrincipal_Load(object sender, EventArgs e)
{
    cmdPlazo.ValueMember = "Id";
    cmdPlazo.DisplayMember = "Desc";
    cmdPlazo.DataSource = PlazosHelper.ObtenerPlazosConItemOpcional();

}

para lo cual solo se necesitara de la definición de la clase

public class Plazo
{
    public int Id { get; set; }
    public string Desc { get; set; }
}

También se contara con la ayuda de una clase responsable (definida como static para que sea fácilmente accesible) que cargue los ítems

public static class PlazosHelper
{

    public static List<Plazo> ObtenerPlazos()
    {
        return new List<Plazo>()
        {
            new Plazo() { Id=1, Desc="Un solo pago" },
            new Plazo() { Id=2, Desc="Semanal" },
            new Plazo() { Id=3, Desc="Quincenal" },
            new Plazo() { Id=4, Desc="Mensual" },
            new Plazo() { Id=5, Desc="Bimestral" },
            new Plazo() { Id=6, Desc="Trimestral" },
            new Plazo() { Id=7, Desc="Semestral" },
            new Plazo() { Id=8, Desc="Anual" }
        };
    }

    public static List<Plazo> ObtenerPlazosConItemOpcional()
    {
        List<Plazo> plazos = ObtenerPlazos();
        plazos.Insert(0, new Plazo() { Id = 0, Desc = "<<<Seleccione>>>" });
        return plazos;
    }

}

 

Resolver Calculo


Para resolver el calculo se crea un método que podrá ser invocado desde dos eventos, el cual además proporcionara validaciones básicas

private void cmdPlazo_SelectionChangeCommitted(object sender, EventArgs e)
{
    Calcular();
}

private void cmdCalcular_Click(object sender, EventArgs e)
{
    Calcular();
}

private void Calcular()
{
    errorProv.Clear();

    decimal monto = 0;
    if (!decimal.TryParse(txtMonto.Text, out monto))
    {
        errorProv.SetError(txtMonto, "Debe ingresar un valor numerico");
        return;
    }

    decimal montoporcuota = 0;
    if (!decimal.TryParse(txtMontoPorCuota.Text, out montoporcuota))
    {
        errorProv.SetError(txtMontoPorCuota, "Debe ingresar un valor numerico");
        return;
    }

    PazoPagoResult result = PlazosHelper.CalcularPlazo(Convert.ToInt32(cmdPlazo.SelectedValue), 
                                                        dtpFechaInicio.Value,
                                                        monto,
                                                        montoporcuota);

    txtResultado.Text = result.FechaFin.ToShortDateString();

    dgvListaPagos.DataSource = result.ListaPagos;

}

Pero para llegar al calculo haremos uso de la potencia que provee las interfaces

public interface IResolverPlazo
{
    PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota);
}

La interfaz defina una entidad compleja para devolver la info del calculo

public class PazoPagoResult
{
    public PazoPagoResult()
    {
        this.ListaPagos = new List<ItemPago>();
    }
    public DateTime FechaFin { get; set; }
    public List<ItemPago> ListaPagos { get; set; }
}

public class ItemPago
{
    public DateTime Fecha { get; set; }
    public decimal Monto { get; set; }
}

cada implementación define como se resuelve la lógica, por supuesto aquí solo pondré algunas, pero en el código que descarguen están todas las clases implementadas

public class SinPlazoPago : IResolverPlazo
{
    public PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return new PazoPagoResult()
        {
            FechaFin = fechainicio,
            ListaPagos = null
        };
    }
}

public class UnSoloPago : IResolverPlazo
{
    public PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        return new PazoPagoResult()
        {
            FechaFin = fechainicio,
            ListaPagos = new List<ItemPago>()
            {
                new ItemPago(){ Fecha = fechainicio, Monto = monto }
            }
        };
    }
}

public class Semanal : IResolverPlazo
{
    public PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        //
        //Se calculan la cantidad de cuotas
        //
        int cantcuotas = Convert.ToInt32(Math.Floor(monto / montocuota));

        //
        // Se define la entidad de respuesta
        //
        PazoPagoResult result = new PazoPagoResult()
        {
            FechaFin = fechainicio.AddDays(7 * cantcuotas)
        };

        //
        //crea la lista de Pagos
        //
        for (int cuota = 1; cantcuotas >= cuota; cuota++)
        {
            ItemPago item = new ItemPago()
            {
                Fecha = fechainicio.AddDays(7 * cuota),
                Monto = montocuota * cuota
            };

            result.ListaPagos.Add(item);
        }

        return result;
    }
}


public class Bimestral : IResolverPlazo
{
    public PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota)
    {
        int cantcuotas = Convert.ToInt32(Math.Floor(monto / montocuota));

        PazoPagoResult result = new PazoPagoResult()
        {
            FechaFin = fechainicio.AddMonths(2 * cantcuotas)
        };


        for (int cuota = 1; cantcuotas >= cuota; cuota++)
        {
            ItemPago item = new ItemPago()
            {
                Fecha = fechainicio.AddMonths(2 * cuota),
                Monto = montocuota * cuota
            };

            result.ListaPagos.Add(item);
        }

        return result;
    }
}

La clase helper solo define el diccionario que mapea el id de la opción con la implementación que resuelve el calculo

public static class PlazosHelper
{

    public static PazoPagoResult CalcularPlazo(int plazo, DateTime fechainicio, decimal monto, decimal montocuota)
    {
        Dictionary<int, IResolverPlazo> resolverPlazos = new Dictionary<int, IResolverPlazo>()
        {
            {0, new SinPlazoPago()},
            {1, new UnSoloPago()},
            {2, new Semanal()},
            {3, new Quincenal()},
            {4, new Mensual()},
            {5, new Bimestral()},
            {6, new Trimestral()},
            {7, new Semestral()},
            {8, new Anual()},
        };

        return resolverPlazos[plazo].Calcular(fechainicio, monto, montocuota);
    }


}

Se usa el id del plazo para determinar la posición del Dictionary<> y poder así determinar que instancia hacer uso para lanzar el calculo, como todas las clase implementan la misma interfaz se puede invocar sin problemas

Conclusión


La ventaja de usar clases por sobre un simple switch se ve reflejada en la posibilidad de extender el código con el solo hecho de definir nuevas implementaciones de las clases que heredan de la interfaz definida.

En este caso no se implemento, pero podría llevarse el código de las clases que resuelven el calculo a una assembly (dll) separado y poder de forma dinámica alterar la lógica según sea necesario, sin afectar por ello al código core de la aplicación, por configuración se podría indicar que librería cargar en cada momento.

 

Código


[C#]
 

[ASP.NET] Master Page - Referencia relativa a elementos de la página

 

Introducción


Durante el diseño de un sitio web la referencia a los elementos dentro de la página es un aspecto que genera errores en el desarrollo.

Este problema se potencia aun mas si se hace uso de Master Page, pues el diseño del master contendrá objetos que aplicaran con diferentes url relativas según donde se encuentre la página web desarrollada.

Recordemos que al final quien determina la url final es la pagina aspx y no el master page.

 

Detección de problema


Imaginemos que tenemos un desarrollo simple de solo dos paginas y una master page, como ser:

imagen1

Como se observa en la estructura del ejemplo tenemos el master page se encuentra en el raíz del sitio junto con una pagina de inicio, pero también observamos una carpeta que contendrá paginas pertenecientes a un modulo de administración, ambos aspx observados implementaran el mismo MasterPage.

Un diseño lógicamente nos indicaría que aseguráramos que la vista del master page en el Visual Studio este correcta, por lo que si usáramos:

imagen2

En el diseñador del Visual Studio veríamos que parece ir todo sobre ruedas.

Al aplicar el master page a la pagina Default.aspx, no notaríamos mayor problema ya que al igual que la master están ambos al mismo nivel.

imagen3

Pero que sucede si vamos a la pagina que se encuentra dentro de la carpeta Admin

imagen4

Rápidamente se observa que la imagen que define la master no puede ser resuelta adecuadamente, aquí no se observa pero esto mismo que claramente se aprecia con la imagen sucederá con los archivo .js, si se ejecuta la pagina el código cliente fallara porque las librerías de jquery no podrán encontrarse.

 

Resolver el problema


Existen algunas técnicas que pueden ser usadas para resolver el problema de la url relativa.

Una forma simple de lograrlo es por medio del ResolveClientUrl(), si bien en tiempo de diseño no se podrá apreciar el recurso de forma visual en el diseñador del Visual Studio

imagen5

se podrá asegurar con esta técnica que sin importar en que nivel se encuentre la pagina que implementa la master las url generada resolverán de manera correcta.

Ahora al acceder al código de la pagina que se encuentra dentro de la carpeta a un nivel diferente

imagen6

se ha creado la ruta relativa correcta desde la master.

Desde la pagina Default.aspx por encontrarse al mismo nivel la url es distinta pero sigue resolviendo de forma adecuada

imagen7

 

Otras técnicas


Existen otras técnicas que implican código, se podría usar

protected void Page_Load(object sender, EventArgs e)
{
    ScriptManager.RegisterClientScriptInclude(Page, typeof(Page), "jquery", ResolveClientUrl(@"~/Scripts/jquery-1.4.1.js"));
    ScriptManager.RegisterClientScriptInclude(Page, typeof(Page), "jquery.validate", ResolveClientUrl(@"~/Scripts/jquery.validate.js"));
}

El tema es que el ScriptManager.RegisterClientScriptInclude() crea la definición del <script> dentro del tag <form> por lo que el código que podríamos definir en el <head> causaría error.

También se podría intentar usar

protected void Page_Load(object sender, EventArgs e)
{

    HtmlGenericControl jqueryctrl = new HtmlGenericControl("script");
    jqueryctrl.Attributes.Add("type", "text/javascript");
    jqueryctrl.Attributes.Add("src", ResolveClientUrl(@"~/Scripts/jquery-1.4.1.js"));
    this.Page.Header.Controls.Add(jqueryctrl);

    HtmlGenericControl jqvalidatectrl = new HtmlGenericControl("script");
    jqvalidatectrl.Attributes.Add("type", "text/javascript");
    jqvalidatectrl.Attributes.Add("src", ResolveClientUrl(@"~/Scripts/jquery.validate.js"));
    this.Page.Header.Controls.Add(jqvalidatectrl);


}

Esto resolvería el problema anterior colocando el tag <script> dentro del <head>, pero aun así lo ubica por debajo de la sección del ContentPlaceHolder que se define como lugar donde colocar el script de la pagina requiere

Es por eso que lo mas acertado seria definir el html donde se necesita utilizando el ResolveClientUrl()

 

Código


 

jueves, 30 de agosto de 2012

MailMessage – Adjuntar Imagen en el cuerpo del Mail (3/3)

 

Introducción


En la creación de un mail cuyo cuerpo sea HTML se puede requerir que una imagen forme parte del mismo, pero ubicarla como parte del mail será necesario no solo embeberla dentro del contenido sino poder referenciarla para ubicarla en una posición concreta

 

Imagen como recurso


En el archivo embebido como recurso dentro del assembly se define el html del cuerpo del mail

<html>
	<body>
		<h2> Documento con imagen adjunta</h2>
		<br/>
		<img src="cid:estadistica">
	</body>
</html>

en este se usa el cid junto a un nombre para ubicar el recurso de la imagen que se asocia al mail

La definición del recurso se debe realizar en una vista alternativa del mail

AlternateView htmlView = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);

string imagePath = Path.Combine(TestContext.DeploymentDirectory, "estadistica.jpg");
LinkedResource logo = new LinkedResource(imagePath);
logo.ContentId = "estadistica";
htmlView.LinkedResources.Add(logo);

mail.AlternateViews.Add(htmlView);

es allí donde se define el id del resource de la vista html

El test completo además de la vista html se incluye una vista en texto plano en caso de quien reciba el correo lo este visualizando desde un cliente que no soporta html

[TestMethod]
public void SendMailBodyImage()
{

    //
    // se define la lista de destinatarios
    //
    List<string> destinatarios = new List<string>()
    {
        "xx@gmail.com",
        "xx@hotmail.com"
    };


    MailMessage mail = new MailMessage()
    { 
        Subject = "Mail Test con formato html",
        IsBodyHtml = true
    };


    //
    // se asignan los destinatarios
    //
    foreach (string item in destinatarios)
    {
        mail.To.Add(new MailAddress(item));
    }

    //
    // se crea el mensaje como texto plano
    //
    string plaintextBody = "";

    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MailSendTest.MailPlainTextBody.txt"))
    using (StreamReader reader = new StreamReader(stream))
    {
        plaintextBody = reader.ReadToEnd();
    }

    AlternateView plainTextView = AlternateView.CreateAlternateViewFromString(plaintextBody, null, MediaTypeNames.Text.Plain);
    mail.AlternateViews.Add(plainTextView);


    //
    // se crea el mensaje html
    //
    string htmlBody = "";

    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MailSendTest.MailHtmlBody.txt"))
    using (StreamReader reader = new StreamReader(stream))
    {
        htmlBody = reader.ReadToEnd();
    }

    AlternateView htmlView = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);
   
    string imagePath = Path.Combine(TestContext.DeploymentDirectory, "estadistica.jpg");
    LinkedResource logo = new LinkedResource(imagePath);
    logo.ContentId = "estadistica";
    htmlView.LinkedResources.Add(logo);

    mail.AlternateViews.Add(htmlView);


    //
    // se define el smtp
    //
    SmtpClient smtp = new SmtpClient();
    smtp.Send(mail);


}

Definir la imagen en la carpeta output del Test


Para que el proyecto de test funcione será necesario que la imagen que se adjunta al mail sea ubicada en la carpeta donde el test compila.

Para automatizar esta acción se definir un Test Setting, para ello en la sección “Solution Items” se agrega un nuevo ítem

imagen1

imagen2

Una vez que se crea, solo se hace doble click en el archivo de extensión .testsettings y se define la carpeta que contiene la imagen

imagen3

Esto hará que se copie el imagen que se adjunta en el test, y se pueda localizar dinámicamente usando: TestContext.DeploymentDirectory

Código


Se utiliza VS 2010 para desarrollar el ejemplo

[C#]
 

lunes, 27 de agosto de 2012

MailMessage–Adjuntar Archivo sin bloquearlo (2/3)

 

Introducción


Cuando se necesita enviar un mail que adjunte un documento se suele proporcionando directamente la ruta directa al archivo, la librería smtp que proporciona .net realizara correctamente el attach pero lo mantendrá bloqueado, es por eso que una operación posterior que requiera eliminarlo generara un mensaje como ser

imagen1

 

Adjuntar Archivo sin bloquearlo


Para adjuntar un archivo sin bloquearlo la clave esta en asignar un stream al attach del mail y no directamente el archivos indicando su ruta física.

En este caso se crea un método que ayudara en esta tarea, tomando la ruta del archivo y devolviendo el Stream:

public Stream GetStreamFile(string filePath)
{
    using (FileStream fileStream = File.OpenRead(filePath))
    {
        MemoryStream memStream = new MemoryStream();
        memStream.SetLength(fileStream.Length);
        fileStream.Read(memStream.GetBuffer(), 0, (int)fileStream.Length);

        return memStream;
    }
}

Este método es usado en la línea:

mail.Attachments.Add(new Attachment(GetStreamFile(pdfpath), Path.GetFileName(pdfpath), "application/pdf"));

además de asignar el Stream del archivo se proporciona el nombre y el tipo que se adjunta.

El código completo del test usa la librería iTextSharp para crear un archivo pdf de prueba que será adjunto al mail y finalmente eliminado.

[TestMethod]
public void SendMailAttachFile()
{

    //
    // se genera el pdf que luego se adjuntara al mail
    //
    string pdfpath = Path.Combine(TestContext.TestDeploymentDir, "PDFMailAttach.pdf");

    Document document = new Document();
    PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(pdfpath, FileMode.Create));

    document.Open();
    document.Add(new Paragraph("Documento Adjunto sin Bloquear"));
    document.Close();



    //
    // se define la lista de destinatarios
    //
    List<string> destinatarios = new List<string>()
    {
        "xx@gmail.com",
        "xx@hotmail.com"
    };


    //
    // se crea el mensaje
    //
    string body = "";
   
    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MailSendTest.MailBody.txt"))
    using (StreamReader reader = new StreamReader(stream))
    {
        body = reader.ReadToEnd();
    }

    MailMessage mail = new MailMessage()
    {
        Body = body,
        Subject = "Mail Test con adjunto",
        IsBodyHtml = false
    };

    //mail.Attachments.Add(new Attachment(pdfpath, "application/pdf"));
    mail.Attachments.Add(new Attachment(GetStreamFile(pdfpath), Path.GetFileName(pdfpath), "application/pdf"));

    //
    // se asignan los destinatarios
    //
    foreach (string item in destinatarios)
    {
        mail.To.Add(new MailAddress(item));
    }


    //
    // se define el smtp
    //
    SmtpClient smtp = new SmtpClient();
    smtp.Send(mail);


    //
    // se elimina el archivo porque no estara bloqueado
    //
    File.Delete(pdfpath);

    Assert.IsFalse(File.Exists(pdfpath));

}

Código


[c#]