2009-08-12 3 views
14

Ich versuche, einen Baum von Objekten über XML-Serialisierung zu laden, und im Moment wird es die Objekte laden und den Baum ganz glücklich erstellen. Mein Problem dreht sich um die Tatsache, dass diese Klassen eine Ebene des Auditings unterstützen. Was ich gerne tun könnte, ist eine Methode für jedes Objekt aufzurufen, nachdem es geladen wurde.Wie finden Sie heraus, wenn Sie über XML-Serialisierung geladen wurden?

Aus Gründen der Argumentation, geht ich davon aus einem sehr allgemeinen Objektbaum habe mit Klassen auf verschiedene Ebenen unterscheiden, wie:

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

Gibt es eine Möglichkeit, die Standard-Serializer mit (In ähnlicher Weise, dass Sie kann Methoden wie ShouldSerializeFoo) erstellen, um zu bestimmen, wann das Laden für jedes Objekt beendet wurde?

Edit: ich, dass der offensichtliche Fall darauf hinweisen, sollte so etwas wie ein OnLoaded() Verfahren auszusetzen, die ich Anruf nach deserialising konnte, mich schlägt als eine „schlechte Sache zu tun“ zu sein.

Edit2: Aus Gründen der Diskussion dieses mein Strom ist Hack „Ansatz“, die für die Grundstufe arbeitet, aber das Kind Stadt Knoten immer noch denkt, es mit Änderungen gespeichert werden muss (in der realen Welt das Objektmodell ist viel komplexer, aber das wird zumindest kompilieren, ohne die Notwendigkeit einer vollständigen Quelle)

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

Nicht eine wirkliche Antwort, aber warum können Sie die Prüfung in der Eigenschaft get-Accessor nicht tun? – weiqure

+0

Das Auditing befindet sich in der Eigenschaftengruppe, so dass es intern einen Datensatz speichert, der seit dem Laden geändert wurde (und daher gespeichert werden muss. Dies würde dann bedeuten, dass die Werte von ihren Standardwerten auf die tatsächlichen Werte geändert wurden). –

+0

Entschuldigung, ich wollte Satz schreiben. – weiqure

Antwort

14

Hmmm ... es ist immer noch nicht schön, aber Sie könnten Ihre Deserialisierungslogik in eine dedizierte Klasse umgestalten, die das aus XML stammende deserialisierte Objekt benachrichtigen könnte, bevor es es an den Aufrufer zurücksendet.

Update: Ich denke, das sollte ziemlich einfach zu tun sein, ohne zu weit von den durch das Framework gelegten Mustern zu abweichen ... Sie müssten nur sicherstellen, dass Sie den CustomXmlSerializer verwenden. Klassen, die diese Benachrichtigung müssen nur implementieren müssen 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

Ich denke, diese Option macht am meisten Sinn. Verarbeiten Sie die Objekte nach der Deserialisierung nach. – mackenir

+0

Spielend damit Ich denke immer noch, es ist ein wenig cludgy, aber wenn die Klasse, die die Deserialisierung behandelt, Generics verwendet und die deserialisierten Objekte über eine Schnittstelle benachrichtigt, dann ist es ein wenig besser und nicht schlecht, dagegen zu programmieren. – STW

+0

Das ist effektiv meine beste Option "im Moment". Ich kämpfe mit einer eleganten Option, um diese Nachricht in den Baum zu geben - eine 'OnLoaded()' Art von Methode scheint "falsch" –

1

A toughie, da XmlSerializer nicht Serialisierung Callback-Ereignisse unterstützt. Gibt es eine Möglichkeit, DataContractSerializer zu verwenden? Das tut, aber Attribute (wie @name oben) nicht zulassen.

Sonst; Sie könnten IXmlSerializable implementieren, aber das ist Lose der Arbeit, und sehr fehleranfällig.

Sonst - vielleicht Überprüfung des Aufrufers über den Stapel, aber das ist sehr spröde, und riecht reif.

2

ich die Lösung von abatishchev bereitgestellt versucht, aber wie durch die Kommentare unter seiner Antwort darauf hingewiesen, die Deserialize Methode in dem benutzerdefinierten Serializer scheint nie aufgerufen werden.

Ich war in der Lage, dies durch Überlastung aller verschiedenen Deserialize Überladungen, die ich benötigen würde, damit es immer die benutzerdefinierte Methode aufrufen würde.

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); 
    } 
} 

So sehe ich tatsächlich die Deserialize Methode aufgerufen werden.

+0

Leider funktioniert das auch nicht für mich, mit .Net 4.0 :-( –

0

Ich verwende eine Factory-Methode, die mehr Logik hinzufügt, nachdem das strukturierte XML-Objekt deserialisiert wurde. Eine solche Logik beinhaltet das Wiederherstellen der internen Beziehung (Kind-Eltern, Geschwister ..) zwischen Objektmitgliedern.

3

Die akzeptierte Lösung funktionierte nicht ganz für mich. Die überschriebene Deserialize() Methode wurde nie aufgerufen. Ich glaube, dies liegt daran, dass diese Methode nicht öffentlich ist und daher von einem (oder mehreren) der öffentlichen Methoden aufgerufen wird, aber nicht von allen.

Hier ist eine Implementierung, die durch das Verstecken von Methoden funktioniert und die vorhandene IDeserializationCallback Schnittstelle verwendet, so dass jede Deserialisierung mit Nicht-XML-Methoden immer noch die OnDeserialization() Methode dieser Schnittstelle auslösen kann. Es verwendet auch Reflection, um untergeordnete Eigenschaften zu durchlaufen, um zu sehen, ob sie auch IDeserializationCallback implementieren, und ruft sie entsprechend auf.

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

Funktioniert es rekursiv für verschachtelte Eigenschaften? –

+1

Es sollte, ja.Siehe die 'CheckForDeserializationCallbacks()' Methode für die rekursiven Aufrufe, um Eigenschaftenobjekte zu überprüfen. – HotN

1

Nachdem er einige Zeit mit der ersten Antwort zu verschwenden nahm ich Code aus HotN der Post, mit Ausnahme von 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); 
     } 
    } 
    } 
} 

Diese Verwendung von Standard-[OnDeserialized] ermöglicht.

UPD. Aktualisierter Beitrag für rekursiven Spaziergang auf Objektbaum.

0

In meinem Fall war es eine Sammlung von Objekten, so ein freigestelltes Lösung verwendet, die es zu modifizieren, um ein Bit

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); 
       } 
      } 
     } 
    } 

Und dann ist alles perfekt funktioniert

0

ich etwas als gut gekämpft immer die oben Lösungen zum Arbeiten. Ich fand die einfachste Lösung, um meine OnDeserialization() Callbacks zu feuern, während XmlSerializer war, einen Aufruf an BinaryFormatter danach zu verketten. Meine Klasse hatte bereits eine GetClone() Methode so ist es recht einfach war und negiert alle meine Versuche, zwingende 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 
    } 
} 
Verwandte Themen