Using LINQ to Easily Serialize an Exception to XML
Posted on Monday, February 7, 2011
I was working on my capstone project last semester, and ran into a need to log exception data to a portable format. I loves me some XML, so that was my obvious (and, let's be honest, only) choice. .NET includes a lot of built-in code in most data types to allow for seamless serialization, so I thought this would be a simple matter.
Nope.
Turns out the Exception
class in .NET contains an IDictionary
member, Data
, that contains some extra information that the developer might find useful. The problem? IDictionary
does not implement ISerializable
, which is required since it is not a primitive type. So, when I attempt to "lazy" serialize the Exception
object, the runtime hits the Data
member and throws an exception.
Drat.
Since I really did not want to have to write some complicated wrapper class (I was a bit pressed for time by this point in the semester), I turned to my trusty friend Google to see how other people had handled this problem. It took a bit of searching, but eventually I found a perfect solution to my problem here: http://seattlesoftware.wordpress.com/2008/08/22/serializing-exceptions-to-xml/
The solution is remarkably simple: extend the XElement
class in LINQ. I mean, this is "duh" simple. Once you create the extended class, logging an exception is a simple as this:
XDocument log = XDocument.Load( path_to_file );
log.Root.Add( new ExceptionXElement( ex ) );
log.Save( path_to_file );
Here's the code as implemented:
using System;
using System.Collections;
using System.Linq;
using System.Xml.Linq;
/// <summary>Represent an Exception as XML data.</summary>
public class ExceptionXElement : XElement
{
/// <summary>Create an instance of ExceptionXElement.</summary>
/// <param name="exception">The Exception to serialize.</param>
public ExceptionXElement(Exception exception)
: this(exception, false)
{ ; }
/// <summary>Create an instance of ExceptionXElement.</summary>
/// <param name="exception">The Exception to serialize.</param>
/// <param name="omitStackTrace">
/// Whether or not to serialize the Exception.StackTrace member if it's not null.
/// </param>
public ExceptionXElement(Exception exception, bool omitStackTrace)
: base(new Func<XElement>(() =>
{
// Validate arguments
if (exception == null)
{
throw new ArgumentNullException("exception");
}
// The root element is the Exception's type
XElement root = new XElement(exception.GetType().ToString());
if (exception.Message != null)
{
root.Add(new XElement("Message", exception.Message));
}
// StackTrace can be null, e.g.:
// new ExceptionAsXml(new Exception())
if (!omitStackTrace && exception.StackTrace != null)
{
vroot.Add(
new XElement("StackTrace",
from frame in exception.StackTrace.Split('\n')
let prettierFrame = frame.Substring(6).Trim()
select new XElement("Frame", prettierFrame))
);
}
// Data is never null; it's empty if there is no data
if (exception.Data.Count > 0)
{
root.Add(
new XElement("Data",
from entry in exception.Data.Cast<DictionaryEntry>()
let key = entry.Key.ToString()
let value = (entry.Value == null) ? "null" : entry.Value.ToString()
select new XElement(key, value))
);
}
// Add the InnerException if it exists
if (exception.InnerException != null)
{
root.Add(new ExceptionXElement(exception.InnerException, omitStackTrace));
}
return root;
})())
{ ; }
}
Enjoy!