Each time i get a request from a user, i have to serialize and append it , to an existing xml file like this :
<LogRecords>
<LogRecord>
<Message>Some messagge</Message>
<SendTime>2017-12-13T22:04:40.1109661+01:00</SendTime>
<Sender>Sender</Sender>
<Recipient>Name</Recipient>
</LogRecord>
<LogRecord>
<Message>Some message too</Message>
<SendTime>2017-12-13T22:05:08.5720173+01:00</SendTime>
<Sender>sender</Sender>
<Recipient>name</Recipient>
</LogRecord>
</LogRecords>
Currently Serializing data in this way (which works fine):
var stringwriter = new StringWriter();
var serializer = new XmlSerializer(object.GetType());
serializer.Serialize(stringwriter, object);
var smsxmlStr = stringwriter.ToString();
var smsRecordDoc = new XmlDocument();
smsRecordDoc.LoadXml(smsxmlStr);
var smsElement = smsRecordDoc.DocumentElement;
var smsLogFile = new XmlDocument();
smsLogFile.Load("LogRecords.xml");
var serialize = smsLogFile.CreateElement("LogRecord");
serialize.InnerXml = smsElement.InnerXml;
smsLogFile.DocumentElement.AppendChild(serialize);
smsLogFile.Save("LogRecords.xml");
And the properties class
[XmlRoot("LogRecords")]
public class LogRecord
{
public string Message { get; set; }
public DateTime SendTime { get; set; }
public string Sender { get; set; }
public string Recipient { get; set; }
}
But what i want to do is to load the file, navigate to the last element/node of it and append a new List<LogRecord> and save, so i can easily deserialize later.
I have tried various ways using XPath Select Methods like SelectSingleNode and SelectNodes but since i am junior with c# i haven't manage to make them work properly. Does anyone have any idea on how to serialize and append properly?
Thank you
Your approach (and most of the answers given to date) rely on having all of the log file in memory in order to append more records to it. As the log file grows, this could cause issues (such as OutOfMemoryException errors) down the road. Your best bet is to use an approach that streams the data from the original file into a new file. While there might be a few bugs in my untested code. The approach would look something like the following:
// What you are serializing
var obj = default(object);
using (var reader = XmlReader.Create("LogRecords.xml"))
using (var writer = XmlWriter.Create("LogRecords2.xml"))
{
// Start the log file
writer.WriteStartElement("LogRecords");
while (reader.Read())
{
// When you see a record in the original file, copy it to the output
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "LogRecord")
{
writer.WriteNode(reader.ReadSubtree(), false);
}
}
// Add your additional record(s) to the output
var serializer = new XmlSerializer(obj.GetType());
serializer.Serialize(writer, obj);
// Close the tag
writer.WriteEndElement();
}
// Replace the original file with the new file.
System.IO.File.Delete("LogRecords.xml");
System.IO.File.Move("LogRecords2.xml", "LogRecords.xml");
Another idea to consider, does the log file need to be a valid XML file (with the <LogRecords> tag at the start and finish? If you omit the root tag, you could simply append the new records at the bottom of the file (which should be very efficient). You can still read the XML in .Net by creating an XmlReader with the right ConformanceLevel. For example
var settings = new XmlReaderSettings()
{
ConformanceLevel = ConformanceLevel.Fragment
};
using (var reader = XmlReader.Create("LogRecords.xml", settings))
{
// Do something with the records here
}
Try using xml linq :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
LogRecord record = doc.Descendants("LogRecord").Select(x => new LogRecord()
{
Message = (string)x.Element("Message"),
SendTime = (DateTime)x.Element("SendTime"),
Sender = (string)x.Element("Sender"),
Recipient = (string)x.Element("Recipient")
}).OrderByDescending(x => x.SendTime).FirstOrDefault();
}
}
public class LogRecord
{
public string Message { get; set; }
public DateTime SendTime { get; set; }
public string Sender { get; set; }
public string Recipient { get; set; }
}
}
You can perform it by using XDocument like this;
XDocument doc = XDocument.Load("LogRecords.xml");
//Append Node
XElement logRecord = new XElement("LogRecord");
XElement message = new XElement("Message");
message.Value = "Message";
XElement sendTime = new XElement("SendTime");
sendTime.Value = "SendTime";
XElement sender = new XElement("Sender");
sender.Value = "Sender";
XElement recipient = new XElement("Recipient");
recipient.Value = "Recipient";
logRecord.Add(message);
logRecord.Add(sendTime);
logRecord.Add(sender);
logRecord.Add(recipient);
doc.Element("LogRecords").Add(logRecord);
//Append Node
doc.Save("LogRecords.xml");
Related
I have an xml document, where i serialize data dinamically, appending new data if i have a new request. The object properties i serialize are like this
[XmlRoot("LogRecords")]
public class LogRecord
{
public string Message { get; set; }
public DateTime SendTime { get; set; }
public string Sender { get; set; }
public string Recipient { get; set; }
}
Serializing is done in this way :
var stringwriter = new StringWriter();
var serializer = new XmlSerializer(object.GetType());
serializer.Serialize(stringwriter, object);
var smsxmlStr = stringwriter.ToString();
var smsRecordDoc = new XmlDocument();
smsRecordDoc.LoadXml(smsxmlStr);
var smsElement = smsRecordDoc.DocumentElement;
var smsLogFile = new XmlDocument();
smsLogFile.Load("LogRecords.xml");
var serialize = smsLogFile.CreateElement("LogRecord");
serialize.InnerXml = smsElement.InnerXml;
smsLogFile.DocumentElement.AppendChild(serialize);
smsLogFile.Save("LogRecords.xml");
While serializing i use LogFile.CreateElement("LogRecord") and my xml file looks like this :
<LogRecords>
<LogRecord>
<Message>Some messagge</Message>
<SendTime>2017-12-13T22:04:40.1109661+01:00</SendTime>
<Sender>Sender</Sender>
<Recipient>Name</Recipient>
</LogRecord>
<LogRecord>
<Message>Some message too</Message>
<SendTime>2017-12-13T22:05:08.5720173+01:00</SendTime>
<Sender>sender</Sender>
<Recipient>name</Recipient>
</LogRecord>
</LogRecords>
When i try to deserialize like this
XmlSerializer deserializer = new XmlSerializer(typeof(LogRecord));
TextReader reader = new StreamReader("LogRecords.xml");
object obj = deserializer.Deserialize(reader);
LogRecord records = (LogRecord)obj;
reader.Close();
I get null value for each property Message, Sender Recipient and a random value for SendTime, and i know it's because it doesn't recognise the XmlElement LogRecord i added while serializing..
Is there any way to read this xml element so i can take the right property values?
Ps. Sorry if i have messed up the variables, i tried to simplify the code when i added it here and i may have mixed some variables..
Thank you in advance.
You could try to generate POCO classes from XML in Visual Studio as it's described here.
You could serialize/deserialize those POCOs using with simple util methods like:
public static T DeserializeXML<T>(string content)
{
if (content == null)
return default(T);
XmlSerializer xs = new XmlSerializer(typeof(T));
byte[] byteArray = Encoding.ASCII.GetBytes(content);
var contentStream = new MemoryStream(byteArray);
var xml = xs.Deserialize(contentStream);
return (T)xml;
}
public static string SerializeAsXML(object item)
{
if (item == null)
return null;
XmlSerializer xs = new XmlSerializer(item.GetType());
using (var sw = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true }))
{
xs.Serialize(writer, item);
return sw.ToString();
}
}
}
LogRecords probably should be a collection (e.g. an array in this POCO):
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Log
{
/// <remarks/>
[System.Xml.Serialization.XmlArrayAttribute("LogRecords")]
[System.Xml.Serialization.XmlArrayItemAttribute("LogRecord", IsNullable = false)]
public LogRecord[] LogRecords { get; set; }
}
for the next XML format:
<Log>
<LogRecords>
<LogRecord>
<Message>Some messagge</Message>
<SendTime>2017-12-13T22:04:40.1109661+01:00</SendTime>
<Sender>Sender</Sender>
<Recipient>Name</Recipient>
</LogRecord>
<LogRecord>
<Message>Some message too</Message>
<SendTime>2017-12-13T22:05:08.5720173+01:00</SendTime>
<Sender>sender</Sender>
<Recipient>name</Recipient>
</LogRecord>
</LogRecords>
</Log>
You manually add the root element in the xml. Therefore, you must also manually skip it when reading.
XmlSerializer deserializer = new XmlSerializer(typeof(LogRecord));
using (var xmlReader = XmlReader.Create("LogRecords.xml"))
{
// Skip root element
xmlReader.ReadToFollowing("LogRecord");
LogRecord record = (LogRecord)deserializer.Deserialize(xmlReader);
}
Remove the [XmlRoot("LogRecords")] attribute to make it work.
Of course, you will always get the first element in the xml.
As already suggested in the comments, use the list.
List<LogRecord> logRecords = new List<LogRecord>();
var logRecord = new LogRecord { ... };
// Store each new logRecord to list
logRecords.Add(logRecord);
var serializer = new XmlSerializer(typeof(List<LogRecord>));
// Serialization is done with just a couple lines of code.
using (var fileStream = new FileStream("LogRecords.xml", FileMode.Create))
{
serializer.Serialize(fileStream, logRecords);
}
// As well as deserialization
using (var fileStream = new FileStream("LogRecords.xml", FileMode.Open))
{
logRecords = (List<LogRecord>)serializer.Deserialize(fileStream);
}
Thus become unnecessary manipulation using XmlDocument and fuss with manually adding-skipping the root node.
I am working with the USPS Tracking API. The have a specification for a request as I have listed below;
<TrackFieldRequest PASSWORD="" USERID="prodsolclient" APPID="">
<Revision>1</Revision>
<ClientIp>111.0.0.1</ClientIp>
<TrackID ID="5551212699300000962610" />
</TrackFieldRequest>
And they state in their user manual that "Up to 10 tracking IDs may be contained in each request input to the Web Tool server."
I interpret this as meaning that the TrackFieldRequest can have up to 10 of the TrackID child elements. However, these multiple TrackID elements are not defined as being in an array. They are just up to 10 consecutive TrackID child elements of the TrackFieldRequest element.
So, I am not sure how to build up the CLR object to pass to the XMLSerializer if I want to include 10 of the TrackID child elements.
I tried creating a TrackFieldRequest class that has a property that is a "List TrackIds" but the USPS website gives me an error response saying "The element 'TrackFieldRequest' has invalid child element 'TrackIds'. List of possible elements expected: 'TrackID'"
How do I model the CLR class so that the XMLSerializer can use it to generate up to 10 TrackID child elements, without using a List or Array property in my TrackFieldRequest class?
Here is my current TrackFieldRequest class
public class TrackFieldRequest
{
// Based upon USPS Web Tools API User Guide(Track & Confirm API) version 3.3 dated 2/28/16
// at https://www.usps.com/business/web-tools-apis/track-and-confirm-api.pdf
[XmlAttribute("USERID")]
public string UserId { get; set; }
[XmlElement("Revision")]
public int Revision { get; set; }
[XmlElement("ClientIp")]
public string ClientIp { get; set; }
[XmlElement("SourceIdZIP")]
public string SourceIdZip { get; set; }
public List<TrackId> TrackIds { get; set; }
}
Here is my current TrackID class
public class TrackId
{
// Based upon USPS Web Tools API User Guide(Track & Confirm API) version 3.3 dated 2/28/16
// at https://www.usps.com/business/web-tools-apis/track-and-confirm-api.pdf
public TrackId(string a_Id, string a_destinationZipCode, string a_mailingDate)
{
ID = a_Id;
DestinationZipCode = a_destinationZipCode;
MailingDate = a_mailingDate.ToString();
}
// Parameterless constructor is needed for the XMLSerializer
public TrackId()
{
}
[XmlAttribute]
public string ID { get; set; }
[XmlElement("DestinationZipCode")]
public string DestinationZipCode { get; set; }
[XmlElement("MailingDate")]
public string MailingDate { get; set; }
}
Here is my methods to convert the the CLR class into Xml using an XmlWriter
private string ConvertTrackingRequestToXml(TrackFieldRequest a_trackingRequest)
{
try
{
var xmlWriterSettings = new XmlWriterSettings
{
Encoding = new UTF8Encoding(false),
Indent = true,
IndentChars = "\t"
};
XmlSerializer xmlSerializer = new XmlSerializer(a_trackingRequest.GetType());
using (StringWriter stringWriter = new StringWriter())
using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, xmlWriterSettings))
{
xmlSerializer.Serialize(xmlWriter, a_trackingRequest);
return stringWriter.ToString();
}
}
catch (Exception ex)
{
Logger.LogError("Could not convert tracking request into Xml.", ex);
return null;
}
}
I would prefer not to use the XmlSerializer rather than manually building up the request XML string from a string builder, if possible.
Any ideas?
Thanks in advance for any help you can provide.
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] trackingNumbers = {"5551212699300000962610", "5551212699300000962611", "5551212699300000962612"};
XElement trackFieldRequest = new XElement("TrackFieldRequest", new object[] {
new XAttribute("PASSWORD", "password"),
new XAttribute("USERID", "prodsolclient"),
new XAttribute("APPID", ""),
new XElement("Revision",1),
new XElement("ClientIp", "111.0.0.1")
});
foreach (string trackingNumber in trackingNumbers)
{
trackFieldRequest.Add(new XElement("TrackID", trackingNumber));
}
string xml = trackFieldRequest.ToString();
}
}
}
I am loading my data from XML using C# this way:
XmlDocument xmlDoc = new XmlDocument();
TextAsset xmlFile = Resources.Load("levels/" + levelID) as TextAsset;
xmlDoc.LoadXml(xmlFile.text);
XmlNodeList levelsList = xmlDoc.GetElementsByTagName("level");
foreach (XmlNode levelInfo in levelsList)
{
XmlNodeList childNodes = levelInfo.ChildNodes;
foreach (XmlNode value in childNodes)
{
switch (value.Name)
{
case "info":
//levelWidth = getInt(value, 0);
//levelHeight = getInt(value, 1);
break;
}
}
}
And heres XML I am loading:
<?xml version="1.0" encoding="utf-8" ?>
<level>
<info w="1000" h="500"/>
</level>
It works just fine, I am now trying to find best way to load child nodes, inside my level node with multiple points nodes inside
<?xml version="1.0" encoding="utf-8" ?>
<level>
<info w="1000" h="500"/>
<ground>
<point val1="val1" val2="val2"/>
</ground>
</level>
I will be grateful for some guidance how to move in the right direction, thank you.
Using XML Linq
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
"<level>" +
"<info w=\"1000\" h=\"500\"/>" +
"</level>";
XDocument doc = XDocument.Parse(xml);
XElement level = (XElement)doc.FirstNode;
level.Add("ground", new object[] {
new XElement("point", new XAttribute[] {
new XAttribute("val1", "val1"),
new XAttribute("val2", "val2")
})
});
}
}
}
If you need read all points, you can use
var nodeList = Xmldocument.SelectNodes("level/info/ground/point");
SelectNodes return a list of nodes.
I would go for a slidely different way and use a data object. Then you don't have to analyse xml, you just code your data class:
[Serializable()]
public class CLevel
{
public string Info { get; set; }
}
[Serializable()]
public class CDatafile
{
public List<CLevel> LevelList { get; set; }
public CDatafile()
{
LevelList = new List<CLevel>();
}
}
public class DataManager
{
private string FileName = "Data.xml";
public CDatafile Datafile { get; set; }
public DataManager()
{
Datafile = new CDatafile();
}
// Load file
public void LoadFile()
{
if (System.IO.File.Exists(FileName))
{
System.IO.StreamReader srReader = System.IO.File.OpenText(FileName);
Type tType = Datafile.GetType();
System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
object oData = xsSerializer.Deserialize(srReader);
Datafile = (CDatafile)oData;
srReader.Close();
}
}
// Save file
public void SaveFile()
{
System.IO.StreamWriter swWriter = System.IO.File.CreateText(FileName);
Type tType = Datafile.GetType();
if (tType.IsSerializable)
{
System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
xsSerializer.Serialize(swWriter, Datafile);
swWriter.Close();
}
}
Then you can use it to create, save and load the file like this:
DataManager dataMng = new DataManager();
// Create some data
CLevel level = new CLevel();
level.Info = "Testlevel";
dataMng.Datafile.LevelList.Add(level);
// Save to file
dataMng.SaveFile();
// Load from file
dataMng.LoadFile();
So you can do everything in code checked by the compiler. Makes life a lot easier, or what do you think?
How do I serialize an XML-serializable object to an XML fragment (no XML declaration nor namespace references in the root element)?
Here is a hack-ish way to do it without having to load the entire output string into an XmlDocument:
using System;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
public class Example
{
public String Name { get; set; }
static void Main()
{
Example example = new Example { Name = "Foo" };
XmlSerializer serializer = new XmlSerializer(typeof(Example));
XmlSerializerNamespaces emptyNamespace = new XmlSerializerNamespaces();
emptyNamespace.Add(String.Empty, String.Empty);
StringBuilder output = new StringBuilder();
XmlWriter writer = XmlWriter.Create(output,
new XmlWriterSettings { OmitXmlDeclaration = true });
serializer.Serialize(writer, example, emptyNamespace);
Console.WriteLine(output.ToString());
}
}
You should be able to just serialize like you usually do, and then use the Root property from the resulting document.
You may need to clear the attributes of the element first.
By the way this is awesome.
I implemented this code to make it easy to work with xml fragments as classes quickly and then you can just replace the node when finished. This makes the transition between code and xml ultra-easy.
First create some extension methods.
public static class SerializableFragmentExtensions
{
public static XElement ToElement(this ISerializableFragment iSerializableFragment)
{
var serializer = new XmlSerializer(iSerializableFragment.GetType());
var emptyNamespace = new XmlSerializerNamespaces();
emptyNamespace.Add(String.Empty, String.Empty);
var output = new StringBuilder();
var writer = XmlWriter.Create(output,
new XmlWriterSettings { OmitXmlDeclaration = true });
serializer.Serialize(writer, iSerializableFragment, emptyNamespace);
return XElement.Parse(output.ToString(), LoadOptions.None);
}
public static T ToObject<T>(this XElement xElement)
{
var serializer = new XmlSerializer(typeof (T));
var reader = xElement.CreateReader();
var obj = (T) serializer.Deserialize(reader);
return obj;
}
}
Next Implement the required interface (marker interface--I know you are not supposed to but I think this is the perfect reason to it.)
public interface ISerializableFragment
{
}
Now all you have to do is decorate any Serializable class, you want to convert to an XElement Fragment, with the interface.
[Serializable]
public class SomeSerializableClass : ISerializableFragment
{
[XmlAttribute]
public string SomeData { get; set; }
}
Finally test the code.
static void Main(string[] args)
{
var someSerializableClassObj = new SomeSerializableClass() {SomeData = "Testing"};
var element = someSerializableClass.ToElement();
var backToSomeSerializableClassObj = element.ToObject<SomeSerializableClass>();
}
Thanks again for this amazingly useful code.
Which is the best way to map my model to XML file using c# serializer. I mean that if for example I select an deserialized object I could be able to find the xml source text in XML file.
I got a working sample for you and you can explore further on it.
using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApplication5
{
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public int XMLLine { get; set; }
}
public class Persons : List<Person> { }
class Program
{
static void Main(string[] args)
{
//create your objects
Person p = new Person();
p.Age = 35;
p.Name = "Arnold";
Person p2 = new Person();
p2.Age = 36;
p2.Name = "Tom";
Persons ps = new Persons();
ps.Add(p);
ps.Add(p2);
//Serialize them to XML
XmlSerializer xs = new XmlSerializer(typeof(Persons));
XDocument d = new XDocument();
using (XmlWriter xw = d.CreateWriter())
xs.Serialize(xw, ps);
//print xml
//System.Diagnostics.Debug.WriteLine(d.ToString());
// it will produce following xml. You can save it to file.
//I have saved it to variable xml for demo
string xml = #"<ArrayOfPerson>
<Person>
<Age>35</Age>
<Name>Arnold</Name>
<XMLLine>0</XMLLine>
</Person>
<Person>
<Age>36</Age>
<Name>Tom</Name>
<XMLLine>0</XMLLine>
</Person>
</ArrayOfPerson>";
XDocument xdoc = XDocument.Parse(xml, LoadOptions.SetLineInfo);
// A little trick to get xml line
xdoc.Descendants("Person").All(a => { a.SetElementValue("XMLLine", ((IXmlLineInfo)a).HasLineInfo() ? ((IXmlLineInfo)a).LineNumber : -1); return true; });
//deserialize back to object
Persons pplz = xs.Deserialize((xdoc.CreateReader())) as Persons;
pplz.All(a => { Console.WriteLine(string.Format("Name {0} ,Age{1} ,Line number of object in XML File {2}", a.Name, a.Age, a.XMLLine)); return true; });
Console.ReadLine();
}
}
}
and It will give your results like
Name Arnold ,Age35 ,Line number of object in XML File 2
Name Tom ,Age36 ,Line number of object in XML File 7
You can try this extension method:
public static string ToXml<T>(this object obj)
{
using (var memoryStream = new MemoryStream())
{
using (TextWriter streamWriter = new StreamWriter(memoryStream))
{
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(streamWriter, obj);
return Encoding.ASCII.GetString(memoryStream.ToArray());
}
}
}
public static void ToXmlFile<T>(this object obj, string fileName)
{
using (TextWriter streamWriter = new StreamWriter(fileName))
{
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(streamWriter, obj);
}
}
USAGE:
// you will get this on a string variable
var xmlString = yourModel.ToXml<YourModel>();
// you will save our object in a file.
yourModel.ToXmlFile<YourModel>(#"C:\yourModelDump.xml");
Please be noted to add SerializableAttribute on your class
[Serializable]
public class YourModel
{
//...
}
This should do it