Sérialisation XML de la propriété d’interface

Je voudrais sérialiser XML un object qui a (entre autres) une propriété de type IModelObject (qui est une interface).

public class Example { public IModelObject Model { get; set; } } 

Lorsque j’essaie de sérialiser un object de cette classe, je reçois l’erreur suivante:
Msgstr “” “Impossible de sérialiser le membre Example.Model de type Exemple car il s’agit d’une interface.”

Je comprends que le problème est qu’une interface ne peut pas être sérialisée. Cependant, le type d’ object de modèle concret est inconnu jusqu’au moment de l’exécution.

Remplacer l’interface IModelObject par un type abstrait ou concret et utiliser l’inheritance avec XMLInclude est possible, mais semble être une solution de contournement laide.

Aucune suggestion?

Ceci est simplement une limitation inhérente à la sérialisation déclarative où les informations de type ne sont pas incorporées dans la sortie.

En essayant de convertir dans

 public class Flibble { public object Foo { get; set; } } 

Comment le sérialiseur sait-il s’il doit s’agir d’un int, d’une chaîne, d’un double (ou de quelque chose d’autre) …

Pour que cela fonctionne, vous avez plusieurs options, mais si vous ne le savez vraiment pas, le moyen le plus simple d’y parvenir est probablement d’utiliser XmlAtsortingbuteOverrides .

Malheureusement, cela ne fonctionnera qu’avec les classes de base, pas les interfaces. Le mieux que vous puissiez faire est d’ignorer la propriété qui ne suffit pas à vos besoins.

Si vous devez vraiment restr avec des interfaces, vous avez trois options réelles:

Cachez-le et traitez-le dans une autre propriété

Moche, plaque de chaudière déplaisante et beaucoup de répétition mais la plupart des consommateurs de la classe n’auront pas à faire face au problème:

 [XmlIgnore()] public object Foo { get; set; } [XmlElement("Foo")] [EditorVisibile(EditorVisibility.Advanced)] public ssortingng FooSerialized { get { /* code here to convert any type in Foo to ssortingng */ } set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } } 

Cela risque de devenir un cauchemar de maintenance …

Implémenter IXmlSerializable

Semblable à la première option en ce sens que vous prenez le contrôle total des choses mais

  • Avantages
    • Vous n’avez pas de mauvaises propriétés «fausses» qui traînent.
    • vous pouvez interagir directement avec la structure xml en ajoutant de la flexibilité / gestion des versions
  • Les inconvénients
    • vous devrez peut-être réimplémenter la roue pour toutes les autres propriétés de la classe

Les problèmes de duplication des efforts sont similaires au premier.

