【问题标题】:How to deserialize Yahoo GeoPlanet REST XML automatically using WCF DataContracts如何使用 WCF DataContracts 自动反序列化 Yahoo GeoPlanet REST XML
【发布时间】:2011-12-22 18:09:53
【问题描述】:

我是 WCF 的新手。我能够成功地为 GeoNames 服务创建客户端,但现在我正在尝试为 Yahoo GeoPlanet 做同样的事情,我似乎无法让 XML 反序列化为我的 DataContract 类型。这样做的正确方法是什么?这是我正在使用的:

示例 REST 响应:

<places xmlns="http://where.yahooapis.com/v1/schema.rng" 
    xmlns:yahoo="http://www.yahooapis.com/v1/base.rng" 
    yahoo:start="0" yahoo:count="247" yahoo:total="247">
    <place yahoo:uri="http://where.yahooapis.com/v1/place/23424966" 
        xml:lang="en-US">
        <woeid>23424966</woeid>
        <placeTypeName code="12">Country</placeTypeName>
        <name>Sao Tome and Principe</name>
    </place>
    <place yahoo:uri="http://where.yahooapis.com/v1/place/23424824" 
        xml:lang="en-US">
        <woeid>23424824</woeid>
        <placeTypeName code="12">Country</placeTypeName>
        <name>Ghana</name>
    </place>
    ...
</places>

合约界面和客户端:

[ServiceContract]
public interface IConsumeGeoPlanet
{
    [OperationContract]
    [WebGet(
        UriTemplate = "countries?appid={appId}",
        ResponseFormat = WebMessageFormat.Xml,
        BodyStyle = WebMessageBodyStyle.Bare
    )]
    GeoPlanetResults<GeoPlanetPlace> Countries(string appId);
}

public sealed class GeoPlanetConsumer : ClientBase<IConsumeGeoPlanet>
{
    public GeoPlanetResults<GeoPlanetPlace> Countries(string appId)
    {
        return Channel.Countries(appId);
    }
}

反序列化类型:

[DataContract(Name = "places", 
    Namespace = "http://where.yahooapis.com/v1/schema.rng")]
public sealed class GeoPlanetResults<T> : IEnumerable<T>
{
    public List<T> Items { get; set; }

    public IEnumerator<T> GetEnumerator()
    {
        return Items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}


[DataContract]
public class GeoPlanetPlace
{
    [DataMember(Name = "woeid")]
    public int WoeId { get; set; }

    [DataMember(Name = "placeTypeName")]
    public string Type { get; set; }

    [DataMember(Name = "name")]
    public string Name { get; set; }

}

我知道这是错误的。在我的 geonames 客户端中,我的 GeoNamesResults 类有一个没有属性的 [DataContract] 属性,以及 Items 属性上的一个 [DataMember(Name = "geonames")] 属性。不过,这对 GeoPlanet 不起作用,我不断收到反序列化异常。我可以让Countries(appId) 方法毫无例外地执行的唯一方法是将Name 和Namespace 放在DataContract 属性中。但是,当我这样做时,我不知道如何将结果反序列化到 Items 集合中(它为空)。

我该怎么办?

【问题讨论】:

    标签: wcf rest xml-serialization wcf-client yahoo-api


    【解决方案1】:

    DataContractSerializer 不支持完整的 XML 规范,仅支持其中的一个子集。它不支持的是属性,它在您显示的示例响应中被广泛使用。在这种情况下,您需要使用XmlSerializer,并相应地定义类型(使用System.Xml.Serialization 中的属性,而不是System.Runtime.Serialization 中的属性)。下面的代码显示了如何检索您发布的示例 XML。

    public class StackOverflow_8022154
    {
        const string XML = @"<places xmlns=""http://where.yahooapis.com/v1/schema.rng""  
        xmlns:yahoo=""http://www.yahooapis.com/v1/base.rng""  
        yahoo:start=""0"" yahoo:count=""247"" yahoo:total=""247""> 
        <place yahoo:uri=""http://where.yahooapis.com/v1/place/23424966""  
            xml:lang=""en-US""> 
            <woeid>23424966</woeid> 
            <placeTypeName code=""12"">Country</placeTypeName> 
            <name>Sao Tome and Principe</name> 
        </place> 
        <place yahoo:uri=""http://where.yahooapis.com/v1/place/23424824""  
            xml:lang=""en-US""> 
            <woeid>23424824</woeid> 
            <placeTypeName code=""12"">Country</placeTypeName> 
            <name>Ghana</name> 
        </place> 
    </places>";
    
