Skip to content

Instantly share code, notes, and snippets.

@mjul
Last active October 13, 2015 12:18
Show Gist options
  • Save mjul/4194484 to your computer and use it in GitHub Desktop.
Save mjul/4194484 to your computer and use it in GitHub Desktop.
Easily add context to exceptions using anonymous types. // throw new DataException("Import failed.", null, new { DataSource = "FileMaker", Type = "Student", StudentNumber = 123 });
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
namespace ClassLibrary1
{
/// <summary>
/// Standard data exception.
/// Addes the ability to pretty-print (anonymous) types as a
/// simple way to add structured context information to an exception.
/// </summary>
[Serializable]
public class DataException : Exception
{
public DataException()
: this(String.Empty)
{
}
public DataException(string message)
: this(message, null)
{
}
public DataException(string message, Exception innerException)
: this(message, innerException, null)
{
}
// This constructor is needed for serialization.
protected DataException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
/// <summary>
/// Construct an exception, adding the fields and values of <paramref name="messageData"/> to the Message.
/// </summary>
/// <remarks>
/// <example>
/// throw new DataException("Import failed.", null, new { DataSource = "FileMaker", Type = "Student", StudentNumber = 123 });
/// </example>
/// </remarks>
public DataException(string message, Exception innerException, object messageData)
: base(CreateMessage(message, messageData), innerException)
{
}
private static String CreateMessage(string message, object data)
{
var sb = new StringBuilder();
sb.Append(message);
if (data != null)
{
sb.AppendLine();
AddFields(sb, data);
}
return sb.ToString();
}
private static void AddFields(StringBuilder sb, object data)
{
foreach (var property in Properties(data).OrderBy(p => p.Name))
{
sb.AppendLine(FormatProperty(property, data));
}
}
private static IEnumerable<PropertyInfo> Properties(object data)
{
return data.GetType()
.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance
| BindingFlags.Public | BindingFlags.NonPublic);
}
private static string FormatProperty(PropertyInfo property, object data)
{
var key = property.Name;
var val = property.GetValue(data, null);
var text = String.Format(" {0} = {1}", key, val);
return text;
}
}
}
using System;
using NUnit.Framework;
namespace ClassLibrary1
{
[TestFixture]
public class DataExceptionTest
{
// According to the design guidelines for exceptions
// See http://msdn.microsoft.com/en-us/library/vstudio/ms229064(v=vs.100).aspx
[Test]
public void DesignGuidelines_ShouldDeriveFromException()
{
Assert.True(typeof(Exception).IsAssignableFrom(typeof(DataException)));
}
[Test]
public void DesignGuidelines_ShouldNotDeriveFromApplicationException()
{
Assert.False(typeof(ApplicationException).IsAssignableFrom(typeof(DataException)));
}
[Test]
public void DesignGuidelines_NameShouldEndInException()
{
Assert.That(typeof(DataException).Name.EndsWith("Exception"));
}
[Test]
public void DesignGuidelines_ExceptionMustBeSerializable()
{
var exception = new DataException("foo", null, new { Context = "Serialization" });
var deserialized = SerializationTesting.SerializeDeserializeWithBinaryFormatter(exception);
Assert.That(deserialized.Message, Is.EqualTo(exception.Message));
}
[Test]
public void DesignGuidelines_MustHaveDefaultConstructor()
{
var exception = new DataException();
}
[Test]
public void DesignGuidelines_MustHaveMessageConstructor()
{
var exception = new DataException("message");
Assert.That(exception.Message, Is.EqualTo("message"));
}
[Test]
public void DesignGuidelines_MustHaveMessageInnerExceptionConstructor()
{
var inner = new ArgumentException();
var exception = new DataException("message", inner);
Assert.That(exception.Message, Is.EqualTo("message"));
Assert.That(exception.InnerException, Is.EqualTo(inner));
}
[Test]
public void Message_WithDataObject_ShouldContainPublicPropertiesFromDataParameter()
{
var exception = new DataException("Failed", null, new { Action = "FooCommand", Salary = 10000 });
var actual = exception.Message;
Assert.IsTrue(actual.StartsWith("Failed"));
Assert.IsTrue(actual.Contains("Action = FooCommand"));
Assert.IsTrue(actual.Contains("Salary = 10000"));
}
}
}
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace ClassLibrary1
{
public class SerializationTesting
{
public static T SerializeDeserializeWithBinaryFormatter<T>(T graph)
{
return SerializeDeserialize(graph, new BinaryFormatter());
}
private static T SerializeDeserialize<T>(T graph, IFormatter formatter)
{
var ms = new MemoryStream();
formatter.Serialize(ms, graph);
ms.Position = 0;
var deserialized = (T)formatter.Deserialize(ms);
return deserialized;
}
}
}
@DrBrask
Copy link

DrBrask commented Dec 5, 2012

Nice!
But why use ApplicationException...?

@mjul
Copy link
Author

mjul commented Dec 7, 2012

Good point.
Back in the day (.NET 1.0), it was adviced to use ApplicationException as base for custom exceptions to distinguish between CLR and custom exceptions, however, the CLR team messed up and derived from it as well (e.g. TargetInvocationException), and the new guidelines are to just derive from Exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment