Serialization and Deserialization into an XML file, C# - c#

I have the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication28
{
class Program
{
static void Main()
{
List<string> dirs = FileHelper.GetFilesRecursive(#"c:\Documents and Settings\bob.smith\Desktop\Test");
foreach (string p in dirs)
{
Console.WriteLine(p);
}
//Write Count
Console.WriteLine("Count: {0}", dirs.Count);
Console.Read();
}
static class FileHelper
{
public static List<string> GetFilesRecursive(string b)
{
// 1.
// Store results in the file results list.
List<string> result = new List<string>();
// 2.
// Store a stack of our directories.
Stack<string> stack = new Stack<string>();
// 3.
// Add initial directory.
stack.Push(b);
// 4.
// Continue while there are directories to process
while (stack.Count > 0)
{
// A.
// Get top directory
string dir = stack.Pop();
try
{
// B
// Add all files at this directory to the result List.
result.AddRange(Directory.GetFiles(dir, "*.*"));
// C
// Add all directories at this directory.
foreach (string dn in Directory.GetDirectories(dir))
{
stack.Push(dn);
}
}
catch
{
// D
// Could not open the directory
}
}
return result;
}
}
}
}
The code above works well for recursively finding what files/directories lie in a folder on my c:.
I am trying to serialize the results of what this code does to an XML file but I am not sure how to do this.
My project is this: find all files/ directories w/in a drive, serialize into an XML file. Then, the second time i run this app, i will have two XML files to compare. I then want to deserialize the XML file from the first time i ran this app and compare differences to the current XML file and produce a report of changes (i.e. files that have been added, deleted, updated).
I was hoping to get some help as I am a beginner in C# and i am very very shaky on serializing and deserializing. I'm having lots of trouble coding. Can someone help me?
Thanks

Your result is List<string> and that is not directly serializable. You'll have to wrap it, a minimal approach:
[Serializable]
class Filelist: List<string> { }
And then the (De)Serialization goes like:
Filelist data = new Filelist(); // replaces List<string>
// fill it
using (var stream = File.Create(#".\data.xml"))
{
var formatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
formatter.Serialize(stream, data);
}
data = null; // lose it
using (var stream = File.OpenRead(#".\data.xml"))
{
var formatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
data = (Filelist) formatter.Deserialize(stream);
}
But note that you will not be comparing the XML in any way (not practical). You will compare (deserialzed) List instances. And the XML is SOAP formatted, take a look at it. It may not be very useful in another context.
And therefore you could easily use a different Formatter (binary is a bit more efficient and flexible).
Or maybe you just want to persist the List of files as XML. That is a different question.

For anyone who is having trouble with xml serialization and de-serialization. I have created a sample class to do this below. It works for recursive collections also (like files and directories).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Sample
{
[Serializable()]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false, ElementName = "rootnode")]
public partial class RootNode
{
[System.Xml.Serialization.XmlElementAttribute("collection1")]
public List<OuterCollection> OuterCollections { get; set; }
}
[Serializable()]
public partial class OuterCollection
{
[XmlAttribute("attribute1")]
public string attribute1 { get; set; }
[XmlArray(ElementName = "innercollection1")]
[XmlArrayItem("text", Type = typeof(InnerCollection1))]
public List<InnerCollection1> innerCollection1Stuff { get; set; }
[XmlArray("innercollection2")]
[XmlArrayItem("text", typeof(InnerCollection2))]
public List<InnerCollection2> innerConnection2Stuff { get; set; }
}
[Serializable()]
public partial class InnerCollection2
{
[XmlText()]
public string text { get; set; }
}
public partial class InnerCollection1
{
[XmlText()]
public int number { get; set; }
}
}

This class serializes and deserializes itself....hopefully this helps.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
namespace TestStuff
{
public class Configuration
{
#region properties
public List<string> UIComponents { get; set; }
public List<string> Settings { get; set; }
#endregion
//serialize itself
public string Serialize()
{
MemoryStream memoryStream = new MemoryStream();
XmlSerializer xs = new XmlSerializer(typeof(Configuration));
using (StreamWriter xmlTextWriter = new StreamWriter(memoryStream))
{
xs.Serialize(xmlTextWriter, this);
xmlTextWriter.Flush();
//xmlTextWriter.Close();
memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
memoryStream.Seek(0, SeekOrigin.Begin);
StreamReader reader = new StreamReader(memoryStream);
return reader.ReadToEnd();
}
}
//deserialize into itself
public void Deserialize(string xmlString)
{
String XmlizedString = null;
using (MemoryStream memoryStream = new MemoryStream())
{
using (StreamWriter w = new StreamWriter(memoryStream))
{
w.Write(xmlString);
w.Flush();
XmlSerializer xs = new XmlSerializer(typeof(Configuration));
memoryStream.Seek(0, SeekOrigin.Begin);
XmlReader reader = XmlReader.Create(memoryStream);
Configuration currentConfig = (Configuration)xs.Deserialize(reader);
this.Settings = currentConfig.Settings;
this.UIComponents = currentConfig.UIComponents;
w.Close();
}
}
}
static void Main(string[] args)
{
Configuration thisConfig = new Configuration();
thisConfig.Settings = new List<string>(){
"config1", "config2"
};
thisConfig.UIComponents = new List<string>(){
"comp1", "comp2"
};
//serializing the object
string serializedString = thisConfig.Serialize();
Configuration myConfig = new Configuration();
//deserialize into myConfig object
myConfig.Deserialize(serializedString);
}
}
}

John:
May I suggest an improvement? Instead of using filenames, use the FileInfo object. This will allow you to get much more accurate information about each file rather than just if it exists under the same name.
Also, the XmlSerializer class should do you just fine. It won't serialize generic lists, so you'll have to output your List<> to an array or some such, but other than that:
XmlSerializer serial = new XmlSerializer(typeof(FileInfo[]));
StringWriter writer = new StringWriter();
FileInfo[] fileInfoArray = GetFileInfos();
serial.Serialize(writer, fileInfoArrays);
Simple and easy, unless it matters to you how the serialized XML looks.
Whatever you do, lose the empty catch block. You WILL regret swallowing exceptions. Log them or re-throw them.

Help #1. Indent code by four spaces to have it be seen as code when you post here.
2: get rid of that try/catch block, as it will eat all exceptions, including the ones you want to know about.
3: Did you make any attempt at all to serialize your results? Please edit your question to show what you tried. Hint: use the XmlSerializer class.

for xml serialisation, you have several possibilities:
Doing it manually
Using XmlSerializer as detailed above
Using System.Xml.Serialization
Using Linq to Xml
for the last two, see a code example in this answer. (See some gotchas here)
And for your recursive directory visitor, you could consider making it really recursive: here's some interesting code examples.

This question is exactly like this one. I also have a posted answer which will work for you as well:
How to serialize?

Related

Serialize two lists of objects into an XML file

Trying to create an XML file of the following structure:
<Objects>
<ArrayOfObject1>
<Object1>
<Object1>
</ArrayOfObject1>
<ArrayOfObject2>
<Object2>
<Object2>
</ArrayOfObject2>
</Objects>
Is there any way to achieve this using the XmlSerializer? I'm aware that the following code snippet can be used to generate an XML file that has a root node of ArrayOfObject1 and elements corresponding to every object in the list; I'd just like to extend this to have a different root node, and an array of each object within it.
XmlSerializer serializer = new XmlSerializer(typeof(List<Object1>));
Of course, it may be best practice to have two different XML files, but I'm just trying to find out if there's an acceptable/sensible way to have two arrays of objects represented in a single file.
Thanks!
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication2
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(FILENAME, settings);
XmlSerializer serializer = new XmlSerializer(typeof(Objects));
Objects objects = new Objects()
{
objects1 = new List<Object1>()
{
new Object1(), new Object1()
},
objects2 = new List<Object2>()
{
new Object2(), new Object2()
}
};
serializer.Serialize(writer, objects);
}
}
public class Objects
{
[XmlArray("ArrayOfObject1")]
[XmlArrayItem("Object1")]
public List<Object1> objects1 { get; set; }
[XmlArray("ArrayOfObject2")]
[XmlArrayItem("Object2")]
public List<Object2> objects2 { get; set; }
}
public class Object1
{
}
public class Object2
{
}
}

