2017-08-25 4 views
2

Je convertis ma sérialisation XML de travail afin que les classes de modèles héritent des classes de base abstraites (pour permettre l'utilisation future de différents formats de série). Mon serial-as fonctionne correctement, mais lorsque je passe à l'utilisation de modèles dérivés d'une classe de base, j'obtiens toutes sortes d'exceptions, donc je ne sais pas comment procéder.Comment utiliser la classe dérivée pour la sérialisation XML?

Ma classe de base de classe est:

namespace Data 
{ 
    public abstract class Configuration 
    { 
     public abstract string Schema { get; set; } 

     public abstract Command[] Commands { get; set; } 
    } 

    public abstract class Command 
    { 
     public abstract string Name { get; set; } 
    } 
} 

Ma classe concrète dérivée (la classe qui fonctionne très bien avant qu'il ne soit dérivé) est alors dans un espace de nom de l'enfant:

namespace Data.Xml 
{ 
    [Serializable()] 
    [XmlType(AnonymousType = true)] 
    [XmlRoot(Namespace = "", IsNullable = false)] 
    public class Configuration : Data.Configuration 
    { 
     [XmlAttribute("noNamespaceSchemaLocation", 
      Namespace = System.Xml.Schema.XmlSchema.InstanceNamespace)] 
     public override string Schema { get; set; } 

     [XmlArrayItem("Command", IsNullable = false)] 
     public override Data.Command[] Commands { get; set; } 
    } 

    [Serializable()] 
    public class Command : Data.Command 
    { 
     public override string Name { get; set; } 
    } 
} 

J'appelle le sérialiseur dans cet espace de noms enfant comme suit:

public override Data.Configuration DeserializeConfig(StreamReader config) 
{ 
    var cs = new XmlSerializer(typeof(Configuration), 
      new Type[] { typeof(Command) }); 
    return (Configuration)ConfigSerializer.Deserialize(ConfigStreamReader); 
} 

public override string SerializeConfig(Data.Configuration c, Encoding encoding) 
{ 
    string Output = null; 
    var Stream = new MemoryStream(); 
    using (var Writer = new XmlTextWriter(Stream, encoding)) 
    { 
     Writer.Formatting = Formatting.Indented; 
     XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); 
     ns.Add("xsi", XmlSchema.InstanceNamespace); 
     (new XmlSerializer(typeof(Configuration))).Serialize(Writer, c, ns); 
     Output = encoding.GetString(Stream.ToArray()); 
    } 
    Stream.Dispose(); 
    return Output; 
} 

Le XML résultant devrait ressembler :

<?xml version="1.0" encoding="utf-8"?> 
<Configuration 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="SomeSchema.xsd"> 
    <Commands> 
    <Command> 
     <Name>SomeNameValue</Name> 
    </Command> 
    </Commands> 
</Configuration> 

Je vois l'exception suivante lors d'une tentative d'instancier le sérialiseur (première ligne dans la méthode DeserializeConfig() ci-dessus):

InvalidOperationException: Types 'Data.Command' et « Data.Xml.Command 'les deux utilisent le nom de type XML,' Command ', à partir de l'espace de noms' '. Utilisez les attributs XML pour spécifier un nom XML unique et/ou un espace de noms pour le type.

Je ne suis pas vraiment sûr pourquoi le sérialiseur tente de créer quelque chose à partir de la classe de base, que les noms sont les mêmes, que ce genre de l'idée de dérivation et les espaces de noms, etc ... Comment puis-je bien marquer ceci avec des attributs pour l'avoir de/sérialiser correctement?

Pour votre information: J'ai vu plusieurs questions déjà à ce sujet, mais toutes les réponses semblaient assez spécifiques aux exigences de Askers que je ne pouvais pas travailler sur la façon d'appliquer les informations à ce, en apparence simple, scénario. J'ai découvert comment passer les types inclus dans le sérialiseur à l'instanciation au lieu d'avoir à annoter la classe de base. J'ai donc supprimé cette partie de ma question et mis à jour le code. Ceci surpasse la suggestion de bruno et ma réponse (bien que la question suggérée ne s'applique toujours pas).

Mise à jour: J'ai essayé de séparer les noms XML namespaces en ajoutant la classe dérivée à un espace de noms (par exemple l'ajout [XmlElement(Namespace = "http://www.foo.com/data/xml")] à chaque propriété dans la classe dérivée), mais cela n'a fait aucune différence que le sérialiseur semble encore « voir "à la fois la base et la classe dérivée ensemble et pense donc qu'ils sont tous les deux dans cet espace de noms.

+0

est-ce une question de double https://stackoverflow.com/questions/3326481/c -sharp-xml-serialization-of-classes-dérivées? –

+0

@ bruno.almeida, Non, j'ai déjà l'attribut 'XmlInclude' appliqué. – Toby

+0

Ce sont vos noms de classe qui perturbent le compilateur. La bibliothèque Net a System.Net.Data et System.Net.Xml. Les noms de classe que vous utilisez dupliquent les noms Net Library. Donc changez les noms ou utilisez des noms complets comme MyProject.Data.Xml. – jdweng

Répondre

1

Enfin flipping figuré la plupart de cette out. Je me suis reculé et j'ai commencé par un exemple de travail non dérivé très simple et je me suis occupé de ce dont j'avais besoin.

Il y avait deux choses qui se passaient ici. D'abord, les noms des types de conflit, puis les noms des propriétés en conflit. Alors que j'avais des bits de chacun de ces droits, le nombre de permutations d'options pour structurer chacune d'elles en les combinant ensemble m'avait confondu.

Pour éviter que les noms de types abstrait et dérivé ne s'affichent lors de la sérialisation, je devais rendre le type de classe dérivé anonyme (ici en utilisant l'attribut XmlType).

