使用Servicestack.Text进行XML反序列化

9

我正在学习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();
        }
    }

您可以查看程序中提供的str的XML文件。我已经准备好了用于反序列化的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; }
}

当我运行程序时,会出现如下异常:

enter image description here

因此,为了更改Elementnamespace,我进行了以下的解决方法:
我在我的Response类上放置了DataContractAttribute,如下所示:
[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来完成它。

2个回答

24
TLDR:使用XmlSerializer从您无法控制的xml语言中反序列化;ServiceStack旨在进行代码优先开发,不能用于通用xml解析。
ServiceStack.Text没有实现自定义的Xml序列化器 - 它在底层使用DataContractSerializer。FromXml只是语法糖。
使用DataContractSerializer解析Xml
正如您所注意到的,DataContractSerializer对命名空间非常挑剔。一种方法是在类上明确指定命名空间,但如果这样做,您需要在每个DataMember上指定[DataMember],因为它假设如果有任何明确的内容,则所有内容都是明确的。您可以使用程序集级别的属性(例如在AssemblyInfo.cs中)来声明默认命名空间来解决此问题:
[assembly: ContractNamespace("", ClrNamespace = "My.Namespace.Here")]

这解决了命名空间问题。

然而,您无法使用DataContractSerializer解决另外两个问题:

  • 它不会使用属性(在您的情况下,version
  • 它要求集合(如item)既有包装名称又有元素名称(类似于items和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

你的意思是说我无法使用 servicestack 来完成这个任务吗? - Bhushan Firake
你不能使用ServiceStack.Text来完成这个任务。但是要意识到,ServiceStack的重点是Web服务;特别是你提供的服务。.NET框架已经有一个很好的XML序列化器了;它只是使用那个。仅仅为了使用ServiceStack的XML序列化而使用它并没有太多的意义——你可以用几行代码实现FromXml<>——它不过是new DataContractSerializer(typeof(T)).ReadObject(reader)。所以如果你已经在使用ServiceStack,那么拥有它是很好的,但它并不是一个关键的功能。 - Eamon Nerbonne
ServiceStack.Text 可以轻松地使用 JSON 实现这一点。 - Dan Esparza
@DanEsparza...当然,但是,RSS不是JSON? - Eamon Nerbonne
@EamonNerbonne 尴尬的微笑 ... 这会教会我注意了。 - Dan Esparza

0
一些事情:
1. 由于您正在使用[DataContract]属性,因此必须使用[DataMember]属性包含DTO的属性,否则它们将在序列化/反序列化过程中被跳过。按照XML反序列化仅适用于xml中的命名空间中指定的程序集属性使用。
2. 您的xml操作需要更改为将rss包装在响应中,而不是替换它。
xml = xml.Replace("<rss version=\"2.0\">", "<Response><rss version=\"2.0\">");
3. 我建议您自己构建一个测试响应对象,使用ServiceStack的.ToXml()方法将其序列化为XML,以查看它所期望的格式。您将看到服务堆栈将通道项处理为项目的子项列表,这不是RSS格式处理通道项的方式。您需要将所有项目包装到名为<ItemClass>的节点中。

  1. 我尝试了,结果还是一样。
  2. 我没能理解它。
  3. 你建议的结构和我在问题中提出的一样,不是吗?
- Bhushan Firake
请确保您的属性大小写正确。您需要将“Channel”更改为“channel”。此外,请参阅此Gist https://gist.github.com/jokecamp/4979703,了解在使用程序集:ContractNamespace属性时ServiceStack如何序列化您的DTOs。 - kampsj
如果我的大小写有误,那么它们应该只是空值,对吧?我得到的整个rss对象都是空值。 - Bhushan Firake
我更新了gist,包括一些测试代码https://gist.github.com/jokecamp/4979703。将其与您的进行比较并运行我的代码。 - kampsj
谢谢,但它没有起作用。它没有正确反序列化,只会给出版本和通道的空值。 - Bhushan Firake

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接