Writing compact xml with XmlDictionaryWriter.CreateBinaryWriter and a XmlDictionary

I want to write an xml document to disk in a compact format. To this end, I use the net framework method XmlDictionaryWriter.CreateBinaryWriter(Stream stream,IXmlDictionary dictionary)
This method writes a custom compact binary xml representation, that can later be read by XmlDictionaryWriter.CreateBinaryReader. The method accepts an XmlDictionary that can contain common strings, so that those strings do not have to be printed in the output each time. Instead of the string, the dictionary index will be printed in the file. CreateBinaryReader can later use the same dictionary to reverse the process.
However the dictionary I pass is apparently not used. Consider this code:
using System.IO;
using System.Xml;
using System.Xml.Linq;
class Program
{
public static void Main()
{
XmlDictionary dict = new XmlDictionary();
dict.Add("myLongRoot");
dict.Add("myLongAttribute");
dict.Add("myLongValue");
dict.Add("myLongChild");
dict.Add("myLongText");
XDocument xdoc = new XDocument();
xdoc.Add(new XElement("myLongRoot",
new XAttribute("myLongAttribute", "myLongValue"),
new XElement("myLongChild", "myLongText"),
new XElement("myLongChild", "myLongText"),
new XElement("myLongChild", "myLongText")
));
using (Stream stream = File.Create("binaryXml.txt"))
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict))
{
xdoc.WriteTo(writer);
}
}
}
The produced output is this (binary control characters not shown)
#
myLongRootmyLongAttribute˜myLongValue#myLongChild™
myLongText#myLongChild™
myLongText#myLongChild™
myLongText
So apparently the XmlDictionary has not been used. All strings appear in their entirety in the output, even multiple times.
This is not a problem limited to XDocument. In the above minimal example I used a XDocument to demonstrate the problem, but originally I stumbled upon this while using XmlDictionaryWriter in conjunction with a DataContractSerializer, as it is commonly used. The results were the same:
[Serializable]
public class myLongChild
{
public double myLongText = 0;
}
...
using (Stream stream = File.Create("binaryXml.txt"))
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict))
{
var dcs = new DataContractSerializer(typeof(myLongChild));
dcs.WriteObject(writer, new myLongChild());
}
The resulting output did not use my XmlDictionary.
How can I get XmlDictionaryWriter to use the suplied XmlDictionary?
Or have I misunderstood how this works?
with the DataContractSerializer approach, I tried debugging the net framework code (visual studio/options/debugging/enable net. framework source stepping). Apparently the Writer does attempt to lookup each of the above strings in the dictionary, as expected. However the lookup fails in line 356 of XmlbinaryWriter.cs, for reasons that are not clear to me.
Alternatives I have considered:
There is an overload for XmlDictionaryWriter.CreatebinaryWriter, that also accepts a XmlBinaryWriterSession. The writer then adds any new strings it encounters into the session dictionary. However, I want to only use a static dictionary for reading and writing, which is known beforehand.
I could wrap the whole thing into a GzipStream and let the compression take care of the multiple instances of strings. However, this would not compress the first instance of each string, and seems like a clumsy workaround overall.
Yes there is a misunderstanding. XmlDictionaryWriter is primarily used for serialization of objects and it is child class of XmlWriter. XDocument.WriteTo(XmlWriter something) takes XmlWriter as argument. The call XmlDictionaryWriter.CreateBinaryWriter will create an instance of System.Xml.XmlBinaryNodeWriter internally. This class has both methods for "regular" writing:
// override of XmlWriter
public override void WriteStartElement(string prefix, string localName)
{
// plain old "xml" for me please
}
and for dictionary based approach:
// override of XmlDictionaryWriter
public override void WriteStartElement(string prefix, XmlDictionaryString localName)
{
// I will use dictionary to hash element names to get shorter output
}
The later is mostly used if you serialize object via DataContractSerializer (notice its method WriteObject takes argument of both XmlDictionaryWriter and XmlWriter type), while XDocument takes just XmlWriter.
As for your problem - if I were you I'd make my own XmlWriter:
class CustomXmlWriter : XmlWriter
{
private readonly XmlDictionaryWriter _writer;
public CustomXmlWriter(XmlDictionaryWriter writer)
{
_writer = writer;
}
// override XmlWriter methods to use the dictionary-based approach instead
}
UPDATE (based on your comment)
If you indeed use DataContractSerializer you have few mistakes in your code.
1) POC classes have to be decorated with [DataContract] and [DataMember] attribute, the serialized value should be property and not field; also set namespace to empty value or you'll have to deal with namespaces in your dictionary as well. Like:
namespace XmlStuff {
[DataContract(Namespace = "")]
public class myLongChild
{
[DataMember]
public double myLongText { get; set; }
}
[DataContract(Namespace = "")]
public class myLongRoot
{
[DataMember]
public IList<myLongChild> Items { get; set; }
}
}
2) Provide instance of session as well; for null session the dictionary writer uses default (XmlWriter-like) implementation:
// order matters - add new items only at the bottom
static readonly string[] s_Terms = new string[]
{
"myLongRoot", "myLongChild", "myLongText",
"http://www.w3.org/2001/XMLSchema-instance", "Items"
};
public class CustomXmlBinaryWriterSession : XmlBinaryWriterSession
{
private bool m_Lock;
public void Lock() { m_Lock = true; }
public override bool TryAdd(XmlDictionaryString value, out int key)
{
if (m_Lock)
{
key = -1;
return false;
}
return base.TryAdd(value, out key);
}
}
static void InitializeWriter(out XmlDictionary dict, out XmlBinaryWriterSession session)
{
dict = new XmlDictionary();
var result = new CustomXmlBinaryWriterSession();
var key = 0;
foreach(var term in s_Terms)
{
result.TryAdd(dict.Add(term), out key);
}
result.Lock();
session = result;
}
static void InitializeReader(out XmlDictionary dict, out XmlBinaryReaderSession session)
{
dict = new XmlDictionary();
var result = new XmlBinaryReaderSession();
for (var i = 0; i < s_Terms.Length; i++)
{
result.Add(i, s_Terms[i]);
}
session = result;
}
static void Main(string[] args)
{
XmlDictionary dict;
XmlBinaryWriterSession session;
InitializeWriter(out dict, out session);
var root = new myLongRoot { Items = new List<myLongChild>() };
root.Items.Add(new myLongChild { myLongText = 24 });
root.Items.Add(new myLongChild { myLongText = 25 });
root.Items.Add(new myLongChild { myLongText = 27 });
byte[] buffer;
using (var stream = new MemoryStream())
{
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict, session))
{
var dcs = new DataContractSerializer(typeof(myLongRoot));
dcs.WriteObject(writer, root);
}
buffer = stream.ToArray();
}
XmlBinaryReaderSession readerSession;
InitializeReader(out dict, out readerSession);
using (var stream = new MemoryStream(buffer, false))
{
using (var reader = XmlDictionaryReader.CreateBinaryReader(stream, dict, new XmlDictionaryReaderQuotas(), readerSession))
{
var dcs = new DataContractSerializer(typeof(myLongRoot));
var rootCopy = dcs.ReadObject(reader);
}
}
}

