9.03.2010

DataSets o POCOs – Plain Old CLR Object

Al día de hoy no había querido ahondar en este tema porque es un tema un poco dificil de aclarar sin estar presente y sin la posibilidad de “hacer garabatos” en una pizarra para aclarar más las cosas; sin embargo, dado que el tema se repite una y otra vez, voy a tratar de explicar cuales son las razones por las cuales yo siempre prefiero utilizar POCOs a DataSets en este post.

Definiciones

En primera instancia, vamos a definir un POCO. Un POCO es un Plain Old CLR Object – un concepto traido desde Java desde el famoso POJO ( como muchos otros ) – y en el cual simplemente se define una clase que contiene los atributos de las entidades y sus propiedades. Además, la clase contiene un constructor vacío y un constructor para inicializar todos sus elementos.

El DataSet, como ya muchos saben es una clase bastante sofisticada que se encuentra definida en el namespace System.Data y es neutral al repositorio de datos que se este utilizando; – igual que el POCO – esta es la razón por la cual el DataSet esta definido en este Namespace. El DataSet es un contenedor que tiene la capacidad de mantener datos en memoria y de forma desconectada ( sin necesidad de tener una conexión a la base datos viva ) por lo que son útiles en algunos contextos donde se desea seguir trabajando si la base de datos no esta disponible – posible pero arriesgado.

Mi Opción

Yo siempre voy a preferir trabajar con POCO o entidades de negocios planas a trabajar con dataset por las razones que a continuación enumero:

  1. El costo de instanciación de un dataset vrs un POCO es muy alto. El dataset tiene una cantidad enorme de relaciones con clases que le permiten llevar a cabo todas sus tareas cuando se necesita. Por ejemplo, hay colecciones que se instancian para manejo de tablas, relaciones, vistas default, etc. Por lo tanto si vamos a hacer una simple consulta que nos devuelve un registro, el costo de inicialización del dataset supera por mucho el verdadero conjunto de datos del dominio de la aplicación que necesitamos para trabajar.
  2. Si no se usan dataset tipados para trabajar en nuestra aplicación, vamos a tener operaciones de boxing y unboxing cada vez que cargamos el dataset o que modificamos los elementos existentes dentro del mismo. Esto se da porque el Dataset para poder contener cualquier esquema de datos utiliza el tipo object en cada uno de las columnas y por lo tanto cada valor que se obtiene de la fuente de datos se convierte a un object – boxing – y cuando se requiere sacar del dataset se convierte de object al value type requerido – unboxing. Como ya algunos saben, las operaciones de boxing y unboxing son de las más caras que existen en el framework – solo por debajo de algunas como cargar documentos usando el XmlDocument o hacer transformaciones con XSLT.
  3. A la hora de consumir servicios si el servicio expone DataSets la cantidad de XML que se serializa es mucho mayor a la que se genera en el contrato SOAP a la hora de serializar una entidad plana – POCO – y por poco que parezca, cualquier ganancia en lo que respecta a los datos que se envían por el canal es relevante.
  4. Si diseñamos la aplicación y utilizamos UML, todos nuestros esquemas diseñados son inaplicables a este tipo de entidades porque no se puede crear patrones como el adapter o no se puede trabajar directamente sobre el contrato y no la implementación del contrato para tener más libertad a la hora de hacer crecer la aplicación hacia nuevas funcionalidades.
  5. Por la razón anterior, el uso de contenedores de IoC como Unity se vuelve practicamente imposible a nivel de entidades de negocio.
  6. El código desarrollado cuando se trabaja con dataset no tipados es dificil de mantener ya que por lo general las columnas se acceden a través de índices y no a través de nombres en la colección de columnas, por lo que si se agrega o se quita una columna en la base de datos vamos a tener problemas en tiempo de ejecución y no en tiempo de compilación.
  7. Mucha gente utiliza los dataset para escenarios desconectados donde no se tiene acceso al repositorio de datos en un momento determinado, sin embargo estos escenarios son un poco riesgosos ya que si el cliente se queda sin acceso al repositorio por una cantidad de tiempo considerable, puede que su aplicación falle y termine perdiendo todas las operaciones hechas en el dataset durante ese tiempo. Para este tipo de escenarios es mejor tener una base de datos local – SQL server Express y utilizar el Sync Framework de Microsoft para tener los datos sincronizados de manera segura.

Por ahora me quedo con estas razones, aunque tengo algunas más relacionadas a temas de IoC y Transaccionabilidad, pero ocuparía otro post completo para detallarlas. Debo aclarar que la mayoria de las razones expuestas aquí para algunas personas no son relevantes puesto que utilizan diferentes formas de trabajar a las que yo estoy considerando aquí – arquitectura n-layer –; en otros casos no modelan sus aplicaciones en UML y/o tampoco exponen la funcionalidad de su aplicación vía servicios.

Technorati Tags: ,,,

9.01.2010

ValueResolver<T> y AutoMapper

En el post pasado acerca de AutoMapper, se expuso como hacer resúmenes de datos – sumatorias en este caso – utilizando Automapper. Entre los correos y comentarios que me llegaron acerca del post,  hubieron varios que me indicaban que la conversión en la configuración del mapeo, era un código un poco complicado de entender y dificil de dar mantenimiento, por lo cual me decidí a escribir este post en el cual explico como hacerlo de una forma “más mantenible" y entendible”.

AutoMapper tiene una clase abstracta llamada ValueResolver la cual nos permite configurar como queremos mapear a un tipo destino. La definición de esta clase dentro de AutoMapper es la siguiente:

