I have created a little logg program and with this i can save custom classes to xml, and convert them back from xml to a class. this works fine, but the problem is if i want to add one class to the list in the xml, i have to read all of them, add one class and rewrite all of them if i use this method, now i know i can manualy add a class by searching for elements and so on, but i wondered if i could do this in the way that i write all of them.
this is an example of the code that i ame using:
public static void Test()
{
List<LoggInformation> infos = new List<LoggInformation>();
infos.Add(new LoggInformation() { Level = BpuInterface.BpuInterface.BPUController.LoggLevel.Debug, Message = "error1" });
infos.Add(new LoggInformation() { Level = BpuInterface.BpuInterface.BPUController.LoggLevel.Error, Message = "error2" });
DataContractSerializer dd = new DataContractSerializer(typeof(List<LoggInformation>));
using (var writer = new StreamWriter(#"C://testLoggfile.xml"))
{
dd.WriteObject(writer.BaseStream, infos);
}
}
public static void AddOneItem()
{
//??????????
}
[DataContract]
public class LoggInformation
{
[DataMemberAttribute]
public BpuInterface.BpuInterface.BPUController.LoggLevel Level { get; set; }
[DataMemberAttribute]
public Source Source { get; set; }
[DataMemberAttribute]
public string ExceptionMessage { get; set; }
[DataMemberAttribute]
public string ExceptionStack { get; set; }
[DataMemberAttribute]
public string ThreadName { get; set; }
[DataMemberAttribute]
public System.Threading.ApartmentState ThreadApartmentState { get; set; }
[DataMemberAttribute]
public string Message { get; set; }
[DataMemberAttribute]
public DateTime DateTime { get; set; }
}
You can use this instead new StreamWriter(#"C://testLoggfile.xml"), true) will append line at the end of file.
public static void Test()
{
...
using (var writer = new StreamWriter(#"C://testLoggfile.xml"), true) // this will append line at the end of file.
{
dd.WriteObject(writer.BaseStream, infos);
}
}
...
Related
Parsing XML file using SSIS / C#
operations like fetching record count from trailer , TIN from body and store into a variable or somewhere temporarily(your suggestions please) for further processing. I don't want to store it in a table.
Please find the sample xml mentioned below
<ACOParticipantData xmlns:xsi="">
<Header>
<HeaderCode>HDR_PFPRVDR</HeaderCode>
<FileCreationDate>20160101</FileCreationDate>
<ACOProgCode>21</ACOProgCode>
</Header>
<Participants>
<Participant>
<ACO_ID>V199</ACO_ID>
<TIN>123456789</TIN>
<Old_TIN>987654321</Old_TIN>
<Org_NPI>1234567890</Org_NPI>
<Ind_NPI>1234567890</Ind_NPI>
<CCN>123456</CCN>
<PRG_Eff_Dt>20160101</PRG_Eff_Dt>
<PRG_Term_Dt>20161231</PRG_Term_Dt>
</Participant>
</Participants>
<Trailer>
<TrailerCode>TRL_PFPRVDR</TrailerCode>
<FileCreationDate>20160101</FileCreationDate>
<RecordCount>1</RecordCount>
</Trailer>
</ACOParticipantData>
You need to get fist get list of Participants then fetch all Participants tin number into list like
Here i created console app for your demonstration purpose.
class Program
{
static void Main(string[] args)
{
XDocument doc = XDocument.Load(#"Path to your xml file");
List<long> tinList = new List<long>();
tinList = doc.Descendants("Participants").Elements().Elements("TIN").Select(x => (long)x).ToList();
foreach (long tin in tinList)
{
Console.WriteLine(tin);
}
Console.ReadLine();
}
}
Output: (For 2 Participants)
You need to create a class for each node and use XML deserialisation to create the object.
I had to remove the empty namespace as the deserialisation process requires a valid namespace.
Also you can change the type of the properties according to your needs.
using System;
using System.IO;
using System.Xml.Serialization;
using System.Linq;
public class Program
{
public class ACOParticipantData
{
public Header Header { get; set; }
public Participant[] Participants { get; set; }
}
public class Header
{
public string HeaderCode { get; set; }
public string FileCreationDate { get; set; }
public string ACOProgCode { get; set; }
}
public class Participant
{
public string ACO_ID { get; set; }
public string TIN { get; set; }
public string Old_TIN { get; set; }
public string Org_NPI { get; set; }
public string Ind_NPI { get; set; }
public string CCN { get; set; }
public string PRG_Eff_Dt { get; set; }
public string PRG_Term_Dt { get; set; }
}
public class Trailer
{
public string TrailerCode { get; set; }
public string FileCreationDate { get; set; }
public string RecordCount { get; set; }
}
public static void Main()
{
var xmlString = #"<ACOParticipantData>
<Header>
<HeaderCode>HDR_PFPRVDR</HeaderCode>
<FileCreationDate>20160101</FileCreationDate>
<ACOProgCode>21</ACOProgCode>
</Header>
<Participants>
<Participant>
<ACO_ID>V199</ACO_ID>
<TIN>123456789</TIN>
<Old_TIN>987654321</Old_TIN>
<Org_NPI>1234567890</Org_NPI>
<Ind_NPI>1234567890</Ind_NPI>
<CCN>123456</CCN>
<PRG_Eff_Dt>20160101</PRG_Eff_Dt>
<PRG_Term_Dt>20161231</PRG_Term_Dt>
</Participant>
<Participant>
<ACO_ID>V199</ACO_ID>
<TIN>123456780</TIN>
<Old_TIN>987654321</Old_TIN>
<Org_NPI>1234567890</Org_NPI>
<Ind_NPI>1234567890</Ind_NPI>
<CCN>123456</CCN>
<PRG_Eff_Dt>20160101</PRG_Eff_Dt>
<PRG_Term_Dt>20161231</PRG_Term_Dt>
</Participant>
</Participants>
<Trailer>
<TrailerCode>TRL_PFPRVDR</TrailerCode>
<FileCreationDate>20160101</FileCreationDate>
<RecordCount>1</RecordCount>
</Trailer>
</ACOParticipantData>";
var serializer = new XmlSerializer(typeof(ACOParticipantData));
ACOParticipantData obj = null;
using (var reader = new StringReader(xmlString))
{
obj = (ACOParticipantData)serializer.Deserialize(reader);
}
if (obj == null)
{
return;
}
foreach (var tin in obj.Participants.Select(x => x.TIN))
{
Console.WriteLine(tin);
}
}
}
Output:
123456789
123456780
I'm trying to set up a very small database using XML serialization and more specifically XmlSerializer.
My main class is the following :
public class XmlDB
{
[XmlIgnore]
public string FilePath { get; private set; }
public List<FooType> Foos { get; set; }
public List<BarType> Bars { get; set; }
public List<ThirdType> Thirds { get; set; }
private XmlDB():this(null) { }
public XmlDB(string strDBPath) {
this.FilePath = strDBPath;
this.Foos = new List<FooType>();
this.Bars = new List<BarType>();
this.Thirds = new List<ThirdType>();
}
public static XmlDB Load(string strDBPath) {
using (XmlReader reader = XmlReader.Create(strDBPath)) {
XmlDB db = (XmlDB)new XmlSerializer(typeof(XmlDB)).Deserialize(reader);
db.FilePath = strDBPath;
return db;
}
}
public void SaveChanges() {
XmlWriterSettings settings = new XmlWriterSettings() {
Indent = true,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create(this.FilePath, settings)) {
XmlSerializer ser = new XmlSerializer(typeof(XmlDB));
ser.Serialize(writer, this);
}
}
}
My test method creates an instance, populates the lists and calls the SaveChanges method.
Everything works fine on serialization and the Xml output looks consistent.
The problem happens on deserializing : No error is reported but only the first item of the first List is treated, the following items of the first list are not deserialized, neither are the following lists...
If I shuffle the order of the lists in the Xml, it's always the first item of the first list in the Xml file that is deserialized.
I tried the following simple test to confirm (which unfortunately works fine, all lists are populated on deserializing) :
public class DBTestList
{
public List<DBTest> TestList { get; set; }
public List<DBTest2> TestList2 { get; set; }
public DBTestList() {
this.TestList = new List<DBTest>();
this.TestList2 = new List<DBTest2>();
}
}
public class DBTest
{
public int TestInt { get; set; }
public string TestStr { get; set; }
}
public class DBTest2
{
public int TestInt { get; set; }
public string TestStr { get; set; }
}
public void TestSerialProblem() {
//Init data
DBTestList tl = new DBTestList();
tl.TestList.Add(new DBTest() { TestInt = 1, TestStr = "test11" });
tl.TestList.Add(new DBTest() { TestInt = 2, TestStr = "test12" });
tl.TestList2.Add(new DBTest2() { TestInt = 3, TestStr = "test21" });
XmlWriterSettings settings = new XmlWriterSettings() {
Indent = true,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create("test.db", settings)) {
XmlSerializer ser = new XmlSerializer(typeof(DBTestList));
ser.Serialize(writer, tl);
}
using (XmlReader reader = XmlReader.Create("test.db")) {
DBTestList db = (DBTestList)new XmlSerializer(typeof(DBTestList)).Deserialize(reader);
Assert.IsTrue(db.TestList2[0].TestStr == "test21");
}
}
I read a lot of posts on this subject but none helped.
Do you have an idea ?
Thanks,
Best regards.
EDIT :
To give a more detailed idea of the classes used in the lists, here's one basic implementation.
All the types are derived from the parent one a_SolidElement, adding only a few properties (basic value types and/or enum) :
public abstract class a_SolidElement
{
[XmlIgnore]
public int Position { get; set; }
public virtual double Thickness { get; set; }
public virtual double Density { get; set; }
public string SupplierName { get; set; }
public string Name { get; set; }
}
public enum ElementType
{
Undefined=0,
TypeA,
TypeB
}
public class FooType:a_SolidElement
{
public double AdditionalData { get; set; }
public e_ElementType ElementType { get; set; }
}
dbc was actually right about the bad IXmlSerializable implementation in his comment :
In one of my classes, I had one property of a type I didn't write, with a problem in the readXml method.
I didn't see it at first because I successively removed some properties to see which one caused the problem but this one was still in the first deserialized item so that even if the subsequent ones didn't have it, the reader was still already messed up by the first one.
It's so obvious now that I feel bad for asking the question in the first place !
Thank you very much for the help !
I have a Yaml file:
https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/src/icons.yml
And a class:
public class IconSearch
{
public string Name { get; set; }
public string ClassName { get; set; }
public IEnumerable<string> Filters { get; set; }
}
Can you tell me how I can deserialize the yaml to an IEnumerable of objects?
I expect something like this to work, but it returns null - I'm guessing it's because one of my properties is not the root node (icons). Instead, I'm trying to serialize the children of the root?
var input = new StringReader(reply);
var yaml = new YamlStream();
yaml.Load(input);
var icons = deserializer.Deserialize<IconSearch>(input);
The class you are trying to deserialize to seems to be missing properties.
I went the round about way of converting yaml to json to csharp and this is class that was generated:
public class Rootobject
{
public Icon[] icons { get; set; }
}
public class Icon
{
public string[] categories { get; set; }
public object created { get; set; }
public string[] filter { get; set; }
public string id { get; set; }
public string name { get; set; }
public string unicode { get; set; }
public string[] aliases { get; set; }
public string[] label { get; set; }
public string[] code { get; set; }
public string url { get; set; }
}
Resources used :
YAML to JSON online
JSON to CSHARP (I used Paste special in visual studio)
Use this to deserialize
var icons = deserializer.Deserialize<RootObject>(input);
Update
I have commented out the line that you use to create YamlStream as it is not required (it positions the reader to the end of the stream instead of the beginning, which would explain why you were getting null earlier). Your main method looks as follows and works. I have also fixed the bug that Antoine mentioned
public static void Main()
{
string filePath = "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/src/icons.yml";
WebClient client = new WebClient();
string reply = client.DownloadString(filePath);
var input = new StringReader(reply);
//var yamlStream = new YamlStream();
//yamlStream.Load(input);
Deserializer deserializer = new Deserializer();
//var icons = deserializer.Deserialize<IconSearch>(input);
//Testing my own implementation
//if (icons == null)
// Console.WriteLine("Icons is null");
//Testing Shekhar's suggestion
var root = deserializer.Deserialize<Rootobject>(input);
if (root == null)
Console.WriteLine("Root is null");
}
In my common.cs class I have the below declarations for a list based on a class:
public static List<edbService> edb_service;
public class edbService
{
public string ServiceID { get; set; }
public string ServiceName { get; set; }
public string ServiceDescr { get; set; }
public string ServiceInterval { get; set; }
public string ServiceStatus { get; set; }
public string ServiceUrl { get; set; }
public string SourceApplication { get; set; }
public string DestinationApplication { get; set; }
public string Function { get; set; }
public string Version { get; set; }
public string userid { get; set; }
public string credentials { get; set; }
public string orgid { get; set; }
public string orgunit { get; set; }
public string customerid { get; set; }
public string channel { get; set; }
public string ip { get; set; }
}
I have a public method to populate the list from xml data files declared like this in the same class (common.cs):
#region PublicMethods
public List<edbService> populateEDBService(string xmlDataFile)
{
try
{
XElement x = XElement.Load(global::EvryCardManagement.Properties.Settings.Default.DataPath + xmlDataFile);
// Get global settings
IEnumerable<XElement> services = from el in x.Descendants("Service")
select el;
if (services != null)
{
edb_service = new List<edbService>();
foreach (XElement srv in services)
{
edbService edbSrv = new edbService();
edbSrv.ServiceID = srv.Element("ServiceID").Value;
edbSrv.ServiceName = srv.Element("ServiceName").Value;
edbSrv.ServiceDescr = srv.Element("ServiceDescr").Value;
edbSrv.ServiceInterval = srv.Element("ServiceInterval").Value;
edbSrv.ServiceStatus = srv.Element("ServiceStatus").Value;
edbSrv.ServiceUrl = srv.Element("ServiceUrl").Value;
foreach (XElement ServiceHeader in srv.Elements("ServiceHeader"))
{
edbSrv.SourceApplication = ServiceHeader.Element("SourceApplication").Value;
edbSrv.DestinationApplication = ServiceHeader.Element("DestinationApplication").Value;
edbSrv.Function = ServiceHeader.Element("Function").Value;
edbSrv.Version = ServiceHeader.Element("Version").Value;
foreach (XElement ClientContext in ServiceHeader.Elements("ClientContext"))
{
edbSrv.userid = ClientContext.Element("userid").Value;
edbSrv.credentials = ClientContext.Element("credentials").Value;
edbSrv.orgid = ClientContext.Element("orgid").Value;
edbSrv.orgunit = ClientContext.Element("orgunit").Value;
edbSrv.customerid = ClientContext.Element("customerid").Value;
edbSrv.channel = ClientContext.Element("channel").Value;
edbSrv.ip = ClientContext.Element("ip").Value;
}
}
edb_service.Add(edbSrv);
}
}
}
catch (Exception ex)
{
/* Write to log */
Common.logBuilder("CustomerCreate : Form --> CustomerCreate <--", "Exception", Common.ActiveMQ,
ex.Message, "Exception");
/* Send email to support */
emailer.exceptionEmail(ex);
}
return edb_service;
}
but the problem is, in my calling class when I try to have a list returned from this method, it is not found - I get a compile error that an object reference is required.
I am trying to call it like this:
Common.edbService edb_service = Common.populateEDBService("CardUpdate.xml");
and I get the below error:
An object reference is required for the non-static field, method, or property 'EvryCardManagement.Common.populateEDBService(string)'
What am I doing wrong?
I would like to have a generic method that can be called from several classes (which run async after being instantiated by background workers on my form)
You can try making your method as static.
public static List<edbService> populateEDBService(string xmlDataFile)
{
//Your code here
....
}
Now you can call this method from all the other classes by using common.populateEDBService();
You need either to create the class static, or to create an object to call it.
class edbService { }
public static void Main() {
//this is error
edbService.populateEDBService("");
//this is correct
edbService s = new edbService();
s.populateEDBService("");
}
The last line in my example shows the object reference required by the compiler. The s variable here is the object reference.
Are there any missing values in your XML? The.Value property won't work if the value is missing. So if ServiceID is missing then srv.Element("ServiceID").Value; will cause an error. You can get it to return an empty string for missing values, for example, by instead using (string)srv.Element("ServiceID");
I have figured out how to populate a custom class from XML data, but I ran into an issue along the way. Things were working perfectly with my existing method of populating data until I was thrown a bit of a curve ball. The new schema I was sent is similar to this:
<ITEM_REPLY>
<TRAN_ID>1320691307345</TRAN_ID>
<REPLY_CODE>0</REPLY_CODE>
<UNIT_PRICE>8.2784</UNIT_PRICE>
<SUP_LOCS>
<SUP_LOC>
<SUP_LOC_ID>001134</SUP_LOC_ID>
<COUNTRY_ID>USA</COUNTRY_ID>
<QTY_AVL>47.000</QTY_AVL>
<ITEM_UOM>EA</ITEM_UOM>
</SUP_LOC>
<SUP_LOC>
<SUP_LOC_ID>006817</SUP_LOC_ID>
<COUNTRY_ID>USA</COUNTRY_ID>
<QTY_AVL>20.000</QTY_AVL>
<ITEM_UOM>EA</ITEM_UOM>
</SUP_LOC>
</SUP_LOCS>
<MESSAGE />
<QTY_BREAKS />
</ITEM_REPLY>
Pretty standard XML schema, problem is I'm not sure how to populate my custom class with it. Here's what I do have:
static void Main(string[] args)
{
var order = ConvertXMLMessage<ItemReply>(request);
}
protected static T ConvertXMLMessage<T>(String xmlData) where T : class, new()
{
var xml = new XmlDocument();
xml.LoadXml(xmlData);
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (var xmlReader = new XmlNodeReader(xml.DocumentElement))
{
T work = (T)(serializer.Deserialize(xmlReader));
return work;
}
}
public class ItemReply
{
[XmlElement("ITEM_REPLY")]
public ItemAvlReply ITEM_REPLY { get; set; }
}
public class ItemAvlReply
{
[XmlElement("TRAN_ID")]
public string TRAN_ID { get; set; }
[XmlElement("REPLY_CODE")]
public string REPLY_CODE { get; set; }
[XmlElement("UNIT_PRICE")]
public string UNIT_PRICE { get; set; }
[XmlElement("SUP_LOCS")]
public SupplierLocations SUP_LOCS;
[XmlElement("MESSAGE")]
public string MESSAGE { get; set; }
[XmlElement("QTY_BREAKS")]
public string QTY_BREAKS { get; set; }
}
public class SupplierLocations
{
[XmlElement("SUP_LOC")]
public List<SupplierLocation> SUP_LOC;
}
public class SupplierLocation
{
[XmlElement("SUP_LOC_ID")]
public string SUP_LOC_ID { get; set; }
[XmlElement("COUNTRY_ID")]
public string COUNTRY_ID { get; set; }
[XmlElement("QTY_AVL")]
public string QTY_AVL { get; set; }
[XmlElement("ITEM_UOM")]
public string ITEM_UOM { get; set; }
}
This works perfectly minus the List<Item> part. I'm not overly experienced with LINQ and I'm not sure how to go about declaring a sub array in my class via this statement. I am also open to a different approach from creating the List<Item> part, I'm just not sure where to start otherwise. Is there a better approach for what I'm need to do? Is there an easy solution I am just unaware of in LINQ?
Here's a simple way to do it, assuming the example XML file you provided has typos. I assumed the OrderId has a closing tag, and that the closing tag for Items should be /Items.
Here's the version of the xml I used:
<Order>
<TransactionID>123</TransactionID>
<OrderID>1</OrderID>
<Items Number="2">
<Item>
<ItemName>Test</ItemName>
<Color>Red</Color>
</Item>
<Item>
<ItemName>Test1</ItemName>
<Color>Blue</Color>
</Item>
</Items>
</Order>
Here's the code to read/write the XML: (the xml variable is a String)
var order = ConvertXMLMessage<Order>(xml);
WriteXMLFile<Order>(order, #"test.xml");
Here's the ConvertXMLMessage and WriteXMLFile functions:
protected static T ConvertXMLMessage<T>(String xmlData) where T : class, new()
{
var xml = new XmlDocument();
xml.LoadXml(xmlData);
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (var xmlReader = new XmlNodeReader(xml.DocumentElement))
{
T work = (T)(serializer.Deserialize(xmlReader));
return work;
}
}
protected static void WriteXMLFile<T>(T item, String saveLocation) where T : class, new()
{
System.Xml.Serialization.XmlSerializer writer = new System.Xml.Serialization.XmlSerializer(typeof(T));
System.IO.StreamWriter file = new System.IO.StreamWriter(saveLocation);
writer.Serialize(file, item);
file.Close();
}
and here's the class structure:
public class Order
{
[XmlElement("TransactionID")]
public string TransactionId { get; set; }
[XmlElement("OrderID")]
public string OrderId { get; set; }
[XmlElement("Items")]
public ItemsContainer Items;
}
public class ItemsContainer
{
[XmlAttribute("Number")]
public Int32 Number { get; set; }
[XmlElement("Item")]
public List<Item> Items;
}
public class Item
{
[XmlElement("ItemName")]
public string ItemName { get; set; }
[XmlElement("Color")]
public string Color { get; set; }
}
As you'll notice I added some attributes to let the XML parser know how to handle the class when it's converting from/to the XML. I also added another small class called "ItemsContainer" just to hold the details on the Items tag. If you didn't need the "Number" attribute, then you could probably find a way to do away with this. However, this should get you in the right direction.
The example I provided is a simple version of how I usually handle the situation, obviously there's some improvements you can make depending on your needs.
Edit
I changed the Item class to use ItemName instead of TransactionId. It was an oversight on my part.
Edit 2
Here's the corrections you need to make to the newly posted code. The reason the Order class worked in the previous example was it matched the root XML element. You're new XML does align with the base class. So we need to add in a couple more attributes to make this work. You can also remove your ItemReply class. It's not needed.
So here's the new classes:
[XmlRoot("ITEM_REPLY")]
public class ItemAvlReply
{
[XmlElement("TRAN_ID")]
public string TRAN_ID { get; set; }
[XmlElement("REPLY_CODE")]
public string REPLY_CODE { get; set; }
[XmlElement("UNIT_PRICE")]
public string UNIT_PRICE { get; set; }
[XmlElement("SUP_LOCS")]
public SupplierLocations SUP_LOCS;
[XmlElement("MESSAGE")]
public string MESSAGE { get; set; }
[XmlElement("QTY_BREAKS")]
public string QTY_BREAKS { get; set; }
}
public class SupplierLocations
{
[XmlElement("SUP_LOC")]
public List<SupplierLocation> SUP_LOC;
}
public class SupplierLocation
{
[XmlElement("SUP_LOC_ID")]
public string SUP_LOC_ID { get; set; }
[XmlElement("COUNTRY_ID")]
public string COUNTRY_ID { get; set; }
[XmlElement("QTY_AVL")]
public string QTY_AVL { get; set; }
[XmlElement("ITEM_UOM")]
public string ITEM_UOM { get; set; }
}
Everything else should remain the same. The parsing/converting the XML to classes should work without any changes.