Float XML Namespaces to Root?

I have XML within a C# program that looks something like this:
<el1 xmlns="http://URI1">
<el2 xmlns:namespace2="http://URI2"></el2>
<el3>
<el4 xmlns:namespace3="http://URI3"></el4>
</el3>
</el1>
For cleanliness, I would like to move all the namespace declarations to the root element. I cannot change the export that produces this XML, so a solution needs to work on the sample as shown above. What is a good way to accomplish this?
This example is stripped down for brevity, but assume that there are further child elements that actually use these prefixes. These are irrelevant for this question as all the namespace prefix declarations are unique and my goal is only to move them higher in the tree.
I've reviewed the MSDN documentation for XML but there doesn't seem to be a simple way to manipulate namespaces like this. One of the solutions I've tried is interacting with the XML as an XElement and collecting the namespaces based on XAttribute.IsNamespaceDeclaration, replacing each element with its local name and finally creating a new root element with the collected list of namespace XAttributes. That line of experimentation caused a bunch of errors about redefining prefixes, though, and I'm not sure if I'm moving in the right direction or not.
You need to add a prefix
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication2
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
El1 el1 = new El1()
{
el2 = new El2()
{
el3 = new El3() {
el4 = new El4() {
}
}
}
};
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("u4", "http://URI4");
ns.Add("u3", "http://URI3");
ns.Add("u2", "http://URI2");
ns.Add("", "http://URI1");
XmlSerializer serializer = new XmlSerializer(typeof(El1));
StreamWriter writer = new StreamWriter(FILENAME);
serializer.Serialize(writer, el1, ns);
writer.Flush();
writer.Close();
writer.Dispose();
}
}
[XmlRoot("el1", Namespace = "http://URI1")]
public class El1
{
[XmlElement("el2", Namespace = "http://URI2")]
public El2 el2 { get; set; }
}
[XmlRoot("el2")]
public class El2
{
[XmlElement("el3", Namespace = "http://URI3")]
public El3 el3 { get; set; }
}
[XmlRoot("el3", Namespace = "http://URI3")]
public class El3
{
[XmlElement("el4", Namespace = "http://URI4")]
public El4 el4 { get; set; }
}
[XmlRoot("el4", Namespace = "http://URI1")]
public class El4
{
}
}
​
You can locate all the xmls attributes using the namespace xpath axis. Add those attributes to the root element. Finally write out the xml using NamespaceHandling.OmitDuplicates, which will leave the namespace declarations on the root and remove them from all the other elements.
var xml = new XmlDocument();
xml.Load("XMLFile1.xml");
// Find all xmlns: attributes
var attributes = xml.DocumentElement.SelectNodes("//namespace::*");
// Add xmlns: attributes to the root
foreach (XmlAttribute attribute in attributes)
xml.DocumentElement.SetAttribute(attribute.Name, attribute.Value);
// Write out results, ignoring duplicate xmlns: attributes
var settings = new XmlWriterSettings();
settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
settings.Indent = true;
using (var writer = XmlWriter.Create("XMLFile2.xml", settings))
{
xml.Save(writer);
}

