I have a list box that when an item is selected or deselected I want to save the changes to an xml file (so it is always up-to-date on the file and the user does not need a "save" button).
While testing I occasionally am hitting this IOException:
The process cannot access the file 'C:\MyPath\MyFile.xml' because it is being used by another process.
Here is my XML Serialization code:
// Save an object out to the disk
public static void SerializeObject<T>(this T toSerialize, String filename)
{
XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
TextWriter textWriter = new StreamWriter(filename);
xmlSerializer.Serialize(textWriter, toSerialize);
}
// Load an object from the disk
private static T DeserialzeObject<T>(String filename) where T : class
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
try
{
TextReader textReader = new StreamReader(filename);
return (T)xmlSerializer.Deserialize(textReader);
}
catch (FileNotFoundException)
{ }
return null;
}
And here is how it is called:
// Save off the list because the visibility has changed
public void WorkItemColumnTypeOnVisibleChanged(int fieldID, Visibility visibility)
{
ColumnFields.SerializeObject(ColumnFields.GetSerializeFilename());
}
The deserialize is the one that is giving the error:
WorkItemColumnTypes savedVersion = DeserialzeObject<WorkItemColumnTypes>(result.GetSerializeFilename());
Is there a way to optimize my connections to the file so that I am not tripping over myself?
Perhaps you need to call Close on the TextWriter objects in your serialization and deserialization methods? Or alternatively use the using construct to force disposal at the end of the block.
Example:
// Save an object out to the disk
public static void SerializeObject<T>(this T toSerialize, String filename)
{
XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
using(TextWriter textWriter = new StreamWriter(filename))
{
xmlSerializer.Serialize(textWriter, toSerialize);
}
}
// Load an object from the disk
private static T DeserialzeObject<T>(String filename) where T : class
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
try
{
using(TextReader textReader = new StreamReader(filename))
{
return (T)xmlSerializer.Deserialize(textReader);
}
}
catch (FileNotFoundException)
{ }
return null;
}
Related
I am trying to write functions that save a type, the Dispatcher type shown below, into a file, and then reload it later. I wrote that following functions, but they are not working. I get an exception:
Exception thrown: 'System.InvalidOperationException' in System.Xml.dll
Elsewhere I read that maybe because the member in the class is private then I should use BinaryFormatter, but it did not work.
What am I doing wrong ?
(The Dispatcher class will be used to store messages and also will allow users to pull messages from it. so I want to backup the data in case of an error and then be able to reload it).
public class Dispatcher
{
private Dictionary<int, Dictionary<int, Dictionary<int, Message>>> m_DataBase;
private Dispatcher()
{
m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
}
public static Dispatcher LoadFromFile()
{
Dispatcher loadedDataBase;
try
{
using (Stream stream = new FileStream(#".\DispatcherDataBase.xml", FileMode.Open))
{
XmlSerializer serializer = new XmlSerializer(typeof(Dispatcher));
loadedDataBase = serializer.Deserialize(stream) as Dispatcher;
}
}
catch (Exception)
{
loadedDataBase = new Dispatcher();
}
return loadedDataBase;
}
public void SaveToFile()
{
FileMode wantedFileModeForStream;
try
{
if (File.Exists(#".\DispatcherDataBase.xml"))
{
wantedFileModeForStream = FileMode.Truncate;
}
else
{
wantedFileModeForStream = FileMode.CreateNew;
}
using (Stream stream = new FileStream(#".\DispatcherDataBase.xml", wantedFileModeForStream))
{
XmlSerializer serializer = new XmlSerializer(this.GetType());
serializer.Serialize(stream, this);
}
}
catch (Exception)
{
}
}
}
You are trying to use XmlSerializer to serialize your Dispatcher type and are encountering three separate problems:
XmlSerializer requires your type to have a public parameterless constructor.
XmlSerializer does not support dictionaries.
XmlSerializer will not serialize non-public members such as m_DataBase.
DataContractSerializer (as well as DataContractJsonSerializer) do not have these limitations. If you mark your Dispatcher type with data contract attributes you will be able to serialize it to XML using this serializer.
Thus if you modify your type as follows:
public static class Constants
{
public const string DataContractNamespace = ""; // Or whatever
}
[DataContract(Name = "Dispatcher", Namespace = Constants.DataContractNamespace)]
public partial class Dispatcher
{
[DataMember]
private Dictionary<int, Dictionary<int, Dictionary<int, Message>>> m_DataBase;
private Dispatcher()
{
m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
}
[System.Runtime.Serialization.OnDeserialized]
void OnDeserializedMethod(System.Runtime.Serialization.StreamingContext context)
{
// Ensure m_DataBase is not null after deserialization (DataContractSerializer does not call the constructor).
if (m_DataBase == null)
m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
}
internal const string FileName = #"DispatcherDataBase.xml";
public static Dispatcher LoadFromFile()
{
Dispatcher loadedDataBase;
try
{
using (var stream = new FileStream(FileName, FileMode.Open))
{
var serializer = new DataContractSerializer(typeof(Dispatcher));
loadedDataBase = serializer.ReadObject(stream) as Dispatcher;
}
}
catch (Exception)
{
loadedDataBase = new Dispatcher();
}
return loadedDataBase;
}
public void SaveToFile()
{
using (var stream = new FileStream(FileName, FileMode.Create))
using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) // Optional indentation for readability only.
{
var serializer = new DataContractSerializer(this.GetType());
serializer.WriteObject(writer, this);
}
}
}
// Not shown in question, added as an example
[DataContract(Name = "Message", Namespace = Constants.DataContractNamespace)]
public class Message
{
[DataMember]
public string Value { get; set; }
}
You will be able to round-trip your Dispatcher class to XML. Demo fiddle #1 here.
Alternatively, you could use DataContractJsonSerializer and serialize to JSON by simply swapping serializers and eliminating the optional XmlWriter:
public static Dispatcher LoadFromFile()
{
Dispatcher loadedDataBase;
try
{
using (var stream = new FileStream(FileName, FileMode.Open))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(Dispatcher));
loadedDataBase = serializer.ReadObject(stream) as Dispatcher;
}
}
catch (Exception)
{
loadedDataBase = new Dispatcher();
}
return loadedDataBase;
}
public void SaveToFile()
{
using (var stream = new FileStream(FileName, FileMode.Create))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(this.GetType());
serializer.WriteObject(stream, this);
}
}
Demo fiddle #2 here, which results in a fairly simple, clean-looking serialization format:
{"m_DataBase":[{"Key":1,"Value":[{"Key":2,"Value":[{"Key":3,"Value":{"Value":"hello"}}]}]}]}
json.net could also be used to serialize this type if you are willing to use a 3rd party component.
I have followed a good tutorial that shows how to make an Automation Framework in C# Selenium.
The config file is in XML at the moment, but I wanted some more practice and change it to a .json file.
At the moment we are using the namespace System.Xml.XPath; and my question is, are there a similar for JSON? Lets say "System.Json;" that works the same as my XML reader. So I don't need to refactor to much code, or is it unavoidably?
This is how it works at the moment.
//Initialize
ConfigReader.SetFrameworkSettings();
public class ConfigReader
{
public static void SetFrameworkSettings()
{
XPathItem aut;
string strFilename = Environment.CurrentDirectory.ToString() + "\\Config\\GlobalConfig.xml";
FileStream stream = new FileStream(strFilename, FileMode.Open);
XPathDocument document = new XPathDocument(stream);
XPathNavigator navigator = document.CreateNavigator();
//Get XML Details and pass it in XPathItem type variables
aut = navigator.SelectSingleNode("AutoFramework/RunSettings/AUT");
Settings.AUT = aut.Value.ToString();
}
}
public class Settings
{
public static string AUT { get; set; }
}
Would be awesome if you could just change this two lines
XPathDocument document = new XPathDocument(stream);
XPathNavigator navigator = document.CreateNavigator()
And the XpathItem
Cheers
I would recommend using Newtonsoft.Json which is available from Nuget.
To "reuse" your current code you would have to make some basic SettingsConverter first:
public static class SettingsConverter
{
public static string FromXmlToJson(string xml)
{
Settings settings = null;
// read your xml file and assign values to the settings object
// now you can "serialize" settings object into Json
return JsonSerialization.Serialize(settings);
}
public static string FromJsonToXml(string json)
{
Settings settings = JsonSerialization.Deserialize<MeSettings>(json);
// save settings using your "xml" serialization
return xmlString; // return xml string
}
}
To use these methods I'm using this JsonSerialization helper class :
public static class JsonSerialiation
{
public static string Serialize<T>(T obj)
{
using (MemoryStream stream = new MemoryStream())
{
using (JsonTextWriter writer = new JsonTextWriter(new StreamWriter(stream))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serializer(writer, obj);
writer.Flush();
stream.Position = 0;
using (StreamReader reader = new StreamREader(stream))
{
return reader.ReadToEnd();
}
}
}
}
public static T Deserialize<T>(string jsonString)
{
using (JsonTextReader reader = new JsonTextReader(new StringReader(str)))
{
JsonSerializer serializer = new JsonSerializer();
return serializer.Deserialize<T>(reader)
}
}
}
Above example requires from you to change your Settings class from static :
public class Settings
{
public static string AUT { get; set; }
}
To instance :
public class Settings
{
public string AUT { get; set; }
}
If you prefer to keep it as static. You should use JObject from Newtonsoft.Json library :
JObject obj = JObject.Parse(jsonString);
Settings.AUT = obj.SelectToken("AUT").Value<string>();
You can always use JsonConvert.Serialize<T>() and JsonConvert.Deserialize<T>() methods instead of the JsonSerialization helper class that I've made but in my opinion the less control you have over your code the bigger the problems will be.
The problem is simple, I want to create a method to serialize and another to open it by passing any object structure. I have the following which is what I believed should work but, guess what, it doesn't:
List<string> list = new List<string>();
list.Add("aaa");
list.Add("bbb");
FileSystem.SerializeToFile(list, "");
List<string> anotherList = FileSystem.OpenSerialized(typeof(List<string>), "");
public class FileSystem
{
public static void SerializeToFile(object toSerialize, string fileName)
{
XmlSerializer writer = new XmlSerializer(typeof(object));
StreamWriter file = new StreamWriter(fileName);
writer.Serialize(file, toSerialize);
file.Close();
}
public static object OpenSerialized(Type type, string fileName)
{
XmlSerializer serializer = new XmlSerializer(typeof(object));
StreamReader reader = new StreamReader(fileName);
object something = serializer.Deserialize(reader);
return something;
}
}
The serializer’s constructor requires a reference to the type of object it should work, slightly modified your code to fit to requirement.
public class FileSystem
{
public static void SerializeToFile<T>(T toSerialize, string fileName)
{
XmlSerializer writer = new XmlSerializer(typeof(T));
StreamWriter file = new StreamWriter(fileName);
writer.Serialize(file, toSerialize);
file.Close();
}
public static T OpenSerialized<T>(string fileName)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
StreamReader reader = new StreamReader(fileName);
object something = serializer.Deserialize(reader);
return (T)something;
}
}
and now we can use this as
List<string> list = new List<string>();
list.Add("aaa");
list.Add("bbb");
FileSystem.SerializeToFile(list, #"d:\test.txt");
List<string> anotherList = FileSystem.OpenSerialized<List<string>>(#"d:\test.txt");
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?
This example uses a StringWriter to hold the serialized data, then calling ToString() gives the actual string value:
Person john = new Person();
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person));
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, john);
string serializedXML = stringWriter.ToString();
Is there any easier/Cleaner way to do this? All of the Serialize() overloads seem to use a Stream or Writer.
UPDATE: Asked a similar question about serializing an IEnumerable via an Extension Method .
Fun with extension methods...
var ret = john.ToXmlString()
public static class XmlTools
{
public static string ToXmlString<T>(this T input)
{
using (var writer = new StringWriter())
{
input.ToXml(writer);
return writer.ToString();
}
}
public static void ToXml<T>(this T objectToSerialize, Stream stream)
{
new XmlSerializer(typeof(T)).Serialize(stream, objectToSerialize);
}
public static void ToXml<T>(this T objectToSerialize, StringWriter writer)
{
new XmlSerializer(typeof(T)).Serialize(writer, objectToSerialize);
}
}
More or less your same solution, just using an extension method:
static class XmlExtensions {
// serialize an object to an XML string
public static string ToXml(this object obj) {
// remove the default namespaces
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add(string.Empty, string.Empty);
// serialize to string
XmlSerializer xs = new XmlSerializer(obj.GetType());
StringWriter sw = new StringWriter();
xs.Serialize(sw, obj, ns);
return sw.GetStringBuilder().ToString();
}
}
[XmlType("Element")]
public class Element {
[XmlAttribute("name")]
public string name;
}
class Program {
static void Main(string[] args) {
Element el = new Element();
el.name = "test";
Console.WriteLine(el.ToXml());
}
}
I created this helper method, but I haven't tested it yet. Updated the code per orsogufo's comments (twice):
private string ConvertObjectToXml(object objectToSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(objectToSerialize.GetType());
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, objectToSerialize);
return stringWriter.ToString();
}
Seems like no body actually answered his question, which is no, there is no way to generate an XML string without using a stream or writer object.