        const string ElementsNamespace = "http://where.yahooapis.com/v1/schema.rng";
        const string YahooNamespace = "http://www.yahooapis.com/v1/base.rng";
        const string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
    
        [XmlType(Namespace = ElementsNamespace, TypeName = "places")]
        [XmlRoot(ElementName = "places", Namespace = ElementsNamespace)]
        public class Places
        {
            [XmlAttribute(AttributeName = "start", Namespace = YahooNamespace)]
            public int Start { get; set; }
            [XmlAttribute(AttributeName = "count", Namespace = YahooNamespace)]
            public int Count;
            [XmlAttribute(AttributeName = "total", Namespace = YahooNamespace)]
            public int Total;
            [XmlElement(ElementName = "place", Namespace = ElementsNamespace)]
            public List<Place> AllPlaces { get; set; }
    
            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("Places[start={0},count={1},total={2}]:", this.Start, this.Count, this.Total);
                sb.AppendLine();
                foreach (var place in this.AllPlaces)
                {
                    sb.AppendLine("   " + place.ToString());
                }
    
                return sb.ToString();
            }
        }
        [XmlType(TypeName = "place", Namespace = ElementsNamespace)]
        public class Place
        {
            [XmlAttribute(AttributeName = "uri", Namespace = YahooNamespace)]
            public string Uri { get; set; }
            [XmlAttribute(AttributeName = "lang", Namespace = XmlNamespace)]
            public string Lang { get; set; }
            [XmlElement(ElementName = "woeid")]
            public string Woeid { get; set; }
            [XmlElement(ElementName = "placeTypeName")]
            public PlaceTypeName PlaceTypeName;
            [XmlElement(ElementName = "name")]
            public string Name { get; set; }
    
            public override string ToString()
            {
                return string.Format("Place[Uri={0},Lang={1},Woeid={2},PlaceTypeName={3},Name={4}]",
                    this.Uri, this.Lang, this.Woeid, this.PlaceTypeName, this.Name);
            }
        }
        [XmlType(TypeName = "placeTypeName", Namespace = ElementsNamespace)]
        public class PlaceTypeName
        {
            [XmlAttribute(AttributeName = "code")]
            public string Code { get; set; }
            [XmlText]
            public string Value { get; set; }
    
            public override string ToString()
            {
                return string.Format("TypeName[Code={0},Value={1}]", this.Code, this.Value);
            }
        }
        [ServiceContract]
        public interface IConsumeGeoPlanet
        {
            [OperationContract]
            [WebGet(
                UriTemplate = "countries?appid={appId}",
                ResponseFormat = WebMessageFormat.Xml,
                BodyStyle = WebMessageBodyStyle.Bare
            )]
            [XmlSerializerFormat]
            Places Countries(string appId);
        }
    
        public sealed class GeoPlanetConsumer : ClientBase<IConsumeGeoPlanet>
        {
            public GeoPlanetConsumer(string address)
                : base(new WebHttpBinding(), new EndpointAddress(address))
            {
                this.Endpoint.Behaviors.Add(new WebHttpBehavior());
            }
    
            public Places Countries(string appId)
            {
                return Channel.Countries(appId);
            }
        }
    
        [ServiceContract]
        public class SimulatedYahooService
        {
            [WebGet(UriTemplate = "*")]
            public Stream GetData()
            {
                WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";
                return new MemoryStream(Encoding.UTF8.GetBytes(XML));
            }
        }
    
        public static void Test()
        {
            Console.WriteLine("First a simpler test with serialization only.");
            XmlSerializer xs = new XmlSerializer(typeof(Places));
            MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
            object o = xs.Deserialize(ms);
            Console.WriteLine(o);
    
            Console.WriteLine();
            Console.WriteLine("Now in a real service");
            Console.WriteLine();
            string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
            WebServiceHost host = new WebServiceHost(typeof(SimulatedYahooService), new Uri(baseAddress));
            host.Open();
            Console.WriteLine("Host opened");
    
            GeoPlanetConsumer consumer = new GeoPlanetConsumer(baseAddress);
            Places places = consumer.Countries("abcdef");
            Console.WriteLine(places);
        }
    }
    

    【讨论】:

    • 好的,谢谢。我读了很多关于必须使用 xml 序列化程序来休息 xml 属性,但是将 XmlSerializerFormatAttribute 放在错误的位置。我现在将它放在 ServiceContract 接口上,而不是操作合约方法。
    • 如果你把它放在服务合同接口上应该也可以。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-29
    • 1970-01-01
    • 2012-01-20
    • 2012-06-23
    • 2017-06-10
    相关资源
    最近更新 更多