using System;

namespace AutoMapper
{
public abstract class ValueResolver<TSource, TDestination> : IValueResolver
{
protected ValueResolver();

public ResolutionResult Resolve(ResolutionResult source);
protected abstract TDestination ResolveCore(TSource source);
}
}



Esta clase nos permite definir el tipo destino y el tipo fuente a través de la definición genérica de la clase. Además tiene un método crucial < ResolverCore> el cual debe de ser implementado cuando se utiliza al clase abstracta; este método es el que va a contener la lógica que nosotros queramos agregarle a la conversión de nuestros tipos. En nuestro caso, queremos tener un poco mas de control respecto a la forma en que se mapea el campo TotalDeHorasDelProyecto y CostoTotalDelProyecto, los cuales están definidos como atributos en la clase Resumen del Proyecto. Para lograr una configuración de la conversión un poco mas sencilla, vamos a crear un par de clases CostTotalDelProyecto y TotalDeHorasDelProyecto las cuales van a implementar el metodo ResolveCore. La definición de las clases es la siguiente:



public class TotalDeHorasDelProyecto : ValueResolver<Proyecto, double>
{

protected override double ResolveCore(Proyecto source)
{
return source.TareasDelProyecto.Sum(tarea => tarea.HorasAsignadas);
}

}

public class CostTotalDelProyecto : ValueResolver<Proyecto, double>
{

protected override double ResolveCore(Proyecto source)
{
return source.TareasDelProyecto.Sum(tarea => tarea.HorasAsignadas * tarea.CostoXHora);
}
}



Como se ve en el código anterior, en la implementación del método ResolveCore se codifico el código que antes estaba en la configuración del Mapeo. Ahora procedemos a reconfigurar nuestro mapeo en la aplicación de la siguiente manera:



Mapper.CreateMap<Proyecto, ResumenDelProyecto>()
.ForMember(dest => dest.NombreProyecto, src => src.MapFrom(fuente => fuente.Nombre))
.ForMember(dest => dest.TotalHorasDelProyecto, opt => opt.ResolveUsing<TotalDeHorasDelProyecto>())
.ForMember(dest => dest.CostoTotalEnHorasDeProyecto, opt => opt.ResolveUsing<CostTotalDelProyecto>());

var cp = Mapper.Map<Proyecto, ResumenDelProyecto>(p);



Analizando el código en negrita, vemos que estamos utilizando el método ResolveUsing<T> de AutoMapper, el cual nos permite indicarle el tipo de alguna clase que tenga implementada la clase abstracta ValueResolver, esto por cuanto el método ResolveUsing va a invocar por contrato la implementación del método ResolveCore provisto por la clase enviada como parámetro en T.



Technorati Tags: ,

8.25.2010

Seminario de SOA

Aprovecho este espacio para invitarlos a un seminario de Arquitectura Orientada a Servicio, el cual tengo el privilegio de dar el día 1ero de octubre en la Ulacit. Ese día estaremos utilizando el NServiceBus para demostrar como trabajar con un ESB en nuestra organización y además para aclarle a todos aquellos que todavía no lo tienen claro, que para tener una arquiectura orientada a servicios se requieren servicios, no solamente servicios Web. Cualquier consulta comunicarse conmigo o en los teléfonos y correos del anuncio.

SOA Anuncio

Technorati Tags: ,

8.22.2010

AutoMapper 2 – Trabajando con Conversiones de Varios Niveles

Siguiendo con el post anterior, Automapper no solamente nos permite mapear clases con una relación de 1 a 1. En este post vamos a ver como podemos totalizar de diversas formas una relación entre dos clases utilizando AutoMapper. En primera instancia vamos a suponer una relación entre dos clases, una llamada Tarea la cual contiene información referente a las tareas que componen un proyecto y cuya estructura se puede ver aca.

public class Tarea
{
public int Id { get; set; }
public string Nombre { get; set; }
public double HorasAsignadas { get; set; }
public double CostoXHora { get; set; }

public double GetTotal()
{
return HorasAsignadas * CostoXHora;
}
}


En este caso simplemente tenemos 4 propiedades automáticas y una función que me retorna el costo total de esta tarea. Seguidamente vamos a tener una clase llamada proyecto, la cual contiene una lista de tareas, y dos métodos que nos permiten ver el total de horas del proyecto y el costo total en horas del proyecto. Ambos métodos utilizan el método de extensión SUM de las listas en .NET.



public class Proyecto
{
public int Id { get; set; }
public string Nombre { get; set; }
public List<Tarea> TareasDelProyecto { get; set; }
public DateTime FechaInicio { get; set; }
public DateTime FechaFinal { get; set; }

public double GetTotal()
{
return TareasDelProyecto.Sum(tarea => tarea.GetTotal());
}

public double ObtenerTotalHorasDelProyecto()
{
return TareasDelProyecto.Sum(tarea => tarea.CostoXHora);
}

public Proyecto()
{
TareasDelProyecto = new List<Tarea>();

}
}



Seguidamente vamos a mostrar la clase a la que quiero mapear las clases anteriores. Esta clase lo que quiero que tenga es un resumen del proyecto en lo que respecta a costos y tiempo, por lo tanto, esta clase va a tener una propiedad nombre para identificar el proyecto, una campo para mapear el total del horas empleadas en el proyecto, y un campo para mapear el costo total en horas del proyecto.



