#wcf #rest #xml-serialization #wcf-client #yahoo-api
#wcf #rest #xml-сериализация #wcf-клиент #yahoo-api
Вопрос:
Я новичок в 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]
атрибут без свойств и [DataMember(Name = "geonames")]
атрибут в Items
свойстве. Это не работает для GeoPlanet, хотя я продолжал получать исключения десериализации. Единственный способ заставить Countries(appId)
метод выполняться без исключений — это указать имя и пространство имен в атрибуте DataContract. Однако, когда я делаю это, я понятия не имею, как десериализовать результаты в коллекцию Items (это значение равно null).
Что мне делать?
Ответ №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);
}
}
Комментарии:
1. Да, спасибо. Я много читал о необходимости использования xml serializer для rest с атрибутами xml, но помещал атрибут XmlSerializerFormatAttribute не в то место. Теперь у меня есть это в интерфейсе ServiceContract вместо метода operation contract.
2. Это должно сработать, если вы также разместите его в интерфейсе контракта на обслуживание.