Десериализация XML с помощью Servicestack.Text

Я изучаю библиотеку Servicestack.Text, поскольку она обладает одними из лучших функций. десериализовать XML в один из моих DTO, как показано ниже;

Код C#: [соответствующий код с консольным приложением здесь]

class Program
    {
        static void Main(string[] args)
        {
            string str = "http://static.cricinfo.com/rss/livescores.xml";
            WebClient w = new WebClient();
            string xml = w.DownloadString(str);
            Response rss = xml.FromXml<Response>();
            foreach (var item in rss.rss.Channel.item)
            {
                Console.WriteLine(item.title);
            }
            Console.Read();
        }
    }

Вы можете просмотреть файл XML по адресу str[Дано в программе]. Я подготовил DTO для десериализации. Они такие, как показано ниже:

public  class Response
{
   public RSS rss { get; set; }
}

public class RSS
{
   public string Version { get; set; }
   public ChannelClass Channel { get; set; }
}

public class ChannelClass
{
   public string title { get; set; }
   public string ttl { get; set; }
   public string description { get; set; }
   public string link { get; set; }
   public string copyright { get; set; }
   public string language { get; set; }
   public string pubDate { get; set; }
   public List<ItemClass> item { get; set; }
}

public class ItemClass
{
   public string title { get; set; }
   public string link { get; set; }
   public string description { get; set; }
   public string guid { get; set; }
}

Когда я запускаю программу, я получаю исключение, как показано ниже:

введите здесь описание изображения

Итак, чтобы изменить Element и namespace, я сделал следующий обходной путь:

Я поместил DataContractAttribute в свой класс Response, как показано ниже:

[DataContract(Namespace = "")]
public  class Response
{
   public RSS rss { get; set; }
}

Я изменил имя Element, как показано ниже, добавив следующие две строки непосредственно перед десериализацией

  //To change rss Element to Response as in Exception
  xml = xml.Replace("<rss version=\"2.0\">","<Response>");
  //For closing tag
  xml = xml.Replace("</rss>","</Response>");

Но это дало другое исключение в цикле foreach, поскольку десериализованный объект rss был null. Итак, как мне правильно десериализовать его, используя Servicestack.Text?

Примечание:

Я хорошо знаю, как десериализовать с другими библиотеками, хочу сделать это только с ServiceStack.


person Bhushan Firake    schedule 18.02.2013    source источник


Ответы (2)


TLDR: используйте XmlSerializer для десериализации из диалектов xml, которые вы не можете контролировать; ServiceStack предназначен для разработки в первую очередь кода и не может быть адаптирован для синтаксического анализа xml общего назначения.


ServiceStack.Text не реализует пользовательский сериализатор Xml — он использует DataContractSerializer под капотом. FromXml - это просто синтаксический сахар.

Использование DataContractSerializer для разбора Xml

Как вы заметили, DataContractSerializer требователен к пространствам имен. Один из подходов состоит в том, чтобы явно указать пространство имен в классе, но если вы сделаете это, вам нужно будет указать [DataMember] везде, поскольку предполагается, что если что-то явно, то все. Вы можете обойти эту проблему, используя атрибут уровня сборки (например, в AssemblyInfo.cs), чтобы объявить пространство имен по умолчанию:

[assembly: ContractNamespace("", ClrNamespace = "My.Namespace.Here")]

Это решает проблему с пространством имен.

Однако вы не можете решить 2 другие проблемы с DataContractSerializer:

  • Он не будет использовать атрибуты (в вашем случае version)
  • Для этого требуется, чтобы такие коллекции, как item, имели как имя упаковки, так и имя элемента (что-то вроде элементов и элементов).

Вы не можете обойти эти ограничения, поскольку DataContractSerializer не является синтаксическим анализатором XML общего назначения. Он предназначен для простого создания и использования API, а не для сопоставления произвольного XML со структурой данных .NET. У вас никогда не получится разобрать rss; поэтому ServiceStack.Text (который просто обертывает его) также не может его анализировать.

Вместо этого используйте XmlSerializer.

Использование XmlSerializer

Это довольно просто. Вы можете проанализировать ввод с помощью чего-то вроде:

var serializer = new XmlSerializer(typeof(RSS));
RSS rss = (RSS)serializer.Deserialize(myXmlReaderHere);

