Last active
October 13, 2015 12:18
-
-
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 });
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.