I'm trying to de-serialize a class in order to save user specific values (like a dirpath) in an XML-file.
Here's a snippet of my class:
[Serializable]
public class CustomSettings
{
public string initPath;
public string InitPath => initPath;
}
Both the serialization and deserialization work perfectly fine if I do this inside my MainWindow class:
CustomSettings cs = new CustomSettings();
XmlSerializer mySerializer = new XmlSerializer(typeof(CustomSettings));
StreamWriter myWriter = new StreamWriter(#"U:\Alex\prefs.xml");
mySerializer.Serialize(myWriter, cs);
myWriter.Close();
And for the deserialization:
CustomSettings cs = new CustomSettings();
XmlSerializer _mySerializer = new XmlSerializer(typeof(CustomSettings));
FileStream myFstream = new FileStream(#"U:\Alex\prefs.xml", FileMode.Open);
cs = (CustomSettings)_mySerializer.Deserialize(myFstream);
myFstream.Close();
Now since I need to do the de-serialization a few times, I figured I write two methods which will do the above work, but due to readability I want them to be inside another class.
In MainWindow I call this method inside another class, which again, works as intended:
public void WriteSettingsToFile(CustomSettings cs)
{
XmlSerializer mySerializer = new XmlSerializer(typeof(CustomSettings));
StreamWriter myWriter = new StreamWriter(#"U:\Alex\prefs.xml");
mySerializer.Serialize(myWriter, cs);
myWriter.Close();
}
The de-serialization function I wrote does not work, though. It loads the file successfully, returning the correct path value but as soon as it returns to the calling class, the string is always null. I rewrote the method several times, trying void, non-void returning a string, static etc. but I'm stuck.
Any help and ideas are appreciated!
Edit:
Here's one of the tries with the deserialization method:
public void GetInitPath(CustomSettings cs)
{
XmlSerializer _mySerializer = new XmlSerializer(typeof(CustomSettings));
FileStream myFstream = new FileStream(#"U:\Alex\prefs.xml", FileMode.Open);
cs = (CustomSettings)_mySerializer.Deserialize(myFstream);
myFstream.Close();
}
Edit 2:
With the help of Matthew Watson I was able to solve my problem.
public void GetInitPath(ref CustomSettings cs)
{
XmlSerializer serializer = new XmlSerializer(typeof(CustomSettings));
using (var myFstream = new FileStream(#"U:\Alex\prefs.xml", FileMode.Open))
{
cs = (CustomSettings)serializer.Deserialize(myFstream);
}
}
This is your method that doesn't work:
public void GetInitPath(CustomSettings cs)
{
XmlSerializer _mySerializer = new XmlSerializer(typeof(CustomSettings));
FileStream myFstream = new FileStream(#"U:\Alex\prefs.xml", FileMode.Open);
cs = (CustomSettings)_mySerializer.Deserialize(myFstream);
myFstream.Close();
}
The problem with this method is that it DOESN'T RETURN THE RESULT.
It overwrites the reference to cs that is passed to GetInitPath() with a reference to the object returned from .Deserialise(), but that does NOT change the original object passed to GetInitPath().
The solution is either to declare the parameter as a ref parameter, or (much better) change the method to return the result like so:
public CustomSettings GetInitPath()
{
XmlSerializer _mySerializer = new XmlSerializer(typeof(CustomSettings));
FileStream myFstream = new FileStream(#"U:\Alex\prefs.xml", FileMode.Open);
var cs = (CustomSettings)_mySerializer.Deserialize(myFstream);
myFstream.Close();
return cs;
}
Another observation: It's better to use using when possible to wrap resources that are disposable. If you do that, your method would look like this:
public CustomSettings GetInitPath()
{
var serializer = new XmlSerializer(typeof(CustomSettings));
using (var myFstream = new FileStream(#"U:\Alex\prefs.xml", FileMode.Open))
{
return (CustomSettings) serializer.Deserialize(myFstream);
}
}
This will close myFstream even if an exception occurs in Deserialize().
Related
I'm using this code to convert from an XElement to OpenXmlElement
internal static OpenXmlElement ToOpenXml(this XElement xel)
{
using (var sw = new StreamWriter(new MemoryStream()))
{
sw.Write(xel.ToString());
sw.Flush();
sw.BaseStream.Seek(0, SeekOrigin.Begin);
var re = OpenXmlReader.Create(sw.BaseStream);
re.Read();
var oxe = re.LoadCurrentElement();
re.Close();
return oxe;
}
}
Before the conversion I have an XElement
<w:ind w:firstLine="0" w:left="0" w:right="0"/>
After the conversion it looks like this
<w:ind w:firstLine="0" w:end="0" w:start="0"/>
This element then fails OpenXml validation using the following
var v = new OpenXmlValidator();
var errs = v.Validate(doc);
With the errors being reported:
Description="The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:start' attribute is not declared."
Description="The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:end' attribute is not declared."
Do I need to do other things to add these attributes to the schema or do I need to find a new way to convert from XElement to OpenXml?
I'm using the nuget package DocumentFormat.OpenXml ver 2.9.1 (the latest).
EDIT: Looking at the OpenXml standard, it seems that both left/start and right/end should be recognised which would point to the OpenXmlValidator not being quite correct. Presumably I can just ignore those validation errors then?
Many thx
The short answer is that you can indeed ignore those specific validation errors. The OpenXmlValidator is not up-to-date in this case.
I would additionally offer a more elegant implementation of your ToOpenXml method (note the using declarations, which were added in C# 8.0).
internal static OpenXmlElement ToOpenXmlElement(this XElement element)
{
// Write XElement to MemoryStream.
using var stream = new MemoryStream();
element.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
// Read OpenXmlElement from MemoryStream.
using OpenXmlReader reader = OpenXmlReader.Create(stream);
reader.Read();
return reader.LoadCurrentElement();
}
If you don't use C# 8.0 or using declarations, here's the corresponding code with using statements.
internal static OpenXmlElement ToOpenXmlElement(this XElement element)
{
using (var stream = new MemoryStream())
{
// Write XElement to MemoryStream.
element.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
// Read OpenXmlElement from MemoryStream.
using OpenXmlReader reader = OpenXmlReader.Create(stream);
{
reader.Read();
return reader.LoadCurrentElement();
}
}
}
Here's the corresponding unit test, which also demonstrates that you'd have to pass a w:document to have the w:ind element's attributes changed by the Indentation instance created in the process.
public class OpenXmlReaderTests
{
private const string NamespaceUriW = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
private static readonly string XmlnsW = $"xmlns:w=\"{NamespaceUriW}\"";
private static readonly string IndText =
$#"<w:ind {XmlnsW} w:firstLine=""10"" w:left=""20"" w:right=""30""/>";
private static readonly string DocumentText =
$#"<w:document {XmlnsW}><w:body><w:p><w:pPr>{IndText}</w:pPr></w:p></w:body></w:document>";
[Fact]
public void ConvertingDocumentChangesIndProperties()
{
XElement element = XElement.Parse(DocumentText);
var document = (Document) element.ToOpenXmlElement();
Indentation ind = document.Descendants<Indentation>().First();
Assert.Null(ind.Left);
Assert.Null(ind.Right);
Assert.Equal("10", ind.FirstLine);
Assert.Equal("20", ind.Start);
Assert.Equal("30", ind.End);
}
[Fact]
public void ConvertingIndDoesNotChangeIndProperties()
{
XElement element = XElement.Parse(IndText);
var ind = (OpenXmlUnknownElement) element.ToOpenXmlElement();
Assert.Equal("10", ind.GetAttribute("firstLine", NamespaceUriW).Value);
Assert.Equal("20", ind.GetAttribute("left", NamespaceUriW).Value);
Assert.Equal("30", ind.GetAttribute("right", NamespaceUriW).Value);
}
}
I need to deserialize Dictionary<> in C#. Since, XmlSerializer cannot be used for generic Dictionary type, I thought of using DataContractSerializer.I had a StreamReader in which the data was to be deserialized, but it showed unmatching overload parameters on using with DataContractSerializer.ReadObject.
My previous code was:
StreamReader isr = new StreamReader(File.OpenRead(algorithmName + ".conf"));
configuration.Load(isr);
The Load method is defined as:
public void Load(StreamReader isr)
{
DataContractSerializer dcs = new DataContractSerializer(typeof(the class containing this method));
XmlDictionaryReader read = XmlDictionaryReader.CreateTextReader(isr, new XmlDictionaryReaderQuotas());
dcs.ReadObject(isr);
}
The Load method is contained in class that implements Dictionary<>
But this showed parameter mismatched error due to StreamReader as parameter for ReadObject().
So, I modified the code as:
FileStream isr = File.OpenRead(algorithmName + ".conf");
configuration.Load(isr);
And the Load method as:
public void Load(FileStream isr)
{
DataContractSerializer dcs = new DataContractSerializer(typeof(the class containing Load method));
XmlDictionaryReader read = XmlDictionaryReader.CreateTextReader(isr, new XmlDictionaryReaderQuotas());
dcs.ReadObject(isr);
}
Now there are no errors but I wanted to know whether using DataContractSerializer in place of XmlSerializer is correct or not. Also, any updates on replacing streamreader with filestream would be highly appreciated.
I am trying to convert this method that deserializes an object into a string with Async/Await.
public static T DeserializeObject<T>(string xml)
{
using (StringReader reader = new StringReader(xml))
{
using (XmlReader xmlReader = XmlReader.Create(reader))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
T theObject = (T)serializer.ReadObject(xmlReader);
return theObject;
}
}
}
Most serialization APIs do not have async implementations, which means the only thing you can really do is wrap a sync method. For example:
public static Task<T> DeserializeObjectAsync<T>(string xml)
{
using (StringReader reader = new StringReader(xml))
{
using (XmlReader xmlReader = XmlReader.Create(reader))
{
DataContractSerializer serializer =
new DataContractSerializer(typeof(T));
T theObject = (T)serializer.ReadObject(xmlReader);
return Task.FromResult(theObject);
}
}
}
This isn't actually async - it just meets the required API. If you have the option, using ValueTask<T> is preferable in scenarios where the result may often be synchronous/
Either way, you should then be able to do something like:
var obj = await DeserializeObject<YourType>(someXml);
Debug.WriteLine(obj.Name); // etc
without needing to know whether the actual implementation was synchronous or asynchronous.
A little sample, pretty primitive way:
public delegate T Async<T>(string xml);
public void Start<T>()
{
string xml = "<Person/>";
Async<T> asyncDeserialization = DeserializeObject<T>;
asyncDeserialization.BeginInvoke(xml, Callback<T>, asyncDeserialization);
}
private void Callback<T>(IAsyncResult ar)
{
Async<T> dlg = (Async<T>)ar.AsyncState;
T item = dlg.EndInvoke(ar);
}
public T DeserializeObject<T>(string xml)
{
using (StringReader reader = new StringReader(xml))
{
using (XmlReader xmlReader = XmlReader.Create(reader))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
T theObject = (T)serializer.ReadObject(xmlReader);
return theObject;
}
}
}
you define a delegate and using it to Begin/End invoke using callbacks.
using the next versions of C# you can use the async keyword to get your code run asynchronously.
As you are working with a string as data source doing things async would only introduce more overhead and give you nothing for it.
But if you where reading from a stream you could copy from the source stream to a MemoryStream(buffering all data), then deserialize from the MemoryStream, that would increase the memory usage but would lower the amount of time you will block the thread.
You can return
Task.FromResult(theObject)
I'm currently having trouble deserializing an XmlDocument from a web service call, here is my code : -
public void getTest(XmlDocument requestDoc)
{
XmlDocument results = new XmlDocument();
XmlSerializer serial = new XmlSerializer(typeof(DataRequest));
DataRequest req;
XmlNodeReader reader = new XmlNodeReader(requestDoc.DocumentElement);
req = (DataRequest)serial.Deserialize(reader);
response.write(req.toString());
}
now, the trouble I am having is that the XmlNodeReader just contains "{None}" when I step through in debug, the requestDoc definatly has the expected XML structure, any ideas?
Kind regards
Gib
The "none" probably just means it hasn't started iterating yet, and is at BOF (for want of a better term). It should still work. Usually, if it doesn't it means the namespaces are incorrect - double-check for xmlns in the source.
This works fine, for example:
public class Test
{
static void Main()
{
var doc = new XmlDocument();
doc.LoadXml(#"<Test foo=""bar""></Test>");
var ser = new XmlSerializer(typeof(Test));
using (var reader = new XmlNodeReader(doc.DocumentElement))
{
var test = (Test)ser.Deserialize(reader);
Console.WriteLine(test.Foo);
}
}
[XmlAttribute("foo")]
public string Foo { get; set; }
}
Proper object disposal removed for brevity but I'm shocked if this is the simplest way to encode an object as UTF-8 in memory. There has to be an easier way doesn't there?
var serializer = new XmlSerializer(typeof(SomeSerializableObject));
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);
serializer.Serialize(streamWriter, entry);
memoryStream.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(memoryStream, System.Text.Encoding.UTF8);
var utf8EncodedXml = streamReader.ReadToEnd();
No, you can use a StringWriter to get rid of the intermediate MemoryStream. However, to force it into XML you need to use a StringWriter which overrides the Encoding property:
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}
Or if you're not using C# 6 yet:
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding { get { return Encoding.UTF8; } }
}
Then:
var serializer = new XmlSerializer(typeof(SomeSerializableObject));
string utf8;
using (StringWriter writer = new Utf8StringWriter())
{
serializer.Serialize(writer, entry);
utf8 = writer.ToString();
}
Obviously you can make Utf8StringWriter into a more general class which accepts any encoding in its constructor - but in my experience UTF-8 is by far the most commonly required "custom" encoding for a StringWriter :)
Now as Jon Hanna says, this will still be UTF-16 internally, but presumably you're going to pass it to something else at some point, to convert it into binary data... at that point you can use the above string, convert it into UTF-8 bytes, and all will be well - because the XML declaration will specify "utf-8" as the encoding.
EDIT: A short but complete example to show this working:
using System;
using System.Text;
using System.IO;
using System.Xml.Serialization;
public class Test
{
public int X { get; set; }
static void Main()
{
Test t = new Test();
var serializer = new XmlSerializer(typeof(Test));
string utf8;
using (StringWriter writer = new Utf8StringWriter())
{
serializer.Serialize(writer, t);
utf8 = writer.ToString();
}
Console.WriteLine(utf8);
}
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}
}
Result:
<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<X>0</X>
</Test>
Note the declared encoding of "utf-8" which is what we wanted, I believe.
Your code doesn't get the UTF-8 into memory as you read it back into a string again, so its no longer in UTF-8, but back in UTF-16 (though ideally its best to consider strings at a higher level than any encoding, except when forced to do so).
To get the actual UTF-8 octets you could use:
var serializer = new XmlSerializer(typeof(SomeSerializableObject));
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);
serializer.Serialize(streamWriter, entry);
byte[] utf8EncodedXml = memoryStream.ToArray();
I've left out the same disposal you've left. I slightly favour the following (with normal disposal left in):
var serializer = new XmlSerializer(typeof(SomeSerializableObject));
using(var memStm = new MemoryStream())
using(var xw = XmlWriter.Create(memStm))
{
serializer.Serialize(xw, entry);
var utf8 = memStm.ToArray();
}
Which is much the same amount of complexity, but does show that at every stage there is a reasonable choice to do something else, the most pressing of which is to serialise to somewhere other than to memory, such as to a file, TCP/IP stream, database, etc. All in all, it's not really that verbose.
Very good answer using inheritance, just remember to override the initializer
public class Utf8StringWriter : StringWriter
{
public Utf8StringWriter(StringBuilder sb) : base (sb)
{
}
public override Encoding Encoding { get { return Encoding.UTF8; } }
}
I found this blog post which explains the problem very well, and defines a few different solutions:
(dead link removed)
I've settled for the idea that the best way to do it is to completely omit the XML declaration when in memory. It actually is UTF-16 at that point anyway, but the XML declaration doesn't seem meaningful until it has been written to a file with a particular encoding; and even then the declaration is not required. It doesn't seem to break deserialization, at least.
As #Jon Hanna mentions, this can be done with an XmlWriter created like this:
XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });