Share via


Serialización y deserialización

Windows Communication Foundation (WCF) incluye un nuevo motor de serialización, el DataContractSerializer. DataContractSerializer traduce entre objetos .NET Framework y XML, en ambas direcciones. En este tema se explica cómo funciona el serializador.

Al serializar objetos .NET Framework, el serializador entiende varios modelos de programación de la serialización, incluido el nuevo modelo de contrato de datos. Para obtener una lista completa de los tipos admitidos, vea Tipos admitidos por el serializador de contrato de datos. Para obtener una introducción a los contratos de datos, vea Utilización de contratos de datos.

Al deserializar XML, el serializador utiliza las clases XmlReader y XmlWriter. También admite las clases XmlDictionaryReader y XmlDictionaryWriter para permitirle generar XML optimizado en algunos casos, como al utilizar el formato XML binario de WCF.

WCF también incluye un serializador complementario, el NetDataContractSerializer. El NetDataContractSerializer es similar a los serializadores SoapFormatter y BinaryFormatter porque también emite los nombres de tipos de .NET Framework como parte de los datos serializados. Se utiliza cuando se comparten los mismos tipos en los extremos de serialización y deserialización. DataContractSerializer y NetDataContractSerializer derivan de una clase base común, XmlObjectSerializer.

Creación de una instancia de DataContractSerializer

Construir una instancia del DataContractSerializer es un paso importante. Después de la construcción no puede cambiar ninguno de los valores.

Especificación del tipo de raíz

El tipo de raíz es el tipo en el que las instancias se serializan o deserializan. DataContractSerializer tiene muchas sobrecarga del constructor, pero, como mínimo, se debe proporcionar un tipo de raíz utilizando el parámetro type .

Un serializador creado para un tipo de raíz determinado no se puede utilizar para serializar (o deserializar) otro tipo, a menos que el tipo se derive del tipo de raíz. En el siguiente ejemplo se muestran dos clases.

Este código construye una instancia del DataContractSerializer que sólo se puede utilizar para serializar o deserializar instancias de la clase Person.

Especificación de los tipos conocidos

Si el polimorfismo está implicado en los tipos que se están serializando que aún no se administran utilizando el atributo KnownTypeAttribute o algún otro mecanismo, se ha de pasar una lista de posibles tipos conocidos al constructor del serializador utilizando el parámetro knownTypes. Para obtener más información acerca de tipos conocidos, vea Tipos conocidos de contratos de datos.

El siguiente ejemplo muestra una clase, LibraryPatronque incluye una colección de un tipo específico, LibraryItem. La segunda clase define el tipo LibraryItem. Las clases tercera y cuarta Book y Newspaper heredan de la clase LibraryItem.

El siguiente código construye una instancia del serializador utilizando el parámetro knownTypes.

Especificación del espacio de nombres y el nombre de raíz predeterminados

Normalmente, cuando se serializa un objeto, el nombre y espacio de nombres predeterminados del elemento XML extremo se determinan según el nombre y el espacio de nombres del contrato de datos. Los nombres de todos los elementos internos se determinan a partir de los nombres de los miembros de datos y su espacio de nombres es el espacio de nombres del contrato de datos. El siguiente ejemplo establece valores de Name y Namespace en los constructores de las clases DataContractAttribute y DataMemberAttribute.

Al serializar una instancia de la clase Person, se genera código XML similar al siguiente.

<PersonContract xmlns="http://schemas.contoso.com">
  <AddressMember>
    <StreetMember>123 Main Street</StreetMember>
   </AddressMember>
</PersonContract>

Sin embargo, puede personalizar el nombre y espacio de nombres predeterminado del elemento raíz pasando los valores de los parámetros rootNamespace y rootName al constructor DataContractSerializer. Observe que el rootNamespace no afecta al espacio de nombres de los elementos contenidos que corresponden a miembros de datos. Sólo afecta al espacio de nombres del elemento extremo.