public class CostoTotalDeProyecto
{
public string NombreProyecto { get; set; }
public double TotalHorasDelProyecto { get; set; }
public double CostoTotalEnHorasDeProyecto { get; set; }
}



Ahora vamos a configurar AutoMapper para que me haga el mapeo de estas clases.



Mapper.CreateMap<Proyecto, CostoTotalDeProyecto>()
.ForMember(dest => dest.NombreProyecto, src => src.MapFrom(fuente => fuente.Nombre))
.ForMember(dest => dest.TotalHorasDelProyecto, src => src.MapFrom( fuente=> fuente.ObtenerTotalHorasDelProyecto()))
.ForMember(dest => dest.CostoTotalEnHorasDeProyecto, src => src.MapFrom ( fuente => fuente.GetTotal()));



Luego creamos las tres instancias para la clase tarea y se las agregamos a la clase proyecto.



Tarea t1 = new Tarea
{
Id = 1,
Nombre = "Reunión Diaria Seguimiento",
HorasAsignadas = 1,
CostoXHora = 20
};

Tarea t2 = new Tarea
{
Id = 2,
Nombre = "Diseño del plano",
HorasAsignadas = 80,
CostoXHora = 25
};

Tarea t3 = new Tarea
{
Id = 3,
Nombre = "Revisión de Avance",
HorasAsignadas = 160,
CostoXHora = 20
};

Proyecto p = new Proyecto
{
Id = 1,
Nombre = "Construcción Casa",
FechaInicio = DateTime.Now,
FechaFinal = DateTime.Now.AddDays(160)
};

p.TareasDelProyecto.Add(t1);
p.TareasDelProyecto.Add(t2);
p.TareasDelProyecto.Add(t3);


Ahora procedemos a generar el mapeo de la clase. Este procedimiento es el mismo que habiamos visto en el post antes mencionado.



CostoTotalDeProyecto cp = Mapper.Map<Proyecto, CostoTotalDeProyecto>(p);



Por último, vamos a escribir en la consola el resultado del mapeo.



Console.WriteLine("Nombre del Proyecto: {0} ", cp.NombreProyecto);
Console.WriteLine("Total de Horas del Proyecto: {0}", cp.TotalHorasDelProyecto.ToString());
Console.WriteLine("Costo total en horas del proyecto: {0}", cp.CostoTotalEnHorasDeProyecto.ToString());
Console.ReadLine();



El resultado de ejecutar este código se puede ver en la siguiente imagen.



image



Ahora, supongamos que no tenemos la posiblidad de tener los métodos de la clase proyecto que sumarizan los resultados que queremos exponer desde nuestro mapeo, y que lo único que vamos a tener en la clase Proyecto es el estado del objeto. En este caso, vamos a modificar las clases iniciales para que luzcan así:



public class Tarea
{
public int Id { get; set; }
public string Nombre { get; set; }
public double HorasAsignadas { get; set; }
public double CostoXHora { get; set; }

}

public class Proyecto
{
public int Id { get; set; }
public string Nombre { get; set; }
public List<Tarea> TareasDelProyecto { get; set; }
public DateTime FechaInicio { get; set; }
public DateTime FechaFinal { get; set; }

public Proyecto()
{
TareasDelProyecto = new List<Tarea>();

}
}


Como se puede ver en el código anterior, ya la clases no contiene los métodos que nos ayudan a sumarizar los valores que deseamos ver en la clase mapeada, es decir, total de horas del proyecto y costo total en horas del proyecto.  Para lograr que el mapeo funcione, movemos las operaciones de sumatoria a la expresión lambda  tal y como se muestra a continuación



Mapper.CreateMap<Proyecto, CostoTotalDeProyecto>()
.ForMember(dest => dest.NombreProyecto, src => src.MapFrom(fuente => fuente.Nombre))
.ForMember(dest => dest.TotalHorasDelProyecto, src =>
src.MapFrom(fuente => fuente.TareasDelProyecto.Sum(tarea => tarea.CostoXHora)))
.ForMember(dest => dest.CostoTotalEnHorasDeProyecto,
src => src.MapFrom(fuente => fuente.TareasDelProyecto.Sum(tarea => tarea.HorasAsignadas * tarea.CostoXHora)));



Como podemos ver en el texto en negrita, la expresión para procesar una sumatoria tanto del costo por hora del proyecto como del total de horas que requiere el proyecto se realiza de forma simple en el mapeo sin necesidad de depender de las clases para que ellas realicen este cálculo. El resultado de ejecutar esta operación es el siguiente:



image



Como podemos ver, el resultado es exactamente igual a que nos dio la ejecución anterior cuando utilizabamos las operaciones de totalización dentro de las clases.



Technorati Tags: ,,

8.08.2010

¿Qué es AutoMapper?

Según la documentación, AutoMapper es Mapeador de Objeto a Objeto, es decir recibo una instancia de un objeto y genera otra instancia de otro objeto con diferente estructura. Interesante no? pues resulta que esta herramienta gratuita, disponible en codeplex - http://automapper.codeplex.com/ – es una librería que nos puede ahorra muchísimo código cuando estamos desarrollando aplicaciones .NET.

