Click here to Skip to main content
Click here to Skip to main content

Serializable Extra Types for .NET 4

By , 25 May 2011
 

You may follow this code @ serializableex.codeplex.com.

Introduction

This is an updated article and code base from my original publication [Serialization for Rapid Application Development: A Better Approach] here at CodeProject. After several years of growth in ability and advances in technology, I decided it was time to update that code base and article for use in .NET 4. The result of this is a much smaller code base, unit tests to back them up, and fixes to some issues concerning discovery within a web environment.

Background

In short, the problems with the XmlSerializer that existed when I wrote the first article still exist today. They are classic issues that there is still no unified approach in dealing with. The major problem is still how do you resolve unknown classes when serializing and deserializing.

There have been several attempts that I've followed over the years to get around this, anywhere from the creation of entire frameworks like YAXLib to instructions on how to make use of the IXmlSerializable interface at various levels of sophistication.

With all of these solutions, the major problem from a developer perspective that has always crept in is cumbersomeness of the implementation. IXmlSerializable asks the developer to write some form of a customized implementation per class. YAXLib and others ask the developer to use different attributes, or handle outputs that don't look like the clean(ish) XML generated by XmlSerializer. The [XmlInclude(Type type)] attribute demands that all types to be serialized be within the same library. None of them (that I know of) in my opinion relieves the developer from the tedium of working with these solutions.

Goals Realized

Serializable Extra Types is designed to be as thoughtless as possible with an absolute minimum of development consideration. What it does is make use of the standard XmlSerializer and a slightly un-hyped overloaded constructor; it has to incorporate extra type definitions for use in type resolution during serialization and deserialization.

It is actually a very simple idea. Keep a list of all the possible types that the XmlSerializer may have need of during a serialization/deserialization process. Register those types using attribute adornments and provide some Extension Methods to make incorporating those lists thoughtless to the developer.

It works under a parent child relationship. I can best describe it as saying, it's the reverse of the XmlInclude attribute. The XmlInclude attribute is placed on a class to give the serializer knowledge of other classes when serializing the class that is adorned. SerializableExtraType is placed on a class to give the other class knowledge of the adorned class when the other class is serialized.

So...

XmlInclude = Parent => Child
SerializableExtraType = Child => Parent

This allows the SerializableExtraTypes code to integrate related classes across libraries. Additionally, I have exposed a method by which you may register additional relationships at runtime. This solves any situation that you may come across with libraries and applications having complex implied relationships.

Using the code

The code is available in the download and at the CodePlex site. Both have a series of test libraries and a consuming test project that shows the usage very well. Here I will outline the quick and dirty of how to make use of it.

First, adorn a class with the required attribute like this:

// example 1 of registering class and all derived classes
[SerializableExtraType(typeof(Foo))]
// example 2 of registering class with an unrelated class
[SerializableExtraType(typeof(SomethingElse))]
// example 3 of registering with multiple classes in same attribute
[SerializableExtraType(typeof(ClassOne), typeof(ClassTwo))]
public class Foo { public Foo() {} }

The Extension Methods make use of the SerializableExtraTypes under System.Xml.Serialization.

Now make use of the Extension Methods to serialize and deserialize objects.

// example 1 assuming that ClassOne and ClassTwo inherit from Foo
string xml = new Foo { ClassList = {new ClassOne(), new ClassTwo(), }, }.SerializeEx();
Foo obj = "<Foo><ClassList><ClassOne /><ClassTwo /></Foo>".DeserializeEx<Foo>();

// example 2 assuming that Foo bears no relationship with SomethingElse
string xml = new SomethingElse { ObjectList = { new Foo(), new Foo(), }, }.SerializeEx();
SomethingElse obj = "<SomethingElse><ArrayOfObject><Foo />" + 
   "<Foo /></ArrayOfObject></SomethingElse>".DeserializeEx<SomethingElse>();

There is more functionality built into the code base but this is a quick and dirty sample. Please take a look at the download for further examples.

History

This is the second publication of this method. Please send any comments or suggestions on how to improve it. You can email me at danatcofo@gmail.com.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

Daniel Gidman
Architect Stackify
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionProposal cleanupmemberKabwla.Phone21 Sep '11 - 3:10 
Since your attribute will always have to create an array(of types),
you might as well change the constructor to allow passing of an array.
 
public class SerializableExtraTypeAttribute : Attribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="SerializableExtraTypeAttribute"/> class.
    /// </summary>
    /// <param name="types">The types.</param>
    public SerializableExtraTypeAttribute(params Type[] types)
        : base()
    {
        _Types = types;
    }
 
    private IEnumerable<Type> _Types = null;
 
    /// <summary>
    /// Gets the types.
    /// </summary>
    public IEnumerable<Type> Types
    {
        get { return _Types; }
    }
}
 