No root element found. Issue?

I've just wanted to use the XML-Serializer of the .NET framework(version 2.0).
I created to methods to serialize and deserialize my settings:
public static void Save(string filename)
{
var settings = Settings.Instance;
if (File.Exists(filename))
File.Delete(filename);
using (var stream = File.OpenWrite(filename))
{
XmlSerializer serializer = new XmlSerializer(typeof(Settings));
serializer.Serialize(stream, settings);
}
}
The Save-methods works really fine and as a result I get the following xml document:
<?xml version="1.0"?>
<Settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<EnableHooking>true</EnableHooking>
<IncludePressedKeys>false</IncludePressedKeys>
<EnableFastScroll>false</EnableFastScroll>
<FastScrollingHotKeys>
<VirtualKeys>Control</VirtualKeys>
<VirtualKeys>Alt</VirtualKeys>
</FastScrollingHotKeys>
<ScrollSpeed>2</ScrollSpeed>
<FastScrollSpeed>10</FastScrollSpeed>
</Settings>
If I try to deserialize this document I get an exception XmlException which tells me that the root element is missing. I've tried to set the XmlRootAttribute, tried to check the filenames and stream position. Everything is ok. Now I finally tried to read load the file through the XmlDocument class which works perfectly. Now I really don't know what happens. So you may take a look at the Load-method:
public static void Load(string filename)
{
if (!File.Exists(filename))
throw new ArgumentException("File not found.", "filename", new FileNotFoundException());
//works
var doc = new XmlDocument();
doc.Load(XmlReader.Create(File.OpenRead(filename)));
Console.WriteLine(doc.DocumentElement.FirstChild);
using (var stream = File.OpenRead(filename))
{
XmlSerializer serializer = new XmlSerializer(typeof(Settings));
_instance = serializer.Deserialize(stream) as Settings;
}
}
Hopefully anyone got an idea.
When I use the following code (in which I added a simple version of the Settings class) on OSX with Xamarin Studio, I get no errors.
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
class MainClass
{
private static Settings _instance;
public static void Main (string[] args)
{
Load ("Settings.xml");
}
public static void Load(string filename)
{
if (!File.Exists(filename))
throw new ArgumentException("File not found.", "filename", new FileNotFoundException());
//works
var doc = new XmlDocument();
doc.Load(XmlReader.Create(File.OpenRead(filename)));
Console.WriteLine(doc.DocumentElement.FirstChild);
using (var stream = File.OpenRead(filename))
{
XmlSerializer serializer = new XmlSerializer(typeof(Settings));
_instance = serializer.Deserialize(stream) as Settings;
}
}
}
public class Settings
{
public bool EnableHooking {
get;
set;
}
public bool IncludePressedKeys {
get;
set;
}
}
}
When I check the value of _instance, the properties are set to the right values. I stripped the XML after the second property. The problem might be in more "complex" XmlElement "FastScrollingHotKeys". Could you post your Settings class, please?