Pour arrêter les noms de propriété heurtant je avais besoin d'ignorer les deux la propriété dans la classe dérivée et la classe de base. Pour ce faire, sans modifier la classe de base, il me manquait un morceau essentiel, XmlAttributeOverrides. J'avais vu cela mentionné dans la documentation de MSDN pour XmlSerializer.Serialize() mais les informations étaient assez minimes pour expliquer ce à quoi il appartenait. This answer à une autre question m'a conduit à excellent explanation de David Woodward.

Je n'ai pas encore essayé tout cela avec une propriété de liste de type dérivée, ou avec la désérialisation.

Ci-dessous est exemple de base complète d'un programme qui génère une chaîne avec du XML sérialisé sur la sortie de la console:

using System; 
using System.Text; 
using System.IO; 
using System.Xml; 
using System.Xml.Serialization; 

namespace Test 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var TestBar = new MyXml.Bar() 
      { 
       Name = "John Smith", 
      }; 

      Serializer s = new MyXml.Serializer(); 
      var TestOutput = s.Serialize(TestBar); 
      Console.WriteLine(TestOutput); 
     } 
    } 

    public abstract class Bar 
    { 
     public abstract string Name { get; set; } 
    } 

    public abstract class Serializer 
    { 
     public abstract string Serialize(Bar bar); 
    } 

    namespace MyXml 
    { 
     public class Serializer : Test.Serializer 
     { 
      public override string Serialize(Test.Bar bar) 
      { 
       string Output = null; 
       var Stream = new MemoryStream(); 
       var Encoding = new UTF8Encoding(false, true); 

       // Ignore the Name property in the *base* class! 
       var ao = new XmlAttributeOverrides(); 
       var a = new XmlAttributes(); 
       a.XmlElements.Clear(); // Clear any element attributes 
       a.XmlAttribute = null; // Remove any attribute attributes 
       a.XmlIgnore = true; // Set the ignore attribute value true 
       ao.Add(typeof(Test.Bar), "Name", a); // Set to use with Test.Bar.Name 

       using (var Writer = new XmlTextWriter(Stream, Encoding)) 
       { 
        Writer.Formatting = Formatting.Indented; 
        var s = new XmlSerializer(typeof(Bar), ao); 
        s.Serialize(Writer, bar); 
        Output = Encoding.GetString(Stream.ToArray()); 
       } 
       Stream.Dispose(); 
       return Output; 
      } 
     } 

     [Serializable] 
     [XmlType(AnonymousType = true)] // Make type anonymous! 
     [XmlRoot(IsNullable = false)] 
     public class Bar : Test.Bar 
     { 
      [XmlIgnore] // Ignore the Name property in the *derived* class! 
      public override string Name 
      { 
       get => Unreverse(ReverseName); 
       set => ReverseName = Reverse(value); 
      } 

      [XmlElement("Name", IsNullable = false)] 
      public string ReverseName { get; set; } 

      private string Unreverse(string name) 
      { 
       return "John Smith"; // Smith, John -> John Smith 
      } 

      private string Reverse(string name) 
      { 
       return "Smith, John"; // John Smith -> Smith, John 
      } 
     } 
    } 
} 
+1

J'ai un modèle de classe simple qui a hérité d'un autre et recevait "à la fois utiliser le nom de type XML "erreur lors de la sérialisation xml. Ajouter "[XmlType (AnonymousType = true)]" à la classe héritée a résolu le problème. – Vlad