Десериализирайте XML документ в C# клас с множество основни (xmlns) пространства от имена

Опитвам се да десериализирам XML документ, с различни основни пространства от имена, до C# клас.

Накратко, искам да десериализирам множество версии на подобен xml документ така:

<IndexRoot Code="0664" xmlns="http://tempuri/2012/1.0">
    <Name>Foo</Name>
    <Color>blue</Color>
    ...
</IndexRoot>


<IndexRoot Code="0678" xmlns="http://tempuri/2012/2.0">
    <Name>Bar</Name>
    <Character>Smurf</Character>
</IndexRoot>

Всяка версия очевидно може да има различни елементи под нея и въпреки че повечето елементи са еднакви, има някои разлики. В примера по-горе атрибутът Name е наличен във всяка версия, докато Color/Character са уникални за всяка версия.

В идеалния случай искам да абстрахирам това до проста функция, която ми дава отразен конкретен клас. Така:

public IndexRoot Get(string fileName) {
    var doc = XDocument.Load(fileName);
    return xmlSerializer.Deserialize<IndexRoot>(doc);   
}

В текущата ми настройка това се проваля, защото се изисква да се предостави едно пространство от имена на основния елемент, за да може десериализаторът да работи:

[Serializable, XmlRoot(ElementName = "IndexRoot", Namespace = "http://tempuri/2012/2.0")]
public class IndexRoot
{
    [XmlAttribute("Code")]
    public string Code { get; set; }

    [XmlElement(ElementName = "Name")]
    public string Name { get; set; }
}

Както можете да видите, твърдо кодираното пространство от имена ще работи за версии 2.0, но ще се провали за други версии с изключение: "IndexRoot xmlns='http://tempuri/2012/1.0' не се очакваше.“

Въпросът: как мога да десериализирам XML в C# обект, като взема предвид множеството основни пространства от имена?

В идеалния случай това ще бъде отразено в конкретен тип за всяка версия. Но дори ще се задоволя с получаването на "базов клас" с общите, споделени свойства. Така или иначе, в момента съм заседнал с текущата твърдо кодирана стойност на пространството от имена на [XmlRoot].

Опитах:

  1. Добавяне на дублирани [XmlRoot] атрибути (което не се поддържа)
  2. Създайте базов клас (BaseIndexRoot), извличайки два екземпляра от него и украсявайки тези производни с атрибута [XmlRoot] (същата грешка „не се очаква“)
  3. Премахването на пространството от имена заедно също води до грешката „не се очакваше“.

person Juliën    schedule 06.05.2016    source източник


Отговори (2)


Обикновено го правя така

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;


namespace ConsoleApplication91
{
    class Program
    {
        static void Main(string[] args)
        {
            string xml = 
            "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
            "<IndexRoot Code=\"0664\" xmlns=\"http://tempuri/2012/1.0\">" +
               "<Name>Foo</Name>" +
               "<Color>blue</Color>" +
           "</IndexRoot>";

            XDocument doc = XDocument.Parse(xml);
            XElement indexRoot = (XElement)doc.FirstNode;
            XNamespace ns = indexRoot.Name.Namespace;

            string name = indexRoot.Element(ns + "Name").Value;

            XElement indexRoot2 = doc.Descendants().Where(x => x.Name.LocalName == "IndexRoot").FirstOrDefault();

        }


    }

}
person jdweng    schedule 06.05.2016

Успях да разреша проблема с десериализиране на XML документи със същата структура, но с различни пространства от имена, с конструкцията по-долу.

Първо, създадох производни класове за всяка конкретна версия и ги украсих с пространството от имена:

[Serializable, XmlRoot(ElementName = "IndexRoot", Namespace = "http://tempuri/2012/1.0")]
public class IndexRootV10 : IndexRoot { }

[Serializable, XmlRoot(ElementName = "IndexRoot", Namespace = "http://tempuri/2012/2.0")]
public class IndexRootV20 : IndexRoot { }

public class IndexRoot
{
    [XmlAttribute("Code")]
    public string Code { get; set; }

    [XmlElement(ElementName = "Code")]
    public string Code { get; set; }
}

Всичко, което трябваше да направя след това, беше просто да променя функцията за десериализиране, за да определя коя версия (производен клас) да приложа:

public IndexRoot Get(string fileName) {
    var doc = XDocument.Load(fileName);

    switch (doc.Root?.Name.NamespaceName)
    {
        case "http://tempuri/2012/1.0":
            response = xmlSerializer.Deserialize<IndexRootV10>(doc);
            break;
        case "http://tempuri/2012/2.0":
            response = xmlSerializer.Deserialize<IndexRootV20>(doc);
            break;
        default:
            throw new NotSupportedException($"Unsupported xmlns namespace (version): {doc.Root.Name.NamespaceName}");
    }
}

Въпреки че това е частта, от която съм най-малко доволен, поради твърдо кодирания оператор за превключване, той работи правилно. Някак си не мога да не мисля, че има по-сложни начини за решаване на това.

Положителната страна е, че ако конкретна версия има различни свойства, производните класове сега са идеално подходящи да отразяват това.

person Juliën    schedule 21.05.2016