I'm trying to consume the following web service http://ipinfodb.com/ip_location_api.php this web service returns an xml response, the code below gets the XML response, but somehow when phasing the values from the XML response it does not work.
What is wrong with my code?
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.IO;
using System.Net;
using System.Xml;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
HttpWebRequest request = null;
HttpWebResponse response = null;
String Xml;
// Create the web request
request = WebRequest.Create("http://api.ipinfodb.com/v2/ip_query.php?key=<yourkey>&ip=74.125.45.100&timezone=true") as HttpWebRequest;
// Get response
using (response = request.GetResponse() as HttpWebResponse)
{
// Get the response stream
StreamReader reader = new StreamReader(response.GetResponseStream());
Xml = reader.ReadToEnd();
}
// Console xml output
Console.WriteLine(Xml); //see if we get the xml response, (YES we do)
Console.ReadLine();
string _currentField = "";
StringReader _sr = new StringReader(Xml);
XmlTextReader _xtr = new XmlTextReader(_sr);
_xtr.XmlResolver = null;
_xtr.WhitespaceHandling = WhitespaceHandling.None;
// get the root node
_xtr.Read();
if ((_xtr.NodeType == XmlNodeType.Element) && (_xtr.Name == "Response"))
{
while (_xtr.Read())
{
if ((_xtr.NodeType == XmlNodeType.Element) && (!_xtr.IsEmptyElement))
{
_currentField = _xtr.Name;
_xtr.Read();
if (_xtr.NodeType == XmlNodeType.Text)
{
switch (_currentField)
{
case "Status":
Console.WriteLine(_xtr.Value); //we print to console for testing purposes, normally assign it to a variable here!
break;
case "CountryCode":
Console.WriteLine(_xtr.Value);
break;
case "CountryName":
Console.WriteLine(_xtr.Value);
break;
case "RegionCode":
Console.WriteLine(_xtr.Value);
break;
case "RegionName":
Console.WriteLine(_xtr.Value);
break;
case "City":
Console.WriteLine(_xtr.Value);
break;
case "ZipPostalCode":
Console.WriteLine(_xtr.Value);
break;
case "Latitude":
Console.WriteLine(_xtr.Value);
break;
case "Longitude":
Console.WriteLine(_xtr.Value);
break;
case "Gmtoff开发者_C百科set":
Console.WriteLine(_xtr.Value);
break;
case "Dstoffset":
Console.WriteLine(_xtr.Value);
break;
case "TimezoneName":
Console.WriteLine(_xtr.Value);
break;
case "Isdst":
Console.WriteLine(_xtr.Value);
break;
case "Ip":
Console.WriteLine(_xtr.Value);
break;
default:
// unknown field
throw new Exception("Unknown field in response.");
}
}
}
}
}
Console.ReadLine();
}
}
}
EDIT: this is the XML response returned
<?xml version="1.0" encoding="UTF-8" ?>
- <Response>
<Status>OK</Status>
<CountryCode>US</CountryCode>
<CountryName>United States</CountryName>
<RegionCode>06</RegionCode>
<RegionName>California</RegionName>
<City>Mountain View</City>
<ZipPostalCode>94043</ZipPostalCode>
<Latitude>37.4192</Latitude>
<Longitude>-122.057</Longitude>
<Gmtoffset>-28800</Gmtoffset>
<Dstoffset>0</Dstoffset>
<TimezoneName>America/Los_Angeles</TimezoneName>
<Isdst>0</Isdst>
<Ip>74.125.45.100</Ip>
</Response>
My solution would be:
run the
xsd.exe
utility on your result XML twice to convert it to a XSD (first step) and a C# class (second step) - this would give you a C# classResponse
next, you can easily deserialize the response into an instance of that class:
HttpWebRequest request = WebRequest.Create("http://api.ipinfodb.com/v2/ip_query.php?key=--yourkey--&ip=74.125.45.100&timezone=true") as HttpWebRequest; XmlSerializer ser = new XmlSerializer(typeof(Response)); WebResponse response = request.GetResponse(); var result = ser.Deserialize(response.GetResponseStream());
and now your
result
would contain an instance ofResponse
, with all the elements as nice fields in your object.
Read more about xsd.exe on its MSDN doc page.
I use the this same API, I load the response XML into an XDocument and parse e.g.
// build URL up at runtime
string apiKey = ConfigurationManager.AppSettings["geoApiKey"];
string url = String.Format(ConfigurationManager.AppSettings["geoApiUrl"], apiKey, ip);
WebRequest request = WebRequest.Create(url);
try
{
WebResponse response = request.GetResponse();
using (var sr = new System.IO.StreamReader(response.GetResponseStream()))
{
XDocument xmlDoc = new XDocument();
try
{
xmlDoc = XDocument.Parse(sr.ReadToEnd());
string status = xmlDoc.Root.Element("Status").Value;
Console.WriteLine("Response status: {0}", status);
if (status == "OK")
{
// if the status is OK it's normally safe to assume the required elements
// are there. However, if you want to be safe you can always check the element
// exists before retrieving the value
Console.WriteLine(xmlDoc.Root.Element("CountryCode").Value);
Console.WriteLine(xmlDoc.Root.Element("CountryName").Value);
...
}
}
catch (Exception)
{
// handle if necessary
}
}
}
catch (WebException)
{
// handle if necessary
}
What you should also do is introduce a custom class e.g. GeoLocationInfo
and wrap your code in a function e.g. GetGeoLocation(string ip)
then instead of writing the info to the console window you can populate & return an instance of that class.
You are assuming that first node will be root node but that's not correct. You will have XmlDeclaration
node first and that may get followed by Whitespace
nodes. So you should probably structure your code something like
...
bool isRootRead = false;
while (_xtr.Read())
{
if (_xtr.NodeType == XmlNodeType.Element)
{
if (!isRootRead)
{
if (_xter.Name == "Response")
{
// root found
isRootRead = true;
}
// jump to next node if root node / ignore other nodes till root element is read
continue;
}
_currentField = _xtr.Name;
_xtr.Read();
if (_xtr.NodeType == XmlNodeType.Text)
{
switch (_currentField)
{
case "Status":
Console.WriteLine(_xtr.Value); //we print to console for testing purposes, normally assign it to a variable here!
break;
...
But said all that, I would personally prefer to create response XSD (better if web service provides it) and generate classes out of it (using XSD.exe or Xsd2Code) for serialize/deserialise it.
I think you need to use _xtr.MoveToContent(); Method before using the read method.. See if that works
精彩评论