Class serialization in XML

[XmlRoot("Class1")]
class Class1
{
[(XmlElement("Product")]
public string Product{get;set;}
[(XmlElement("Price")]
public string Price{get;set;}
}
This is my class. In this the price contains the '£' symbol. after serializing it to XML I get the '?' instead of '£'.
what I need to do to get the '£' in XML? OR How can I pass the data in price as CDATA?
Your problem must be to do with how the XML is written to the file.
I've written a program that uses the information that you've given me so far, and it when I print the XML string out, it is correct.
I conclude that the error is happening either when the data is written to the XML file, or when it is read back in from the XML file.
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
new Program().Run();
}
void Run()
{
Class1 test = new Class1();
test.Product = "Product";
test.Price = "£100";
Test(test);
}
void Test<T>(T obj)
{
XmlSerializerNamespaces Xsn = new XmlSerializerNamespaces();
Xsn.Add("", "");
XmlSerializer submit = new XmlSerializer(typeof(T));
StringWriter stringWriter = new StringWriter();
XmlWriter writer = XmlWriter.Create(stringWriter);
submit.Serialize(writer, obj, Xsn);
var xml = stringWriter.ToString(); // Your xml This is the serialization code. In this Obj is the object to serialize
Console.WriteLine(xml); // £ sign is fine in this output.
}
}
[XmlRoot("Class1")]
public class Class1
{
[XmlElement("Product")]
public string Product
{
get;
set;
}
[XmlElement("Price")]
public string Price
{
get;
set;
}
}
}

Categories