Estos valores se pueden pasar como cadenas o instancias de la clase XmlDictionaryString para permitir su optimización mediante el formato XML binario.

Establecimiento de la cuota de objetos máximos

Algunas sobrecargas del constructor de DataContractSerializer tienen un parámetro maxItemsInObjectGraph. Este parámetro determina el número máximo de objetos que el serializador serializa o deserializa en una única llamada al método ReadObject. (El método siempre lee un objeto raíz, pero este objeto puede tener otros objetos en sus miembros de datos. Esos objetos pueden tener otros objetos, etc.) El valor predeterminado es 65536. Tenga en cuenta que al serializar o deserializar matrices, cada entrada de matriz cuenta como un objeto independiente. Observe también que algunos objetos pueden tener una representación de memoria grande, por lo que esta cuota por sí sola puede no ser suficiente para evitar ataques por denegación de servicio. Para obtener más información, consulte Consideraciones de seguridad para datos. Si necesita aumentar esta cuota por encima del valor predeterminado, es importante hacerlo en los lados de envío (serialización) y recepción (deserialización), porque se aplica a ambos al leer y escribir datos.

Acciones de ida y vuelta

Una acción de ida y vuelta (round trip) se produce cuando un objeto se deserializa y se vuelve a serializar en una operación. De este modo, va de XML a una instancia de objeto y de vuelta a una secuencia XML.

Algunas sobrecargas del constructor del DataContractSerializer tienen un parámetro ignoreExtensionDataObject , que está establecido de forma predeterminada en false . En este modo predeterminado, los datos se pueden enviar en un viaje de ida y vuelta (round trip) desde una versión más reciente de un contrato de datos a través de una versión anterior y de vuelta a la versión más reciente sin pérdidas, siempre que el contrato de datos implemente la interfaz IExtensibleDataObject. Por ejemplo, suponga que la versión 1 del contrato de datos de la Person contiene los miembros de datos PhoneNumber y Name, y la versión 2 agrega un miembro Nickname. Si se implementa IExtensibleDataObject, al enviar información de la versión 2 a la versión 1, los datos Nickname se almacenan y, a continuación, se vuelven a emitir cuando se vuelven a serializar los datos; por tanto, no se pierden datos en el viaje de ida y vuelta. Para obtener más información, consulte Contratos de datos compatibles con el reenvío y Versiones de contratos de datos.

Aspectos a tener en cuenta sobre seguridad y validez del esquema en relación con los viajes de ida y vuelta (round trip)

Los viajes de ida y vuelta pueden tener implicaciones en cuanto a la seguridad. Por ejemplo, deserializar y almacenar grandes cantidades de datos extraños puede constituir un riesgo para la seguridad. Puede haber problemas de seguridad al reemitir estos datos que no se pueden comprobar, sobre todo si hay firmas digitales implicadas. Por ejemplo, en el escenario anterior, el extremo de versión 1 podría estar firmando un valor Nickname que contuviese datos malintencionados. Finalmente, puede haber problemas de validez de esquema: un extremo puede desear emitir siempre datos que se adhieran de manera estricta a su contrato expresado y ningún valor adicional. En el ejemplo anterior, el contrato del extremo de versión 1 dice que sólo emite Name y PhoneNumber, y si se utiliza la validación de esquema, se produciría un error de validación al emitir el valor Nickname adicional.

Habilitar y deshabilitar los viajes de ida y vuelta

Para desactivar los viajes de ida y vuelta, no implemente la interfaz IExtensibleDataObject. Si no tiene ningún control sobre los tipos, establezca el parámetro ignoreExtensionDataObject en true para lograr el mismo efecto.

Preservación de gráfico de objeto

Normalmente, el serializador no se preocupa de la identidad del objeto, como en el código siguiente.

El siguiente código crea un pedido de compra.

Observe que los campos billTo y shipTo están establecidos en la misma instancia de objeto. Sin embargo, el XML generado duplica la información duplicada y parece similar al siguiente XML.

