10.21.2010

Imágenes y WCF – Como Retornar una Imagen desde un Servicio en WCF y Presentarlo en WPF

Una de las tareas más normales en la vida de un desarrollador de aplicaciones es interactuar con imagenes. Esta tarea está normalmente relacionada con presentar imagenes que representan alguna información adicional respecto al dato o registro con el que estamos interactuando. Esto representa un desafío si nos apegamos a la arquitectura n-layer con una capa de servicios descrita en post anteriores. En este post vamos a trabajar en crear un servicio que retorna una imagen para que esta pueda ser utilizada por cualquier cliente.

WCF Message Encoders – Definición

En WCF utilizamos los endpoints para decirle a los clientes como es que se va a transferir los datos entre el cliente y el servidor. En el binding es donde se especifica como vamos a codificar nuestros mensaje para que el cliente pueda consumirlo. El atributo que establece como se codifican nuestros mensajes de respuesta en el binding es el messageEncoding. Desde el punto de vista de WCF el encoder seleccionado serializa el mensaje de salida y lo pasa a la capa de transporte. En la llegada – en el cliente – el encoder seleccionado recibe el mensaje y los deserializa utilizando el formato establecido. Para aclarar el concepto de Encoding/Decoding vamos a hacer una breve definción de los mismos. Encoding es el proceso de transformar un mensaje en una secuencia de bytes. Decoding es el proceso inverso.

WCF incluye 3 tipos de encoders para mensajes SOAP: Texto, Binario y MTOM – Message Transmission Optimization Mechanism. Estos encoders se representan a través de los siguientes elementos en el binding del mensaje:

  1. TextMessageEncodingBindingElement: soporta XML y SOAP. Es interoperable con clientes que no son WCF.
  2. BinaryMessageEncodingBindingElement: encoder de mensajes binario. No es interoperable.
  3. MTOMMessageEncodingBindingElements: MTOM es una tecnología para transmitir datos binarios en los mensajes WCF. Crea un balance entre eficiencia e interoperabilidad.

Ejemplo

Ahora vamos a trabajar directamente en WCF para obtener una imagen desde un directorio en la máquina que corre el servicio. Este servicio va a ser consumido por un cliente en WPF. Para este ejemplo voy a usar Web Developer Express 2010. Primero vamos a crear un proyecto del tipo  WCF Service Application. Si usas VS 2008, el tipo del proyecto debería ser a WCF Service Library.

image

Seguidamente procedemos a crear el contrato del servicio. En este caso solo vamos a tener una operación: GetUserImage. Esta operación recibo la ruta y el nombre de la fotografía que se desea obtener, y retorna un arreglo de bytes en donde la foto va codificada.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace ImageManagerService
{
[ServiceContract]
public interface IImageManager
{
[OperationContract]
byte[] GetUserImage(string pPath);
}

}


El siguiente paso es implementar este contrato. En este caso simplemente vamos a leer la imagen desde la ruta que se especifica en el parámetro y la vamos a retornar como un arreglo de bytes.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

using System.IO;

namespace ImageManagerService
{
public class ImageManager : IImageManager
{
public byte[] GetUserImage(string pPath)
{
return File.ReadAllBytes(@pPath);
}
}
}


Seguidamente procedemos a trabajar con la configuración del servicio. En este caso recordemos que vamos a retornar una imagen y vamos a utilizar el decoder MTOM para transmitir nuestra imagen de una forma más rápida. Para esto tenemos que crear un nuevo binding configuration y proceder a ligarlo al servicio que queremos exponer a través de wsHttp.



<system.serviceModel>
<
bindings>
<
wsHttpBinding>
<
binding name="wsMTOM" maxReceivedMessageSize="665536" messageEncoding="Mtom"
/>
</
wsHttpBinding
>
</
bindings>
<
services>
<
service behaviorConfiguration="MTOMBehavior" name="ImageManagerService.ImageManager" >
<
endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsMTOM" contract="ImageManagerService.IImageManager"
/>
<
endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</
service>
</
services>
<
behaviors>
<
serviceBehaviors>
<
behavior name="MTOMBehavior">
<
serviceMetadata httpGetEnabled="true"/>
<
serviceDebug includeExceptionDetailInFaults="false"/>
</
behavior>
</
serviceBehaviors>
</
behaviors>


Lo importante a destacar en la configuración anterior, es que el binding del servicio esta configurado para codificar el mensaje utilizando MTOM, y por lo tanto optimizando la forma en que vamos a retornar la imagen – el arreglo de bytes – al cliente.



