2009-08-12 27 views
14

Estoy tratando de cargar un árbol de objetos a través de la serialización de XML, y en este momento cargará los objetos y creará el árbol muy felizmente. Mi problema gira en torno al hecho de que estas clases admiten un nivel de auditoría. Lo que me gustaría hacer es llamar a algún método en cada objeto una vez que se haya cargado.¿Cómo se puede saber cuándo se ha cargado mediante serialización XML?

Por el bien del argumento, supongamos que tengo un árbol de objetos bastante genérico con diferentes clases en diferentes niveles, como:

<Customer name="Foo Bar Inc."> 
    <Office IsHq="True"> 
    <Street>123 Any Street</Street> 
    <Town name="Anytown"> 
     <State name="Anystate"> 
     <Country name="My Country" /> 
     </State> 
    </Town> 
    </Office> 
    <Office IsHq="False"> 
    <Street>456 High Street</Street> 
    <Town name="Anycity"> 
     <State name="Anystate"> 
     <Country name="My Country" /> 
     </State> 
    </Town> 
    </Office> 
</Customer> 

¿Hay alguna manera el uso de los serializadores por defecto (de forma similar que pueda crear métodos como ShouldSerializeFoo) para determinar cuándo se ha completado la carga de cada objeto?

Editar: Debo señalar que el caso evidente de la exposición de algo parecido a un método OnLoaded() que podía llamada después deserialising, me llama la atención por ser una "mala cosa que hacer".

Edit2: Por el bien de la discusión esta es mi actual piratear "enfoque", que trabaja para el nivel básico, pero el nodo Ciudad hijo todavía piensa que necesita ser salvado con los cambios (en lo real mundo el modelo de objetos es mucho más complejo, pero esto será al menos compilar, sin necesidad de fuente completo)

public class Office 
{ 
    [XmlAttribute("IsHq")] 
    public bool IsHeadquarters { get; set; } 

    [XmlElement] 
    public string Street { get; set; } 

    [XmlElement] 
    public Town Town { get; set; } 

    protected virtual void OnLoaded() {} 

    public static OfficeCollection Search() 
    { 
     OfficeCollection retval = new OfficeCollection(); 
     string xmlString = @" 
        <Office IsHq='True'> 
         <Street>123 Any Street</Street> 
         <Town name='Anytown'> 
          <State name='Anystate'> 
           <Country name='My Country' /> 
          </State> 
         </Town> 
        </Office>"; 

     XmlSerializer xs = new XmlSerializer(retval.GetType()); 
     XmlReader xr = new XmlTextReader(xmlString); 
     retval = (OfficeCollection)xs.Deserialize(xr); 

     foreach (Office thisOffice in retval) 
     { 
      thisOffice.OnLoaded(); 
     } 
     return retval; 
    } 
} 
+0

No es una respuesta real, pero ¿por qué no puede hacer la auditoría en la propiedad de acceso get? – weiqure

+0

La auditoría está en el conjunto de propiedades, por lo que mantiene un registro interno de que se ha modificado desde que se cargó (y por lo tanto necesita guardarse, lo que significaría que todos los valores cambiaron de sus valores predeterminados a los valores reales cargados) –

+0

Lo siento, quise escribir el set. – weiqure

Respuesta

14

Hmmm ... todavía no es bonita, pero podría refactorizar su lógica de deserialización en una clase dedicada que podría notificar al objeto deserializado que se originó en XML antes de devolverlo a la persona que llama.

Actualización: Creo que esto debería ser bastante fácil de hacer sin alejarse demasiado de los patrones establecidos por el marco ... solo tendría que asegurarse de usar CustomXmlSerializer. Las clases que necesitan esta notificación sólo hay que poner en práctica IXmlDeserializationCallback

using System.Xml.Serialization; 

namespace Custom.Xml.Serialization 
{ 
    public interface IXmlDeserializationCallback 
    { 
     void OnXmlDeserialization(object sender); 
    } 

    public class CustomXmlSerializer : XmlSerializer 
    { 
     protected override object Deserialize(XmlSerializationReader reader) 
     { 
      var result = base.Deserialize(reader); 

      var deserializedCallback = result as IXmlDeserializationCallback; 
      if (deserializedCallback != null) 
      { 
       deserializedCallback.OnXmlDeserialization(this); 
      } 

      return result; 
     } 
    } 
} 
+0

Creo que esta opción tiene más sentido. Postprocesa los objetos después de la deserialización. – mackenir

+0

Jugando con eso, todavía creo que es un poco torpe, pero si la clase que maneja la deserialización usa genéricos y notifica a los objetos deserializados a través de una interfaz, entonces es un poco mejor y no demasiado malo para codificar. – STW

+0