<PurchaseOrder>
  <billTo><street>123 Main St.</street></billTo>
  <shipTo><street>123 Main St.</street></shipTo>
</PurchaseOrder>

Sin embargo, este enfoque tiene las siguientes características, que puede que no se deseen:

  • Rendimiento. La replicación de datos es ineficaz.
  • Referencias circulares. Si los objetos hacen referencia a ellos mismos, incluso a través de otros objetos, la serialización mediante resultados de replicación resulta en un bucle infinito. (Si esto sucede, el serializador inicia una SerializationException.)
  • Semántica. A veces es importante conservar el hecho de que se realicen dos referencias al mismo objeto, y no a dos objetos idénticos.

Por estas razones, algunas sobrecargas del constructor de DataContractSerializer tienen un parámetro preserveObjectReferences (el valor predeterminado es false). Cuando este parámetro está establecido en true, se usa un método especial para la codificación de referencias a objetos, que sólo WCF entiende. Cuando se establece en true, el ejemplo de código XML tiene ahora el siguiente aspecto.

<PurchaseOrder ser:id="1">
  <billTo ser:id="2"><street ser:id="3">123 Main St.</street></billTo>
  <shipTo ser:ref="2"/>
</PurchaseOrder>

El espacio de nombres "ser" hace referencia al espacio de nombres de serialización estándar, https://schemas.microsoft.com/2003/10/Serialization/. Cada parte de datos se serializa sólo una vez y se proporciona un número de Id. a cada una de esas partes de datos, y los posteriores usos resultan en una referencia a los datos ya serializados.

ms731073.note(es-es,VS.90).gif
Si los atributos "id" y "ref" están presentes en el elemento XMLElement del contrato de datos, se observa el atributo "ref" y se omite "id".

Es importante conocer las limitaciones de este modo:

  • El XML que produce el DataContractSerializer con preserveObjectReferences establecido en true no es interoperable con ninguna otra tecnología y sólo puede obtenerse acceso a él mediante otra instancia del DataContractSerializer, también con preserveObjectReferences establecido en true.
  • No hay compatibilidad con metadatos (esquema) para esta característica. El esquema que se genera sólo es válido para el caso en que preserveObjectReferences está establecido en false.
  • Esta característica puede hacer que el proceso de serialización y deserialización se ejecuten más lentamente. Aunque los datos no tienen que replicarse, las comparaciones con objetos adicionales se deben realizar en este modo.
ms731073.Caution(es-es,VS.90).gifPrecaución:
Cuando se habilita el modo preserveObjectReferences, es especialmente importante establecer el valor maxItemsInObjectGraph en la cuota correcta. Debido a la manera en la que se administran las matrices en este modo, es fácil que un atacante construya un pequeño mensaje malintencionado que provoque un uso de memoria grande limitado únicamente por la cuota maxItemsInObjectGraph.

Especificación de un contrato de datos suplente

Algunas sobrecargas del constructor del DataContractSerializer tienen un parámetro dataContractSurrogate, que se puede establecer en null. De lo contrario, puede utilizarlo para especificar un contrato de datos suplente, que es un tipo que implementa la interfaz IDataContractSurrogate. Puede utilizar a continuación la interfaz para personalizar el proceso de serialización y deserialización. Para obtener más información, consulte Suplentes de contratos de datos.

Serialización

La siguiente información se aplica a cualquier clase que herede del XmlObjectSerializer,incluido las clases DataContractSerializer y NetDataContractSerializer.

Serialización simple

La manera más básica de serializar un objeto es pasarlo al método WriteObject. Hay tres sobrecargas, que permiten escribir en una Stream, un XmlWriter o un XmlDictionaryWriter. Con la sobrecarga Stream, el resultado es XML en la codificación UTF-8. Con la sobrecarga XmlDictionaryWriter, el serializador optimiza su resultado para XML binario.

