viernes, 13 de agosto de 2010

[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.

6 comentarios:

  1. Buenas Andrés, soy también estudiante de la UA y sufridor este año de la asignatura de SOR. Es por eso por lo que casi por casualidad he dado con tu blog (en concreto con los posts que hacen referencia a servicios WCF) y tengo que decirte que escribes realmente bien!
    La verdad, no voy a empezar a contarte mi vida sobre la inmensa cantidad de problemas que hemos tenido a la hora de implementar WCF, que la verdad son muy quisquillosos, pero si hacerte referencia a la solución que adoptamos a la hora de serializar nuestras entidades a XML para el envio y recepción de datos y que CREO que puede tener relación con lo que comentas en este post.
    Supongo que tu problema viene a raiz de que en SL no puedes tener acceso a disco (cabe decir que no he tocado SL en la vida), e implementas un servicio web de WCF para el acceso remoto de dicho fichero, aprovechando que WCF ya hace la serialización automaticamente.
    Nosotros, como ya sabrás de haber cursado la asignatura, no podiamos invocar a los WCF directamente (es decir, pasando los objetos de nuestras entidades directamente), puesto que para posteriores entregas del proyecto, los mensajes SOAP enviados a través de los servicios web deberían ir encriptados A MANO con algún tipo de algoritmo implementado por nosotros (aún sabiendo que WCF también es capaz de enviar los mensajes ecriptados automáticamente).
    Como hacer los XML a mano incluso utilizando la API correspondiente resultaba un coñazo, hicimos lo siguiente, haciendo uso del XMLSerializer pero sin llegar a escribir en disco (de ahí viene mi duda de si tú realmente intentaste esta opción):
    http://nopaste.voric.com/paste.php?f=lfijol
    Así, además de poder resolver cualquier tipo de problema con la codificación, cuando en la segunda entrega tengamos que realizar la encriptación, solo tendremos que incluir en dicho método la llamada a la función que realiza la encritación sobre una cadena de texto (en este caso, xml).
    Un saludo.

    ResponderEliminar
  2. Muy buenas señor!
    Al ver tu nombre ya sé quien eres...ya me contó un pajarillo que habíais usado WCF para comunicar los sistemas. De lo cual me alegro mucho y me alegro que mi blog te guste y te sirva a tal propósito :P
    En el caso que comentas, el contexto es diferente al mío. Mi contexto es que tengo una aplicación en Silverlight junto con mi librería de lógica propia (capa interfaz y capa de lógica). Con Silverlight, por motivos de seguridad no puedes hacer ningún tipo de acceso a disco y requieres de servicios externos que sí lo hagan. Por otra parte, según qué versión de XMLSerializer uses funcionará o no (eso es lo que pongo en el primer artículo).
    En vuestro caso como bien indicas tenéis que pasar los atributos/propiedades de los objetos para enviarlos como SOAP y que después no tengáis problemas en la parte de seguridad.
    Con el código que me has puesto veo lo que quieres hacer y lo veo correcto. Nosotros hicimos una capa de encriptación entre los servicios web de los sistemas, de modo que cada servicio era el responsable de encriptar/desencriptar la información, haciendo que esta sea totalmente independiente por parte del usuario. Ídem con los ficheros. Como sugerencia, y si quieres que todo vaya más rápido, te puedes crear un gestor de serialización donde mediante con una tabla hash/diccionario almacenes los diferentes tipos de serializer que vas creando. Así evitarás instanciar entidades más veces de la cuenta. Ya me cuentas, cualquier duda que tengas la pones por aquí :)

    ResponderEliminar
  3. Hola amigos .....para variar sufriendo con los servicios web, necesito una ayuda urgente , ahora tengo que conectarme desde php aun web service en wcf,para recibir los datos no tengo problemas pero tengo que enviar los datos a un servicio el cual recibe un arreglo de datos.

    Este es el arreglo "DatosDetalleConsumo" que recibe el web service:


    10
    30
    2010-12-01T16:51:00
    1
    1
    4500002505
    78454860-0
    15


    He realizado el envio de 3 formas y tengo error:

    Forma 1:

    $client = new SoapClient('http://IP/DemandaWs/Demandas.svc?wsdl');
    $param = array('Cantidad' => $Cantidad,'CodigoConsumo' => $CodigoConsumo,
    'FechaConsumo' =>$FechaConsumo, 'IdCasino' => $IdCasino, 'IdTipoServicio' => $IdTipoServicio,'NumeroContrato' => $NumeroContrato,
    'RutContratista' => $RutContratista,'ValorNeto' => $ValorNeto);
    $result =$client->Consumos($param);
    var_dump($param);

    De esta Forma me retorno el sgte error:

    object(stdClass)#2 (1) { ["ConsumosResult"]=> object(stdClass)#3 (3) { ["ConError"]=> bool(true) ["Detalle"]=> string(40) "El par{ametro consumos no puede ser nulo" ["Mensaje"]=> string(43) "El informe de consumo debe contener valores" } }


    Converse con el proveedor del Servicio y me indico que estaba enviando los parametros en forma directa al servicio y que no estaba pasando primero por el arreglo DatosDetalleConsumo, me indico que en .net ello hacia asi:

    using (DemandasClient proxy = new DemandasClient())
    {
    List detallesConsumo = new List();

    detallesConsumo.Add(new DatosDetalleConsumo() { Cantidad = 0, CodigoConsumo = 1, FechaConsumo = DateTime.Now, IdCasino = 1, IdTipoServicio = 1, NumeroContrato = "4500002505", RutContratista = "78454860-0", ValorNeto = 100 });

    Respuesta r = proxy.Consumos(detallesConsumo);
    }

    Por lo tanto intente hacerlo asi

    Forma 2

    $client = new SoapClient('http://10.120.2.30/DemandaWs/Demandas.svc?wsdl');
    $param = array('DatosDetalleConsumo' => array('Cantidad' => $Cantidad,'CodigoConsumo' => $CodigoConsumo,
    'FechaConsumo' =>$FechaConsumo, 'IdCasino' => $IdCasino, 'IdTipoServicio' => $IdTipoServicio,'NumeroContrato' => $NumeroContrato,
    'RutContratista' => $RutContratista,'ValorNeto' => $ValorNeto));
    $result =$client->Consumos($param);
    var_dump($param);

    Forma 3

    $client = new SoapClient('http://10.120.2.30/DemandaWs/Demandas.svc?wsdl');
    $param = array('Cantidad' => $Cantidad,'CodigoConsumo' => $CodigoConsumo,'FechaConsumo' =>$FechaConsumo, 'IdCasino' => $IdCasino, 'IdTipoServicio' => $IdTipoServicio,'NumeroContrato' => $NumeroContrato, 'RutContratista' => $RutContratista,'ValorNeto' => $ValorNeto);
    $result =$client->Consumos("DatosDetalleConsumo",$param);
    var_dump($result);


    El error que me indica para las formas 2 y 3 :

    Fatal error: Uncaught SoapFault exception: [a:DeserializationFailed] The formatter threw an exception while trying to deserialize the message: Error in deserializing body of request message for operation 'Consumos'. End element 'Body' from namespace 'http://schemas.xmlsoap.org/soap/envelope/' expected. Found element 'param1' from namespace ''. Line 2, position 149. in C:\Archivos de programa\Apache Group\Apache2\htdocs\WEB\consumo.php:26 Stack trace: #0 C:\Archivos de programa\Apache Group\Apache2\htdocs\WEB\consumo.php(26): SoapClient->__call('Consumos', Array) #1 C:\Archivos de programa\Apache Group\Apache2\htdocs\WEB\consumo.php(26): SoapClient->Consumos('DatosDetalleCon...', Array) #


    El problema que tengo es que no se como pasar los parametros al arreglo que recibe el servicio?
    Se agradece cualquier ayuda....

    ResponderEliminar
  4. Hola,
    Aquí he encontrado algo que te puede ayudar: http://social.msdn.microsoft.com/Forums/en-US/microsofttranslator/thread/a42948f8-aa4d-4ec6-999d-e4c8e60e7035/

    ResponderEliminar
  5. Hola, se que este blog ya fue escrito hace rato, pero me gustaría saber si me pueden ayudar.

    Tengo un sistema existente en WinForms a tres capas IU,LogicaNegocio, AccesoDatos; y se necesita crear una aplicación Web (WCF) que sirva de interfaz entre otras aplicaciones web (WS), WinForm y otras arquitecturas diferentes a .NET, pero la idea es poder conservar la lógica de negocio existente dado que es muy extensa, varios dlls.

    He tratado de buscar por todo lado y no encuentro nada que me de una luz al respecto.

    podrán ayudarme?

    Esteban Andrade
    eandrade.ibva@gmail.com

    ResponderEliminar
  6. WCF te servirá para comunicar tu lógica con lo que quieras. Únicamente esa capa estará encima de la logíca y actuará de interfaz, que será la que reciba las llamadas de otros sistemas a esa lógica.

    ResponderEliminar