Хитрость заключается в том, чтобы аннотировать различные поля, чтобы они соответствовали вашему диалекту xml. Например, в вашем случае это будет:

[XmlRoot("rss")]
public class RSS
{
    [XmlAttribute]
    public string version { get; set; }
    public ChannelClass channel { get; set; }
}

public class ChannelClass
{
    public string title { get; set; }
    public string ttl { get; set; }
    public string description { get; set; }
    public string link { get; set; }
    public string copyright { get; set; }
    public string language { get; set; }
    public string pubDate { get; set; }
    [XmlElement]
    public List<ItemClass> item { get; set; }
}

public class ItemClass
{
    public string title { get; set; }
    public string link { get; set; }
    public string description { get; set; }
    public string guid { get; set; }
}

Таким образом, некоторых разумных атрибутов достаточно, чтобы заставить его анализировать XML так, как вы хотите.

Подводя итог: вы не можете использовать для этого ServiceStack, поскольку он использует DataContractSerializer.ServiceStack/DataContractSerializer предназначены для сценариев, в которых вы управляете схемой. Вместо этого используйте XmlSerializer.

person Eamon Nerbonne    schedule 21.02.2013
comment
Вы имеете в виду, что у меня нет возможности сделать это с servicestack? - person Bhushan Firake; 22.02.2013
comment
Вы не можете использовать для этого ServiceStack.Text. Но поймите, что в центре внимания сервисного стека находятся веб-сервисы; в частности услуги, которые вы предоставляете. В .net framework уже есть прекрасный сериализатор xml; он просто использует это. Использование сервисного стека только для его XML-сериализации мало что дает — вы можете реализовать FromXml<> всего в нескольких строках кода — это ненамного больше, чем new DataContractSerializer(typeof(T)).ReadObject(reader). Так что это хорошо, если вы все равно используете сервисный стек, но вряд ли это критическая функция. - person Eamon Nerbonne; 23.02.2013
comment
Однако ServiceStack.Text может сделать это с помощью JSON — и довольно легко. - person Dan Esparza; 25.11.2013
comment
@DanEsparza ... конечно, но RSS - это не JSON? - person Eamon Nerbonne; 26.11.2013
comment
@EamonNerbonne застенчивая ухмылка ... это научит меня обращать внимание - person Dan Esparza; 26.11.2013

Несколько вещей:

  1. Поскольку вы используете атрибут [DataContract]. Вы должны включить свойства DTO с атрибутом [DataMember], иначе они будут пропущены в процессе сериализации/десериализации. Используйте атрибут сборки, как указано в Десериализация XML работает только с пространством имен в XML

  2. Ваши манипуляции с xml должны измениться, чтобы вместо этого обернуть rss внутри ответа или заменить его.

    xml = xml.Replace("<rss version=\"2.0\">", "<Response><rss version=\"2.0\">");

  3. Я бы порекомендовал самостоятельно создать объект тестового ответа, сериализовать его в XML с помощью метода ServiceStack .ToXml(), чтобы увидеть ожидаемый формат. Вы увидите, что стек служб обрабатывает элементы канала как дочерний список элементов, а не так, как RSS форматирует элементы канала. Вам нужно будет обернуть все свои элементы в узел с именем <ItemClass>.

person kampsj    schedule 18.02.2013
comment
1. Попробовал, результат тот же 2. Я не понял 3. Предложенная вами структура такая же, как я указал в вопросе, не так ли? - person Bhushan Firake; 18.02.2013
comment
Убедитесь, что в ваших свойствах указан правильный регистр. Вам нужно будет изменить канал на канал. Также см. эту суть gist.github.com/jokecamp/4979703, чтобы узнать, как ServiceStack будет сериализовать ваши DTO. при использовании сборки: атрибут ContractNamespace. - person kampsj; 18.02.2013
comment
Если я ошибаюсь с каким-то регистром, то они должны быть только нулевыми, верно? Я получаю весь объект rss нулевым. - person Bhushan Firake; 18.02.2013
comment
Я обновил суть, добавив тестовый код gist.github.com/jokecamp/4979703. Сравните со своим и запустите мой. - person kampsj; 18.02.2013
comment
Спасибо, но это не работает. Он не десериализует его должным образом, он дает только нулевые значения версии и канала. - person Bhushan Firake; 18.02.2013