Este producto tiene muchas características que se pueden aplicar en el día a día, ya que la conversión de objetos es algo que hacemos casi de forma “instintiva” en nuestro esfuerzo por desarrollar software. Por ejemplo, supongamos que tenemos un servicio que retorna un tipo específico que existe en nuestro dominio de negocio y que sin embargo contiene propiedades que no queremos exponer a los que estén interesados en consumir este servicio. Esta entidad es un empleado que nuestra empresa da en outsourcing a otras compañías. Como se puede suponer, la empresa no tiene interés de exponerle al comprador del servicio cuánto gana ni el nivel que tiene catalogado dentro de la organización. Para lograr esto con automapper, iniciamos por definir las clases que vamos a utilizar. La primera Empleado que es la clases interna, y la segunda EmpleadoOferta que es la clase que le vamos a presentar al cliente.

public class Empleado
{
public int Id { get; set; }
public string Nombre { get; set; }
public string Especialidad { get; set; }
public double SalarioXHora { get; set; }
public string Nivel { get; set; }
public double Experiencia { get; set; }

}



public class EmpleadoOferta
{
public int Id { get; set; }
public string Nombre { get; set; }
public string AnnosExperiencia { get; set; }
public string Especialidad { get; set; }
}



Seguidamente en el método Main de una aplicación de consola ejemplo, procedemos a crear instancias de la clase empleado y las auto inicializamos



