Serialization using XElements

Introduction

I recently starting fooling around with the new XML LinQ stuff in .Net, and it is very impressive. One thing that I found missing was the ability to serialize objects directly into XElements. However, Microsoft did provide the means to get me to hit the ground running and it took a couple of minutes for me to get it right.

Readers and Writers

One thing the classical .Net XML always lacked was an "XmlNodeWriter", a writer that basically allows you to create DOM XML nodes using a standard XmlWriter. There are several implementations, but they can only insert nodes into a document (or a node within that document).

Microsoft has now filled that gap with the XNode.CreateReader() and XContainer.CreateWriter() methods, and using these with the Xml.Serialization classes is dirt easy.

Code

I decided to use extension methods to make it really sweet in terms of .Net 3.5 functionality. So without further adue here is the code:

/// <summary>
    /// Represents extension methods.
    /// </summary>
    public static class ExtensionMethods
    {
        private static Dictionary<Type, XmlSerializer> _knownTypes
            = new Dictionary<Type, XmlSerializer>();
        private static ReaderWriterLockSlim _rwl = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        /// <summary>
        /// Deserializes an <see cref="XNode"/> to a specific type.
        /// </summary>
        /// <typeparam name="T">The type of object to deserialize to.</typeparam>
        /// <param name="node">The <see cref="XNode"/> to deserialize.</param>
        /// <returns>A new object of type <see cref="T"/>.</returns>
        public static T Deserialize<T>(this XNode node) where T : class
        {
            XmlSerializer serializer;
            Type t = typeof(T);
            using(_rwl.Read(true))
            {
                if (!_knownTypes.TryGetValue(t, out serializer))
                {
                    using(_rwl.Write())
                    {
                        if (!_knownTypes.TryGetValue(t, out serializer))
                        {
                            serializer = new XmlSerializer(t);
                            _knownTypes.Add(t, serializer);
                        }
                    }
                }
            }

            using (XmlReader reader = node.CreateReader())
            {
                return serializer.Deserialize(reader) as T;
            }
        }

        /// <summary>
        /// Serializes an object into an <see cref="XContainer"/>.
        /// </summary>
        /// <param name="target">The target <see cref="XContainer"/>.</param>
        /// <param name="value">The value to serialize.</param>
        public static void Serialize(this XContainer container, object value)
        {
            if (value == null)
                return;

            XmlSerializer serializer;
            Type t = value.GetType();
            using (_rwl.Read(true))
            {
                if (!_knownTypes.TryGetValue(t, out serializer))
                {
                    using (_rwl.Write())
                    {
                        if (!_knownTypes.TryGetValue(t, out serializer))
                        {
                            serializer = new XmlSerializer(t);
                            _knownTypes.Add(t, serializer);
                        }
                    }
                }
            }

            using (XmlWriter writer = container.CreateWriter())
            {
                serializer.Serialize(writer, value);
            }
        }
    }

You will also need a utility class that I wrote that basically takes the chore of using ReaderWriterLockSlims:

    public static class ExtensionMethods
    {
        private class ReaderWriterLockSlimController : IDisposable
        {
            private bool _closed = false;
            private ReaderWriterLockSlim _slim;
            private bool _read = false;
            private bool _upgrade = false;

            public ReaderWriterLockSlimController(ReaderWriterLockSlim slim, bool read, bool upgrade)
            {
                _slim = slim;
                _read = read;
                _upgrade = upgrade;

                if (_read)
                {
                    if (upgrade)
                    {
                        _slim.EnterUpgradeableReadLock();
                    }
                    else
                    {
                        _slim.EnterReadLock();
                    }
                }
                else
                {
                    _slim.EnterWriteLock();
                }
            }

            #region IDisposable Members

            ~ReaderWriterLockSlimController()
            {
                Dispose();
            }

            public void Dispose()
            {
                if (_closed)
                    return;
                _closed = true;

                if (_read)
                {
                    if (_upgrade)
                    {
                        _slim.ExitUpgradeableReadLock();
                    }
                    else
                    {
                        _slim.ExitReadLock();
                    }
                }
                else
                {
                    _slim.ExitWriteLock();
                }

                GC.SuppressFinalize(this);
            }

            #endregion
        }

        public static IDisposable Read(this ReaderWriterLockSlim slim, bool upgradeable)
        {
            return new ReaderWriterLockSlimController(slim, true, upgradeable);
        }

        public static IDisposable Read(this ReaderWriterLockSlim slim)
        {
            return new ReaderWriterLockSlimController(slim, true, false);
        }

        public static IDisposable Write(this ReaderWriterLockSlim slim)
        {
            return new ReaderWriterLockSlimController(slim, false, false);
        }
    }

And finally, something to test it:

    [XmlRoot("test")]
    public class Test
    {
        [XmlAttribute("testAttribute")]
        public string Foo;
    }

    class Program
    {
        static void Main(string[] args)
        {
            XDocument doc = new XDocument();

            Test tst = new Test();
            tst.Foo = "Hello World!";

            doc.Serialize(tst);

            using (XmlWriter writer = new XmlTextWriter(System.Console.Out))
            {
                doc.WriteTo(writer);
            }

            Console.ReadLine();
        }
    }

Conclusion

I feel that this is an oversight, but a small one at that. It took me not 15 minutes to get it right, so I hope this will save you 15 minutes some time in the future.

posted @ Wednesday, August 27, 2008 6:34 PM

Print

Comments on this entry:

# re: Serialization using XElements

Left by klaus at 4/2/2009 2:12 AM
Gravatar
nice, but how do you deserialize again ?
now you manually have to add an xml header, right?

# re: Serialization using XElements

Left by Jonathan Dickinson at 4/2/2009 2:42 AM
Gravatar
If you can guarantee the name+namespace pair of your element name is unique you can store them in a dictionary (keyed XName, valued XmlSerializer). I do have this implemented and I can vouch it works - but it requires that you use XML The Proper Way (TM).

I am not entirely sure I got the gist of your question, if I didn't nail it for you maybe rephrase it a little?

Your comment:



 (will not be displayed)


 
 
 
 

Live Comment Preview:

 
«July»
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678