How to use derived class for XML serialization? - c#

I am converting my working XML serialization so that the model classes inherit from abstract base classes (to allow for future use of different serial formats).
My serialization as-is is working fine but when I switch to using models derived from a base class I get all kinds of exceptions so I'm unsure of how to proceed.
My class base class is:
namespace Data
{
public abstract class Configuration
{
public abstract string Schema { get; set; }
public abstract Command[] Commands { get; set; }
}
public abstract class Command
{
public abstract string Name { get; set; }
}
}
My derived concrete class (the class that is working just fine before it was derived) is then in a child namespace:
namespace Data.Xml
{
[Serializable()]
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false)]
public class Configuration : Data.Configuration
{
[XmlAttribute("noNamespaceSchemaLocation",
Namespace = System.Xml.Schema.XmlSchema.InstanceNamespace)]
public override string Schema { get; set; }
[XmlArrayItem("Command", IsNullable = false)]
public override Data.Command[] Commands { get; set; }
}
[Serializable()]
public class Command : Data.Command
{
public override string Name { get; set; }
}
}
I call the serializer within that child namespace like so:
public override Data.Configuration DeserializeConfig(StreamReader config)
{
var cs = new XmlSerializer(typeof(Configuration),
new Type[] { typeof(Command) });
return (Configuration)ConfigSerializer.Deserialize(ConfigStreamReader);
}
public override string SerializeConfig(Data.Configuration c, Encoding encoding)
{
string Output = null;
var Stream = new MemoryStream();
using (var Writer = new XmlTextWriter(Stream, encoding))
{
Writer.Formatting = Formatting.Indented;
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("xsi", XmlSchema.InstanceNamespace);
(new XmlSerializer(typeof(Configuration))).Serialize(Writer, c, ns);
Output = encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
The resulting XML should look like:
<?xml version="1.0" encoding="utf-8"?>
<Configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="SomeSchema.xsd">
<Commands>
<Command>
<Name>SomeNameValue</Name>
</Command>
</Commands>
</Configuration>
I am seeing the following exception when attempting to instantiate the serializer (first line in DeserializeConfig() method above):
InvalidOperationException: Types 'Data.Command' and 'Data.Xml.Command' both use the XML type name, 'Command', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.
I'm not really sure why the serializer is trying to create something from the base class, sure the names are the same, that's kind of the idea of derivation and namespaces etc ... How do I properly mark this up with attributes to have it de/serialize properly?
FYI: I did see several questions already on this, but the answers all seemed specific enough to the askers requirements that I could not work out how to apply the information to this, seemingly simple, scenario.
Update: I figured out how to pass included types into the serializer at instantiation instead of having to annotate the base class so I have removed that part from my question and updated the code. This outdates bruno's suggestion and my response (although the suggested question still does not apply).
Update: I attempted to separate the names in XML namespaces by adding the derived class to a namespace (i.e. adding [XmlElement(Namespace = "http://www.foo.com/data/xml")] to each property in the derived class) but this made no difference as the serializer still seems to "see" both the base and derived class together and so thinks they're both in that namespace.

Finally flipping figured most of this out.
I stepped back and started with a very simple working non-derived example and worked up to what I needed.
There were two things going on here. First the clashing type names, then the clashing property names. While I had bits of each of these right, the amount of permutations of options for structuring each when combined together had me confused.
To prevent the abstract and derived type names from clashing when serialized I needed to make the derived class type anonymous (here using the XmlType attribute).
To stop the property names clashing I needed to ignore both the property in the derived class and the base class. To do this without editing the base class I was missing a vital piece, XmlAttributeOverrides. I had seen this mentioned in the MSDN documentation for XmlSerializer.Serialize() but the information there was pretty minimal in explaining what it pertained to. This answer to another question led me to David Woodward's excellent explanation.
I have yet to try any of this with a derived type list property, or with deserialization.
Below is complete basic example of a program that outputs a string with some serialized XML on the console output:
using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var TestBar = new MyXml.Bar()
{
Name = "John Smith",
};
Serializer s = new MyXml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
}
}
public abstract class Bar
{
public abstract string Name { get; set; }
}
public abstract class Serializer
{
public abstract string Serialize(Bar bar);
}
namespace MyXml
{
public class Serializer : Test.Serializer
{
public override string Serialize(Test.Bar bar)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);
// Ignore the Name property in the *base* class!
var ao = new XmlAttributeOverrides();
var a = new XmlAttributes();
a.XmlElements.Clear(); // Clear any element attributes
a.XmlAttribute = null; // Remove any attribute attributes
a.XmlIgnore = true; // Set the ignore attribute value true
ao.Add(typeof(Test.Bar), "Name", a); // Set to use with Test.Bar.Name
using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
var s = new XmlSerializer(typeof(Bar), ao);
s.Serialize(Writer, bar);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}
[Serializable]
[XmlType(AnonymousType = true)] // Make type anonymous!
[XmlRoot(IsNullable = false)]
public class Bar : Test.Bar
{
[XmlIgnore] // Ignore the Name property in the *derived* class!
public override string Name
{
get => Unreverse(ReverseName);
set => ReverseName = Reverse(value);
}
[XmlElement("Name", IsNullable = false)]
public string ReverseName { get; set; }
private string Unreverse(string name)
{
return "John Smith"; // Smith, John -> John Smith
}
private string Reverse(string name)
{
return "Smith, John"; // John Smith -> Smith, John
}
}
}
}

