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