Welcome to Dream.In.Code
Getting C# Help is Easy!

Join 109,491 C# Programmers for FREE! Ask your question and get quick answers from experts. There are 1,184 online right now! We've got more than 500 tutorials and 2,000 snippets. Join and find out why Dream.In.Code is the #1 programming help community on the internet! Registration is fast and FREE... Join Now!



Custom (Structured) Exception Handling in C#

 
Reply to this topicStart new topic

> Custom (Structured) Exception Handling in C#

PsychoCoder
Group Icon



post 3 Jan, 2008 - 10:02 PM
Post #1


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:

Attached Image

Attached Image


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 tongue.gif . 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!

Attached File  PC_Exception_CSharp.zip ( 35.52k ) Number of downloads: 262
Go to the top of the page
+Quote Post


Register to Make This Ad Go Away!


Fast ReplyReply to this topicStart new topic
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:

 

Lo-Fi Version Time is now: 9/7/08 02:12PM

Live C# Help!

C# Tutorials

Reference Sheets

C# Snippets

Bye Bye Ads

Free DIC T-Shirt

T-Shirt Example

Related Sites

Monthly Drawing

Thumb Drive

Partners

Top Contributors

Top 10 Kudos This Month