Related

How to solve "Both use the XML type name X, use XML attributes to specify a unique XML name and/or namespace for the type"?

I have the following enum definitions...
namespace ItemTable
{
public enum DisplayMode
{
Tiles,
Default
}
}
namespace EffectiveItemPermissionTable
{
public enum DisplayMode
{
Tree,
FullPaths
}
}
...and then i have the following classes...
public class Table<TDisplayMode>
where TDisplayMode: struct
{
// public
public TDisplayMode DisplayMode
{
get { return mDisplayMode; }
set { mDisplayMode = value; }
}
// private
private TDisplayMode mDisplayMode;
}
public class ItemTable : Table<ItemTable.DisplayMode>
{}
public class EffectiveItemPermissionTable : Table<EffectiveItemPermissionTable.DisplayMode>
{}
public class UISettings
{
public UISettings()
{
ItemTable = new ItemTable();
EffectiveItemPermissionTable = new EffectiveItemPermissionTable();
}
public ItemTable ItemTable { get; set; }
public EffectiveItemPermissionTable EffectiveItemPermissionTable { get; set; }
}
...and when i try to serialize an instance of UISettings with...
System.Xml.Serialization.XmlSerializer lSerializer =
new System.Xml.Serialization.XmlSerializer(typeof(UISettings));
...i get the following error:
Types 'UISettings.Table`1[EffectiveItemPermissionTable.DisplayMode]' and
'UISettings.Table`1[ItemTable.DisplayMode]' both use the XML type name,
'TableOfDisplayMode', from namespace ''.
Use XML attributes to specify a unique XML name and/or namespace for the type.
I have tried to use XmlType attribubtes and all sorts of solutions posted on the web but nothing works. The XML type name is always TableOfDisplayMode as mentioned in the error.
The only solution right now is to rename one of the enums, e.g. to DisplayMode_ but I find that rather ugly.
You need to provide the Namespace by using the XmlElement attribute on the properties of your UISettings class:
public class UISettings
{
public UISettings()
{
ItemTable = new ItemTable();
EffectiveItemPermissionTable = new EffectiveItemPermissionTable();
}
[XmlElement(Namespace = "Item")]
public ItemTable ItemTable { get; set; }
[XmlElement(Namespace = "Permissions")]
public EffectiveItemPermissionTable EffectiveItemPermissionTable { get; set; }
}
When applied here this will be your serialized output:
<?xml version="1.0" encoding="utf-16"?>
<UISettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ItemTable xmlns="Item">
<DisplayMode>Tiles</DisplayMode>
</ItemTable>
<EffectiveItemPermissionTable xmlns="Permissions">
<DisplayMode>FullPaths</DisplayMode>
</EffectiveItemPermissionTable>
</UISettings>
Alternatively, and maybe cleaner, you can provide the Namespace on the types:
[XmlType(Namespace="Item")]
public class ItemTable : Table<ItemTableNS.DisplayMode>
{ }
[XmlType(Namespace = "Permission")]
public class EffectiveItemPermissionTable : Table<EffectiveItemPermissionTableNS.DisplayMode>
{ }
This will serialize as:
<?xml version="1.0" encoding="utf-16"?>
<UISettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ItemTable>
<DisplayMode xmlns="Item">Tiles</DisplayMode>
</ItemTable>
<EffectiveItemPermissionTable>
<DisplayMode xmlns="Permission">FullPaths</DisplayMode>
</EffectiveItemPermissionTable>
</UISettings>
I realize this answer probably comes way too late for the OP, but there is a way to do this without using namespaces, so I'm going to leave an answer here in case somebody comes along after me and needs the solution.
The problem is caused by the fact that the way the XmlSerializer names a type X<Y> is by giving it the name XOfY. Thus, when you have two types that both derive from Table<TDisplayMode>, you get that error, since they'll both be known internally as TableOfDisplayMode, despite actually using different enums.
This is because ItemTable and EffectiveItemPermissionTableare actually not inheriting from the same type! One inherits from Table<ItemTable.DisplayMode> and the other from Table<EffectiveItemPermissionTable.DisplayMode>. Not that this is limited to inheritance; you'd face the same problem if you tried to use them directly in the same XML object graph also.
Now, for the non-generic counterpart to this problem, you'd just smack an [XmlType] on the two types, and call it a day. But you can't do that here. While Table<ItemTable.DisplayMode> and Table<EffectiveItemPermissionTable.DisplayMode> are different types, they share the same class definition, so by trying to use [XmlType], you're giving them a different name, but still the same name.
So what can you do? XmlAttributeOverrides to the rescue! It lets you override the names the XmlSerializer gives to closed generic types, meaning that you can finally give a different name to Table<ItemTable.DisplayMode> and Table<EffectiveItemPermissionTable.DisplayMode>:
var xmlOverrides = new XmlAttributeOverrides();
var xmlAttribs = new XmlAttributes();
xmlAttribs.XmlType = new XmlTypeAttribute("TableOfItemTableDisplayMode");
xmlOverrides.Add(typeof(Table<ItemTable.DisplayMode>), xmlAttribs);
xmlAttribs = new XmlAttributes();
xmlAttribs.XmlType = new XmlTypeAttribute("TableOfEffectiveItemPermissionTableDisplayMode");
xmlOverrides.Add(typeof(Table<EffectiveItemPermissionTable.DisplayMode>), xmlAttribs);
System.Xml.Serialization.XmlSerializer lSerializer =
new System.Xml.Serialization.XmlSerializer(typeof(UISettings), xmlOverrides);
And voilĂ ! Assuming now that you've also put [XmlType] with different names for your DisplayMode enums, so that their names don't conflict either, you've got yourself a working setup!