Esa es efectivamente mi mejor opción "en este momento". Estoy luchando con una opción elegante para pasar ese mensaje por el árbol: exponer un método de tipo 'OnLoaded()' parece "incorrecto" –

1

una decisión difícil, ya que XmlSerializer no admite eventos de serialización de devolución de llamada. ¿Hay alguna manera de usar DataContractSerializer? Eso hace, pero no permite atributos (como @name arriba).

De lo contrario; podría implementar IXmlSerializable, pero eso es lotes de trabajo, y muy propenso a errores.

De lo contrario - quizá la comprobación de la persona que llama a través de la pila, pero que es muy frágil, y los olores madura.

2

Probé la solución proporcionada por abatishchev pero como se ha señalado por los comentarios a continuación su respuesta, el método Deserialize en el serializador personalizado nunca parece ser llamado.

Pude hacer que funcionara al sobrecargar todas las sobrecargas diferentes Deserialize que necesitaría para que siempre llamara al método personalizado.

protected object Deserialize(System.IO.StringReader reader) 
{ 
    var result = base.Deserialize(reader); 

    CallBack(result); 

    return result; 
} 

protected object Deserialize(System.IO.TextReader reader) 
{ 
    var result = base.Deserialize(reader); 

    CallBack(result); 

    return result; 
} 

protected object Deserialize(System.Xml.XmlReader reader) 
{ 
    var result = base.Deserialize(reader); 

    CallBack(result); 

    return result; 
} 

protected object Deserialize(System.IO.Stream stream) 
{ 
    var result = base.Deserialize(stream); 

    CallBack(result); 

    return result; 
} 

private void CallBack(object result) 
{ 
    var deserializedCallback = result as IXmlDeserializationCallback; 
    if (deserializedCallback != null) 
    { 
     deserializedCallback.OnXmlDeserialization(this); 
    } 
} 

De esta manera, en realidad veo que se llama al método Deserialize.

+0

Desafortunadamente, esto tampoco funciona para mí, utilizando .Net 4.0 :-( –

0

Utilizo un método de fábrica que agrega más lógica después de que se ha deserializado el objeto estructurado XML. Dicha lógica incluye restaurar la relación interna (hijo-padre, hermano ...) entre los miembros del objeto.

3

La solución aceptada no funcionó para mí. El método Deserialize() reemplazado nunca se llamó. Creo que esto se debe a que ese método no es público y, por lo tanto, lo llaman uno (o más) de los métodos públicos Deserialize(), pero no todos.

Aquí hay una implementación que funciona mediante el método de ocultación y hace uso de la interfaz existente IDeserializationCallback por lo que cualquier deserialización utilizando métodos que no sean xml aún puede desencadenar el método OnDeserialization() de esa interfaz. También utiliza la reflexión para recorrer propiedades secundarias para ver si también implementan IDeserializationCallback y las llama en consecuencia.

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.IO; 
using System.Reflection; 
using System.Runtime.Serialization; 
using System.Xml; 
using System.Xml.Serialization; 

namespace Xml.Serialization 
{ 
    class XmlCallbackSerializer : XmlSerializer 
    { 
     public XmlCallbackSerializer(Type type) : base(type) 
     { 
     } 

     public XmlCallbackSerializer(XmlTypeMapping xmlTypeMapping) : base(xmlTypeMapping) 
     { 
     } 

     public XmlCallbackSerializer(Type type, string defaultNamespace) : base(type, defaultNamespace) 
     { 
     } 

     public XmlCallbackSerializer(Type type, Type[] extraTypes) : base(type, extraTypes) 
     { 
     } 

     public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides) : base(type, overrides) 
     { 
     } 

     public XmlCallbackSerializer(Type type, XmlRootAttribute root) : base(type, root) 
     { 
     } 

     public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, 
      XmlRootAttribute root, string defaultNamespace) : base(type, overrides, extraTypes, root, defaultNamespace) 
     { 
     } 

     public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, 
      XmlRootAttribute root, string defaultNamespace, string location) 
      : base(type, overrides, extraTypes, root, defaultNamespace, location) 
     { 
     } 

     public new object Deserialize(Stream stream) 
     { 
      var result = base.Deserialize(stream); 

      CheckForDeserializationCallbacks(result); 

      return result; 
     } 

     public new object Deserialize(TextReader textReader) 
     { 
      var result = base.Deserialize(textReader); 

      CheckForDeserializationCallbacks(result); 

      return result; 
     } 

     public new object Deserialize(XmlReader xmlReader) 
     { 
      var result = base.Deserialize(xmlReader); 

      CheckForDeserializationCallbacks(result); 

      return result; 
     } 

     public new object Deserialize(XmlSerializationReader reader) 
     { 
      var result = base.Deserialize(reader); 

      CheckForDeserializationCallbacks(result); 

      return result; 
     } 

     public new object Deserialize(XmlReader xmlReader, string encodingStyle) 
     { 
      var result = base.Deserialize(xmlReader, encodingStyle); 

      CheckForDeserializationCallbacks(result); 

      return result; 
     } 

     public new object Deserialize(XmlReader xmlReader, XmlDeserializationEvents events) 
     { 
      var result = base.Deserialize(xmlReader, events); 

      CheckForDeserializationCallbacks(result); 

      return result; 
     } 

     public new object Deserialize(XmlReader xmlReader, string encodingStyle, XmlDeserializationEvents events) 
     { 
      var result = base.Deserialize(xmlReader, encodingStyle, events); 

      CheckForDeserializationCallbacks(result); 

      return result; 
     } 

     private void CheckForDeserializationCallbacks(object deserializedObject) 
     { 
      var deserializationCallback = deserializedObject as IDeserializationCallback; 

      if (deserializationCallback != null) 
      { 
       deserializationCallback.OnDeserialization(this); 
      } 

      var properties = deserializedObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); 

      foreach (var propertyInfo in properties) 
      { 
       if (propertyInfo.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null) 
       { 
        var collection = propertyInfo.GetValue(deserializedObject) as IEnumerable; 

        if (collection != null) 
        { 
         foreach (var item in collection) 
         { 
          CheckForDeserializationCallbacks(item); 
         } 
        } 
       } 
       else 
       { 
        CheckForDeserializationCallbacks(propertyInfo.GetValue(deserializedObject)); 
       } 
      } 
     } 
    } 
} 
+0