Ahora procedemos a crear el cliente de la aplicación. Para esta tarea vamos a utilizar el Visual C# express 2010, el cual nos va a permitir construir nuestra aplicación en WPF. Inicialmente vamos a crear una pantalla que me permite digitar la ruta y el nombre de la fotografía que queremos desplegar. La pantalla inicialmente luce así:



image



El siguiente paso es crear la referencia al servicio que tenemos expuesto.



image



Seguidamente procedemos a crear el código del evento Click del botón cargar para presentar la imagen en la pantalla.



private void btnCargar_Click(object sender, RoutedEventArgs e)
{
SRManageImage.ImageManagerClient cliente = new SRManageImage.ImageManagerClient();
byte[] imageBytes = cliente.GetUserImage(@txtImageName.Text);

BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = new MemoryStream(imageBytes);
image.EndInit();
imgPerson.Source = image;
}


El siguiente paso es ejecutar la aplicación. El resultado de esta ejecución se puede ver en la siguiente figura:



image



Technorati Tags: ,,

10.08.2010

Haciendo Sumatorias con Listas de Entidades con VB.NET

Una de las tantas excelentes funcionalidades que tienen las listas genéricas en .NET es la capacidad de hacer sumatorias basado en un predicado que se le pasa al método Sum como parámetro. Este predicado es una expresión lambda. En este post vamos a crear un par de entidades {proyecto y tarea} y vamos a proceder a crear una sumatoria en base a la relación entre estas entidades. Este post voy a desarrollarlo en VB.NET con el fin de no obligar a todos los que no programan en C# a buscar programas de traducción de código entre VB.NET y C# tal como este.

En primer lugar, vamos a crear una clase { poco – entidad } que me permita manejar cada una de las tareas de un proyecto.

Public Class Tarea
Public Property Id As Integer
Public Property
Nombre As String
Public Property
FechaInicio As DateTime
Public Property FechaFinal As DateTime

Public Sub New(ByVal pId As Integer, ByVal pNombre As String, ByVal pFechaInicio As DateTime, ByVal pFechaFinal As DateTime)
Id = pId
Nombre = pNombre
FechaInicio = pFechaInicio
FechaFinal = pFechaFinal
End Sub

Public Function
TotalHoras() As Double
Return
FechaFinal.Subtract(FechaInicio).TotalHours
End Function

End Class


En esta clase además de las propiedades – automáticas – y el constructor de la clase tenemos un método que calcula el total de horas de la tarea basado en la fecha de inicio de la tarea y la fecha final {la resta entre ambos}. La siguiente clase es la que encapsula todo el estado del proyecto.



Public Class Proyecto
Public Property Id As Integer
Public Property
Nombre As String
Public Property
FechaInicio As DateTime
Public Property FechaFinal As DateTime

Public Property TareasDelProyecto As New List(Of Tarea)

Public Sub New(ByVal pId As Integer, ByVal pNombre As String, ByVal pFechaInicio As DateTime, ByVal pFechaFinal As DateTime)
Id = pId
Nombre = pNombre
FechaInicio = pFechaInicio
FechaFinal = pFechaFinal
End Sub

End Class


En esta clase tenemos una lista de tareas, las cuales son las que al final vamos a utilizar para realizar la sumatoria a través de la clase List(of T).  Seguidamente vamos a hacer un método que nos permite crear una lista de tareas dummy para utilizarla en el proyecto.



Public Class GeneradorDeDatos
'Generar Tareas
Public Shared Function GenerarTareas() As List(Of Tarea)
Dim m_Lista As New List(Of Tarea)

Dim m_Tarea1 As New Tarea(1, "Entrevistar Usuarios", DateTime.Now, DateTime.Now.AddHours(4))
Dim m_Tarea2 As New Tarea(2, "Tabular Requerimientos", m_Tarea1.FechaFinal, m_Tarea1.FechaFinal.AddHours(4))
Dim m_Tarea3 As New Tarea(3, "Priorizar Requerimientos", m_Tarea2.FechaFinal, m_Tarea2.FechaFinal.AddHours(4))
Dim m_Tarea4 As New Tarea(4, "Filtrar Requerimientos", m_Tarea3.FechaFinal, m_Tarea3.FechaFinal.AddHours(4))

