127.0.0.1: Hogar dulce hogar
Mostrando entradas con la etiqueta wcf. Mostrar todas las entradas
Mostrando entradas con la etiqueta wcf. Mostrar todas las entradas

miércoles, 15 de septiembre de 2010

[WCF] Codificaciones de texto y controlando la serialización mediante eventos

El motor de serialización de WCF está limitado a unos cuantos formatos de codificación y por defecto siempre codifica el tipo string en el formato UTF8. A priori no hay ningún problema, pero te puede ocurrir el siguiente escenario:

Tenemos una serie de entidades serializadas con WCF en las que se almacena texto. Por ejemplo, objetos de negocio como artículos en una tienda en la que algunos de sus campos son la descripción, el nombre del artículo, además de todos aquellos que se crean necesarios. Partamos de la base de que hemos serializado nuestro objeto siguiendo el artículo correspondiente. En un momento determinado, comienzas a escribir la información de los objetos en el XML, de un modo a como sigue:

<ArticleManager xmlns="http://schemas.datacontract.org/2004/07/Logic" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

<Articles>

<Article>

<Name>Halo Reach</Name>

<Description>La apasionante última entrega de la exitosa saga de shooters. </Description>

</Article>

</ArticleManager>

Para una codificacion en UTF-8 no tendríamos ningún problema en deserializar el campo Name, puesto que todos los caracteres son válidos en bytes desde el punto de vista de esa codificación. Sin embargo, no ocurre lo mismo con el campo Description. Este campo incluye caracteres que no son válidos para esa coficación, como por ejemplo los caracteres con tildes (en última) y otros propios del castellano (como el caracter ñ). En el momento en que se deserializa ese dato, provoca una excepción en XmlSerializer por no encontrarse dentro del esquema de UTF8 y similares. ¿Qué podemos hacer? Muchas cosas, como por ejemplo establecer un XmlSerializer propio para nuestras clases de modo que los campos de tipo string se codifiquen y decodifiquen empleando las codificaciones que queramos, crear un decodificador propio exclusivo para manejar texto o bien lo que propongo aquí, que consiste en almacenar la información con bytes de cara al fichero XML pero mostrándola al usuario como string controlando mediante eventos los procesos de serialización y deserialización. Veamos cómo se puede hacer:

Nuestra clase artículo tiene el siguiente juego de atributos:

   1: [DataContract()]
   2: public class Article
   3: {
   4:     [DataMember()]
   5:     public string Name;
   6:     [DataMember()]
   7:     public string Description;
   8: }

Y lo que vamos a hacer es transformar y encapsular los campos miembros serializables dentro de arrays de bytes:

   1: [DataContract()]
   2: public class Article
   3: {
   4:     [DataMember()]
   5:     public byte[] ByteName;
   6:     [DataMember()]
   7:     public byte[] ByteDescription;
   8:  
   9:     public string Name;
  10:     public string Description;
  11:     ...
  12: }

La idea es sencilla y se expuso anteriormente pero la recuerdo. Vamos a guardar los campos en el xml como bytes y al cargar el fichero, automáticamente se transforma en string. Para ello se proporcionan una serie de etiquetas de eventos que se pueden asociar a funciones de la clase que sufre el proceso para que actúen de manejadores del propio proceso. Estas etiquetas son:

  • OnDeserialized: Se invoca cuando el objeto ya se ha deserializado.
  • OnDeserializing: Se invoca justo antes de deserializar el objeto.
  • OnSerialized: Se invoca cuando el objeto ya ha pasado por el proceso de serialización.
  • OnSerializing: Se invoca justo antes de serializar el objeto.

Debemos asociar posteriormente estas etiquetas a un método que nos sirva de manejador. Este método debe tener un único parámetro de tipo StreamingContext cuyo cometido es controlar el origen y destino de la secuencia de serialización. Dicho de otro modo más llano y campechano indica qué objeto es quien inicia la secuencia de datos (en este caso para la seriailzación/deserialización) a través de Context y con State indica cuál es el origen o destino de los datos. Esto último puede servir para especificar si es para un fichero, remoto, serializado, otra máquina, otro proceso, etc. Sabiendo esto, ya sólo nos falta escribir el método para deserializar:

   1: [OnDeserialized()]
   2: private void onDeserialize(StreamingContext context)
   3: {
   4:     this.Name = Encoding.Unicode.GetString(this.ByteName);
   5:     this.Description = Encoding.Unicode.GetString(this.ByteDescription);
   6: }

