Mostrando entradas con la etiqueta web services. Mostrar todas las entradas
Mostrando entradas con la etiqueta web services. Mostrar todas las entradas

Lo que todo desarrollador debería saber sobre serialización a XML en .NET (y cómo afecta a los Web Services)

lunes 23 de abril de 2007 | categorías: , , , , | 6 comentarios -- da clic aquí para dejar el tuyo

En la plática y taller de este mes, alguien hizo una pregunta muy común: "Quiero construir un Web Service en .NET que haga X, ¿por dónde comienzo?"

Así que le prometí escribir un artículo al respecto. Sin embargo, en cuanto comencé, me di cuenta que hay un concepto más básico que debe ser amaestrado para en verdad entender lo que está sucediendo: Serialización. Específicamente serialización a XML.

¿Qué es serialización y cómo se utiliza?

Serialización, no es más que una palabra dominguera que significa transformar una instancia de una clase a una serie de bytes con un formato determinado. En este caso estoy hablando de agarrar una instancia de una clase y transformarla a un documento XML.

En la plática, algunas personas se sorprendieron cuando les dije que prácticamente cualquier clase podía ser serializada casi automáticamente, de una manera relativamente sencilla. Solo hay un "pero" del que debes estar consciente: únicamente los miembros y propiedades públicas pueden ser serializados. En otras palabras, no convierte métodos, indexadores, campos privados o protegidos o propiedades solo-lectura (excepto colecciones solo-lectura).

Toma como ejemplo una clase sencilla como la siguiente:

using System;
 
public class MiClase
{
    protected string _campo1 = "campo1 es protegido";
    public string _campo2 = "campo2 es publico";
    private string _campo3 = "campo3 es privado";
    private int _propiedad1 = 1000;
    private int _propiedad2 = 9999;
    public int _propiedad3 = 7777;
 
    public int UnaPropiedad
    {
        get { return _propiedad1; }
        set { _propiedad1 = value; }
    }
 
    protected int OtraPropiedad
    {
        get { return _propiedad2; }
        set { _propiedad2 = value; }
    }
 
    public int PropiedadPublica
    {
        get { return _propiedad3; }
    }
 
    // Se requiere un constructor publico default para que 
    // funcione la serializacion
    public MiClase() { }
}

Podemos serializarla (transformarla a XML pues) con un código como este:

using System;
using System.IO;
using System.Xml.Serialization;
 
class Program
{
    static void Main(string[] args)
    {
        MiClase m = new MiClase();
 
        XmlSerializer serializador = new XmlSerializer(typeof(MiClase));
        StringWriter escritor = new StringWriter();
 
        // se puede serializar a casi cualquier tipo de Stream o Writer
        // p.ej. System.IO.StreamWriter, System.Xml.XmlWriter, etc.
        serializador.Serialize(escritor, m);
        Console.WriteLine(escritor.ToString());
    }
}

Si corremos el programa, el resultado será este:

O, visto de una manera más amigable (utilizando el visualizador de XML de Visual Studio):

Nota que únicamente el _campo2, _propiedad3 y UnaPropiadad fueron serializados, ya que eran los únicos con un nivel de acceso public. PropiedadPublica, a pesar de ser public, era solo-lectura y por lo tanto no fue serializada. También nota que automáticamente construyó el documento utilizando el nombre de la clase (MiClase) como el elemento raíz y tomó el nombre de cada campo para los elementos hijos (_campo2, _propiedad3, UnaPropiedad).

La magia la hace la clase XmlSerializer, la cual tiene 3 métodos interesantes:

  • Serialize() convierte una instancia de una clase a XML.
  • Deserialize() hace lo contrario, toma un documento XML y lo transforma en una instancia de una clase.
  • CanDeserialize() regresa un booleano para probar si el XML en realidad se puede des-serializar.

