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: ,,

6.10.2010

Error Configurando el Enterprise SSO con Biztalk Server 2009

Resulta que tenía una instalación de Biztalk Server 2009 en mi máquina con Vista Enterprise y Visual Studio 2008 la cual funcionaba perfectamente – en especial para demos o pruebas antes de llevar la orquestación a producción. Por una razón de negocio, tuve que desinstalar el Biztalk SErver 2009 de mi máquina por un tiempo, y por lo tanto desinstalar el SSO. Hoy trato de instalar Biztalk en mi máquina de nuevo y me encuentro con el siguiente error de configuración

“Failed to connect to SQL Database SSODB”

Resulta que aunque reinstalé el SSODB – que es necesario para la configuración de Biztalk, no me permitía seguir con la configuración del servidor y me pedía que configurara correctamente el SSODB.

Después de luchar un poco – desinstalar un par de veces – y buscar en la web, me encontré que lo que sucedía era que tenía que registrar de nuevo el componente de SSO llamado SSOSQL.DLL – el cual estaba registrado con la configuración anterior – y listo. Tal y como se ve en la siguiente figura, para registrarlo se utiliza el comando regasm ssosql.dll.

image

Una vez registrado, el Enterprise SSO se configura correctamente y la configuración de Biztalk Server continúa normalmente.

image

Technorati Tags: ,

4.03.2010

Invocando un Servicio Web desde JavaScript en ASP.NET

En una de las tareas que normalmente uno tiene que llevar a cabo, me encontré con la necesidad de invocar un servicio Web –asmx – desde javascript en mi página cliente. Los detalles del porque tenía que llevar a cabo esta tarea de esta forma son irrelevantes para el propósito del post, sin embargo me parece que la solución del problema puede serle útil a muchos de ustedes.

En primera instancia, vamos a crear un servicio web muy simple y le vamos a llamar WebServiceObtenerValor.asmx. Este servicio web simplemente recibe un string, lo concatena con el string de respuesta y se retorna la respuesta. Por supuesto, aquí pudimos haber ido a traer datos a una base de datos, a leer un archivo de configuración, cargar una imagen, etc. El código del servicio se ve a continuación.

using System.Web.Services;

[WebService(Namespace = "http://www.formsevolution.net")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class WebServiceObtenerValor : WebService {
[WebMethod]
public string ObtenerValor( string parametro) {
return "Obteniendo el Valor solicitado:" + parametro;
}
}


Un punto importante a destacar es el uso del atributo [System.Web.Script.Services.ScriptService] el cual nos permite que este servicio sea invocado desde el script manager de Ajax.NET de manera asincrona.



El siguiente paso es configurar el script manager del lado del cliente en la página aspx. Para esto, vamos a agregar una referencia al servicio desde el ScriptManager utilizando el ServiceReference presente dentro del ScriptManager.



<asp:ScriptManagerID="ScriptManager1"runat="server">

    <
Services>        <asp:ServiceReferencePath="~/WebServiceObtenerValor.asmx" />

    </
Services>

</
asp:ScriptManager>




El último paso es crear los métodos en java script para procesar el llamado, inicialmente vamos a agregar dos controles de tipo HTML – un textbox y un botón – y los vamos a utilizar para hacer el llamado. El evento click del botón va a tener el llamado al web service, y vamos a utilizar el DOM para obtener el valor del texto que acabamos de escribir.



image



En la invocación del servicio, al ser este asincrónico, debemos indicar donde se espera la respuesta del servicio en caso de que sea exitosa, donde esperar un timeout o donde esperar un error. En todos los casos vamos a desplegar una alerta con la respuesta recibida, sin embargo, podríamos hacer con los datos retornados lo que como desarrolladores necesitemos en la página.



    <script language="javascript" type="text/javascript">
// <!CDATA[

function Button1_onclick() {
ret = WebServiceObtenerValor.ObtenerValor(document.getElementById('Text1').value, OnComplete, OnTimeOut, OnError);
return (true);
}

function OnComplete(arg) {
alert(arg);
}

function OnTimeOut(arg) {
alert('Timeout invocando el servicio');
}

function OnError(arg) {
alert('Error en la invocación del servicio');
}
// ]]>
</script>



Technorati Tags: ,,