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 love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With