Ahora, puedes controlar varios aspectos de la serialización, aplicando atributos:

  • [Serializable] no solo indica que la clase puede ser serializada, sino que también revisa que todos los tipos contenidos dentro de la clase (otra clase, por ejemplo) también sean serializables. Si no lo son, entonces arroja un SerializationException.
  • [NonSerialized] y [XmlIgnore] indican que no queremos que se serialize el campo o propiedad. El primero afecta al SoapFormatter (el utilizado por WebServices), y el segundo afecta a XmlSerializer.
  • [XmlRoot], que su vez tiene parámetros como Namespace y ElementName para especificar el nombre y espacio de nombres del elemento raíz. Este se aplica solo a la clase.
  • [XmlElement], es parecido al anterior, pero se aplica a los campos o propiedades. Puedes especificar el tipo de dato de XML Schema que debe aplicar y si es o no nulleable mediante los parámetros DataType y IsNullable. También puedes especificar el espacio de nombres y el nombre del elemento como en XmlRoot.
  • [XmlAttribute] indica que quieres serializar el valor como un atributo, en lugar de un elemento.

Para ver el impacto, vamos a aplicar estos conceptos al ejemplo anterior:

using System;
using System.Xml.Serialization;
 
[Serializable()]  //indica explicitamente que esta clase es serializable
[XmlRoot(Namespace = "http://comunidadnetjuarez.org/2007/04",
    ElementName = "MiClaseSerializada")] // indica el namespace y nombre 
public class MiClase                     // del elemento raiz
{
    protected string _campo1 = "campo1 es protegido";
 
    [XmlElement(ElementName = "SegundoCampo",
        DataType = "string",
        IsNullable = false)]  // indica el nombre y tipo del elemento
    public string _campo2 = "campo2 es publico";
 
    private string _campo3 = "campo3 es privado";
    private int _propiedad1 = 1000;
    private int _propiedad2 = 9999;
 
    [NonSerialized()] // no serializar cuando se use SoapFormatter
    [XmlIgnore()]     // no serializar cuando se use XmlSerializer
    public int _propiedad3 = 7777;
 
    [XmlAttribute(AttributeName = "valor")] // indica que deseamos que
    public int UnaPropiedad                 // se serialize como atributo XML
    {                                       // y no como elemento XML
        get { return _propiedad1; }
        set { _propiedad1 = value; }
    }
 
    protected int OtraPropiedad
    {
        get { return _propiedad2; }
        set { _propiedad2 = value; }
    }
 
    public int PropiedadPublica
    {
        get { return _propiedad3; }
    }
 
    // Se requiere un constructor publico default para que 
    // funcione la serializacion
    public MiClase() { }
}

El resultado sería el siguiente:

La cosa comienza a ponerse más interesante mientras más complejidad le agreguemos a la clase. Por ejemplo, ¿qué pasa si mi clase tiene una colección o lista de cosas? Bueno, pues resulta que únicamente las colecciones que implementen ICollection o IEnumerable podrán serializarse (p.ej. un ArrayList). Si implementan IDictionary (como un HashTable) estas no son serializadas (de hecho levanta una excepción).

Agreguemos el siguiente miembro a la clase:

    public string[] arreglo = { "arreglo uno", 
                                "arreglo dos", 
                                "arreglo tres" };

El resultado sería el siguiente:

Y finalmente veamos qué pasa si aplicamos [XmlElement]:

    [XmlElement(ElementName = "UnArreglo")]
    public string[] arreglo = { "arreglo uno", 
                                "arreglo dos", 
                                "arreglo tres" };

Creación automática de una clase serializable

Como puedes ver, tienes bastante control sobre la conversión de una clase .NET a un documento XML. Sin embargo, mientras más complejo sea el documento XML que quieras producir, más complejo y cuidadoso tendrás que ser con la aplicación de los atributos para obtener el resultado que quieres.

Muchas veces quizá es más sencillo comenzar con un documento XML de ejemplo o mejor aún con el contrato XML Schema que debe cumplir el documento XML que quieres producir cuando se serialice tu clase. Una vez que tienes esto, puedes utilizar la utilería xsd.exe para automáticamente generar los tipos adecuados en C# (o VB.NET).