Al utilizar el método WriteObject, el serializador utiliza el nombre y espacio de nombres predeterminado para el elemento contenedor y lo escribe junto con el contenido (vea la sección anterior "Especificación del espacio de nombres y el nombre de raíz predeterminados").

En el ejemplo siguiente se muestra cómo escribir con un XmlDictionaryWriter.

Esto produce un XML similar al siguiente.

<Person>
  <Name>Jay Hamlin</Name>
  <Address>123 Main St.</Address>
</Person>

Serialización paso a paso

Utilice los métodos WriteStartObject, WriteObjectContenty WriteEndObject para escribir el elemento de fin, escribir el contenido del objeto y cerrar el elemento contenedor, respectivamente.

Nota

No hay ninguna sobrecarga Stream de estos métodos.

Esta serialización paso a paso tiene dos usos extendidos. Uno consiste en insertar contenido como atributos o comentarios entre WriteStartObject y WriteObjectContent, tal y como se muestra en el siguiente ejemplo.

Esto produce un XML similar al siguiente.

<Person serializedBy="myCode">
  <Name>Jay Hamlin</Name>
  <Address>123 Main St.</Address>
</Person>

Otro uso común consiste en evitar utilizar WriteStartObject y WriteEndObject por completo, y escribir su propio elemento contenedor personalizado (o incluso pasar por alto totalmente la escritura de un contenedor), tal y como se muestra en el siguiente código.

Esto produce un XML similar al siguiente.

<MyCustomWrapper>
  <Name>Jay Hamlin</Name>
  <Address>123 Main St.</Address>
</MyCustomWrapper>

Nota

El uso de la serialización paso a paso puede producir un XML de esquema no válido.

Deserialización

La siguiente información se aplica a cualquier clase que herede del XmlObjectSerializer,incluido las clases DataContractSerializer y NetDataContractSerializer.

La manera más básica de deserializar un objeto consiste en llamar a una de las sobrecargas del método ReadObject. Hay tres sobrecargas, que permiten leer con un XmlDictionaryReader, un XmlReadero una Stream. Observe que la sobrecarga Stream crea un XmlDictionaryReader textual que no está protegido por ninguna cuota, por lo que sólo debería utilizarse para leer datos de confianza.

También tenga en cuenta que el objeto que devuelve el método ReadObject debe convertirse al tipo adecuado.

El siguiente código construye una instancia de DataContractSerializer y XmlDictionaryReader, a continuación, deserializa una instancia de Person.

Antes de llamar al método ReadObject, coloque el lector XML en el elemento contenedor o en un nodo de no contenido que preceda al elemento contenedor. Puede realizar esto llamando al método Read del XmlReader o su derivación y probando NodeType, tal y como se muestra en el código siguiente.

Observe que puede leer atributos en este elemento contenedor antes de entregar el lector a ReadObject.

Al utilizar una de las sobrecargas simples ReadObject, el deserializador busca el nombre y espacio de nombres predeterminado en el elemento contenedor (consulte la sección anterior, "Especificación del nombre y espacio de nombres raíz predeterminado") e inicia una excepción si encuentra un elemento desconocido. En el ejemplo anterior se espera el elemento contenedor <Person>. Se llama al método IsStartObject para comprobar que el lector está ubicado en un elemento que se denomina como esperado.

Hay una manera de deshabilitar esta comprobación de nombre del elemento contenedor; algunas sobrecargas del método ReadObject toman el parámetro booleano verifyObjectName, que está establecido de forma predeterminada en true. Cuando se establece en false, se ignoran el nombre y el espacio de nombres del elemento contenedor. Esto es útil para leer XML escrito mediante el mecanismo de serialización paso a paso descrito previamente.

Uso del NetDataContractSerializer

La principal diferencia entre DataContractSerializer y NetDataContractSerializer es que DataContractSerializer utiliza nombres de contratos de datos, mientras que NetDataContractSerializer genera un completo ensamblado .NET Framework y nombres de tipos en el XML serializado. Esto significa que se han de compartir exactamente los mismos tipos entre los extremos de serialización y deserialización. Esto significa que el mecanismo de tipos conocidos no se requiere con el NetDataContractSerializer, porque siempre se conocen los tipos exactos que se van a deserializar.