Unable to make ShouldSerialize pattern work with XmlSerializer

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>.

Protobuf-net deserializing after defining inheritance

I'm using protobuf-net version 2.0.0.640 to serialize some data as shown below.
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public interface ITestMessage
{
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrder : ITestMessage
{
public int Amount { get; set; }
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrderWrapper
{
public MyOrder Order { get; set; }
}
[TestMethod]
public void TestOrderSerialize()
{
var order = new MyOrder() {Amount = 10};
var orderWrapper = new MyOrderWrapper() { Order = order };
using (var file = File.Create("C:\\temp\\order.bin"))
{
Serializer.Serialize<MyOrderWrapper>(file, orderWrapper);
}
}
Now, If I declare an inheritance dependency between 'ITestMessage' & 'MyOrder' via code using:
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
I get the following error when trying to deserialize my prevously saved data.
"No parameterless constructor found for ITestMessage".
[TestMethod]
public void TestOrderDeserialize()
{
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
MyOrderWrapper orderWrapper;
using (var file = File.OpenRead("C:\\temp\\order.bin"))
{
orderWrapper = Serializer.Deserialize<MyOrderWrapper>(file);
}
}
Can someone please explain why this would happen when 'MyOrderWrapper' does not reference anything higher than 'MyOrder' in the inheritance hirarchy.
Also, why it works when I explictly include '[ProtoInclude(2, typeof(MyOrder))]' on 'ITestMessage'
Thanks
Basically, that is a breaking change as far as the serializer is concerned - at the wire layer, neither "classes" nor "interfaces" exist, so in terms of storage, this is akin to changing the base-type of the class; when serializing, the root type was MyOrder - and during deserialization the root type is ITestMessage. This will not make it happy.
Basically: you can't do that.

Using Web API to deserialize into class with abstract property

I'm attempting to write a set of classes to represent a particularly complex object, and in one of those classes, I have a property that is set as the base (abstract) class of three possible derived classes. I'm setting up an ASP.NET Web API to handle the serialization and deserialization, which means that, by default, it uses Json.NET for JSON. How can I get the Web API to properly deserialize JSON sent via POST or PUT into the proper derived class?
The class with the abstract member looks like this (I'm including the Xml decorators for clarity and because they work perfectly well for deserializing xml using the XmlSerializer)
[Serializable]
public class FormulaStructure {
[XmlElement("column", typeof(ColumnStructure))]
[XmlElement("function", typeof(FunctionStructure))]
[XmlElement("operand", typeof(OperandStructure))]
public AFormulaItemStructure FormulaItem;
}
The abstract class is pretty basic:
[Serializable]
public abstract class AFormulaItemStructure { }
And there are three derivatives of the abstract class:
[Serializable]
public class ColumnStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlAttribute("field")]
public string Field;
[XmlAttribute("display")]
public string Display;
}
[Serializable]
public class FunctionStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlAttribute("name")]
public string Name;
[XmlElement("parameters")]
public string Parameters;
}
[Serializable]
public class OperandStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlElement("left")]
public string Left;
[XmlElement("right")]
public string Right;
}
At present, using [DataContract] attributes, the Json.NET formatter fails to populate the derived class, leaving the property null.
Questions
Can I mix XmlSerializer attributes with DataContractSerializer attributes on the same class? I use the XmlSerializer because I use xml attributes in the xml I designed, but that can be changed if necessary since I am developing the xml schema myself.
What is the equivalent in Json.NET to [KnownType()] ? Json.NET doesn't appear to respect the DataContractSerializer version of KnownType. Will I need to roll my own JsonConverter to determine the proper type?
How would I decorate the classes so that DataContractSerializer or DataContractJsonSerializer will properly deserialize the objects in both Xml and Json? My goal is to put this into an ASP.NET Web API, so I want the flexibility to generate Xml or Json, as appropriate to the requested type. Is there an alternative formatter that I need to use to work with this complex class, if Json.NET won't work?
I need the ability to generate an object on the client side without necessarily including the .NET class names into the object.
Testing and Refinement
In my testing of the Web API, the default serialization sends down to the client:
{"FormulaItem":{"type":"int","field":"my_field","display":"My Field"}}
which is ideal for my purposes. Getting this to go back to the API and deserialize into the proper derived types, though, isn't working (it's generating null for the property).
Testing Tommy Grovnes answer below, the DataContractSerializer he used for testing generates:
{"FormulaItem":{"__type":"column:#ExpressionStructureExperimentation.Models","display":"My Field","field":"my_field","type":"int"}}
which doesn't work for me, or for code maintainability (refactoring becomes a PITA if I hard-code the entire namespace into the JavaScript for generating these objects).
You can mix as mentioned already but I don't think you need to, haven't used WEB api myself but WCF Rest produces xml and json from DataContracts (without Xml.. tags), tag your classes like this:
[DataContract]
public class FormulaStructure
{
[DataMember]
public AFormulaItemStructure FormulaItem;
}
[DataContract]
[KnownType(typeof(ColumnStructure))]
[KnownType(typeof(FunctionStructure))]
[KnownType(typeof(OperandStructure))]
public abstract class AFormulaItemStructure { }
[DataContract(Name="column")]
public class ColumnStructure : AFormulaItemStructure
{
[DataMember(Name="type")]
public string Type;
[DataMember(Name = "field")]
public string Field;
[DataMember(Name = "display")]
public string Display;
}
[DataContract(Name="function")]
public class FunctionStructure : AFormulaItemStructure
{
[DataMember(Name = "type")]
public string Type;
[DataMember(Name = "name")]
public string Name;
[DataMember(Name = "parameters")]
public string Parameters;
}
[DataContract(Name = "operand")]
public class OperandStructure : AFormulaItemStructure
{
[DataMember(Name = "type")]
public string Type;
[DataMember(Name = "left")]
public string Left;
[DataMember(Name = "right")]
public string Right;
}
If you need more control over the XML/JSON generated you might have to tweak this further. I used this code to test:
public static string Serialize(FormulaStructure structure)
{
using (MemoryStream memoryStream = new MemoryStream())
using (StreamReader reader = new StreamReader(memoryStream))
{
var serializer = new DataContractSerializer(typeof(FormulaStructure));
serializer.WriteObject(memoryStream, structure);
memoryStream.Position = 0;
return reader.ReadToEnd();
}
}
public static FormulaStructure Deserialize(string xml)
{
using (Stream stream = new MemoryStream())
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
stream.Write(data, 0, data.Length);
stream.Position = 0;
var deserializer = new DataContractSerializer(typeof(FormulaStructure));
return (FormulaStructure)deserializer.ReadObject(stream);
}
}
After we ran into some issues much further down the line with my previous answer, I discovered the SerializationBinder class that JSON can use for serializing/deserializing namespaces.
Code First
I generated a class to inherit the SerializationBinder:
public class KnownTypesBinder : System.Runtime.Serialization.SerializationBinder {
public KnownTypesBinder() {
KnownTypes = new List<Type>();
AliasedTypes = new Dictionary<string, Type>();
}
public IList<Type> KnownTypes { get; set; }
public IDictionary<string, Type> AliasedTypes { get; set; }
public override Type BindToType(string assemblyName, string typeName) {
if (AliasedTypes.ContainsKey(typeName)) { return AliasedTypes[typeName]; }
var type = KnownTypes.SingleOrDefault(t => t.Name == typeName);
if (type == null) {
type = Type.GetType(Assembly.CreateQualifiedName(assemblyName, typeName));
if (type == null) {
throw new InvalidCastException("Unknown type encountered while deserializing JSON. This can happen if class names have changed but the database or the JavaScript references the old class name.");
}
}
return type;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName) {
assemblyName = null;
typeName = serializedType.Name;
}
}
How it works
Let's say I have a set of classes defined thus:
public class Class1 {
public string Text { get; set; }
}
public class Class2 {
public int Value { get; set; }
}
public class MyClass {
public Class1 Text { get; set; }
public Class2 Value { get; set; }
}
Aliased Types
What this does is allows me to generate my own names for classes that will be serialized/deserialized. In my global.asax file, I apply the binder as such:
KnownTypesBinder binder = new KnownTypesBinder()
binder.AliasedTypes["Class1"] = typeof(Project1.Class1);
binder.AliasedTypes["WhateverStringIWant"] = typeof(Project1.Class2);
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Binder = binder;
Now, whenever I serialize, say, MyClass as JSON, I get the following:
{
item: {
$type: "Project1.MyClass",
Text: {
$type: "Class1",
Text: "some value"
},
Value: {
$type: "WhateverStringIWant",
Value: 88
}
}
}
Known Types
I can also choose to strip off the assembly information and strictly use the class name by adding information to the KnownTypesBinder:
KnownTypesBinder binder = new KnownTypesBinder()
binder.KnownTypes.Add(typeof(Project1.Class1));
binder.KnownTypes.Add(typeof(Project1.Class1));
In the two examples given, Class1 is referenced the same way. However, if I refactor Class1 to, say, NewClass1, then this second example will start sending a different name. That may or may not be a big deal, depending on whether you are using the types or not.
Final Thoughts
The advantage of the AliasedTypes is that I can give it any string that I want, and it doesn't matter how much I refactor the code, the communication between the .NET and the JavaScript (or whatever consumer is out there) is unbroken.
Be careful not to mix AliasedTypes and KnownTypes that have the exact same class name, because the code is written that the AliasType will win out over KnownType. When the binder doesn't recognize a type (aliased or known), it will provide the full assembly name of the type.
In the end, I broke down and added the .NET class information to the module in string variables to make refactoring easier.
module.net = {};
module.net.classes = {};
module.net.classes['column'] = "ColumnStructure";
module.net.classes['function'] = "FunctionStructure";
module.net.classes['operand'] = "OperandStructure";
module.net.getAssembly = function (className) {
return "MyNamespace.Models." + module.net.classes[className] + ", MyAssembly";
}
and generated the JSON as
{
"FormulaItem": {
"$type": module.net.getAssembly('column'),
"type": "int",
"field": "my_field",
"display": "My Field"
}
}