Para habilitar caracteres con tildes, ñ y otros, he usado la codificación de Unicode que si permite usarlos. Como se aprecia, se invoca una vez el objeto ya está serializado y se han leído los miembros (los campos ByteName y ByteDesription tendrás sus oportunos valores). Ahora vayamos a por la serialización, que invocaremos antes de serializar para preparar los arrays de bytes:

   1: [OnSerializing()]
   2: private void onSerialize(StreamingContext context)
   3: {
   4:     this.ByteName = Encoding.Unicode.GetBytes(this.Name);
   5:     this.ByteDescription = Encoding.Unicode.GetBytes(this.Description);
   6: }

Con este sencillo procedimiento no tendremos problemas de conversiones entre cualquier tipo de codificaciones.

martes, 24 de agosto de 2010

[WCF] Desplegando en IIS, error 404.2

Personalmente considero a la rama IT como algo sencillo desde el punto de vista idealista, puesto que muy, muy en el fondo son tareas que se pueden considerar mecánicas una vez conoces el funcionamiento de todo. El problema es cuando estás en proceso de aprender y tardas a veces más tiempo en configurar el despliegue que en otras tareas. Eso es lo que me ha pasado al desplegar un servicio WCF en IIS.

Al desplegar mi servicio WCF obtenía el error 404.2 indicando que no podía acceder al servicio correspondiente, por lo que si no es visible para el propio servidor menos aún lo será para cualquier aplicación que desee consumirlo.

El problema de acceso puede consistir en dos pasos fundamentales:

  • Algún problema en el webconfig (o no están bien definidos los endpoints del servicio o bien directamente no puede acceder al fichero). Para el primer punto basta con editarlo y arreglar la parte que está mal especificada y para la segunda basta verificar si el fichero es accesible por los permisos para el servidor y si está contemplado en las directivas de Filtrado de Solicitudes:image
  • No puede acceder al servicio debido a alguna restricción ISAPI o CGI, en cuyo caso deberemos comprobar el tipo de aplicación que permite ejecutar el servidor. En este caso, al emplear WCF deberemos asegurarnos que IIS se está ejecutando con la versión 4.x como mínimo de .NET Framework y que además, los filtros de las solicitudes ISAPI y CGI permiten la ejecución de estas características:image

Una vez teniendo esto correctamente configurado, IIS ya podrá ejecutar nuestro servicio correctamente.

miércoles, 18 de agosto de 2010

[SilverLight] NotFound Exception

Una de las mayores desventajas que nos podemos encontrar en SilverLight es la depuración de errores, en especial si estamos usando servicio WCF con RIA. Siempre podemos depurar la aplicación SilverLight cuando usemos Visual Studio, pero el problema es cuando disponemos de un servicio correctamente desplegado en IIS y nuestra aplicación falla al conectarse al servicio. El error más común al lanzar nuestra aplicación que se conecta a un servicio es una excepción de WCF indicando lo siguiente:

   1: {System.Net.WebException: El servidor remoto devolvió un error: NotFound. ---> System.Net.WebException: El servidor remoto devolvió un error: NotFound.
   2:    en System.Net.Browser.BrowserHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)
   3:    en System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClass5.<EndGetResponse>b__4(Object sendState)
   4:    en System.Net.Browser.AsyncHelper.<>c__DisplayClass2.<BeginOnUI>b__0(Object sendState)
   5:    --- Fin del seguimiento de la pila de excepciones internas ---
   6:    en System.Net.Browser.AsyncHelper.BeginOnUI(SendOrPostCallback beginMethod, Object state)
   7:    en System.Net.Browser.BrowserHttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   8:    en System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteGetResponse(IAsyncResult result)}

