Serialización personalizada
La serialización personalizada es el proceso de control de la serialización y deserialización de un tipo. El control de la serialización permite garantizar la compatibilidad de la serialización, es decir, la capacidad de serializar y deserializar entre versiones de un tipo sin interrumpir la funcionalidad central del tipo. Por ejemplo, en la primera versión de un tipo, puede haber sólo dos campos. En la próxima versión de un tipo, es posible agregar varios campos más. Sin embargo, la segunda versión de una aplicación debe poder serializar y deserializar ambos tipos. En las siguientes secciones se describe cómo controlar la serialización.
Ejecutar métodos personalizados durante y después de la serialización
El método más recomendable y sencillo (incorporado en la versión 2.0 de .NET Framework) es aplicar los siguientes atributos a métodos que se utilizan para corregir datos durante y después de la serialización:
Estos atributos permiten al tipo participar en cualquiera de las cuatro fases (o en todas ellas) de los procesos de serialización y deserialización. Los atributos especifican los métodos del tipo que debe invocarse durante cada fase. Los métodos no tienen acceso a la secuencia de serialización pero sí le permiten modificar el objeto antes y después de la serialización, o antes y después de la deserialización. Los atributos se pueden aplicar a todos los niveles de la jerarquía de herencia de tipos, y cada método de la jerarquía es llamado del base al más derivado. Este mecanismo evita la complejidad y cualquier problema ocasionado por la implementación de la interfaz ISerializable haciendo recaer la responsabilidad de la serialización y deserialización en la implementación más derivada. Asimismo, este mecanismo permite a los formateadores omitir el rellenado de los campos y la recuperación desde la secuencia de serialización. Para obtener detalles y ejemplos del control de la serialización y deserialización, haga clic en cualquiera de los vínculos incluidos anteriormente.
Además, al agregar un nuevo campo a un tipo serializable existente, aplique el atributo OptionalFieldAttribute al campo. BinaryFormatter y SoapFormatter omiten la ausencia del campo cuando se procesa una secuencia a la que le falta el nuevo campo.
Implementar la interfaz ISerializable
El otro modo de controlar la serialización es mediante la implementación de la interfaz ISerializable en un objeto. Sin embargo, recuerde que el método descrito en la sección anterior sustituye a este método para controlar la serialización.
No debe utilizar la serialización predeterminada en clases que estén marcadas con el atributo Serializable y que tengan seguridad imperativa o declarativa en el nivel de clase o en sus constructores. En su lugar, estas clases deben implementar siempre la interfaz ISerializable.
La implementación de ISerializable implica la implementación del método GetObjectData y de un constructor especial que se utiliza al deserializar el objeto. En el siguiente ejemplo de código se muestra cómo implementar ISerializable en la clase MyObject
de una sección anterior.
[Serializable]
public class MyObject : ISerializable
{
public int n1;
public int n2;
public String str;
public MyObject()
{
}
protected MyObject(SerializationInfo info, StreamingContext context)
{
n1 = info.GetInt32("i");
n2 = info.GetInt32("j");
str = info.GetString("k");
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("i", n1);
info.AddValue("j", n2);
info.AddValue("k", str);
}
}
Cuando se llama a GetObjectData durante la serialización, el usuario es el responsable de rellenar la clase SerializationInfo proporcionada con la llamada al método. No tiene más que agregar las variables que se van a serializar como parejas de nombre y valor. Se puede utilizar cualquier texto como nombre. Tiene libertad para decidir las variables miembro que se agregan a SerializationInfo, siempre y cuando se serialicen datos suficientes para restaurar el objeto durante la deserialización. Las clases derivadas deben llamar al método GetObjectData en el objeto base si éste implementa ISerializable.
Observe que la serialización puede permitir que otro código vea o modifique los datos de la instancia del objeto, a los que no se puede tener acceso en cualquier otro caso. Por lo tanto, el código encargado de llevar a cabo la serialización requiere un permiso SecurityPermission con el indicador SerializationFormatter especificado. De acuerdo con la directiva predeterminada, no se concede este permiso al código descargado de Internet o de la intranet; únicamente se concede este permiso al código del equipo local. El método GetObjectData debe protegerse explícitamente, ya sea mediante la petición de SecurityPermission con el indicador SerializationFormatter especificado o mediante la petición de otros permisos que específicamente protejan datos privados.
Si un campo privado almacena información confidencial, debe solicitar los permisos adecuados en GetObjectData para proteger los datos. Recuerde que el código que tiene garantizado el permiso SecurityPermission con el indicador SerializationFormatter especificado puede ver y modificar los datos almacenados en campos privados. Un llamador malicioso al que se haya concedido este permiso SecurityPermission puede ver datos como, por ejemplo, ubicaciones de directorios ocultos o permisos concedidos y utilizarlos para atacar puntos de seguridad vulnerables del equipo. Para obtener una lista completa de los indicadores de permisos de seguridad que puede especificar, vea SecurityPermissionFlag (Enumeración).
Es importante resaltar que, cuando se agrega ISerializable a una clase, es necesario implementar tanto GetObjectData como el constructor especial. El compilador le avisa si no encuentra GetObjectData. No obstante, dado que no se puede exigir la implementación de un constructor, no se proporcionan advertencias si no se encuentra y se inicia una excepción al intentar deserializar una clase sin el constructor.
Se prefirió el diseño actual sobre el método SetObjectData para obtener más seguridad potencial y evitar problemas de control de versiones. Por ejemplo, el método SetObjectData debe ser público si se define como parte de una interfaz; por lo tanto, los usuarios deben escribir código para impedir que se llame varias veces al método SetObjectData. De lo contrario, una aplicación maliciosa que llame al método SetObjectData en un objeto durante el proceso de ejecución de una operación puede producir problemas.
Durante la deserialización, SerializationInfo se pasa a la clase con el constructor proporcionado para este propósito. Las restricciones de visibilidad del constructor se pasan por alto cuando se deserializa el objeto, por lo que la clase puede marcarse como pública, protegida, interna o privada. Sin embargo, es conveniente crear el constructor como protegido a menos que la clase sea sealed, en cuyo caso el constructor debe marcarse como privado. El constructor debe realizar también validación de entrada exhaustiva. Para evitar un uso indebido por parte del código malicioso, el constructor debe aplicar las mismas comprobaciones de seguridad y los mismos permisos que los requeridos para obtener una instancia de la clase utilizando cualquier otro constructor. Si no hace caso de esta recomendación, el código malicioso puede serializar un objeto previamente, obtener control con el permiso SecurityPermission que tiene el indicador SerializationFormatter especificado y deserializar el objeto en un equipo cliente pasando por alto cualquier tipo de seguridad aplicada durante la construcción de instancias estándar utilizando un constructor público.
Para restaurar el estado del objeto, no tiene más que recuperar los valores de las variables de SerializationInfo con los nombres utilizados durante la serialización. Si la clase base implementa ISerializable, se debe llamar al constructor base para permitir que el objeto base restaure sus variables.
Cuando se deriva una nueva clase de una que implementa ISerializable, la clase derivada debe implementar el constructor y el método GetObjectData si tiene variables que se deban serializar. En el siguiente ejemplo de código se muestra cómo hacer esto con la clase MyObject
mostrada anteriormente.
[Serializable]
public class ObjectTwo : MyObject
{
public int num;
public ObjectTwo() : base()
{
}
protected ObjectTwo(SerializationInfo si, StreamingContext context) : base(si,context)
{
num = si.GetInt32("num");
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
public override void GetObjectData(SerializationInfo si, StreamingContext context)
{
base.GetObjectData(si,context);
si.AddValue("num", num);
}
}
No olvide llamar a la clase base en el constructor de deserialización. Si no lo hace, nunca se llama al constructor de la clase base y el objeto no se construye completamente después de la deserialización.
Los objetos se reconstruyen desde dentro hacia fuera y llamar a métodos durante la deserialización puede tener efectos colaterales no deseados porque los métodos llamados pueden hacer referencia a objetos que no se han deserializado en el momento de realizar la llamada. Si la clase que se va deserializar implementa la interfaz IDeserializationCallback, se llama automáticamente al método OnDeserialization cuando se ha deserializado el objeto del gráfico completo. En este momento, todos los objetos secundarios a los que se hace referencia se han restaurado completamente. Una tabla hash constituye un ejemplo típico de una clase que es difícil de deserializar sin utilizar el agente de escucha de eventos. Es fácil recuperar las parejas de clave y valor durante la deserialización, pero agregar estos objetos de nuevo a la tabla hash puede causar problemas porque no existe ninguna garantía de que se hayan deserializado las clases derivadas de la tabla hash. Por lo tanto, no es aconsejable llamar a métodos en una tabla hash en esta fase.
Vea también
Conceptos
Otros recursos
Serialización binaria
Objetos remotos
Serialización XML y SOAP
Seguridad de acceso a código