I am collecting XML data from a weather website xml file here.
I have created the following code, which finds the first instance of 'temp_c' and returns the value. This is the temperature at the current moment.
using (XmlReader reader = XmlReader.Create(new StringReader(weather)))
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (reader.Name.Equals("temp_c"))
{
reader.Read();
temp_c = reader.Value;
}
break;
}
}
}
return temp_c
This returns the value of the first instance in the XML file called "temp_c" to the string called "temp_c"
What I would now like to do is use the Element in the XML document called "period" and the element found with period called "fcttext". When "period = 0" it means "today", 1 = tomorrow, etc; and I am after the "fcttext_metric" data that is associated with that period value.
I've been struggling to work the examples and XML reading codes I've found into the current code that I have here. Can anyone please point me in the right direction?
Edit
Here is an example of the XML file:
<forecast>
<forecastday>
<period>0</period>
<fcttext_metric>Sunny</fcttext_metric>
<forecastday>
<period>1</period>
<fcttext_metric>Cloudy</fcttext_metric>
This ended up being more annoying that I originally expected, but you can create an object graph from the XML using a DataContractSerializer and a set of classes that match the XML you are reading.
First, you create your classes, with appropriate DataContract attributes.
[DataContract(Name = "response", Namespace = "")]
public class WeatherData
{
[DataMember(Name = "forecast")]
public Forecast Forecast { get; set; }
}
[DataContract(Name = "forecast", Namespace = "")]
public class Forecast
{
[DataMember(Name = "txt_forecast")]
public TxtForecast TxtForecast { get; set; }
}
[DataContract(Name = "txt_forecast", Namespace = "")]
public class TxtForecast
{
[DataMember(Name = "forecastdays")]
public ForecastDay[] ForecastDays { get; set; }
}
[DataContract(Name = "forecastday", Namespace = "")]
public class ForecastDay
{
[DataMember(Name = "period", Order = 1)]
public int Period { get; set; }
public string FctText { get; set; }
[DataMember(Name = "fcttext", EmitDefaultValue = false, Order = 5)]
private CDataWrapper FctTextWrapper
{
get { return FctText; }
set { FctText = value; }
}
}
Heres where it got complicated. The fcttext element is CDATA, which the DataContractSerializer doesn't support by default.
Using the wonderful answer available at Using CDATA with WCF REST starter kits, you create a CDataWrapper class. I won't repeat the code here (because that would be pointless), just follow the link.
You can see that I've already used the CDataWrapper class above in order to work with the fcttext element.
Once you've got the class structure setup you can use the following code to extract the information you were after. You're just navigating the object graph at this point, so you can use whatever C# you want (I've used a simple LINQ query to find Period 0 and print out the fcttext for it).
var s = new DataContractSerializer(typeof(WeatherData));
var reader = XmlReader.Create("http://api.wunderground.com/api/74e1025b64f874f6/forecast/conditions/q/zmw:00000.1.94787.xml");
var o = (WeatherData)s.ReadObject(reader);
var firstPeriod = o.Forecast.TxtForecast.ForecastDays.Where(a => a.Period == 0).Single();
Console.WriteLine(firstPeriod.FctText);
You can extend the classes as necessary to give you access to additional fields inside the XML. As long as the DataMember names match the XML fields it will all work.
Here's a short summary of some problems I ran into for anyone interested:
I had to set the Namespace attributes of all of the classes to the empty string, because the XML doesn't have any namespace info in it.
The Order attributes were important on the ForecastDay class. If they are omitted, the DataContractSerializer ends up not reading the fcttext field in at all (because it thinks fcttext should come first? not sure why to be honest).
Related
This is my current XML Response tag
<Response_Data_1234 diffgr:id="Response_Data_1234" msdata:rowOrder="0" diffgr:hasChanges="inserted">
<Status>File received.</Status>
<Time>2022-01-25T09:44:15.73+08:00</Time>
<Location>HKG</Location>
</Response_Data_1234>
the number 1234 in <Response_Data_1234> is a Response Data id, which will be dynamic depending on the request.
Could someone please me create a C# class in this scenario so that I can map the response directly to the class. Thanks in advance
Actually you should not have any unique identifier attached to XML element name. It should be added as attribute.
To answer your question:
There is no direct class attribute to ignore class name while deserialisation of xml string to object.
Option1 (better): Create mapper extension method for XNode to class attribute mapping and reuse it.
Option2:
If your xml string is small you can try to update name of root element and use it to deserialise.
sample code:
var responseDataXDoc = XDocument.Parse(xml);
responseDataXDoc.Root.Name = "ResponseData";
var serializer = new XmlSerializer(typeof(ResponseData));
var responseData = serializer.Deserialize(new StringReader(responseDataXDoc.ToString(SaveOptions.DisableFormatting)));
With Cinchoo ETL - an open source library, you can deserialize such xml easily as below
using (var r = ChoXmlReader<ResponseData>.LoadText(xml)
.WithXPath("//")
.WithXmlNamespace("diffgr", "")
.WithXmlNamespace("msdata", "")
)
{
var rec = r.FirstOrDefault();
rec.Print();
}
public class ResponseData
{
public string Status { get; set; }
public DateTime Time { get; set; }
public string Location { get; set; }
}
Sample fiddle: https://dotnetfiddle.net/HOOPOg
I have a XML file which has all information about a voyage and all details inside.
I want to read all records in XML file, after combining records I want to write it to SQL database.
So far I arranged getting header , company and voyage to array but getting details for all records to array I failed.
Here are my task to handle:
Select and read any XML Data to RAM by FileDialog (completed)
Create Arrays and Read XML data to Arrays (partly completed)
Write XML data to DataView (partly completed)
Create T-SQL INSERT Command (Partly completed)
Write Data to Database (Waiting to finish early steps)
While reading from XML to DataView I can get data to memory but could not seperated multi level data as requested.
The exact problem is trying to handle different levels of data in XML in every XML file I recieve.
foreach (var child in childElem.Elements("ManifestData"))
{
foreach(var x in child.Elements())
{
var checkName = x.Name.ToString();
switch (checkName)
{
case "Company":
Globals.Companys.Clear();
foreach (var y in x.Elements())
{
Globals.Companys.Add(y.Name.ToString(), y.Value.ToString());
}
break;
case "Voyage":
Globals.Voyages.Clear();
foreach (var y in x.Elements())
{
Globals.Voyages.Add(y.Name.ToString(), y.Value.ToString());
}
break;
case "BLs":
int recs = 0;
Globals.BL.Clear();
textBox2.Clear();
foreach (var y in x.Elements())
{
foreach (var z in x.Elements("units"))
{
Globals.Units.Add(y.Element("number").Value.ToString(), z.Value.ToString());
}
Globals.BL.Add(y.Element("number").Value.ToString(), y.Value.ToString());
recs = recs + 1;
textBox2.AppendText("\n" + y.ToString());
string output = string.Join("\n", Globals.BL);
MessageBox.Show(output);
}
break;
default:
break;
}
}
}
In my example XML you see that there is 3 BLs and all BL data has different levels.There can be hundreds of BLs with different levels of Goods & Dangerous Goods.
I am having trouble handling multi level XML data here.
I 'll be glad if you help me solve this very basic problem. I am hoping to learn and leave it for the people to figure out to understand making desktop XML reader application for their own DBs.
Here is the XML Data example
You can find all sources here : Project Reading XMLbyC#
The xml processing part can be made simple by deserializing your xml into c# classes which you can then use to do whatever you want.
[XmlRoot(ElementName = "ManifestMessage")]
public class ManifestMessage
{
[XmlElement(ElementName = "Header")]
public Header Header { get; set; }
[XmlElement(ElementName = "ManifestData")]
public ManifestData ManifestData { get; set; }
}
[XmlRoot(ElementName = "Header")]
public class Header
{
[XmlElement(ElementName = "sender")]
public string Sender { get; set; }
[XmlElement(ElementName = "reciever")]
public string Reciever { get; set; }
[XmlElement(ElementName = "timeOfDocument")]
public string TimeOfDocument { get; set; }
[XmlElement(ElementName = "typeOfMessage")]
public string TypeOfMessage { get; set; }
}
// Then when you want to get the xml deserialized into your class hierarchy
var xmlSerializer = new XmlSerializer(typeof(ManifestMessage));
var manifestMessage = xmlSerializer.Deserialize(data) as ManifestMessage;
// now you can use this object to drill down the whole hierarchy
Console.WriteLine(xmlData.Header.Sender);
Console.WriteLine(xmlData.ManifestData.Company.ComanyName);
Console.WriteLine(xmlData.ManifestData.Voyage.CrewNumber);
foreach (var bl in xmlData.ManifestData.BLs.BL)
{
Console.WriteLine(bl.Collect);
Console.WriteLine(bl.Consegnee.Name);
Console.WriteLine(bl.Customer.Name);
}
You can use https://xmltocsharp.azurewebsites.net/ site to generate the whole c# class hierarchy from your xml.
Console.WriteLine is just for demo purpose you can adopt it according to your needs.
<?xml version="1.0" encoding="utf-8"?>
<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:code />
<m:message xml:lang="en-US">An error occurred while processing this request.</m:message>
<m:innererror>
<m:message>Exception has been thrown by the target of an invocation.</m:message>
<m:type>System.Reflection.TargetInvocationException</m:type>
<m:stacktrace></m:stacktrace>
<m:internalexception>
<m:message>An error occurred while executing the command definition. See the inner exception for details.</m:message>
<m:type>System.Data.Entity.Core.EntityCommandExecutionException</m:type>
<m:stacktrace></m:stacktrace>
<m:internalexception>
<m:message>Cannot insert duplicate key row in object 'XXXX' with unique index 'XXXXXX'. The duplicate key value is (XXXXX)</m:message>
<m:type>System.Data.SqlClient.SqlException</m:type>
<m:stacktrace></m:stacktrace>
</m:internalexception>
</m:internalexception>
</m:innererror>
</m:error>" System.Data.Services.Client.DataServiceClientException
I have this XML structure coming back from a WEB API 2 ODATA source. I need to parse this error at the server then pass a new error to the client.I have tried deserializing this to various Exception types, but the fact that Exception Implement IDICTIONARY stops that. How can I deserialize this into a strongly typed object?
If the above isn't easily down, I guess my other question would be what is best practice for handling these errors
My suggestion as to how to handle these error messages that you get, is to create a similar looking class structure instead of trying to serialize the information into an Exception object.
This allows for a bit more flexibility on your end, in case the source of your XML decides to change the structure of their error messages instead of basing it on the structure of an Exception, plus it removes the requirement to try and figure out how to deserialize an Exception object (which according to a few searches is a bit more complexer).
So using the tool in Visual Studio to convert XML text into C# classes (found under Edit => Paste Special => Paste XML as Classes), the below class structure is obtained.
The actual structure is modified from what that tool generates, since it likes to bloat the classes a lot.
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata")]
[XmlRootAttribute(ElementName = "error", Namespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata", IsNullable = false)]
public partial class ODATAException
{
public string code { get; set; }
public ODATAErrorMessage message { get; set; }
public ODATAInternalException innererror { get; set; }
}
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata")]
public partial class ODATAErrorMessage
{
[XmlAttributeAttribute(Form = System.Xml.Schema.XmlSchemaForm.Qualified, Namespace = "http://www.w3.org/XML/1998/namespace")]
public string lang { get; set; }
[XmlTextAttribute()]
public string Value { get; set; }
}
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata")]
public partial class ODATAInternalException
{
public string message { get; set; }
public string type { get; set; }
public string stacktrace { get; set; }
public ODATAInternalException internalexception { get; set; }
}
Utilizing this class structure, you can then easily deserialize the received XML into a strong typed object and use it further in whichever way you like.
using (TextReader sampleTextReader = new StringReader(txtInput.Text)) // Change this whereever you get the XML string from
{
XmlSerializer sampleXmlSeri = new XmlSerializer(typeof(ODATAException));
ODATAException newExc = sampleXmlSeri.Deserialize(sampleTextReader) as ODATAException;
if (newExc != null)
{
// Do something with the error
}
}
You are also now free to give the variables any names that are more descriptive to yourself/your team and use the XmlElement attributes to link your properties to the actual names inside the XML.
There is also a very interesting and good Q/A regarding making an exception serializable, that I would recommend reading as well:
What is the correct way to make a custom .NET Exception serializable?
I have an issue where if my json file looks like this
{ "Numbers": "45387", "Words": "space buckets"}
I can read it just fine, however if it looks like this:
{ "Main" :{ "Numbers": "45387", "Words": "space buckets"},
"Something" :{"Numbers": "12345", "Words": "Kransky"} }
I get no information back. I have no idea how to switch between Main and Something!
Loading a JSON with this 'nested' information using this code,
var ser = new DataContractJsonSerializer(typeof(myInfo));
var info = (myInfo)ser.ReadObject(e.Result);
// The class being using to hold my information
[DataContract]
public class myInfo
{
[DataMember(Name="Numbers")]
public int number
{ get; set; }
[DataMember(Name="Words")]
public string words
{ get; set; }
}
Causes the class to come back empty.
I've tried adding the group name to DataContract eg. [DataContract, Name="Main"] but this still causes the classes values to be empty.
I've also tried adding "main" to the serializer overloader eg. var ser = new DataContractJsonSerializer(typeof(myInfo), "Main");
This causes an error: Expecting element 'Main' from namespace ''.. Encountered 'Element' with name 'root', namespace ''.
I'd prefer to just use the supplied json reader. I have looked into json.NET but have found the documentation to be heavy on writing json and sparse with information about reading.
Surely I'm missing something simple here!
You could add a wrapper class:
[DataContract]
public class Wrapper
{
[DataMember]
public myInfo Main { get; set; }
[DataMember]
public myInfo Something { get; set; }
}
Now you could deserialize the JSON back to this wrapper class and use the two properties to access the values.
Just for some fun I was playing with the API of Last.fm. The XML file they return for top artists is structured like this:
<lfm status="ok">
<topartists user="xbonez" type="overall">
<artist rank="1">
<name>Evanescence</name>
<playcount>4618</playcount>
<mbid>f4a31f0a-51dd-4fa7-986d-3095c40c5ed9</mbid>
<url>http://www.last.fm/music/Evanescence</url>
<streamable>1</streamable>
<image size="small">http://userserve-ak.last.fm/serve/34/48488613.png</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/48488613.png</image>
<image size="large">http://userserve-ak.last.fm/serve/126/48488613.png</image>
<image size="extralarge">http://userserve-ak.last.fm/serve/252/48488613.png</image>
<image size="mega">http://userserve-ak.last.fm/serve/500/48488613/Evanescence++PNG.png</image>
</artist>
</topartists>
</lfm>
This is how I'm deserealizing it.
I have a class called lfmStatus:
[Serializable()]
[XmlRootAttribute("lfm")]
public class lfmStatus
{
[XmlElement("artist")]
public List<Artists> TopArtists { get; set; }
}
And a Class Artists:
[Serializable()]
public class Artists
{
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("playcount")]
public int playcount { get; set; }
[XmlElement("url")]
public string url { get; set; }
[XmlElement("streamable")]
public int streamable { get; set; }
[XmlElement("image")]
public string image { get; set; }
}
And then I deserealize using this code:
string XmlFile = "artists.xml";
XmlSerializer serializer = new XmlSerializer(typeof(lfmStatus));
lfmStatus LoadFile;
using (Stream reader = new FileStream(XmlFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
try
{
Console.WriteLine("Beginning deserialization.");
// Call the Deserialize method to restore the object's state.
LoadFile = (lfmStatus)serializer.Deserialize(reader);
return LoadFile.TopArtists;
}
Now, this code works great for the XML if it did not have the topartists tag enveloping all the artists. But since it does, how do I change my code to handle that? I'm assuming I need to add another class.
You are missing the attribute(s) on a few types.
See XmlAttributeAttribute for more detail.
You are also missing the topartists element's type.
If I was you, I would get the XML schema and just use xsd.exe to generate the C# classes, and modify from there. It can also infer the schema based on XML if you really can't find it, this will give you a parsable result based on the input XML.
To see that you wrote correct code for deserializing the response XML you can use XSD. Open VS command prompt and give XSD LastFM.xml which generates and XSD file. Now give XSD LastFM.XSD which will generate a CS file. compare that one with the one you have written and check if you made any mistakes.