Para ver qué esta ocurriendo realmente debemos realizar los siguientes pasos:

1 – Debemos asegurarnos que nuestro servicio está creado perfectamente dentro del servidor (accediendo al servicio a través del mismo para comprobar que se ha creado correctamente).

2 – Verificar si el ámbito del servicio y nuestra aplicación es el mismo. En el caso de que no lo sea, debemos especificar las políticas de acceso de dos modos: uno para las aplicaciones SilverLight y otro para el estándar de Adobe (como bien se indica en este artículo para SilverLight y este otro para dominios cruzados )

3 – Por último nos queda establecer una directiva para poder imprimir la traza. La excepción de NotFound es un tanto ambigua, ya que aparentemente indica que no ha encontrado el servicio pero no indica por qué. Antes de dedicarse mucho tiempo a analizar la configuración del cliente y el servidor conviene primero imprimir la traza del error para conocer de primera mano qué es lo que impide al cliente conectarse con el servicio. Para ello debemos indicarlo expresamente mediante las siguientes líneas en el web.config (fichero de configuración del lado servidor):

 

<system.serviceModel>

    <diagnostics>
      <messageLogging logEntireMessage="true"
        maxMessagesToLog="300"
        logMessagesAtServiceLevel="false"
        logMalformedMessages="true"
        logMessagesAtTransportLevel="true" />
    </diagnostics>

</system.serviceModel>

Poca explicación se puede añadir a esas líneas. Únicamente le estamos indicando que queremos activar el sistema de log, las características de los que admitimos y la capacidad del log.

Por otra parte tenemos que añadir el siguiente nodo al XML:

<system.diagnostics>
    <sources>
      <source name="System.ServiceModel.MessageLogging" switchValue="Verbose">
        <listeners>
         <add name="xml" type="System.Diagnostics.XmlWriterTraceListener"
           initializeData="c:\Temp\WcfMessage.log" />
        </listeners>
      </source>
    </sources>
    <trace autoflush="true" />
</system.diagnostics>

Donde evidentemente estamos indicando dónde se almacenará el log que podremos ir consultando. Por último, sólo falta indicarle a las clases de los servicios que queremos incluirlos en el sistema de logging especificándole el comportamiento IncludeExpcetionDetailInFaults en la cabecera de la clase:

   1: [ServiceBehavior(IncludeExceptionDetailInFaults= true)]
   2: public class Service
   3: {
   4: ...
   5: }

Ahora, cada vez que se produzca un fallo podemos verlo en el fichero WcfMessage que hemos especificado para ver exactamente que es lo que está ocurriendo por debajo y así poder facilitar las tareas de depuración.

viernes, 13 de agosto de 2010

[WCF] Implementando objeto serializable ( II )

Con este artículo concluyo la miniserie sobre SilverLight y WPF, ya que simplemente queda explicar cómo se puede serializar y deserializar un objeto en WCF. Recordemos que todo esto ha venido por una aplicación SL que estoy montando y que a través de un servicio web en WCF ejerzo dicho proceso de serialización sobre los objetos.

Para ello, tenemos que tener en cuenta las siguientes partes:

  • DataContractSerializer: Es la clase encargada de serializar el tipo. Nosotros podemos espeficiar y crear nuevos tipos de serializadores para nuestros objetos de datos según nuestras necesidades.
  • Stream: Un flujo de datos, sea de memoria o ficheros que permita abrir o cerrarlos para acceder a los datos.

Ya está. Sencillo, ¿no? Pues veamos un par de ejemplos:

   1: public bool SaveToXML(Foo foo)
   2: {
   3:     bool retValue = false;
   4:     FileStream file = null;
   5:     try
   6:     {
   7:         DataContractSerializer dcs = new DataContractSerializer(foo.GetType());
   8:         file = new FileStream(@"fooPath", FileMode.Create);
   9:         dcs.WriteObject(file, galleryManager);
  10:         retValue = true;
  11:     }
  12:     catch ( Exception ex )
  13:     {
  14:         //...
  15:     }
  16:     finally
  17:     {
  18:         if ( file != null )
  19:         {
  20:             file.Close();
  21:         }
  22:     }
  23:     return retValue;
  24: }