static void Main(string[] args)
{
Empleado empleado1 = new Empleado
{
Id = 1,
Nombre = "Gabriel Fernandez",
Especialidad = "ASP.NET",
Experiencia = 4,
Nivel = "Sennior 1",
SalarioXHora = 14
};
Empleado empleado2 = new Empleado
{
Id = 2,
Nombre = "Gabriela Montés",
Experiencia = 2,
Especialidad = "Silverlight",
Nivel = "Junior 3",
SalarioXHora = 9
};


Ahora si, llegó el momento de utilizar automapper, en este caso vamos a configurar por código la transformación deseada.



//Se configura el mapeo
Mapper.CreateMap<Empleado, EmpleadoOferta>()
.ForMember(dest => dest.Nombre, fte => fte.MapFrom(src => src.Nombre))
.ForMember(dest => dest.Id, fte => fte.MapFrom(src => src.Id))
.ForMember(dest => dest.AnnosExperiencia, fte => fte.MapFrom(src => src.Experiencia))
.ForMember(dest => dest.Especialidad, fte => fte.MapFrom(src => src.Especialidad));


Como podemos ver en el código anterior, para configurar el mapeo se utilizan expresiones lambda. En primera instancia creamos un mapa indicándole cuál es la clase fuente y cual es la clase destino



Mapper.CreateMap<Empleado, EmpleadoOferta>()



Inmediatamente después procedemos a crear el mapeo que vamos a necesitar en nuestro código; este mapeo se hace expresando a través de expresiones lambda el campo que corresponde a la clase destino, desde la clase fuente – en nuestro caso fte:



.ForMember(dest => dest.Nombre, fte => fte.MapFrom(src => src.Nombre))



Ahora creamos un método que mapea e imprime el valor de cada una de las clases, la fuente y la transformada



private static void ProcesarEmpleado(Empleado empleado1)
{
Console.WriteLine("--------------Iniciando Mapeo------------------");

Console.WriteLine("-----Empleado para uso Interno-----");
Console.WriteLine("Id Empleado {0}", empleado1.Id);
Console.WriteLine("Nombre {0}", empleado1.Nombre);
Console.WriteLine("Experiencia del Empleado {0}", empleado1.Experiencia);
Console.WriteLine("Especialidad del Empleado {0}", empleado1.Especialidad);
Console.WriteLine("Nivel del Empleado {0}", empleado1.Nivel);
Console.WriteLine("Salario por hora ${0}", empleado1.SalarioXHora);

EmpleadoOferta empleadoOferta1 = Mapper.Map<Empleado, EmpleadoOferta>(empleado1);

Console.WriteLine("----- Empleado para la Oferta -----");
Console.WriteLine("Id Empleado {0}", empleadoOferta1.Id);
Console.WriteLine("Nombre {0}", empleadoOferta1.Nombre);
Console.WriteLine("Experiencia del Empleado {0}", empleadoOferta1.AnnosExperiencia);
Console.WriteLine("Especialidad del Empleado {0}", empleadoOferta1.Especialidad);

Console.WriteLine("--------------Mapeo Finalizado------------------");
}


Quizá la parte más importante de este método es la invocación Mapper.Map, la cual como podemos ver en el código resaltado recibe vía tipos parametrizados el tipo de las clases que se desea transformar – así también por reflección puede determinar cual mapeo usar. Seguidamente procedemos a enviar por parámetro la instancia del tipo de la clase origen, en nuestro caso empleado1 que es una instancia de Empleado. Como podemos ver en el código, el método estático Map, retorna una instancia del tipo destino con la transformación aplicada, por esta razón, creamos una variable de tipo EmpleadoOferta y le asignamos el resultado del mapeo. Desde el método Main invocamos al método ProcesarEmpleado con las instancias de Empleado que creamos anteriormente.



ProcesarEmpleado(empleado1);
ProcesarEmpleado(empleado2);



El resultado de la ejecución del codigo anterior es el siguiente:



image



Ahora supongamos que queremos hacer una transformación un poquito más compleja, por ejemplo un cálculo en medio de la transformación. Supongamos que ahora ahora deseamos exponer en la nueva clase la tarifa por hora del empleado consultado. Para este caso modificamos la clase EmpleadoOferta para que tenga una propiedad más para que nos sirva para este propósito, esta propiedad es CostoAlCliente.



public class EmpleadoOferta
{
public int Id { get; set; }
public string Nombre { get; set; }
public string AnnosExperiencia { get; set; }
public string Especialidad { get; set; }
public double CostoAlCliente { get; set; }
}



En nuestro caso, vamos a agregarle un 40% de ganancia por cada hora de trabajo del recurso que el cliente desea tener, para lo cual vamos a modificar el mapeo para que la clase mapeada tenga este valor calculado. El código modificado es el siguiente:



//Se configura el mapeo
Mapper.CreateMap<Empleado, EmpleadoOferta>()
.ForMember(dest => dest.Nombre, fte => fte.MapFrom(src => src.Nombre))
.ForMember(dest => dest.Id, fte => fte.MapFrom(src => src.Id))
.ForMember(dest => dest.AnnosExperiencia, fte => fte.MapFrom(src => src.Experiencia))
.ForMember(dest => dest.Especialidad, fte => fte.MapFrom(src => src.Especialidad))
.ForMember(dest => dest.CostoAlCliente, fte => fte.MapFrom(src => (src.SalarioXHora * 0.40) + src.SalarioXHora));



Como podemos ver en el código anterior, se agrego una nueva propiedad al mapeo, pero en este caso, la expresión lambda se modificó para que haga el cálculo del CostoAlCliente de este empleado. Para entender más las expresiones lambda pueden leer este post que escribí tiempo atrás. Por último vamos a modificar el método para imprimir las entidades antes y después del mapeo.



EmpleadoOferta empleadoOferta1 = Mapper.Map<Empleado, EmpleadoOferta>(empleado1);

Console.WriteLine("----- Empleado para la Oferta -----");
Console.WriteLine("Id Empleado {0}", empleadoOferta1.Id);
Console.WriteLine("Nombre {0}", empleadoOferta1.Nombre);
Console.WriteLine("Experiencia del Empleado {0}", empleadoOferta1.AnnosExperiencia);
Console.WriteLine("Especialidad del Empleado {0}", empleadoOferta1.Especialidad);
Console.WriteLine("Costo al cliente {0}", empleadoOferta1.CostoAlCliente);



Al ejecutar el código modificado el resultado obtenido es el siguiente:



image



Como podemos ver en la figura anterior, se puede ver el cáculo hecho en el mapeo al calcular a partir del salario por hora un sobre precio del 40%.





Technorati Tags: ,,

8.02.2010

Presentando Imágenes y Texto en un ComboBox en WPF

Cuando realizamos proyectos en WFP, es común tener que hacer que la aplicación sea lo más visual posible para el usuario final, esto por que cuando una empresa decide moverse de windows Forms a WPF normalmente lo hace por que quiere aprovecharse de todas las bondades visuales de XAML y WPF para hacer lucir sus aplicaciones espectaculares. En este post vamos a ver como desplegar una imagen dentro de un combo box, y además le vamos a agregar una descripción a esta imagen en el mismo control.

Detalle de la Solución

En este caso vamos a crear una pantalla muy simple en donde vamos a pone un combo box, para que el usuario seleccione el empleado que desea. Al mismo tiempo vamos a desplegar el nombre y los apellidos del empleado seleccionado en la parte inferior del combo box.

Solución #1: Imágenes en el Path

La primera solución que vamos a plantear es tener las imágenes un directorio en el disco duro de la máquina, y en el registro del empleado, vamos a poner el nombre de la imagen que posee. Esta solución es la mas simple sin embargo tiene sus contras, por ejemplo con esta opción es imposible garantizar la integridad referencial de los datos, ya que si se elimina el registro no se puede garantizar el eliminado de la foto en el directorio – entre otras cosas.

Para iniciar, vamos a conseguir las imagenes de los empleados que vamos a tener disponibles en el combo, yo en mi caso busque imagenes de caricatura en Bing->Images y las agregue a un directorio que hice en mi proyecto para tener las imagenes disponibles. La siguiente imágen muestra el agregado de las fotos del usuario y su ubicación en el proyecto

image

image

Seguidamente procedemos a crear la base de datos donde vamos a guardar los datos del empleado, para este le damos al proyecto agregar nuevo ítem y procedemos a agregar una base de datos local la cual llamaremos PeopleDB.sdf como se ve en la siguiente imagen.

image

Luego creamos una tabla en la base de datos utilizando el Database Explorer de VS2010 – funciona igual en VS2008 -

image

En la tabla vamos a crear los siguientes campos.

image

Ahora procedemos a agregarle campos de forma manual utilizando el database explorer en la opción “Show Table Data”. Como podemos ver, hemos puesto la ruta de las fotos como “Images\<Nombre Foto>”

image

Seguidamente vamos a crear una clase que me permita representar cada uno de los empleados con los que voy a interactuar, para así poder crear una clase de lógica de negocios que me permita obtener los datos del empleado. – en este caso estamos creando la clase de entidades, de acceso a datos y de negocios en el mismo proyecto, pero como se puede leer en posts anteriores, lo recomendable es tener librerías para cada una de estas capas.

La capa de entidades tiene cada una de las propiedades automáticas para almacenar cada uno de los valores de los campos de la tabla especificada enteriormente.

image

Seguidamente procedemos a construir la clase para el acceso a datos. Esta clase se presenta  a continuación

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlServerCe;
using System.Data;

namespace WPFTricks
{
public static class Empleado_DAL
{

static string dbFile = @"Data Source=PeopleDB.sdf";

public static List<Empleado> ObtenerEmpleados()
{
List<Empleado> empleados = new List<Empleado>();
try
{
using (SqlCeConnection connection = new SqlCeConnection(dbFile))
{
SqlCeCommand command = new SqlCeCommand("SELECT * FROM Empleados", connection);
connection.Open();
IDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Empleado emp = new Empleado();

emp.Id = System.Convert.ToInt32(reader["Id"]);
emp.Nombre = reader["Nombre"].ToString();
emp.Apellido1 = reader["Apellido1"].ToString();
emp.Apellido2 = reader["Apellido2"].ToString();
emp.Direccion = reader["Direccion"].ToString();
emp.Email = reader["Email"].ToString();
emp.FotoUrl = reader["Imagen"].ToString();

empleados.Add(emp);
}
}
}
catch (Exception ex)
{
throw ex;
}
return empleados;
}
}
}



