This Blog Has A New Home!

This blog has been moved to www.SoftwareRockstar.com. Articles already here will remain intact, while new content will only be added to the new location at www.SoftwareRockstar.com.

Sunday, December 10, 2006

Using IXmlSerializable To Overcome "not expected" Error On Derived Classes

This article extends upon Simon Hewitt's article on CodeProject. It is strongly recommended that you read Simon's article also in order to fully understand the concepts. BACKGROUND XML serialization is an indispensable technique that has built-in support for in the .NET Framework. Using XML serialization, objects can be serialized to XML streams that may be persisted to permanent storage such as files, as well as XML streams can be converted back to objects with exactly the same state as at the time of serialization. With .NET Framework's built-in support for XML serialization, all this can be achieved with only a few lines of code using the System.Xml.Serialization.XmlSerializer object. PROBLEM The problem with XmlSerializer is, however, that it works by generating an on-the-fly assembly behind the scenes at compilation time, that has logic for serialization and deserialization of a given type. For this reason, the exact (concrete) type of the object and it's public properties must be known to the compiler at the time of compilation. If we try to serialize an object that, for example, has a public member of a given base type, then at run-time we receive a System.InvalidOperationException if the public member was set to a derived type. WORKAROUND The workaround to above problem has generally been resorting to custom XML serialization, which can get pretty complicated at times. SOLUTION An ingenious solutions was discovered by Simon Hewitt using the System.Xml.Serialization.IXmlSerializable interface that allows us to mitigate the by-design issue of XmlSerializer object. While this solution works very well, it requires that a new class be created for each base class that we need serialization support for. Using C# generics, I took the liberty of extending Simon's solution, eliminating the need for such new classes, and encapsulating the grunt work into just one class. Once this class has been added to our project (our referenced from another assembly), all we really need to do is decorate any of our public members of base type (that may be substituted with derived types at runtime) with an attribute. Once the above class has been added to our project, all we need to do is add an attribute directly on the base class property as follows:

18 comments:

Anonymous said...

I am new to xmlSerialization as a whole. From what I gather this is a very convenient solution. I do however have a few questions:

1. The second part where you said to add an attribute directonly on the base class property, are you referring in this case to the _parameters?

2. Do you have any examples of how you would create serialize a derived object using this class?

Many thanks for your efforts,

Joe - sorry no blogger identity

Muhammad Haroon said...

In (1) what I am referring to is that e.g. if you have a property in a class of some base class type, you need to place this attribute on that property.

Example:

[XmlElement(Type = ...]
public EmployeeBase Employee
{
get {// retrun employee;}
}

I hope it helps.

Anonymous said...

Muhammad, with this post you saved my day!

Thank you!
Nicola

Unknown said...

what if i had a base collection within the collection? could you show an example of that

Muhammad Haroon said...

Jeremy, I am not sure what you mean by "a base collection within the collection". If you can elaborate, I might be able to help.

R. Fusai said...

What about List<baseClass> ?
__________________________________
I have this situation:

class Container
{
List<myBaseClasses> _list;
List<myBaseClasses> List { get and set on _list }

void SetupList() {
_list.Add(new myInheritedClass1());
_list.Add(new myInheritedClass2());
}
}

where both myInheritedClass1 and myInheritedClass2 inherit from myBaseclass..

The question is: how can i serialize a Container in a simple way?
___________________________________

Muhammad Haroon said...

fusai, what you have here is a list of lists. The technique mentioned in this article doesn't care about the actual data structure you are using. As long as base class properties can be XML serialized, and you use proper attributes on the public properties of the (container) class being serialized, this technique should work just fine.

R. Fusai said...

sorry, I didn't explain very well..

what I have is a list of object of the BaseClass type, not a list of lists.

The problem is that every object in the list belongs to a different type, InheritedClass1 and InheritedClass2, ecc..

the property that i have made exposes the entire list, and I want to serialize this property.

do I have to tag with "[XmlElement(Type = ...]
" the property that expose the list
?

Muhammad Haroon said...

Fusai, no, I am afraid that won't work. For this to work, you'll have to add the "[XmlElement(Type=...)]" attribute to the item property of the list itself. This attribute basically tells .NET that the property it's been applied to should be serialized using the base class type specified in the attribute. I hope it makes sense.

Unknown said...

Hi Fusai,

I had the same problem with generics list and here is what I’ve done.
I have a Filter object that one of its properties was a List< Criteria > that I was unable to serialize/deserialize properly with the technique proposed above.
Like you, Criteria is my base class for others like ClientCriteria, InvoiceCriteria etc.

First of all, I got rid of my List< Criteria > and I replaced it with a new class CriteriaList that contains the list.
I’ve implemented the IXmlSerializable interface.
WriteXml method serialize each criteria (whatever is real type is).
Do be able to deserialize it properly, I’ve added a Type property set as xmlattribute in Critera with “return this.GetType().FullName” as get.

Please let me know of any improvement.

The xml generated looks like this :

< CriteriaCollection >
< ClientCriteria Name="ClientGuID" Value="8acaa757-41bc-498a-9505-9cbcd4c5720d" SelectionType="Include" Apply="true" Type="Criterias.ClientCriteria" / >
< Criteria Value="318d15a0-43b0-47b3-aae1-a0b3b11b9b0a" SelectionType="Include" Apply="true" Type="Criterias.Criteria" / >
< Criteria Value="02c3a6cd-1a2c-4361-8ffa-abad4dceabda" SelectionType="Include" Apply="true" Type="Criterias.Criteria" / >
< /CriteriaCollection >

/// < summary >< /summary >
[Serializable()]
public class CriteriaList : IXmlSerializable
{
#region Public Property Member...

/// < summary >< /summary >
public System.Collections.Generic.List< Criteria > List
{
get { return this._List; }
set { this._List = value; }
}

#endregion
#region Private Property Member...

/// < summary >< /summary >
System.Collections.Generic.List< Criteria > _List = new System.Collections.Generic.List< Criteria >();

#endregion

#region Public Method Member...
#region Constructor
#endregion

/// < summary >< /summary >
/// < returns >< /returns >
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}

/// < summary >< /summary >
/// < param name="reader" >< /param >
public void ReadXml(System.Xml.XmlReader reader)
{
System.Xml.XmlReader subReader = reader.ReadSubtree();
subReader.ReadStartElement();

while (subReader.Depth > 0)
this.List.Add((Criteria)new XmlSerializer(Type.GetType(reader.GetAttribute("Type"))).Deserialize(subReader));
}

/// < summary >< /summary >
/// < param name="writer" >< /param >
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (Criteria oCriteria in List)
new XmlSerializer(oCriteria.GetType()).Serialize(writer, oCriteria);
}

#endregion
#region Private Method Member
#endregion

#region Public Class Member
#endregion
#region Private Class Member
#endregion
}