Y ahora otro ejemplo para abrir un fichero y deserializarlo:

   1: public Foo LoadFromXML()
   2: {
   3:     FileStream file = null;
   4:     Foo foo = null;
   5:     try
   6:     {
   7:         DataContractSerializer dcs = new DataContractSerializer(Foo.GetType());
   8:         file = new FileStream(@"FooPath", FileMode.OpenOrCreate);
   9:         foo = (Foo)dcs.ReadObject(file);
  10:     }
  11:     catch (Exception ex)
  12:     { }
  13:     finally
  14:     {
  15:         if (file != null)
  16:         {
  17:             file.Close();
  18:         }
  19:     }
  20:     return foo;
  21: }

Con estas dos funciones ya podemos escribir y cargar datos del XML de nuestra clase serializada Foo.

[WCF] Implementando objeto serializable ( I )

Siguiendo con la serie de artículos sobre SilverLight, ahora toca explicar cómo se puede serializar un objeto. Recordemos que en el primer artículo mencioné dos problemas que me encontré a la hora de desarrollar la aplicación en SilverLight:

  • - Problemas derivados del baile de versiones entre ASP4, .NET4 y SilverLight (resuelto en el artículo anterior)
  • - Imposibilidad de usar Xml.Serialize para emplear la serialización de objetos. (que es a lo que vamos ahora)

Como solución al primer apartado empleé Windows Communication Foundation para levantar un servicio web que sea consumido por la aplicación web SilverLight sin que haya ningún problema. Ahora toca explicar cómo podemos serializar nuestros elementos de lógica mediante WCF.

En primer lugar debemos colocar en la clase a serializar la etiqueta de DataContract, que permite agregarle tres tipos de atributos:

  • IsReference: Por defecto está a False, pero si se ajusta a True se garantiza el tipo del objeto mediante una referencia. Para ver un ejemplo, ver este artículo
  • Name: El nombre de la clase.
  • Namespace: El espacio de nombres al que pertence.

Y después en cada propiedad o atributo público que queramos marcar, lo etiquetaremos como DataMember que incluye los siguientes atributos:

  • EmitDefaultValue: Esta propiedad está activa por defecto. Su función es que cuando el miembro al que está asociado tenga el valor por defecto (0, null, etc.) NO se escriba en XML si está con valor FALSE.
  • IsRequired: Indica si este campo debe existir o no. Es conveniente añadirlo para asegurar la compatibilidad de un tipo en futuras versiones.
  • Name: El nombre del miembro en el XML.
  • Order: Indica el orden de serialización que aparecerán en el XML.
  • TypeId: Indica el tipo de objeto que se emplea durante el proceso de serialización. Es conveniente indicarlo cuando trabajamos con jerarquías de herencia.

Ahora veamos un ejemplo. Tenemos una clase Foo que vamos a serializar y sus miembros:

   1: [DataContract()]
   2: public class Foo
   3: {
   4:     [DataMember()]
   5:     public int x;
   6:  
   7:     [DataMember()]
   8:     public int y;
   9:  
  10:     //...
  11: }