This way you will only need one constructor and you can use as many types as you want.
AnswerRe: Proposal cleanupmemberDaniel Gidman21 Sep '11 - 3:44 
Doesn't work due to it being an Attribute. The IDE and compiler forces the exclusion of the params in constructors for Attributes.
 
Basically throws an error.
 
However I do think that one of the constructors is for an actual array to get around this.
GeneralRe: Proposal cleanupmemberKabwla.Phone21 Sep '11 - 3:48 
I am working with Vs2010 with everything up to date, and it works fine on my machine.
 
I am not working with designable classes, so that may be it.
GeneralRe: Proposal cleanupmemberDaniel Gidman21 Sep '11 - 4:20 
Nice, well the attribute change must have been put into .NET 4 because .NET 2 doesn't allow that.
 
Thanks for the update. I did put a little error anticipation around your suggestion though.
 
        public SerializableExtraTypeAttribute(params Type[] types)
            : base() { _Types = types.Where(t => t != null).ToArray(); }
 
I've updated the codeplex repository with this change.
GeneralRe: Proposal cleanupmemberKabwla.Phone21 Sep '11 - 4:36 
Hey, the serialization process suddenly can access private constructors too.
 
In dotNet 2.0 the TimeSpan class was not serializable because it had a private (paramterless) constructor. Which means you could not use it in a webservice, or in a DataSet.
 
Now with 4.0, you get a Column of type TimeSpan when your sql column is of type Time.
So they fixed some things in the serialization process.
(Then you might ask, did they not fix this problem....)
 
This is great because you can now build serializable objects that HAVE to be initialized:
public class Demo
{
  string _ThisHasToHaveAValue;
 
  public string ThisHasToHaveAValue
  {
    get {return _ThisHasToHaveAValue;}
    set { 
        // ensure value.
        if(string.IsNullOrEmpty(value)
           throw new InvalidOperationException("no no");
       _ThisHasToHaveAValue = value;
     }
  }
 
  private void Demo()
  {
     //hide constructor.
  }
  public void Demo(string value)
  {
     ThisHasToHaveAValue  = value;
  }
  
}

QuestionThis looks very promising!memberBob Sandberg1 Aug '11 - 7:36 
Daniel,
 
I've struggled with this problem for a long time. Doing everything you did short of writing this into a nice looking extension library. This is a major pain for building class libraries that are meant to be derived from and marshaled via XML.
 
I want to give this a try, but I downloaded and tried to run the tests and I get this error. Any ideas?
 
Test 'SerializableExtraTypes.Tests.SerializableExtensionMethodsTest.SerializeExTest' failed: Test method SerializableExtraTypes.Tests.SerializableExtensionMethodsTest.SerializeExTest threw exception:
System.TypeInitializationException: The type initializer for 'Net.AMightyOak.SerializableExtraTypes' threw an exception. ---> System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
at System.Reflection.RuntimeModule.GetTypes()
at System.Reflection.Assembly.GetTypes()
SerializableExtraTypes.cs(91,0): at Net.AMightyOak.SerializableExtraTypes.d__1f.MoveNext()
SerializableExtraTypes.cs(30,0): at Net.AMightyOak.SerializableExtraTypes..ctor()
SerializableExtraTypes.cs(105,0): at Net.AMightyOak.SerializableExtraTypes..cctor()
--- End of inner exception stack trace ---
at Net.AMightyOak.SerializableExtraTypes.GetTypes(Type type)
Bob Sandberg

AnswerRe: This looks very promising!memberDaniel Gidman1 Aug '11 - 8:10 
1) Do you have .NET 4 loaded? (I know stupid question)
2) Try running it from a get latest from the codeplex project @ http://serializableex.codeplex.com/[^]
* be aware there is also an older .NET 3.5 solution there as well.
 
Other than that, it might be a configuration issue or something. I ran this in debug just fine. The "Unable to load" the requested types might be a bad project reference as well and this is the first push back on about 80 downloads or so that has said anything about an error so while I don't want to relegate it to an "its on your side" it might really be.
GeneralMy vote of +5memberBillWoodruff14 Jun '11 - 2:59 
Excellent technical writing ! Thanks for this.
 
best, Bill
"Reason is the natural order of truth; but imagination is the organ of
meaning." C.S. Lewis

GeneralRe: My vote of +5memberDaniel Gidman14 Jun '11 - 4:45 
Thanks Bill!
GeneralMy vote of 5memberFilip D'haene30 May '11 - 2:31 
Excellent work!
 
Thanks for sharing. Wink | ;)
GeneralRe: My vote of 5memberDaniel Gidman30 May '11 - 13:50 
Thanks!
GeneralI could be missing something here, butmvpSacha Barber25 May '11 - 2:42 
We work with WCF a lot and use the Netdatacontractserializer[^] and DataContractSerializer[^] and with those I can have something like
 

[DataContract]
public Class1
{
   [DataMember]
   public Class2 { get; set; }
}
 
And serialize Class1 just fine, and it will know how to also serialize Class2 just fine too.
 
Is your problems more aimed at the Xml serializer?
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: I could be missing something here, butmemberDaniel Gidman25 May '11 - 3:02 
XmlSerializer will handle that just fine too. Change the equation up.
 
public abstract class Class1 { }
public class Class2 : Class1 { }
public class Class3 : Class1 { }
public class Class4 : Class2 { }
 
[SerializableExtraType(typeof(Class1))]
public class SomethingElse
{
  public List<Class1> ListOfClasses { get; set; }
}
 
SomethingElse obj = new SomethingElse { ListOfClasses = new List<Class1> { new Class2(), new Class3(), new Class4(), }, };
 
string xml = obj.SerializeEx();
SomethingElse obj2 = xml.DeserialzieEx<SomethingElse>();
 
Try doing that with an XmlSerializer and it will bomb because Class1 is abstract and there is no type knowledge of its children.
I don't think the DataSerializer will handle that either.
GeneralRe: I could be missing something here, butmvpSacha Barber25 May '11 - 3:30 
Again I could be missing something but I can have this using the KnownTypeAttribute[^]
 
[KnownType(typeof(Class2)]
[KnownType(typeof(Class3)]
[KnownType(typeof(Class4)]
public abstract class Class1 { }
 
[KnownType(typeof(Class4)]
public class Class2 : Class1 { }
 
public class Class3 : Class1 { }
 
public class Class4 : Class2 { }

 
Where I have something like
 
[DataContract]
public SomeOtherClass
{
  [DataMember]
  public Class2 MyClass2 {get; set;}
 
  [DataMember]
  public Class3 MyClass3 {get; set;} 
 
  [DataMember]
  public Class4 MyClass4 {get; set;}
 
}
 

And I could also have your example too
 

public class SomethingElse
{
  public List<Class1> ListOfClasses { get; set; }
}
 

SomethingElse obj = new SomethingElse { ListOfClasses = new List<Class1> { new Class2(), new Class3(), new Class4() }, };
 

Like I say I could be missing something, but I thought that was what the KnownTypeAttribute[^] was all about
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: I could be missing something here, butmemberDaniel Gidman25 May '11 - 3:44 
If KnownType is anything like XmlInclude then it only works within the same library or directly referenced libraries where you have access to the class being serialized.
 
This can work across libraries where the class you are serializing is one you have no access to and can not physically adorn with KnownType or XmlInclude.
 
All of your examples you are assuming that you have full access and control, I'm making no such assumption.
 
Of course I'm not going to go out and claim that DataContract doesn't handle all these issues in one form or another as I do not know, however this solution IS aimed at XmlSerializer.
GeneralRe: I could be missing something here, butmvpSacha Barber25 May '11 - 4:06 
Ah fair enough, just wanted to be clear in my own mind is all.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

QuestionRe: I could be missing something here, butmemberJohn Kasra15 Jun '11 - 12:07 
Daniel, so to paraphrase your entire article, this provides a mechanism to serialise a child class by simply registering the base class? And this mechanism works even if you do not have access to the source code of the base class?
AnswerRe: I could be missing something here, but [modified]memberDaniel Gidman15 Jun '11 - 12:28 
Yes, that is it in a nutshell. Its broad enough in scope that it covers some obvious and less than obvious gotcha's but if you were to one line it... that is a good summary.

modified on Thursday, June 16, 2011 8:22 AM

GeneralRe: I could be missing something here, butmemberJefis25 May '11 - 4:10 
I'm not sure, if XmlSerializer will throw exceptions, or will not serialize child classes, which is not passed to XmlSerializer constructor as types.
GeneralRe: But will it throw?memberKabwla.Phone21 Sep '11 - 3:22 
Yes it will throw exceptions.
 
The exception will say the following:
There is an error in the xml at (location).
 
The inner exception will say:
The specified type was not recognized (some type).

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 25 May 2011
Article Copyright 2011 by Daniel Gidman
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid