We are using XMLSerializer.Deserialize(XMLReader) on .Net 4.5 to deserialize XML into an Object Graph generated by xsd.exe.
I would like to know what the expected deserialization behavior is for an array which is annotated with the XMLElementAttribute - specifically with regard to ordering. For example:
For the following property:
[System.Xml.Serialization.XmlElementAttribute("GivenName")]
public string[] GivenName {
// get() and set() methods
}
And the following XML:
<root>
<GivenName>One</GivenName>
<GivenName>Two</GivenName>
<GivenName>Three</GivenName>
</root>
Will this always deserialize as ['One', 'Two', 'Three']
So that the array order always matches the XML order
Also is there any documentation I can reference that clearly states this.
Thanks
Rob
Yes Its deserialize same order. Check the code sample code and output:
[System.SerializableAttribute()]
public class SampleClass
{
[System.Xml.Serialization.XmlElementAttribute(Order = 10)]
public string Foo { get; set; }
[System.Xml.Serialization.XmlElementAttribute(Order = 5)]
public string Bar { get; set; }
[System.Xml.Serialization.XmlElementAttribute("GivenName", Order = 15)]
public string[] GivenNames { get; set; }
}
class Program
{
static void Main(string[] args)
{
string[] names = new string[3] { "One", "Two", "Three" };
SampleClass TestObj = new SampleClass { Bar = "dsdfsdf", Foo = "test", GivenNames = names };
XmlSerializer SerializerObj = new XmlSerializer(typeof(SampleClass));
// Create a new file stream to write the serialized object to a file
TextWriter WriteFileStream = new StreamWriter(#"C:\files\test.xml");
SerializerObj.Serialize(WriteFileStream, TestObj);
// Cleanup
WriteFileStream.Close();
// Create a new file stream for reading the XML file
FileStream ReadFileStream = new FileStream(#"C:\files\test.xml", FileMode.Open, FileAccess.Read, FileShare.Read);
// Load the object saved above by using the Deserialize function
SampleClass LoadedObj = (SampleClass)SerializerObj.Deserialize(ReadFileStream);
// Cleanup
ReadFileStream.Close();
}
}
You can find some awesome information about it on msdn which is provide by microsoft.
This is the "official" documentation
The serialization is linear so you will alway get the same order normally.
Related
I want to load a XML file with XML serialization. The type now should be an enum type.
So the XML looks like this:
<Ressource name="ressource_name" type= "Integer" >
...
</Ressource>
And I wanted to load it into a class like this:
[Serializable]
public enum Res_Type
{
[XmlEnum(Name = "Integer")]
Integer,
[XmlEnum(Name = "Decimal")]
Decimal,
[XmlEnum(Name = "Text")]
Text
}
public class Ressource
{
[XmlAttribute]
public string name { set; get; }
[XmlAttribute]
public Res_Type type { get; set; }
}
When I search for this topic I only find different ways of solving it, then I need it to. I need to have the XML like shown above, but I have no idea how to load the information in type as an enum.
Update:
To test the serialization and the deserialization I am using this code:
Ressource res = new Ressource();
res.name = "ressource_name";
res.type = Res_Type.Integer;
XmlSerializer serializer = new XmlSerializer(res.GetType());
using (StreamWriter writer = new StreamWriter(#"h:\test.xml"))
{
serializer.Serialize(writer, res);
}
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Ressource));
StringReader stringReader = new StringReader(#"h:\test.xml");
res = (Ressource)xmlSerializer.Deserialize(stringReader);
And I am getting the error: InvalidOperationException
Your problem is that you are using a StringReader rather than a StreamReader:
StringReader stringReader = new StringReader(#"h:\test.xml");
This means that your code is attempting to deserialize the contents of the string literal #"h:\test.xml" itself rather than the file to which it refers. This of course fails because the string h:\test.xml is not even well-formed XML.
Instead you should do:
var fileName = #"h:\test.xml";
// Write the file as before
using (var reader = new StreamReader(fileName))
{
res = (Ressource)xmlSerializer.Deserialize(reader);
}
Working .Net fiddle here.
So I have a class called OutputInformation which I'd like to store and then read on another computer to retrieve the data.
I'm using binary serialization.
[Serializable()]
public class OutputInformation
{
public List<string> filenames { get; set; }
public long[] filesizes { get; set; }
}
public void Write()
{
OutputInformation V = new OutputInformation();
V.filesizes = sizearray;
V.filenames = namelist;
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("D:\\MyFile.bin", FileMode.Create,
FileAccess.Write, FileShare.None);
formatter.Serialize(stream, V);
stream.Close();
}
The serialization is done within a user control and if I deserialize in the user control, it works fine.
But if I try to deserialize from my main window, I get an invalidcastexception. So I would imagine the same problem would occur if I tried to deserialize the file from another computer.
How do I solve this? I just need to store the class in a file and retrieve it later from a different computer. Preferably not use XML serialization.
[Serializable()]
public class OutputInformation
{
public List<string> filenames { get; set; }
public long[] filesizes { get; set; }
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("D:\\MyFile.bin", FileMode.Open,
FileAccess.Read, FileShare.Read);
OutputInformation obj = (OutputInformation)formatter.Deserialize(stream);
stream.Close();
Error is an InvalidCastException. Additional information: [A]OutputInformation cannot be cast to [B]OutputInformation.
Both classes should be in the same namespace. Define your OutputInformation class on deserialization in exact the same namespace as on serialization (or refer assembly with it).
Or, if it is not possible, or you prefer not to do it, you should write your own implementation of SerializationBinder
public class ClassOneToNumberOneBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
typeName = typeName.Replace(
"oldNamespace.OutputInformation",
"newNamespace.OutputInformation");
return Type.GetType(typeName);
}
}
And set it before deserialization:
formatter.Binder = new ClassOneToNumberOneBinder();
Look here for more details: Is it possible to recover an object serialized via "BinaryFormatter" after changing class names?
I have been able to use ShouldSerializeProperty pattern with XmlSerializer to ignore a property of type ICollection<>. Below is the sample code. It works if I use XmlIgnore on ListOfTestClassB property but I want to achieve the same functionality utilizing ShouldSerialize pattern. If I run the code below I get the 'System.InvalidOperationException' saying:
Cannot serialize member ConsoleApplication3.TestClassA.ListOfTestClassB of type System.Collections.Generic.ICollection`1[[ConsoleApplication3.TestClassB, ConsoleApplication3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface.
Can anyone highlight what am I missing?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication3
{
[Serializable]
public class TestClassA
{
public int A { get; set; }
public int B { get; set; }
public string C { get; set; }
public TestClassB TestClass { get; set; }
public virtual ICollection<TestClassB> ListOfTestClassB { get; set; }
public bool ShouldSerializeListOfTestClassB()
{
return false;
}
}
[Serializable]
public class TestClassB
{
public int Int32 { get; set; }
public string String { get; set; }
}
class Program
{
static object GetObject()
{
return new TestClassA { A = 1, B = 2, C = "test class a", TestClass = new TestClassB { Int32 = 11, String = "test class b"} };
}
static void Main(string[] args)
{
var result = new StringBuilder();
var entity = GetObject();
var ser = new XmlSerializer(entity.GetType());
var settings = new XmlWriterSettings { OmitXmlDeclaration = true };
using (var stream = new MemoryStream())
{
// make a copy of the entity - we do not want to serialize ZIP file !
var formatter = new BinaryFormatter();
formatter.Serialize(stream, entity);
stream.Position = 0;
entity = formatter.Deserialize(stream);
}
// serialize object
ser.Serialize(XmlWriter.Create(result, settings), entity);
Console.WriteLine(result.ToString());
Console.ReadLine();
}
}
}
This is where the check happens: http://referencesource.microsoft.com/#System.Xml/System/Xml/Serialization/Models.cs,249.
As you can see, it doesn't call the ShouldSerialize* method until after it's already identified fields/properties to serialize. So, your ListOfTestClassB has to be serializable, or it must be decorated with [XmlIgnore]. In order to be serializable your property must be of a concrete type that has the [Serializable] attribute applied.
There's a workaround if you can't modify the class. One of the overloads of the XmlSerializer.Serialize(...) method accepts an overrides object. I've created a simple example below:
[Serializable]
public class Foo
{
public IList<int> Numbers { get; set; }
public string TheNumber { get; set; }
}
class Program
{
private static void Main(string[] args)
{
var attributes = new XmlAttributes
{
XmlIgnore = true
};
var overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Foo), "Numbers", attributes);
var serializer = new XmlSerializer(typeof(Foo), overrides);
// the rest of this is for demo purposes.
// the code above is whats important
//
using (var ms = new MemoryStream())
using (var reader = new StreamReader(ms))
{
serializer.Serialize(ms, new Foo() { TheNumber = "5" });
ms.Flush();
ms.Seek(0, SeekOrigin.Begin);
Debug.WriteLine(reader.ReadToEnd());
}
}
}
This generates:
<?xml version="1.0"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TheNumber>5</TheNumber>
</Foo>
As you can see, I'm dynamically adding the XmlIgnore attribute to my Numbers element, and the serializer consequently ignores it. :) You should have no trouble adapting this to your own code.
Note: As noted by dbc, it's important to cache this serializer and re-use it, otherwise you're going to have a lot of memory leaks. You can keep a static reference to it, or use a hashtable to store different serializers for different types.
To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when using the following constructors:
XmlSerializer.XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type, String)
If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.
If you use XmlIgnore, then it will not care about that property at all. If you use ShouldSerialize, it doesn't know until runtime whether it should serialize that type, so it must be able to. In this case, the type you're trying to serialize must be a concrete class. Try using List<TestClassB>.
I have a class which I need to serialize using XMLserializer in C#
[DataContract]
Public class X
{
[Datamember]
Public List<string> CodeList {get;set;}
}
But as the Data type of CodeList is string, I am getting below xml
<CodeList>
<string>asd</string>
<string>cvb</string>
</CodeList>
But I want to show the xml in the format wherein string is shown as code i.e I want to give string an alias name of code.
<CodeList>
<Code>asd</Code>
<Code>cvb</Code>
</CodeList>
You mention using the XMLSerializer but you are using attributes for the DataContractSerializer. If you stick to using the XMLSerializer it is dead simple.
class Program
{
static void Main(string[] args)
{
X x = new X();
x.CodeList = new List<string>() {"test", "test1"};
var xml = new XmlSerializer(typeof (X));
TextWriter writer = new StreamWriter("test.xml");
xml.Serialize(writer,x);
writer.Close();
}
}
public class X
{
[XmlArrayItem("Code")]
public List<string> CodeList { get; set; }
}
You notice that I don't need any attributes other than the [XmlArrayItem]. That is because the XmlSerializer will go ahead and serialize all public properties. If that is a problem, you can use the [System.Xml.Serialization.XmlIgnoreAttribute], which will ignore a property while serializing.
This is the output I got from my code above:
<?xml version="1.0" encoding="utf-8"?>
<X xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CodeList>
<Code>test</Code>
<Code>test1</Code>
</CodeList>
</X>
I have two classes SccmAction and TicketAction which both implement interface IDelivery. These classes will be transmitted on a processing queue from which I just want to pull the message and act upon the Deliver method.
It seems however that I cannot deserialize to an interface because when I attempt to do so a System.NotSupportedException is thrown. From what I have gathered the XMLSerializer requires a concrete type for serialization. Does this leave me having to create an abstract class which implements IDelivery and inheriting SccmAction and ArsAction from it? Have I missed something that?
Edit Further Clarification
I may have a design flaw but my purpose is to pull messages off of a queue containing these objects. My intention was to have all objects coming off of this queue implement the interface ensuring that the application processing the objects just operates on the Deliver method. Each objects implementation of Deliver would vary.
Code to Deserialize
using (SqlConnection connection =
new SqlConnection(ConnectionString))
{
SqlCommand command = new SqlCommand(ReceiveString, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
byte[] sb = (byte[])reader["message_body"];
SccmAction sa = DeserializeObject<SccmAction>(sb);
IDelivery iD = DeserializeObject<IDelivery>(sb);
}
reader.Close();
}
public static T DeserializeObject<T>(byte[] ba)
{
XmlSerializer xs = new XmlSerializer(typeof(T));
MemoryStream memoryStream = new MemoryStream(ba);
XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
return (T)xs.Deserialize(memoryStream);
}
Classes and Interface
[Serializable]
public class SccmAction : IDelivery
{
public string MachineName { get; set; }
public string CollectionName { get; set; }
public string Action { get; set; }
public DateTime EntryDateTime { get; set; }
public SccmAction () { }
public void Deliver()
{
throw new NotImplementedException();
}
}
[Serializable]
public class ArsAction : IDelivery
{
public string MachineName { get; set; }
public string CustomerName { get; set; }
public ArsAction() { }
public void Deliver()
{
throw new NotImplementedException();
}
}
public interface IDelivery
{
void Deliver();
}
Simply put, you can't serialize an interface without any dirtiness. But why do you want to serialize your interface anyway, it doesn't hold any state. It only has a declaration for a method.
You might want to create a Serialize method inside your classes. So they can serialize themselves individually, they know which type they are.
Besides, how do you expect the XmlSerializer to Deserialize to an exact type without providing it? It can't simply pick what to Deserialize to....
You could however change this line
IDelivery iD = DeserializeObject<IDelivery>(sb);
to this
IDelivery iD = DeserializeObject<SccmAction>(sb);
If you dont know what type you are deserializing to initially (ArsAction or SccmAction), then you can do something like this.
public static IDelivery DeserializeFromString(string xml)
{
//replace this stream with whatever you are useing
StringReader strReader = new StringReader(xml);
XmlReader reader = new XmlTextReader(fs); //important to use XmlReader
reader.MoveToContent(); //move to root
String className = reader.Name.Trim(); //read the class name
//use the namespace IDelivery is located in
className = "IDeliveryNamespace." + className;
//get the type
Type classType = Type.GetType(className);
XmlSerializer serializer = new XmlSerializer(Type.GetType(className));
// Declare an object variable of the type to be deserialized.
IDelivery i;
// Use the Deserialize method to restore the object's state.
i = (IDelivery)Convert.ChangeType(serializer.Deserialize(reader),classType);
return i;
}
Of course this assumes that you your Serializable class Name is not changed. Meaning ArsAction class serializes to and as far as I can tell from what you posted that is the case.
This code is a little dirty, but it should give you a starting point.