Por defecto se serializará tal cual está indicado (primero la clase Foo, los valores X e Y por este orden con los valores por defecto, etc). Pero podemos introducir atributos en el miembro para especificar lo contrario, como por ejemplo:

   1: [DataContract()]
   2: public class Foo
   3: {
   4:     [DataMember("Name=XMember, Order = 1"]
   5:     public int x;
   6:     
   7:     [DataMember("Name=YMember, Order = 0"]
   8:     public int y;
   9: }

Lo cual implicaría un XML parecido al siguiente:

<xml def…/>

<Foo>

<YMember>0</YMember>

<XMember>0</XMember>

</Foo>

Los nombres de los atributos públicos han sido etiquetados de forma distinta y además, en el orden inverso a la aparición de código ya que hemos especificado un orden distinto a través del atributo correspondiente.

martes, 10 de agosto de 2010

[SilverLight] Implementando Servicios ( II )

Siguiendo con el artículo anterior, ahora mostraré un ejemplo de cómo se pueden implementar servicios empleando WCF para que sean consumidos por una aplicación de SilverLight.

Implementar un servicio en WCF es muy similar a un servicio web “de toda la vida”, pero tiene unas diferencias de funcionamiento leves. En primer lugar, en mi proyecto web voy a agregar el servicio:

image

Una vez que ya está agregado nuestro servicio, que será el encargado de obtener los datos de una clase serializados mediante WCF con xml situados en el lado servidor podremos apreciar indicar qué operaciones queremos que aparezcan en la especificación del servicio como funciones mediante la siguiente etiqueta:

   1: [OperationContract]
   2: public void DoWork()
   3: {
   4:     // Add your operation implementation here
   5:     return;
   6: }

OperationContract permitirá que esa función sea consumida externamente. A la operación le podemos especificar una serie de parámetros de WCF para cambiar el nivel de seguridad, el nombre, etc. En otro artículo hablaré de estos parámetros, ya que ahora no nos ocupa.

Volviendo a nuestro servicio, ahora simplemente tenemos que implementar los métodos marcándolos siempre con OperationContract. Una vez los tengamos, compilamos el servicio para asegurarnos que no haya ningún problema en la ejecución por parte del servidor:

image

Y procedemos a agregarlo como referencia de servicio a nuestra aplicación para que lo pueda consumir:

image

Prácticamente el paso anterior es el mismo para un servicio web. Ahora, vamos a la aplicación de SL para trabajar con el servicio:

   1: GalleryService.GalleryServiceClient cliente = new GalleryService.GalleryServiceClient();

Si despleguamos con IntelliSense los métodos que proporciona el cliente del servicio web, veremos que por cada método que hemos incluido se ha creado un par de métodos (aquí es donde tal vez radica la mayor diferencia respecto a un servicio web convencional):

  • MétodoAsync: Este método se emplea para consumir el respectivo método del servicio web. El problema, es que pese a que nosotros habremos indicado en algún método que devuelva algo (por ejemplo un string, int, arrays, etc) el Async siempre devuelve void. ¿Como obtenemos por lo tanto el valor devuelto?
  • MétodoCompleted: Este es un evento que ocurre cuando la llamada al método respectivo Async ha finalizado y por lo tanto ya tenemos la totalidad el valor devuelto. A través de los parámetros de este evento obtendremos el resultado. El código sería como sigue:
   1: //GalleryCliente es nuestro cliente de servicio WCF
   2: //asignamos el delegado del evento a un metodo privado nuestro
   3: this.galleryClient.GetGalleryNamesCompleted += new EventHandler<GetGalleryNamesCompletedEventArgs>(galleryClient_GetGalleryNamesCompleted);
   4: //invocamos al metodo en si
   5: this.galleryClient.GetGalleryNamesAsync();

El método GetGalleryNames devuelve un array de string. Esos datos irán a un comboBox, por lo tanto en el evento tendremos lo siguiente:

   1: void galleryClient_GetGalleryNamesCompleted(object sender, GetGalleryNamesCompletedEventArgs e)
   2: {
   3:     comboGalleries.Items.Clear();
   4:     for (int i = 0; i < e.Result.Count; i++)
   5:     {
   6:         comboGalleries.Items.Add(e.Result[i]);
   7:     }
   8: }

A través del parámetro del evento disponemos de las siguientes propiedades:

  • Cancelled: Indica si la ejecución del método se ha cancelado.
  • Error: La excepción que ha ocurrido durante la ejecución del método.
  • Result: El valor devuelto (puede ser único o colecciones, según lo que hayamos especificado).
  • UserState: Obtiene un identificador único de la ejecución del método.

En el ejemplo, únicamente emplearé la propiedad Result para añadir los elementos del array al comboBox.

Con todo esto ya tenemos nuestro servicio WCF disponible y una aplicación SL que lo consuma correctamente.