Brandon said...

Moonshield,

Thanks you very much for your comment. It helped me out tremendiously.

birsch said...

Here's a takeoff on the Generics List solution. It doesn't require adding any additional information to your interface or XMLs. Instead it uses the actual list element tag to reconstruct and deserialize the object.

NOTE: This solution was also modified to skip whitespaces and remove annoying xsi attributes.

Items in the list will be written to XML using the real class name, as in:
<MyList>
<ClassA>
...
</ClassA>
<ClassB>
...
</ClassB>
</MyList>

(assuming both ClassA and ClassB implement IMyInterface)

public class MyList : IXmlSerializable
{

private List<IMyInterface> list;

public List<IMyInterface> List
{
get { return list; }
set { list = value;}
}

public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}

public void ReadXml(System.Xml.XmlReader reader)
{
//Skip whitespaces
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;

//Create a reader that will read the list's content
System.Xml.XmlReader subReader = XmlReader.Create(reader.ReadSubtree(), settings);
subReader.ReadStartElement();

while (subReader.Depth > 0)
{
//turn the element name back to a fully-qualified concrete class name
//NOTE: Assuming this class is sharing the same namespace. If not,
// place your own logic here to reconstruct full class name
Type type = Type.GetType(this.GetType().Namespace + "." + reader.Name);
Items.Add((IMyInterface)new XmlSerializer(type).Deserialize(subReader));
}
}

public void WriteXml(System.Xml.XmlWriter writer)
{
//avoid adding xsi attributes to keep the XMLs nice and clean
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
foreach (IMyInterface item in List)
new XmlSerializer(item.GetType()).Serialize(writer, item, ns);
}
}

noon said...

birsch your method works but there is a bug in the ReadXML function.
The reader is not at the right position when exiting. You should call reader.Skip at the end.

Check this :
http://forums.xna.com/forums/p/15939/83573.aspx

Anonymous said...

Is there any way to create custom name instead class name in the generated XML?

Unknown said...

Has anyone tried any of these solutions where the derived classes are in a different assembly AND the derived xml elements are in a different XML-namespace?

I've tried all these techniques but I keep getting the "&ltMyType xmlns='MyNamespace'&gt was not expected" error.

I've got two schemas - the first defines the abstract base in one namespace, the second defines the derived types and I have matching assemblies for each schema.

I'm beginning to think the namespace thing may be a blocker as the XmlSerializer-related classes only ever allow you to define one namespace (unlike the XmlDocument-related classes) as far as I can tell.

Thanks.

Odi said...

Muhammad,

This might sound silly, but would you please confirm that you are releasing your modifications to Simon Hewitt's code into the public domain and that you retain no copyrights and such on it?

Thanks,
Odi

Greeno said...

Works great. Thank you. Dan

Unknown said...

When I originally commented I clicked the -Notify me when new comments are added- checkbox and now each time a comment is added I get four emails with the same comment. Is there any manner you can take away me from that service? Thanks! casino games

New Articles On Software Rockstar