m_Lista.Add(m_Tarea1)
m_Lista.Add(m_Tarea2)
m_Lista.Add(m_Tarea3)
m_Lista.Add(m_Tarea4)

Return m_Lista
End Function
End Class


En este caso estamos utilizando el método AddHours para establecer el tiempo de cada tarea. En este proyecto en específico todas las tareas son secuenciales, es decir una tarea inicia hasta que la precede termine. Para mostrar las tareas del proyecto y para calcular el total de horas que dura el proyecto vamos a crear una pantalla igual a esta.



image



En el evento Load de la forma vamos a agregar el código necesario para poder desplegar las tareas en el grid y para poner el total de horas del proyecto en el text box de total de horas.



Public Class Form1

Property ProyectoActual As Proyecto

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
ProyectoActual = New Proyecto(1, "Desarrollo de Sistema X", DateTime.Now, DateTime.Now) 'No conocemos la fecha final, no hemos agregado las tareas
ProyectoActual.TareasDelProyecto.AddRange(GeneradorDeDatos.GenerarTareas())
dgProductos.DataSource = ProyectoActual.TareasDelProyecto

'Generar el total de horas del proyecto
Dim m_TotalHoras As Double = ProyectoActual.TareasDelProyecto.Sum(Function(t As Tarea) t.TotalHoras)

txtTotales.Text = m_TotalHoras.ToString()
End Sub
End Class



Como podemos ver en la línea con negrita, estamos usando la función sum de la lista de T. Esta función recibe como parámetro un predicado el cual se va a ejecutar. En este caso no vamos a aplicar ninguna operación en la expresión lambda, pero perfectamente podríamos hacerlo. El resultado de ejecutar la forma es el siguiente.



image



Technorati Tags: ,

10.04.2010

WCF y Excepciones – Parte 2 - FaultContracts

Siguiendo con el post anterior relacionado con el manejo de excepciones en WCF en este post vamos a trabajar con excepciones personalizadas en WCF. Como vimos anteriormente, las excepciones en WCF no se propagan porque WCF no entiende de excepciones. Igualmente, debemos recordar que utilizamos Fault Messages para poder retornar un error más legible al cliente. En este post vamos a atacar el caso en donde queremos propagar la excepción lanzada en .NET desde el servicio al cliente.

Problema: Excepciones Personalizadas

Existen casos en donde por la naturaleza del error, no es posible enviar simplemente un FaultException del lado del cliente para que el mismo la maneje, si no que se requiere enviar más información de vuelta al cliente de forma encapsulada. Este tipo de excepciones es posible llevarlas a cabo en WCF utilizando los FaultContracts.

Para entender mejor la solución al problema vamos a trabajar con un ejemplo. Supongamos que deseamos enviar de vuelta un error al cliente con un código de error interno, el cual tiene un significado relevante para el cliente. Esto por supuesto sumado al código de error y a la descripción del mensaje.

En primer lugar debemos crear un contratopara representar nuestro nuevo tipo de excepción, el cual será una clase etiquetada con el Atributo DataContract para que pueda ser serializada en WCF. El código de esta clase se puede ver en el siguiente listado:

using System.Runtime.Serialization;

namespace WFCErrorManagment
{
[DataContract]
public class InvalidRequestCode
{
[DataMember]
public string InternalCodeError { get; set; }
}
}



El siguiente paso es especificar el tipo de excepción que queremos lanzar en la operación seleccionada.



[ServiceContract]
public interface IAuthenticationService
{
[OperationContract]
[FaultContract(typeof(InvalidRequestCode))]
bool AuthenticateUser(WcfUser user);
}



Con el paso anterior, vamos a tener la posiblidad de tener el tipo de la excepción del lado del cliente, es decir, el contrato de error estará del lado del cliente para su manipulación. Seguidamente procedemos a lanzar la excepción desde la implementación del servicio.



using System.ServiceModel;

namespace WFCErrorManagment
{
public class AuthenticationService : IAuthenticationService
{
public bool AuthenticateUser(WcfUser user)
{
InvalidRequestCode request = new InvalidRequestCode { InternalCodeError = "ERR1002" };
throw new FaultException<InvalidRequestCode>(request);

}
}
}



A diferencia del FaultContract del post anterior, esta vez procedemos a crear una instancia de la clase personalizada con la que vamos a manejar el error y seguidamente procedemos a lanzar una excepción generica del tipo InvalidRequestCode que es el tipo del DataContract definido para manejar el error.



Para probar el funcionamiento del código anterior, vamos a crear un cliente utilizando VB.NET – por aclamación :) – para consumir el error y manejarlo apropiadamente en el cliente. En primera instancia hacemos una referencia la servicio, seguidamente creamos una instancia del proxy y procedemos a escribir el código que nos servirá para procesar el error.



Imports WCF_BlogClient.SRExceptionTest
Imports System.ServiceModel

Module Module1

Sub Main()
Try
Dim
cliente As New AuthenticationServiceClient
cliente.AuthenticateUser(New WcfUser())

Catch ex As FaultException(Of InvalidRequestCode)
Console.WriteLine("Error invocando el servicio. Código del error: {0}", ex.Detail.InternalCodeError)
End Try


End Sub

End Module



Como pueden ver en el código anterior, tenemos acceso a la clase InvalidRequestCode que fue creada en el server, la cual sin embargo, al ser un DataContract y al ser utilizada por el FaultException termina siendo exportada al proxy para que el cliente tenga acceso a ella. Al ejecutar el código anterior con el servicio corriendo obtenemos la siguiente pantalla:



image



Technorati Tags: ,,,,

10.03.2010

Reportando Excepciones al Cliente en WCF

Uno de los problemas más comúnes a la hora de trabajar con WCF es el manejo de excepciones del lado del servicio. Es normal ver como en la implementación del servicio, el desarrollador pone un manejo de excepciones normal, basado en su conocimiento del uso de las mismas en el desarrollo de sus aplicaciones en el framework; sin embargo, a la hora de reportar la excepción el resultado no es el esperado, ya que la excepción no llega al cliente, si no mas bien llega una excepción al cliente como la que se muestra a continuación:

The server was unable to process the request due to an internal error.  For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.

The server was unable to process the request due to an internal error.  For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.

Server stack trace:

   at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)

   at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)

   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)

   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)

   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)

   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]:

   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)

   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)

   at IAuthenticationService.AuthenticateUser(WcfUser user)

   at AuthenticationServiceClient.AuthenticateUser(WcfUser user)

La excepción anterior fue lanzada por un servicio que simplemente lanza una excepción tal y como se puede ver en el siguiente código

public class AuthenticationService : IAuthenticationService
{
public bool AuthenticateUser(WcfUser user)
{
throw new Exception("Invalid Action");
}
}



Claramente el servicio encapsula la excepción del lado del cliente por muchas razones entre ellas seguridad. Cuando alguien quiere hackear nuestros servicios, una de las cosas que hace es invocar servicios y provocar que estos fallen para poder ver en la información que retornan las excepciones y así poder tener pistas de como “violar la seguridad” del servidor donde se hospedan nuestros servicios. Es por esto que pese a que es posible “transportar el error” al lado del cliente en WCF utilizando la directiva IncludeExceptionDetailsInFaults no es recomendable hacerlo cuando la aplicación sale a producción.



Para reproducir el error anterior basta con ejecutar el servicio en el WCF Test Client y podemos ver que aunque sabemos el tipo de excepción que vamos a lanzar, la misma nunca llega a propagarse hasta donde esta el cliente.



image



La Solución



El escenario anterior se da porque WCF fue diseñado con la interoperabilidad en mente y por esta razón utiliza SOAP Faults para manejar los errores en lugar de expceción. Para poder ser interoperables, los SOAP Fault Messages son mucho más simples que las expecpciones. Los SOAP Fault Messages tiene dos elements:





  • Code: El cual provee la información del error para comprensión a nivel de máquina – es decir, un código de error natural.




  • Reason: La misma información del error pero legible para un humano.




Si se requiere agregar información adicional acerca del error, el servicio va a tener que definir un Fault Data Contract que puede ser consumido por el cliente.



En el código siguiente se puede ver el servicio anterior pero utilizando SOAP Fault Messages.





using System.ServiceModel;

namespace WFCErrorManagment
{
public class AuthenticationService : IAuthenticationService
{
public bool AuthenticateUser(WcfUser user)
{
throw new FaultException(new FaultReason("Error Autenticando el Usuario"),
new FaultCode("Error Autenticación"));
}
}
}


A diferencia del código anterior, en este caso estamos usando la clase FaultException la cual propaga el error de forma transparente desde el servidor hasta el cliente tal y como se puede ver en la siguiente figura:



image



En el siguiente post vamos a explorar los contratos de error personalizados en WCF.



Technorati Tags: ,,

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