Veamos un ejemplo. Supongamos que tienes un archivo llamado SchemaEjemplo.xsd con el siguiente contenido:

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="personas">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="persona"
                    type="personaType"
                    minOccurs="1"
                    maxOccurs="unbounded" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="personaType">
    <xs:sequence>
      <xs:element name="titulo"
                  type="tituloType"
                  minOccurs="0" />
      <xs:element name="nombre"
                  type="nombreType" />
      <xs:element name="edad"
                  type="xs:integer" />
      <xs:element name="direccion"
                  type="direccionType" />
      <xs:element name="genero"
                  type="generoType" />
    </xs:sequence>
    <xs:attribute name="colorOjos"
                  type="xs:string" />
  </xs:complexType>
  <xs:simpleType name="tituloType">
    <xs:restriction base="xs:string">
      <xs:enumeration value="Sr." />
      <xs:enumeration value="Sra." />
      <xs:enumeration value="Señorita" />
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType name="direccionType">
    <xs:sequence>
      <xs:element name="calle"
                  type="xs:string" />
      <xs:element name="numero"
                  type="xs:string" />
      <xs:element name="numeroDepartamento"
                  type="xs:string"
                  minOccurs="0" />
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="nombreType">
    <xs:all>
      <xs:element name="primerNombre"
                  type="xs:string" />
      <xs:element name="segundoNombre"
                  type="xs:string"
                  minOccurs="0" />
      <xs:element name="apellidos"
                  type="xs:string" />
    </xs:all>
  </xs:complexType>
  <xs:complexType name="generoType">
    <xs:choice>
      <xs:element name="hombre"
                  type="xs:boolean" />
      <xs:element name="mujer"
                  type="xs:boolean" />
    </xs:choice>
  </xs:complexType>
</xs:schema>

La siguiente línea de comando de Visual Studio 2005 genera las clases necesarias:

xsd.exe SchemaEjemplo.xsd /classes /language:CS

El archivo resultante (SchemaEjemplo.cs) se ve algo así como este (solo muestro una parte dado que es muy largo el fragmento de código). Nota que define una clase fuertemente tipada por cada elemento o complexType definido en el Schema:

//------------------------------------------------------------------------------
// 
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.42
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// 
//------------------------------------------------------------------------------
 
using System.Xml.Serialization;
 