Como se puede ver en el código anterior, tenemos un método que devuelve todos los empleados almacenados en la tabla empleados. Algo a señalar es que el connection string utilizado es de la forma static string dbFile = @"Data Source=PeopleDB.sdf";  el cual es de esta forma, porque nuestra base de datos tiene modificada la propiedad “Copy to Output Directory” tal y como se ve en la siguiente figura. En este caso el valor de esta propiedad ha sido cambiado a “Copy Always” con lo cual la base de datos se copia al directorio de salida de compilación, por lo que la base de datos residirá en el mismo directorio que el ejecutable y las librerías de nuestra aplicación demo.



image



Ahora el siguiente paso es crear la lógica de negocios, la cual se muestra a continuación:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WPFTricks
{
public class Empleado_BL
{
public List<Empleado> ObtenerEmpleados()
{
return Empleado_DAL.ObtenerEmpleados();
}
}
}



Esta clase es bastante simple, ya que por la naturaleza de nuestro demo, el único método que vamos a tener en la clase es el que retorna la lista de empleados desde la capa de acceso a datos, y al ser los métodos del DAL estáticos, lo único que tenemos que hacer es invocar y retornar el resultado de la invocación del método ObtenerEmpleados.



A nivel de UI si vamos a tener un combobox en donde vamos a desplegar el nombre y la foto de nuestro empleado. En modo diseño así es como luce nuestra pantalla:



image



El código para cargar el ComboBox es bastante simple y ya lo hemos visto en varios post anteriores de WPF, por lo que esta vez lo vamos a poner pero no lo vamos a comentar:



public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}


private void Window_Loaded(object sender, RoutedEventArgs e)
{
Empleado_BL empleado = new Empleado_BL();
List<Empleado> lista = empleado.ObtenerEmpleados();
cmbEmpleados.ItemsSource = lista;
}
}



El código donde esta el truco es en el código XAML. El código se puede ver a continuación:



<Window x:Class="WPFTricks.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="383" Width="659" Loaded="Window_Loaded">
<
Grid>
<
ComboBox ItemsSource="{Binding}" Height="40" HorizontalAlignment="Left" Margin="56,49,0,0" Name="cmbEmpleados" VerticalAlignment="Top" Width="215" >
<
ComboBox.ItemTemplate>
<
DataTemplate>
<
Grid>
<
Grid.ColumnDefinitions>
<
ColumnDefinition></ColumnDefinition>
<
ColumnDefinition></ColumnDefinition>
</
Grid.ColumnDefinitions>
<
Label Content="{Binding Path=Nombre}" Grid.Column="1" Height="28" HorizontalAlignment="Left" Margin="10,10,0,0" Name="label1" VerticalAlignment="Top" />
<
Image Source="{Binding Path=FotoUrl}" Grid.Column="0" Height="40" Width="40" Stretch="Fill"></Image>
</
Grid>
</
DataTemplate>
</
ComboBox.ItemTemplate>
</
ComboBox>

</
Grid>
</
Window>



En este caso, lo importante esta en el ItemTemplate. Esta clase nos permite personalizar la manera en que se van a desplegar los ítems en el combo. El Ítem template permite asignar un DataTemplate, el cual describe la estructura visual de un elemento dentro de un control en WPF, esto cuando se hace una operación de binding a un ItemsSource. En este caso, vamos a agregar un Grid en el DataTemplate, luego vamos a agregar dos columnas por cada ítem, en la primera columna vamos a poner la foto, y en la segunda columna el nombre del empleado; para lograr esto definimos dentro del DataTemplate los controles antes mencionados y procedemos a indicar en cual columna se deben de ubicar utilizando el propiedad Grid.Column. Por último hacemos el binding directo a los campos de la clase, por lo que aunque los campos tenga otro nombre en la tabla, lo importante en este caso es el nombre de las propiedades que lo representan en el objeto, en nuestro caso, FotoUrl y Nombre. El resultado al ejecutar el demo anterior es el siguiente:



image



Si seleccionamos uno de los elementos, el ítem seleccionado se ve de la siguiente manera:



image



Technorati Tags: ,,

7.06.2010

Exponiendo un Workflow como un Servicio – WF + WCF

En aspectos relacionados con SOA – Arquitectura orientada a servicios – hay dos componentes en lo que concierne al desarrollo en .NET que están muy relacionados: WCF – Windows Comunication Foundation – y WF – Workflow Foundation. La razón es que normalmente los servicios en SOA son utilizados para exponer lógica de negocios; es decir, yo expongo funcionalidad de mi negocio a través de servicios en WCF, y en algunos casos – o muchos – expongo procesos de negocios a travpes de estos servicios. Estos servicios están desarrollados en WCF y los procesos de negocios implementados utilizando WF. Esto alineado con el hecho que normalmente en los ESB se exponen orquestaciones a través de servicios, hace que las únicas dos actividades nuevas en el Framework 3.5 tengan mucho sentido – SendActivity y ReceiveActivity. En este post vamos a explicar como exponer un workflow hecho en WF, a través de un servicio en WCF.

