Welcome to my tutorial on Custom (Structured) Exception handling in C#. In this tutorial we will look at creating a Class Library to help with dealing with exception handling in a custom way. I also did this tutorial to show C# users just how easy it can be to handle exceptions the correct (in my opinion) way.
As a developer all of us have the same goal, to write error free applications, but the chances of that are about the same as finding BigFoot. As we all know, the end user will always find a way to break our applications, in ways we never thought of in the first place. We can ask them "What were doing when the error happened" and, across the board, the answer is always "I don't know ." Then our next question, like we're trained robots, is "What did the error say", and we always get something from "It said something like <insert meaningless statement here>.", or "Im not sure, I just clicked the
OK button and tried to finish, great help huh.
Most applications give basic Microsoft designed and generated messages, like the ones pictured below:

This kind of message is enough to scare most end users from ever using that application again, doesn't do much for customer loyalty. So I spent hours and hours thinking that there has to be a better way of handling exceptions that inevitably happen, no one is the "perfect" programmer, except for maybe Martur2

. All the .Net classes derive from the
SystemException, but it also provides the
ApplicationException. It is Microsoft's "
Best Practices" to inherit from the
ApplicationException class rather then the
SystemException when writing your own exception handling class.
Before asking, or stating your personal opinion, this is a discussion that has been raging for years and may never end, so I suggest you go with what you feel comfortable, I choose to inherit from
System.Exception because thats how I feel comfortable.
So now lets get into our Class Library for our own structured custom exception handling, bear in mind this is a long tutorial as there just isn't any shortcut to accomplish this. First thing you need, as with all classes you create, you need to add and inherit the particular Namespaces you need to accomplish the tasks of your class, in this case our custom exception handling, so now the Namespaces:
CODE
using System.Configuration;
using System.Runtime;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.IO;
using System;
using Microsoft.VisualBasic;
NOTE: In this version I use the
Microsoft.VisualBasic Namespace, now I know this is a C# Class Library but I'm using the
Strings.Split available in VB.Net but not C# (and it works much faster).
You will notice as we get into this class we have used the
<Serializable()> , meaning this class can be
Serialized, meaning it can be populated once and pass it from object to object without having to repopulate it, just deserialize it when you need to.
An exception has many, many properties, and as you know, when creating properties, whether they be ReadOnly or Read/Write, they all need their own variable, so now we're going to look at the variables we need for our Class Library, many which are instantiated once declared:
CODE
#region Variables Declarations
private string _appName;
private string _appVersion = "0.0";
private string _methodAction = "Unknown";
private string _appCulture = "Unknown";
private string _userName = System.Environment.UserName;
private string _machineName = System.Environment.MachineName;
private string _osVersion = System.Environment.OSVersion.ToString();
private DateTime _errorDate;
private DateTime _errorTime;
private string _exceptionType;
private bool _bInnerException = false;
private bool _bCulture;
private bool _bVersion;
private string _exceptionLevel = string.Empty;
private string _message;
private string _stackTrace;
const string DEFAULT_CONN_STRING = "YourConnString";
#endregion
NOTE: You will once again notice I break my class files into different sections using the
Region Keyword, this, in my opinion, makes it easier to find thing in a large class file, such as this one. This, of course, is a design decision of all developers.
In this version I included a
const for a default DB connection. If you want to log your exceptions, which I feel is an excellent idea, just add your connection string and add methods (none included in this Class) to add your exception information to a Database (or even a flat file).
Next thing we're going to look at are the properties themselves. In this class there are 26 variables, 25 ReadOnly (retrieved directly from the Exception itself) and 1 Read/Write. We have the ReadOnly variables because this information comes straight from the Exception being trapped so theres no need to allow the developer to set them.
The single Read/Write variable allows the developer to add text describing what they were doing in the method that caused the Exception to happen in the first place. First we will look at the ReadOnly, and this is information that can be extracted directly from the Exception itself:
CODE
#region ReadOnly Properties
#region Exception Type
public string ExceptionType {
get {
_exceptionType = GetBaseException();
return _exceptionType;
}
}
#endregion
#region Application Culture
public string ApplicationCulture {
get {
if (_bCulture == false)
{
GetApplicationAttributes();
}
return _appCulture;
}
}
#endregion
#region Application Version
public string ApplicationVersion {
get {
if (_bVersion == false)
{
GetApplicationAttributes();
}
return _appVersion;
}
}
#endregion
#region Error Date
public string ErrorDate {
get { return _errorDate.ToString("yyyy/MM/dd"); }
}
#endregion
#region Error Time
public string ErrorTime {
get { return _errorTime.ToShortTimeString(); }
}
#endregion
#region Machine Name
public string MachineName {
get { return _machineName; }
}
#endregion
#region Object Method
public string Method {
get { return base.TargetSite.Name; }
}
#endregion
#region OS Version
public string OSVersion {
get { return _osVersion; }
}
#endregion
#region Inner Exception
public string AppInnerException {
get
{
if (_bInnerException)
{
return base.InnerException.ToString();
}
else
{
return string.Empty;
}
}
}
#endregion
#region StackTrace
public override string StackTrace {
get {
if (_bInnerException == false)
{
_stackTrace = base.StackTrace;
}
else
{
if ((this.InnerException != null))
{
_stackTrace = this.InnerException.StackTrace;
}
else
{
_stackTrace = string.Empty;
}
}
return _stackTrace;
}
}
#endregion
#region User Name
public string UserName {
get { return _userName; }
}
#endregion
#endregion
Two of the properties,
ApplicationCulture and
ApplicationVersion both call functions, we will get to these functions later, then the properties will make more sense on how they're populated. The Read/Write property is the only that allows the developer to interject his own text into the exception object, something currently not available in the current .Net
Exception Object:
CODE
#region Read/Write Properties
#region Object Method Action
public string MethodAction
{
get { return _methodAction; }
set { _methodAction = value; }
}
#endregion
#region Error Level
public string ErrorLevel
{
get { return _exceptionLevel; }
set {
if (!string.IsNullOrEmpty(value))
{
_exceptionLevel = value;
}
}
}
#endregion
#endregion
Next thing we're going to look at is the
Constructors of our Class Library class. Most classes need constructors in order to be initialized. In this class we have 6 constructors. 3 are recommended by Microsoft, the other 3 utilize items such as
SerializationInfo and
StreamingContext, which are used for deserializing Exception.
Instead of showing all 6 at once, Il show the
Standard[b]constructors, then the [b]Custom constructors. First the
Standard:
CODE
#region Standards
public PCException() : base()
{
this.Clear();
}
public PCException(string message):base(message)
{
this.Clear();
}
public PCException(string message, SystemException innerException) : base(message, innerException)
{
this.Clear();
_bInnerException = true;
}
#endregion
If you notice, those are pretty standard constructors for an Exception class, now we look at the
Custom constructors and they will seem to be somewhat
different than the
Standard once:
CODE
#Region Custom Constructors
& #39;****************************************************************************
***********
' Standard constructions utilizing items such as the methods action (what was the
' method doing when the exception occurred), and SerializationInfo & StreamingContext
' for deserializing an exception
'& #39;****************************************************************************
***********
public PCException(string methodAction, string message) : base(message)
{
_methodAction = methodAction;
}
public PCException(string methodAction, string message, SystemException innerException) : base(message, innerException)
{
_methodAction = methodAction;
_bInnerException = true;
}
public PCException(SerializationInfo info, StreamingContext context) : base(info, context)
{
_appCulture = info.GetString("ApplicationCulture");
_appVersion = info.GetString("ApplicationVersion");
_bCulture = info.GetBoolean("ApplicationCultureFlag");
_bVersion = info.GetBoolean("ApplicationVersionFlag");
_methodAction = info.GetString("MethodAction");
_machineName = info.GetString("MachineName");
_userName = info.GetString("UserName");
_osVersion = info.GetString("OSVersion");
_errorDate = info.GetDateTime("ErrorDate");
_errorTime = info.GetDateTime("ErrorTime");
_exceptionLevel = info.GetString("ExceptionLevel");
_appName = info.GetString("ApplicationName");
_exceptionType = info.GetString("ExceptionType");
_stackTrace = info.GetString("StackTrace");
}
#endregion
These are the constructors that allow the developer to interject his own words on what he has doing in the method when the exception occurred. Now we can get into the meat of our Exception handler, we can discuss the methods that actually make it work. The first method we will be looking at will allow us to get the base exception of an Exception. In the hierarchal listing of exceptions, there can be an exception, but 3 levels down there lies the exception that actually caused the exception in the first place. So lets take a look at that method:
CODE
#region GetBaseException
/// <summary>
/// Function to retrieve the base exception in string format
/// </summary>
/// <returns>The base exception type</returns>
/// <remarks></remarks>
public new string GetBaseException()
{
return GetBaseException().ToString();
}
#endregion
Here we Override the
http://msdn2.microsoft.com/en-us/library/s...etBaseException. Overriding this in our class is what allows us to reach the Exception that was at the root of the Exception thrown. The next method we will be looking at is the [url=http://msdn2.microsoft.com/en-us/library/system.object.tostring.aspx]ToString()[/ur] Method. But in our class we will re overriding it so we can have the items we wish to have regarding the exception wee just trapped in it. Lets say that at the end of your code you have
MessageBox.Show(ex.ToString()), it would contains all the Exception information we want since we over rote the base method, it looks something like this:
CODE
#region ToString
public override string ToString()
{
return base.ToString() + Environment.NewLine +
"Source Object : " + this.Source + Environment.NewLine +
"Source Method : " + this.TargetSite.Name + Environment.NewLine +
"Method Action : " + _methodAction;
}
#End Region
Here, as a quick example I show what can be added as your override the
ToString(), I suggest in your version you add all you want to be returned when you call the
ToString() method of our Class Library.
The next method we have, which we over wrote as well, is call
GetObjectData, which we used to serialize our Exception Object. We over write this method so we can add the items we wish to have in our serialized Exception object. Here we use the
SerializationInfo Class to add all the data we have collected about out Exception to the Serialized object:
CODE
#region GetObjectData()
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("ApplicationCulture", _appCulture);
info.AddValue("ApplicationVersion", _appVersion);
info.AddValue("ApplicationCultureFlag", _bCulture);
info.AddValue("ApplicationVersionFlag", _bVersion);
info.AddValue("MethodAction", _methodAction);
info.AddValue("MachineName", _machineName);
info.AddValue("UserName", _userName);
info.AddValue("OSVersion", _osVersion);
info.AddValue("ErrorDate", _errorDate);
info.AddValue("ErrorTime", _errorTime);
info.AddValue("ExceptionLevel", _exceptionLevel);
info.AddValue("ApplicationName", _appName);
info.AddValue("ExceptionType", _exceptionType);
info.AddValue("StackTrace", _stackTrace);
}
#endregion
The next, and last 4 methods, are the main ones, they're the ones we're using to actually use
Reflection to dig as deep into the Exception object we have acquired to get all the information we can find, some of the information being the application version, and the application culture.
The first method we'll look at is called
GetAttributes, and this method drills down as deep as it can into the
AssemblyQualifiedName to get as much information as possible before calling the
GetAppInfo method:
CODE
#region GetApplicationAttributes()
private void GetApplicationAttributes()
{
if (_bInnerException == false)
{
if ((this.TargetSite != null))
{
if ((this.TargetSite.ReflectedType != null))
{
if ((this.TargetSite.ReflectedType.AssemblyQualifiedName != null))
{
GetVersionCulture(this.TargetSite.ReflectedType.AssemblyQualifiedName);
}
}
}
}
else
{
if ((this.InnerException != null))
{
if ((this.InnerException.TargetSite != null))
{
if ((this.InnerException.TargetSite.ReflectedType != null))
{
if ((this.InnerException.TargetSite.ReflectedType.AssemblyQualifiedName != null))
{
GetVersionCulture(this.InnerException.TargetSite.ReflectedType.AssemblyQualifiedName);
}
}
}
}
}
_errorDate = DateTime.Now;
_errorTime = DateAndTime.TimeOfDay;
}
#endregion
In all of that code you will notice the
>>, this is where it is calling this method. This methods main function is to retrieve the applications culture and version from the assembly. When I first write this Class Library last year this was one ugly method, so in time I have refactored it to make it more readable. This method parses and splits the Assembly Name string passed from the
GetApplicationSttributes Method. for 2 main keywords[b]Version, and
Culture. So, lets take a look at the new refactored version:
CODE
#region GetVersionCulture
/// <summary>
/// Procedure for retrieving the application culture and version
/// </summary>
/// <param name="sAssemblyQualifiedName"></param>
/// <remarks></remarks>
private void GetVersionCulture(string sAssemblyQualifiedName)
{
string[] assembly = Strings.Split(sAssemblyQualifiedName, " ", -1, CompareMethod.Text);
foreach (string s1 in assembly)
{
if (_bVersion == false)
{
//check t make sure a value was returned
if (GetAppVersion(s1) != string.Empty && GetAppVersion(s1) != "")
{
//call our refactored functions
_appVersion = GetAppVersion(s1);
_bVersion = true;
}
else
{
_appVersion = "Unknown";
_bVersion = false;
}
}
if (_bCulture == false)
{
//check to make sure a value was returned
if (GetAppCulture(s1) != string.Empty && GetAppCulture(s1) != "")
{
//call our refactored functions
_appCulture = GetAppCulture(s1);
_bCulture = true;
}
else
{
_appCulture = "Unknown";
_bCulture = false;
}
}
if (_bVersion == true & _bCulture == true)
{
break;
}
}
}
#endregion
In this method, like the one before, you will notice 2 sets of
//call our refactored functions's, these are the 2 functions responsible for parsing the string sent to them to look for both the version and culture of the application of the application that caused the exception. Notice I said the application, all of this is retrieving assembly information from the application that generated the exception, not the Exception item itself.
First, we'll look at the
GetAppVersion function, which retrieves a string delimited with equals signs (=), and based on this, if it exists, it retrieves the culture of the application where the Exception occurred:
CODE
#region GetAppVersion
public string GetAppVersion(string str)
{
//string to hold our return value
string version = string.Empty;
if (str.IndexOf("Version", 0) >= 0)
{
string[] sttributes = Strings.Split(str, "=", -1, CompareMethod.Text);
version = sttributes[1];
if (version.IndexOf(",") >= 0)
{
version = version.Substring(0, version.IndexOf(","));
}
}
return version;
}
#endregion
The next function,
GetAppCulture is also passed a equals sign delimited string (=), if it finds the delimiter then it searches the string for the Applications version, in the same manner we search for the applications version:
CODE
#region GetAppCulture
public string GetAppCulture(string str)
{
//string to hold our return value
string culture = string.Empty;
if (str.IndexOf("Culture", 0) >= 0)
{
string[] attributes = Strings.Split(str, "=", -1, CompareMethod.Text);
culture = attributes[1];
if (culture.IndexOf(",") >= 0)
{
culture = culture.Substring(0, culture.IndexOf(","));
}
_bCulture = true;
}
return culture;
}
#endregion
Refactoring 1 method into 3 smaller to maintain methods makes things more maintainable and scalable as you choose to grow this Class Library. I may be doing some more refactoring over time to make it more maintainable, if I do that I will upload the new version of the Class Library and alter the tutorial to show the differences.
Believe it or not, thats what i takes to do custom (structured) exception handling in C#, but we're not finished yet. This last method is simply used to make sure all variables are either emptied out, or reset to their defaults each time our exception class is initialized. Clear is called each time the class is instantiated, with this function we don't end up with
orphan data from the last exception:
CODE
#region Clear
/// <summary>
/// Procedure to clear the exception items when the
/// class is initialized
/// </summary>
/// <remarks></remarks>
private void Clear()
{
_appVersion = "0.0";
_appCulture = "Unknown";
_methodAction = "Unknown";
_userName = System.Environment.UserName;
_machineName = System.Environment.MachineName;
_osVersion = System.Environment.OSVersion.ToString();
_bVersion = false;
_bCulture = false;
_exceptionLevel = string.Empty;
_exceptionType = string.Empty;
_stackTrace = null;
GetApplicationAttributes();
}
#endregion
NOTE: Notice that all variables are reset, to either default values or "Unknown" or am empty string, we call
GetApplicationAttributes to repopulate our Exception object for its next use.
That is what it takes to do your own structured custom exception handling. I wrote this Class Library at home, but it is used is several applications, both web and Windows, at work. I do suggest one thing, if you are going to use this object for applications that have many (I mean 10,000+) concurrent users do what we did, this object was altered to simply trap the exception and pass a user friendly message back to the application, it then forwarded the exception information, via a custom MessageSchema.xsd I created (making it platform independent) to a Web Service that logged the Exceptions and send either emails or SMS's to the appropriate groups based on exception level, application and time of day.
What I did leave out of here is the logging of the Exception, this was done intentionally. All developers log ifferently, use different databases, and some even use network flat files, so leaving that aspect out gives you the opportunity to add your own finishing touches to a very powerful object that took weeks of research and coding to complete.
Since this Class Library us under the
GNU General Public License I will be distributing this class with this tutorial. With this license you can alter, modify, add or delete as you see fit, but the license file and license header must stay in tact at all times.
Remember, there is so much more that can be added to this Class Library, this is designed to get you jump started in the right direction. This idea first came up at work months ago, so I started writing the code in my own time (meaning I own the code) and now work, after seeing what I have done, and what is possible as far as capturing information from Exceptions, whats to use the same code across the board in all our Web and Windows Applications.
Thank you so much for reading, and I hope you found it useful & informative as I did while creating it. Happy
Coding!
PC_Exception_CSharp.zip ( 35.52k )
Number of downloads: 262