How to revert back to 'default' XML serialization when implementing IXmlSerializable in a base class?

I'm trying to serialize a class that inherits from a base class that implements IXmlSerializable.
The base class, called PropertyBag is a class that allows dynamic properties (credits to Marc Gravell).
I implemented IXmlSerializable so that the dynamic properties (stored in a Dictionary) are written as normal xml elements.
e.g.
When serializing a class Person with a public property (non dynamic) Name and a dynamic property Age, I would like for it to generate the following XML:
<Person>
<Name>Tim</Name>
<DynamicProperties>
<Country>
<string>USA</string>
</Country>
</DynamicProperties>
<Person>
I can get the part to work with the following implementation of WriteXml in the base PropertyBag class:
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("DynamicProperties");
// serialize every dynamic property and add it to the parent writer
foreach (KeyValuePair<string, object> kvp in properties)
{
writer.WriteStartElement(kvp.Key);
StringBuilder itemXml = new StringBuilder();
using (XmlWriter itemWriter = XmlWriter.Create(itemXml))
{
// serialize the item
XmlSerializer xmlSer = new XmlSerializer(kvp.Value.GetType());
xmlSer.Serialize(itemWriter, kvp.Value);
// read in the serialized xml
XmlDocument doc = new XmlDocument();
doc.LoadXml(itemXml.ToString());
// write to modified content to the parent writer
writer.WriteRaw(doc.DocumentElement.OuterXml);
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
However, when serializing the Person class, it no longer serializes the normal (non dynamic) properties unless I overwrite the WriteXml method in Person (which I do not want to do). Is there any way that in the base class I can automatically add the static properties? I know I can do this manually using reflection, but I was wondering if there is some built-in functionality in the .Net Framework?
I've spent quite a bit of time with XmlSerializer (and various other serialization APIs), and I'm pretty sure that simply: you can't. Implementing IXmlSerializable is all or nothing.
The closest I can think of is to cheat and move all the fixed properties to a sub-object; this would give you slightly different xml - something like:
<FixedProperties>
<Name>Tim</Name>
</FixedProperties>
<DynamicProperties>
<Country>
<string>USA</string>
</Country>
</DynamicProperties>
but I expect it would work. You would have pass-thru properties on your base object:
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public FixedProperties FixedProps {get;set;}
public string Name {
get {return FixedProps.Name;}
set {FixedProps.Name = value;}
}
Make sense? You could also mark Name as [XmlIgnore], but it seems pretty redundant. In your bespoke serialize method you'd use new XmlSerializer(typeof(FixedProperties))
Edit: Here's a working "serialize" example:
using System;
using System.ComponentModel;
using System.Xml.Serialization;
static class Program
{
static void Main()
{
MyType obj = new MyType { Name = "Fred" };
var ser = new XmlSerializer(obj.GetType());
ser.Serialize(Console.Out, obj);
}
}
public class MyType : IXmlSerializable
{
public MyType()
{
FixedProperties = new MyTypeFixedProperties();
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public MyTypeFixedProperties FixedProperties { get; set; }
[XmlIgnore]
public string Name
{
get { return FixedProperties.Name; }
set { FixedProperties.Name = value; }
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
throw new System.NotImplementedException();
}
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("DynamicProperties");
writer.WriteElementString("Foo", "Bar");
writer.WriteEndElement();
fixedPropsSerializer.Serialize(writer, FixedProperties);
}
static readonly XmlSerializer fixedPropsSerializer
= new XmlSerializer(typeof(MyTypeFixedProperties));
}
[XmlRoot("FixedProperties")]
public class MyTypeFixedProperties
{
public string Name { get; set; }
}
Marc, your answer on putting the FixedProperties in a seperate collection got me thinking that instead of inheriting from PropertyBag, I should create a property of that type.
So I created a PropertyBagWrapper class that my Person class inherits from and it works.
[Serializable]
[TypeDescriptionProvider(typeof(PropertyBagDescriptionProvider))]
public abstract class PropertyBagWrapper
{
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public PropertyBag DynamicProperties { get; set; }
public object this[string name]
{
get { return DynamicProperties[name]; }
set { DynamicProperties[name] = value; }
}
protected PropertyBagWrapper()
{
DynamicProperties = new PropertyBag(this.GetType());
}
}
[Serializable]
public class Person : PropertyBagWrapper
{
[Browsable(true)]
public string Name { get; set; }
}
I won't repeat all the code for the PropertyBag and the custom classes needed for ICustomTypeDescriptor implementation, you can find that here.
I did move the TypeDescriptionProvider attribute from the PropertyBag class to the PropertyBagWrapper class.
The PropertyBag class still has the same implementation for WriteXml() method as posted in the question.

Categories