El primer paso es definir que vamos a modelar en el workflow. En este caso y por brevedad vamos a hacer un workflow muy simple que recibe el id de un usuario y me devuelve todas las aprobaciones que tiene pendientes por hacer.

El primer paso es crear un proyecto del tipo Sequential Workflow Service Library tal y como se ve en la siguiente figura.

image

Esta plantilla de proyecto, genera un contrato para el servicio y además genera un workflow que implementa el contrato, acompañando estos dos elementos, se agrega el app.config para exponer el servicio y sus endpoints.

image

image

Como mencionamos antes, el proyecto genera un contrato de forma automática, el cual es implementado por el workflow del proyecto.  Como se puede ver en la figura anterior, el contrato simplemente tiene un método llamado GetData, el cual vamos a renombrar a ObtenerAprobacionesPendientes, el cual recibe de parámetro un entero que va a representar el id del usuario, y devuelve una lista de strings – podría ser de objetos – en los cuales se indica que falta de aprobar. Al mismo tiempo, el proyecto genera un archivo workflow1.cs, el cual contiene la implementación del contrato, este workflow tiene una actividad de recibo con el método de la interface Workflow1 implementada

image

Para hacer el cambio del nombre de la operación del contrato, voy a utilizar refactoring, y en mi caso voy a utilizar R# – www.jetbrains.com – la cual es una herramienta que tiene muchas posibilidades extra de refactoring respecto a las utilidades estándar de VS – además de un gran número de funcionalidad que aumenta la productividad del desarrollador de gran forma [100% recomendada]. Al seleccionar el metodo y con botón derecho darle “refactor” aparece la siguiente pantalla:

image

Esta pantalla me solicita el nuevo nombre que le voy a poner al método. Seguidamente me indica los cambios contextuales – además de tipificados – en donde se va a realizar el cambio.

image

Como podemos ver en la figura anterior, el nombre del método debe de cambiarse en la asignación en la propiedad tpedooperationinfo1.name. Como podemos ver en la siguiente figura del designer.cs del workflow, la propiedad ServiceOperationInfo del receiveActivity1 es la que configura como se ejecuta el servicio. En esta propiedad se establece el contrato a utilizarse y el método que se va a implementar en esta actividad.

image

Una vez hecho el refactor de método, el workflow lucirá de esta forma en modo gráfico:

image

Seguidamente cambiamos el tipo de retorno del contrato de la siguiente forma:

image

Igualmente tenemos que actualizar la propiedad ServiceOperationInfo para refrescar el tipo de retorno. Para hacer esto, vamos a la propiedad en el receiveActivity y le damos click en la elipsis para llevar a cabo esta configuración.

image

La pantalla para refrescar los parámetros es la siguiente

image

Una vez cargada esta pantalla, podemos ver los tipos de retorno e ingreso refrescados correctamente y de manera automática.  Seguidamente vamos a declarar la variable que va a retornar todas las aprobaciones pendientes ( esta vez en una lista de strings ) y vamos a crear una propiedad para exponerla. Esta declaración se ve en la siguiente figura

image

Luego agregamos un codeActivity y lo configuramos de la siguiente manera.

image

El siguiente paso es en el método retornarAprobaciones del codeActivity AprobacionesRestantes ingresamos el código para obtener las aprobaciones faltantes – en este caso vamos a llenar la lista de manera manual con el fin de no alargar innecesariamente el post, pero la situación normal es ir a traer estos datos ya sea a través de conexiones a una base de datos o vía servicios. El código de este método sería el siguiente:

private void retornarAprobaciones(object sender, EventArgs e)
{
AprobacionesPendientes.Add("Vacaciones Luis");
AprobacionesPendientes.Add("Adelantos de Salario Julio");
AprobacionesPendientes.Add("Compras Suministros");
AprobacionesPendientes.Add("Pagos varios");
}


Seguidamente configuramos el receiveActivity para que retorne la lista ya creada. Para esto, seleccionamos la propiedad return value del receiveActivity y seleccionamos la propiedad AprobacionesPendientes como binding para el retorno.



image



Para probar el servicio, le damos F5 al visual studio para que el servicio WCF se hostee en el WCF Service Host para pruebas que trae Visual Studio. En este paso veremos dos pantallas, la primera es la del host del servicio, y la segunda la que nos permite probar el servicio. Ambas pantallas las podemos ver a continuación.



image



Como podemos ver en la prueba anterior, ya expusimos un workflow como un servicio. El siguiente paso es consumir el servicio desde un cliente.



Creando un Cliente Windows para Consumir el Servicio


El siguiente paso es crear un cliente para poder consumir este servicio, y en este caso vamos a utilizar Visual C# Express 2010. Primero creamos una aplicación del tipo WPF : File –> New Project –> WPF Application. En este caso le vamos a poner ClienteWFAsAService. Seguidamente procedemos a agregar una referencia al servicio para poder consumirlo. La dirección del servicio es la estipulada en el app.config del servicio creado, sin embargo, si le damos botón derecho sobre el servicio en el WCF Service Host nos sale la opción de “copy address metadata”, con la cual podemos copiar la dirección para agregar dicha referencia.