¿Funciona recursivamente para los objetos de propiedades anidadas? –

+1

Debería, sí. Ver el método 'CheckForDeserializationCallbacks()' para las llamadas recursivas para verificar objetos de propiedad. – HotN

1

Después de pasar algún tiempo con la primera respuesta, adopté código de puesto de HotN, a excepción de CheckForDeserializationCallbacks:

private static void ProcessOnDeserialize(object _result) { 
    var type = _result != null ? _result.GetType() : null; 
    var methods = type != null ? type.GetMethods().Where(_ => _.GetCustomAttributes(true).Any(_m => _m is OnDeserializedAttribute)) : null; 
    if (methods != null) { 
    foreach (var mi in methods) { 
     mi.Invoke(_result, null); 
    } 
    } 
    var properties = type != null ? type.GetProperties().Where(_ => _.GetCustomAttributes(true).Any(_m => _m is XmlElementAttribute || _m is XmlAttributeAttribute)) : null; 
    if (properties != null) { 
    foreach (var prop in properties) { 
     var obj = prop.GetValue(_result, null); 
     var enumeration = obj as IEnumerable; 
     if (obj is IEnumerable) { 
     foreach (var item in enumeration) { 
      ProcessOnDeserialize(item); 
     } 
     } else { 
     ProcessOnDeserialize(obj); 
     } 
    } 
    } 
} 

Esto permite el uso de la norma [OnDeserialized].

UPD. Puesto actualizado para caminata recursiva en el árbol de objetos.

0

En mi caso se trataba de una colección de objetos, por lo que utiliza una solución exceptuado tener que modificar un poco

private static void PostDeserializedProcess<T>(T deserializedObj) 
    { 
     var deserializedCallback = deserializedObj as IXmlPostDeserializationCallback; 
     if (deserializedCallback != null) 
     { 
      deserializedCallback.OnXmlDeserialized(deserializedObj); 
     } 
     else 
     { 
      // it could be a List of objects 
      // and we need to check for every object in the list 
      var collection = deserializedObj as System.Collections.IEnumerable; 
      if (collection != null) 
      { 
       foreach (var item in collection) 
       { 
        PostDeserializedProcess(item); 
       } 
      } 
     } 
    } 

Y entonces todo está funcionando perfectamente

0

me ha costado un poco así conseguir lo anterior soluciones para trabajar Encontré la solución más simple para que mis callbacks OnDeserialization() se activaran mientras usaba XmlSerializer para luego encadenar una llamada a BinaryFormatter. Mi clase ya tenía un método GetClone() así que era bastante sencillo y negado todos mis intentos de anular XmlSerializer

public static Foo Deserialize(string path) { 
    Foo foo; 
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(Foo)); 
    using (StreamReader textReader = new StreamReader(path)) { 
     foo = (Foo)xmlSerializer.Deserialize(textReader); // this does NOT fire the OnDeserialization callbacks 
    } 
    return foo.GetClone(); 
} 

public Foo GetClone() { 
    using (var ms = new MemoryStream()) { 
     var formatter = new BinaryFormatter(); 
     formatter.Serialize(ms, this); 
     ms.Position = 0; 
     return (Foo)formatter.Deserialize(ms); // this DOES fire the OnDeserialization callbacks 
    } 
} 
Cuestiones relacionadas