Sin embargo, pueden producirse varios problemas:

  • Seguridad. Se carga cualquier tipo en el XML que se esté deserializando. Esto se puede explotar para forzar la carga de tipos malintencionados. El uso del NetDataContractSerializer con datos que no son de confianza sólo se debería realizar si se utiliza un Enlazador de Serialización (mediante el parámetro del constructor o la propiedad Binder). El enlazador sólo permite cargar tipos seguros. El mecanismo del enlazador es idéntico al que utilizan los tipos en el espacio de nombres de System.Runtime.Serialization.
  • Control de versiones El uso de nombres de ensamblado y tipos completos en el XML restringe en gran medida el control de versión de los tipos. No se pueden cambiar lo siguientes elementos: nombres de tipos, espacios de nombres, nombres de ensamblados y versiones de ensamblados. Establecer la propiedad AssemblyFormat o el parámetro de constructor en Simple en lugar del valor predeterminado de Full permite los cambios de versión de ensamblado, pero no para tipos de parámetros genéricos.
  • Interoperabilidad. Dado que los nombres de ensamblado y el tipo de .NET Framework se incluyen en el XML, sólo la plataforma .NET Framework puede tener acceso a los datos resultantes.
  • Rendimiento. Escribir los nombres del ensamblado y el tipo aumenta significativamente el tamaño del XML resultante.

Este mecanismo es similar a la serialización binaria o de SOAP utilizada por la comunicación remota de .NET Framework (específicamente, BinaryFormatter y SoapFormatter).

El uso del NetDataContractSerializer es similar al uso del DataContractSerializer, con las siguientes diferencias:

  • Los constructores no le exigen que especifique un tipo de raíz. Puede serializar cualquier tipo con la misma instancia del NetDataContractSerializer.
  • Los constructores no aceptan ninguna lista de tipos conocidos. El mecanismo de tipos conocidos no es necesario si los nombres de tipos se serializan en el XML.
  • Los constructores no aceptan un contrato de datos suplente. En su lugar, aceptan un parámetro ISurrogateSelector llamado surrogateSelector (que asigna a la propiedad SurrogateSelector ). Éste es un mecanismo suplente de herencia.
  • Los constructores aceptan un parámetro denominado assemblyFormat del FormatterAssemblyStyle que asigna a la propiedad AssemblyFormat. Tal y como se comentó previamente, esto se puede utilizar para mejorar las capacidades del control de versiones del serializador. Esto es idéntico al mecanismo FormatterAssemblyStyle en la serialización binaria o de SOAP.
  • Los constructores aceptan un parámetro StreamingContext denominado context que asigna a la propiedad Context. Puede utilizar esto para pasar información en los tipos que se serializan. Este uso es idéntico al del mecanismo StreamingContext utilizado en otras clases System.Runtime.Serialization.
  • Los métodos Serialize y Deserialize son alias de los métodos WriteObject y ReadObject. Éstos existen para proporcionar un modelo de programación con serialización binaria o de SOAP más coherente.

Para obtener más información acerca de estas características, vea Binary Serialization.

Generalmente, los formatos XML que usan el NetDataContractSerializer y el DataContractSerializer no son compatibles. Es decir, intentar serializar con uno de estos serializadores y deserializar con el otro no es un escenario admitido.

Asimismo, observe que el NetDataContractSerializer no genera el nombre de ensamblado y tipo .NET Framework completo para cada nodo del gráfico de objeto. Sólo genera esa información cuando hay ambigüedad. Es decir, produce el resultado en el nivel del objeto raíz y para cualquier caso polimórfico.

Consulte también

Referencia

DataContractSerializer
NetDataContractSerializer
XmlObjectSerializer

Conceptos

Tipos admitidos por el serializador de contrato de datos

Otros recursos

Binary Serialization