image





Seguidamente le damos botón derecho al proyecto WPF y seleccionamos add service reference, luego copiamos la dirección ( ctrl  V) del servicio obtenida a traves del WCF Service Host. El siguiente paso es seleccionar el botón de Go para obtener la especificación del servicio, y renombrar la referencia como SRAprobaciones para accederla desde el código. La pantalla en este paso debe lucir de la siguiente manera:



image



Ahora vamos a agregar un ListBoxpara exponer el resultado de la invocación al servicio.



image





Por último, en el evento load de la pantalla principal, vamos a agregar la invocación al servicio, y el resultado de la misma se la vamos a ligar al LsitBox tal y como se muestra el siguiente código:



private void Window_Loaded(object sender, RoutedEventArgs e)
{
SRAprobaciones.Workflow1Client cliente = new SRAprobaciones.Workflow1Client();
List<string> lstResultado = cliente.ObtenerAprobacionesPendientes(1).ToList<string>();
listBox1.ItemsSource = lstResultado;
}


El resultado de la ejecución de el código anterior es:



image 





Technorati Tags: ,,,

6.11.2010

Invocar un Workflow desde Otro Workflow y Detectar el Evento de Terminación de cada Workflow

Una tarea muy común cuando se usa el Workflow Foundation es invocar un workflow desde otro Workflow. Esta tarea no tiene mucha dificultad; sin embargo, en muchas ocasiones me he encontrado con la necesidad de atrapar el evento de finalización de uno de los dos workflow para llevar a cabo una acción determinada. Este post detalla lo que se debe hacer para saber cuando finalizó el workflow que estamos buscando.

En primera instancia vamos a crear un proyecto de Consola que contenga un workflow integrado secuencua – lo vamos a usar a manera de ejemplo, porque para un escenario en la vida real, deberíamos escojer una librería de workflow. Vamos a crear un workflow secuencial sencillo con un “Code Activity” al inicio del workflow y otro al final del workflow. En el medio de ambas actividades vamos a colocar la actividad de invocación del otro workflow. La siguiente figura nos muestra el workflow secuencial comentado.

image

El código de las actividades de código ( valga la redundancia :) ) se presenta a continuación:

private void InicializarWorkflow(object sender, EventArgs e)
{
Trace.WriteLine("Iniciando el Workflow 1");
}

private void WorkflowFinalizado(object sender, EventArgs e)
{
Trace.WriteLine("Workflow 1 Finalizado");
}



Lo único que harán esas actividades es poner un mensaje en la pantalla de output de Visual Studio cuando estemos ejecutando la aplicación, lo cual lo hago con el fin de que se pueda ver el orden de ejecución del workflow – es importante destacar que el trace es una clase que esta en el namespace System.Diagnostics, por lo tanto tenemos que incluir el namespace utilizando la directiva usign.



Seguidamente procedemos a crear un segundo workflow secuencial, el cual tendrá solamente un “Code Activity” el cual nos indicará que estamos ejecutando el workflow 2.



image



El código de la actividad funciona igual que las anteriores



private void IniciandoWF2(object sender, EventArgs e)
{
Trace.WriteLine("Iniciando Workflow 2");
}



Ahora procedemos a configurar la figura del workflow 1 en donde se invoca el workflow 2. Esto lo hacemos seleccionado la Actividad “invokeWorkflowActivity1” y en la propiedad TargetWorkflow le damos click al botón para que nos aparezca la pantalla para seleccionar el workflow que queremos llamar.



image









Una vez seleccionado el workflow, las propiedades de la actividad para invocar el workflow 2 deberían quedar así:



image



y el workflow 1 debería verse así:



image



El siguiente paso es determinar cuando ha finalizado el workflow 1 y cuando ha finalizado el workflow 2. Para lograrlo, primero vamos a ir al método main de la consola, y vamos a cambiar el handler para el método WorkflowCompleted perteneciente al Runtime y vamos a agregarle el método para que se vea de la siguiente forma:



using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += workflowRuntime_WorkflowCompleted;
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(InvocarOtroWF.Workflow1));
instance.Start();

waitHandle.WaitOne();
}



Seguidamente procedemos a codificar el handler del evento WorkflowCompleted:



static void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
if (e.WorkflowDefinition is Workflow1)
{
Trace.WriteLine("Workflow 1 Completado");
}
else
{
Trace.WriteLine("Workfow 2 Completado");
}
}



Como se puede ver en el código anterior, el evento workflow completed en la variable e del tipo WorkflowCompletedEventArgs tiene una propiedad llamada WorkflowDefinition, la cual me da el tipo del workflow que disparo el evento. En este caso para verificar cual workflow disparó el evento, simplemente debo de preguntar si esta propiedad es del tipo Workflow1; si es así, procedo a indicarlo con la clase Trace de lo contrario el workflow que lanzó el evento es el Workflow 2.



Si ejecutamos esta aplicación, veremos los resultados en el output Window de Visual Studio tal y como se ve en la siguiente figura:



image



Hay que notar la diferencia entre el texto de Workflow finalizado escrito por la actividad de código y el evento workflow completado del runtima, ya que este último es un evento disparado por el runtime de los workflows cuando recibe una notificación indicandole que el workflow X esta completo y no hay más actividades por ejecutar, mientras que en la actividad, es simplemente el código que pusimos en la actividad y que debe de ejecutarse cuando el flujo del workflow vaya por la misma, lo cual es diferente a que este completado.




Technorati Tags: ,,