// 
// This source code was auto-generated by xsd, Version=2.0.50727.42.
// 
 
 
/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class personas
{
 
    private personaType[] personaField;
 
    /// 
    [System.Xml.Serialization.XmlElementAttribute("persona", 
        Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public personaType[] persona
    {
        get { return this.personaField; }
        set { this.personaField = value; }
    }
}
 
/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class personaType
{
 
    private tituloType tituloField;
    private bool tituloFieldSpecified;
    private nombreType nombreField;
    private string edadField;
    private direccionType direccionField;
    private generoType generoField;
    private string colorOjosField;
 
    /// 
    [System.Xml.Serialization.XmlElementAttribute(
        Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public tituloType titulo
    {
        get { return this.tituloField; }
        set { this.tituloField = value; }
    }
 
    /// 
    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public bool tituloSpecified
    {
        get { return this.tituloFieldSpecified; }
        set { this.tituloFieldSpecified = value; }
    }
 
    /// 
    [System.Xml.Serialization.XmlElementAttribute(
        Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public nombreType nombre
    {
        get { return this.nombreField; }
        set { this.nombreField = value; }
    }
 
    /// 
    [System.Xml.Serialization.XmlElementAttribute(
        Form = System.Xml.Schema.XmlSchemaForm.Unqualified, DataType = "integer")]
    public string edad
    {
        get { return this.edadField; }
        set { this.edadField = value; }
    }
 
    /// 
    [System.Xml.Serialization.XmlElementAttribute(
        Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public direccionType direccion
    {
        get { return this.direccionField; }
        set { this.direccionField = value; }
    }
 
    /// 
    [System.Xml.Serialization.XmlElementAttribute(
        Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public generoType genero
    {
        get { return this.generoField; }
        set { this.generoField = value; }
    }
 
    /// 
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string colorOjos
    {
        get { return this.colorOjosField; }
        set { this.colorOjosField = value; }
    }
}
 
// ... así continúa ...

Una vez que tus clases han sido generadas es solo cuestión de utilizarlas:

using System;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
 
class Program
{
    static void Main(string[] args)
    {
        // objeto principal
        personas p = new personas();
 
        // declara dos personas
        personaType p1 = new personaType();
        personaType p2 = new personaType();
 
        // poner las propiedades de p1
        p1.colorOjos = "Cafe";
 
        direccionType d1 = new direccionType();
        d1.calle = "Chinches Bravas";
        d1.numero = "123";
        d1.numeroDepartamento = "";
        p1.direccion = d1;
 
        p1.edad = "25";
 
        generoType g1 = new generoType();
        g1.ItemElementName = ItemChoiceType.hombre;
        g1.Item = true;
        p1.genero = g1;
 
        nombreType n1 = new nombreType();
        n1.primerNombre = "Chucho";
        n1.segundoNombre = "El";
        n1.apellidos = "Roto";
        p1.nombre = n1;
 
        p1.titulo = tituloType.Sr;
 
        // poner las propiedades de p2
        p2.colorOjos = "Azul";
 
        direccionType d2 = new direccionType();
        d2.calle = "La laguna";
        d2.numero = "7777";
        d2.numeroDepartamento = "9A";
        p2.direccion = d2;
 
        p2.edad = "15";
 
        generoType g2 = new generoType();
        g2.ItemElementName = ItemChoiceType.mujer;
        g2.Item = true;
        p2.genero = g2;
 
        nombreType n2 = new nombreType();
        n2.primerNombre = "Lola";
        n2.segundoNombre = "La";
        n2.apellidos = "Trailera";
        p2.nombre = n2;
 
        p2.titulo = tituloType.Señorita;
 
        // agregar p1 y p2 al array de personas
        p.persona = new personaType[] {p1, p2};
 
        //
        // serializar la instancia
        //
        XmlSerializer serializador = new XmlSerializer(typeof(personas));
        StringWriter escritor = new StringWriter();
        serializador.Serialize(escritor, p);
 
        Console.WriteLine(escritor.ToString());
    }
}

El resultado:

Bueno, ¿pero qué carajos tiene que ver con Web Services?

Pues sin ahondar mucho en detalles (eso lo haré en otro artículo), resulta que uno de los primeros pasos al crear un Web Service es precisamente definir el formato de los mensajes que se intercambiarán entre el cliente y el servicio (despues de todo, Web Services, visto desde un punto de vista sobre-simplificado no son más que mensajes XML sobre HTTP). ASP.NET y el .NET Framework automáticamente hacen este tipo de conversión de y hacia XML.

También es importante comprender la serialización, porque la siguiente generación de herramientas para servicios (Windows Communication Foundation) utiliza otros mecanismos para transformar tipos de .NET a XML y hay algunas diferencias importantes. Así que solo hemos comenzado con el tema, pero por ahora creo que es suficiente.

¿Confundido con la Orientación a Servicios?

miércoles 22 de febrero de 2006 | categorías: , , , | 0 comentarios -- da clic aquí para dejar el tuyo

Si alguna vez han intentado definir o explicarle a alguien qué es Orientación a Servicios (SO, Service Orientation), y se dan cuenta que trataron un montón de temas, sin llegar a algo concreto, no se preocupen, no son los únicos. Agarren a 5 expertos, métanlos en un cuarto y pregúntenles qué es y seguramente recibirán 5 respuestas distintas.

¿Por qué es tan difícil de definir?

  • Se ha hablado, escrito y hasta podcasteado demasiado sobre el tema—espero que este artículo no sea otro del montón.
  • Es un tema aún nuevo en la conciencia colectiva de los desarrolladores así que, en realidad, aún no hay una definición universalmente aceptada.
  • Hubo demasiado marketing de productos para un concepto que es abstracto. Esto llevó a muchas personas a la conclusión de que SO==SOA==Web Services, al grado que hasta los gurúes de pronto hablan indistintamente de esas 3 “cosas” y no sabe uno dónde quedó la bolita.
¿Qué problemas resuelve?
  • SO(A) promete aliviar muchos problemas de re-uso, integración y comunicación para los sistemas disgregados y sistemas distribuidos.
  • Permite la <buzzword>interoperabilidad</buzzword>: la capacidad de que sistemas en distintas plataformas, que funcionan con distintas tecnologías interactúen felizmente.

¿Qué NO es SO?

Quizá Rich Turner tenga razón, y la manera más sencilla de aclarar la confusión y de explicar qué es SO, es desmentir algunas ideas sobre lo que NO es SO. En otra entrada les detallaré más sobre las características que debe cumplir “algo” orientado a servicios. Por ahora veamos lo que no es.

SO != tecnología

SO en realidad no tiene nada que ver con tal o cual tecnología (.NET, J2EE, Web Services, etc.). SO es una metáfora, una manera de representar—abstraer—las cosas, para ayudar a atacar un problema: la interacción en sistemas distribuidos. Es análogo a la Orientación a Objetos (OO), que es una manera de abstraer “cosas” de la vida real para modelarlas y poder formar un sistema.

Service orientation refers to a mindset that centers on the four SO tenets.” (Selly, Troelsen y Barnaby, p. 303)
“It is philosophy.” (Lhotka)
SO != una arquitectura per se, por lo tanto SO != SOA
“First, services are just a mechanism, a specific mechanism for allowing communication across standard Web protocols. As such, the best service-oriented architectures seem to come from good component-oriented architectures, meaning that the mere imposition of services does not an architecture make. Second, services are a useful but insufficient mechanism for interconnection among systems of systems.” (Booch) [Énfasis mía].
La SO por sí sola no lleva a producir un sistema completo. Aún no hay una metodología establecida, aunque afortunadamente ya se está trabajando en eso. SO(A) ha sido promovida como la cura de todos los males: broncas en procesos de negocios, arquitectura a nivel enterprise y a nivel soluciones, y hasta arquitectura de aplicaciones. Y es ahí el meollo de la confusión. Vean la figura 1 y figura 2 en el artículo de Zimmermann, Krogdahl, y Gee.
“Service Oriented Architecture [vs. SO] is more specific in that it refers to the activity of developing an architecture or can be used to describe an existing architecture. When the service notion first appeared within the developer world, it was commonly referred to as SOA. The problem with the SOA term, however, is that it implies that service orientation is simply an architectural approach.” (Selly, Troelsen y Barnaby, p. 303)

SO != OO

Esto pudiera parecer obvio, pero algunas personas piensan que SO es un “paradigma-moda-arquitectura” que pretende sustituir a la OO. Esto es incorrecto. Son “cosas” que se complementan, ya que, aunque hay traslape, atacan problemas distintos. Específicamente, el principio en SO de que “las fronteras son explícitas” es importantísimo, ya que rompe de una manera fundamental con OO, que asume que todo es local y stateful (que retiene su estado). Esto puede parecer trivial, pero fue donde la puerca torció el rabo. ¿Recuerdan COM+/DCOM, o RMI? Estos fueron intentos de aplicar OO a sistemas distribuidos, pero al intentar esto el primer tope fue con el costo de la comunicación de las partes, lo cual llevó a los “objetos” stateless (sin estado). El clásico querer cuadrar un círculo. ¿Ven por qué SO se ve chida?
OO analysis is a very powerful and proven approach, and as such, SOAD should make use of OO analysis techniques as much as possible. To successfully apply OO analysis to SOA projects, you must examine more than one system at a time. Use case models will continue to play an important role. However, SOAD must be predominantly process, rather than use-case driven.” (Zimmermann, Krogdahl, y Gee.)

SO != Web Services / WSE

Web Services es una implementación de orientación a servicios, un ejemplo concreto. Web Services no es SO. Las Web Service Extensions son extensiones que Microsoft agregó al .NET Framework para cumplir con las especificaciones WS-I y permitir que su implementación de Web Services interoperara.

SO != RPC / .NET Remoting

Parte del punto anterior es que cuando se comenzaron a popularizar los Web Services, muchas personas—incluyéndome—interpretaron Web Services == XML RPC sobre HTTP, y comenzaron a implementarlos bajo ese modelo. Eso llevó al embarradero erróneo de que SO tenía que ver con COM+/DCOM/Enterprise Services o hasta con Remoting. Aparte de que en dado caso estas serían implementaciones de SO, no cumplen pero para nada con la interoperabilidad, ya están casadotas con las tecnologías de Microsoft.

SO != Algo nuevo

Rocky Lhotka, y Gary Booch, han sido de los que se han quejado de esto. Cierto, SO definitivamente no es algo nuevo, solo pareció así por la popularidad de los Web Services. Sin embargo,

“One of the many complaints we often hear regarding SO/A is that it offers nothing that sophisticated and successful distributed implementations aren’t already doing. To which we say: ‘That’s the point’. We've all learned many hard lessons over the past few years by watching distributed applications deliver disappointing results or completely fail. The primary goal of SO/A is to take those lessons to heart and document the characteristics of the most successful distributed systems. The hope is that this information will help future developers avoid the same mistakes that crippled many early attempts at building distributed applications.” (Selly, Troelsen y Barnaby, pp. 300-301)

¿Aún confundidos?

No se agüiten, como dijo Edward R. Murrow: “Cualquiera que no esté confundido no entiende realmente la situación”.