2013-06-21 5 views
13

Wir verwenden XmlSerializer, und ich möchte benutzerdefinierte Serialisierung für bestimmte Klassen bereitstellen. Ich habe jedoch nicht immer die Möglichkeit, den Quellcode der betreffenden Klasse zu ändern, sonst könnte ich es einfach implementieren IXmlSerializable. Gibt es eine Möglichkeit, dies zu tun?Kann ich eine benutzerdefinierte Serialisierung für XmlSerializer bereitstellen, ohne IXmlSerializable zu implementieren?

+0

Ich weiß, dass Sie die Klassen mit verschiedenen XmlAttributes schmücken können (wie 'XmlElement',' XmlArray', etc), aber Sie fragen speziell darüber, wie die XML-Ausgabe von nicht-veränderbaren Klassen zu kontrollieren? –

+1

Ich verwende "Proxy" -Klassen, die IXmlSerializable mit dem angegebenen root-Tag-Namen implementieren und die ReadXml-Funktion verwenden, um den tatsächlichen zugrunde liegenden Wert zu erstellen, der dann in einer öffentlichen Eigenschaft verfügbar gemacht wird. – asawyer

+0

Nun, es gibt diese [Konstruktorüberladung] (http://msdn.microsoft.com/en-us/library/65k4wece.aspx), mit der Sie die für die Typen verwendeten Attribute überschreiben können, aber ich weiß nicht, ob es Sie erlaubt Ändern Sie das Verhalten/Ergebnis von 'IXmlSerializable.ReadXml'. EDIT: Können Sie die Serialisierungsprobleme zwischen Ihren Geschäftsobjekten und den Objekten von Drittanbietern trennen? –

Antwort

12

Hier ist ein einfaches Beispiel für die Proxy-deserialize Helfer:

eine Art gegeben, die wir nicht direkt Serialisierung auf Klassenebene steuern:

public sealed class Class //contrived example 
{ 
    public string Property {get;set;} 
} 

Und die xml wir mit deserialisieren müssen:

<Class> 
    <Property>Value</Property> 
</Class> 

Sie könnten einen Proxy-Typ manuell erstellen die Deserialisierung des Zieltyps zu verarbeiten wie so:

[XmlRoot("Class")] // <-- Very important 
public sealed class ClassSerializerProxy : IXmlSerializable 
{ 
    public Class ClassValue {get;set;} 

    public System.Xml.Schema.XmlSchema GetSchema(){return null;} 
    public void WriteXml(System.Xml.XmlWriter writer){} 

    public void ReadXml(System.Xml.XmlReader reader) 
    { 
     var x = XElement.ReadFrom(reader) as XElement; 
     this.ClassValue = new Class(); 
     //again this is a simple contrived example 
     this.ClassValue.Property = x.XPathSelectElement("Property").Value; 
    } 
} 

Verbrauch ist:

void Main() 
{ 
    // get the xml value somehow 
    var xdoc= XDocument.Parse(@"<Class><Property>Value</Property></Class>"); 

    // deserialize the xml into the proxy type 
    var proxy = Deserialize<ClassSerializerProxy>(xdoc); 

    // read the resulting value 
    var value = proxy.ClassValue; 
} 

public object Deserialize(XDocument xmlDocument, Type DeserializeToType) 
{ 
    XmlSerializer xmlSerializer = new XmlSerializer(DeserializeToType); 
    using (XmlReader reader = xmlDocument.CreateReader()) 
     return xmlSerializer.Deserialize(reader); 
} 

Jetzt in einigen Generika werfen und eine Erweiterungsmethode, und wir können für eine endgültige (AUSSER EXCEPTION HANDLING) Version Aufrufort up ein wenig reinigen:

Verbrauch:

void Main() 
{ 
    var xml = @"<Class><Property>Value</Property></Class>"; 

    var value = xml.DeserializeWithProxy<ClassSerializerProxy,Class>(); 

    value.Dump(); 
} 

Ihre Instance-Typ:

public sealed class Class 
{ 
    public string Property {get;set;} 
} 

Eine Schnittstelle, die Proxy-Typ

public interface ISerializerProxy<TInstanceType> where TInstanceType : class 
{ 
    TInstanceType Value { get; } 
} 

Das Beispiel Proxy implementiert nun die neue Schnittstelle

[XmlRoot("Class")] 
public sealed class ClassSerializerProxy : IXmlSerializable, ISerializerProxy<Class> 
{ 
    public Class Value {get;set;} 

    public System.Xml.Schema.XmlSchema GetSchema(){return null;} 
    public void WriteXml(System.Xml.XmlWriter writer){} 

    public void ReadXml(System.Xml.XmlReader reader) 
    { 
     var x = XElement.ReadFrom(reader) as XElement; 
     this.Value = new Class(); 
     this.Value.Property = x.XPathSelectElement("Property").Value; 
    } 
} 

Die Deserialisierung Methode ist jetzt eine Erweiterungsmethode auf string und kann mit jedem Proxy implementieren muss verwendet werden, Art.

public static class ExtensionMethods 
{ 
    public static TInstanceType DeserializeWithProxy<TProxyType,TInstanceType>(this string xml) 
     where TProxyType : ISerializerProxy<TInstanceType> 
     where TInstanceType : class 
    { 
     using (XmlReader reader = XDocument.Parse(xml).CreateReader()) 
     { 
      var xmlSerializer = new XmlSerializer(typeof(TProxyType)); 
      return (xmlSerializer.Deserialize(reader) as ISerializerProxy<TInstanceType>).Value; 
     } 
    } 
} 
+9

Gibt es eine Möglichkeit, diesen Ansatz zu verwenden, wenn ich eine große Objektstruktur habe und die Standardserialisierung für die Struktur als Ganzes, aber die benutzerdefinierte Serialisierung für bestimmte Blätter verwenden möchte? Wenn ich Zugriff auf die Quelle habe, kann ich diese Blätter dazu bringen, IXmlSerializable zu implementieren und benutzerdefinierte Serialisierung dorthin zu stellen. Aber wenn ich die Blätter nicht ändern kann, muss ich Serialisierungscode für den gesamten Baum schreiben? Oder kann ich die kleine Teilmenge der Problemklassen irgendwie gezielt ansprechen? – Rob

+1

@asawyer Dies ist eine großartige Antwort! Ich habe mich entschieden, auch die WriteXml-Funktionalität zu implementieren, da meine reale Klasse nicht selbst serialisierbar ist. Es besteht aus einer Reihe schreibgeschützter Zugriffsmethoden, die nur über den Konstruktor festgelegt werden können. –

+0

@natephette Ich bin so glücklich, dass ich helfen konnte! – asawyer