Modifier votre propriété pour utiliser un type d’emballage

 public sealed class XmlAnything : IXmlSerializable { public XmlAnything() {} public XmlAnything(T t) { this.Value = t;} public T Value {get; set;} public void WriteXml (XmlWriter writer) { if (Value == null) { writer.WriteAtsortingbuteSsortingng("type", "null"); return; } Type type = this.Value.GetType(); XmlSerializer serializer = new XmlSerializer(type); writer.WriteAtsortingbuteSsortingng("type", type.AssemblyQualifiedName); serializer.Serialize(writer, this.Value); } public void ReadXml(XmlReader reader) { if(!reader.HasAtsortingbutes) throw new FormatException("expected a type atsortingbute!"); ssortingng type = reader.GetAtsortingbute("type"); reader.Read(); // consume the value if (type == "null") return;// leave T at default value XmlSerializer serializer = new XmlSerializer(Type.GetType(type)); this.Value = (T)serializer.Deserialize(reader); reader.ReadEndElement(); } public XmlSchema GetSchema() { return(null); } } 

Utiliser ceci impliquerait quelque chose comme (dans le projet P):

 public namespace P { public interface IFoo {} public class RealFoo : IFoo { public int X; } public class OtherFoo : IFoo { public double X; } public class Flibble { public XmlAnything Foo; } public static void Main(ssortingng[] args) { var x = new Flibble(); x.Foo = new XmlAnything(new RealFoo()); var s = new XmlSerializer(typeof(Flibble)); var sw = new SsortingngWriter(); s.Serialize(sw, x); Console.WriteLine(sw); } } 

qui vous donne:

     0    

Ceci est évidemment plus lourd pour les utilisateurs de la classe mais évite beaucoup de chaudière.

Un média heureux fusionne peut-être l’idée XmlAnything dans la propriété “backing” de la première technique. De cette façon, l’essentiel du travail est fait pour vous, mais les consommateurs de la classe ne subissent aucun impact au-delà de la confusion avec l’introspection.

La solution consiste à utiliser la reflection avec DataContractSerializer. Vous n’avez même pas besoin de marquer votre classe avec [DataContract] ou [DataMember]. Il sérialisera n’importe quel object, qu’il possède ou non des propriétés de type d’interface (y compris des dictionnaires) dans XML. Voici une méthode d’extension simple qui sérialisera n’importe quel object en XML, même s’il possède des interfaces (notez que vous pourriez modifier ceci pour qu’il s’exécute également de manière récursive).

  public static XElement ToXML(this object o) { Type t = o.GetType(); Type[] extraTypes = t.GetProperties() .Where(p => p.PropertyType.IsInterface) .Select(p => p.GetValue(o, null).GetType()) .ToArray(); DataContractSerializer serializer = new DataContractSerializer(t, extraTypes); SsortingngWriter sw = new SsortingngWriter(); XmlTextWriter xw = new XmlTextWriter(sw); serializer.WriteObject(xw, o); return XElement.Parse(sw.ToSsortingng()); } 

ce que fait l’expression LINQ, c’est qu’elle énumère chaque propriété, retourne chaque propriété qui est une interface, obtient la valeur de cette propriété (l’object sous-jacent), obtient le type de cet object concret et l’ajoute au sérialiseur. liste des types connus.

Maintenant, le sérialiseur sait à propos des types qu’il sérialise pour qu’il puisse faire son travail.

Vous pouvez utiliser ExtendedXmlSerializer . Ce sérialiseur prend en charge la sérialisation de la propriété d’interface sans aucune astuce.

 var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create(); var obj = new Example { Model = new Model { Name = "name" } }; var xml = serializer.Serialize(obj); 

Votre xml ressemblera à:

    name   

ExtendedXmlSerializer prend en charge .net 4.5 et .net Core.

Si vous connaissez vos implémenteurs d’interface, il existe un hack assez simple que vous pouvez utiliser pour que votre type d’interface se sérialise sans écrire de code d’parsing:

 public interface IInterface {} public class KnownImplementor01 : IInterface {} public class KnownImplementor02 : IInterface {} public class KnownImplementor03 : IInterface {} public class ToSerialize { [XmlIgnore] public IInterface InterfaceProperty { get; set; } [XmlArray("interface")] [XmlArrayItem("ofTypeKnownImplementor01", typeof(KnownImplementor01)] [XmlArrayItem("ofTypeKnownImplementor02", typeof(KnownImplementor02)] [XmlArrayItem("ofTypeKnownImplementor03", typeof(KnownImplementor03)] public object[] InterfacePropertySerialization { get { return new[] { InterfaceProperty } } set { InterfaceProperty = (IInterface)value.Single(); } } } 

Le xml résultant devrait ressembler à

   

Remplacer l’interface IModelObject par un type abstrait ou concret et utiliser l’inheritance avec XMLInclude est possible, mais semble être une solution de contournement laide.

S’il est possible d’utiliser une base abstraite, je recommanderais cette route. Ce sera toujours plus propre que d’utiliser la sérialisation à la main. Le seul problème que je vois avec la base abstraite est que vous allez toujours avoir besoin du type concret? Au moins, c’est comme ça que je l’ai utilisé dans le passé, quelque chose comme:

 public abstract class IHaveSomething { public abstract ssortingng Something { get; set; } } public class MySomething : IHaveSomething { ssortingng _sometext; public override ssortingng Something { get { return _sometext; } set { _sometext = value; } } } [XmlRoot("abc")] public class seriaized { [XmlElement("item", typeof(MySomething))] public IHaveSomething data; } 

Malheureusement, il n’y a pas de réponse simple, car le sérialiseur ne sait pas quoi sérialiser pour une interface. J’ai trouvé une explication plus complète sur la façon de contourner ce problème sur MSDN

dans mon projet, j’ai
List FormatStyleTemplates;
contenant différents types.

J’utilise ensuite la solution ‘XmlAnything’ ci-dessus pour sérialiser cette liste de différents types. Le xml généré est magnifique.

  [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlArray("FormatStyleTemplates")] [XmlArrayItem("FormatStyle")] public XmlAnything[] FormatStyleTemplatesXML { get { return FormatStyleTemplates.Select(t => new XmlAnything(t)).ToArray(); } set { // read the values back into some new object or whatever m_FormatStyleTemplates = new FormatStyleProvider(null, true); value.ForEach(t => m_FormatStyleTemplates.Add(t.Value)); } } 

Malheureusement pour moi, j’ai eu un cas où la classe à sérialiser avait des propriétés qui avaient aussi des interfaces en tant que propriétés, donc j’avais besoin de traiter récursivement chaque propriété. De plus, certaines propriétés d’interface étaient marquées comme [XmlIgnore], donc je voulais les ignorer. J’ai pris des idées que j’ai trouvées sur ce sujet et y ai ajouté quelques éléments pour le rendre récursif. Seul le code de désérialisation est affiché ici:

 void main() { var serializer = GetDataContractSerializer(); using (FileStream stream = new FileStream(xmlPath, FileMode.Open)) { XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas()); var obj = (MyObjectWithCascadingInterfaces)serializer.ReadObject(reader); // your code here } } DataContractSerializer GetDataContractSerializer() where T : new() { Type[] types = GetTypesForInterfaces(); // Filter out duplicates Type[] result = types.ToList().Distinct().ToList().ToArray(); var obj = new T(); return new DataContractSerializer(obj.GetType(), types); } Type[] GetTypesForInterfaces() where T : new() { return GetTypesForInterfaces(typeof(T)); } Type[] GetTypesForInterfaces(Type T) { Type[] result = new Type[0]; var obj = Activator.CreateInstance(T); // get the type for all interface properties that are not marked as "XmlIgnore" Type[] types = T.GetProperties() .Where(p => p.PropertyType.IsInterface && !p.GetCustomAtsortingbutes(typeof(System.Xml.Serialization.XmlIgnoreAtsortingbute), false).Any()) .Select(p => p.GetValue(obj, null).GetType()) .ToArray(); result = result.ToList().Concat(types.ToList()).ToArray(); // do the same for each of the types identified foreach (Type t in types) { Type[] embeddedTypes = GetTypesForInterfaces(t); result = result.ToList().Concat(embeddedTypes.ToList()).ToArray(); } return result; } 

J’ai trouvé une solution plus simple (vous n’avez pas besoin du DataContractSerializer), grâce à ce blog ici: sérialisation XML des types dérivés lorsque le type de base est dans un autre espace de noms ou DLL

Mais 2 problèmes peuvent survenir dans cette mise en œuvre:

(1) Que se passe-t-il si DerivedBase ne se trouve pas dans l’espace de noms de la classe Base, ou même pire dans un projet dépendant de l’espace de noms Base, de sorte que BaseInclude DerivedBase

(2) Que se passe-t-il si nous avons seulement la classe Base en tant que dll, alors encore une fois, Base ne peut pas inclure XMLInclu DerivedBase

Jusqu’à maintenant, …

La solution aux deux problèmes consiste donc à utiliser le constructeur XmlSerializer (Type, array []) :

 XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)}); 

Un exemple détaillé est fourni ici sur MSDN: Constructeur XmlSerializer (Type, extraTypesArray [])

Il me semble que pour les XML DataContracts ou Soap, vous devez vérifier le XmlRoot comme mentionné ici dans cette question SO .

Une réponse similaire est ici sur SO mais elle n’est pas marquée comme telle, car le PO ne semble pas l’avoir déjà considéré.