Introduction
Creating custom events allows us to tailor events to be the most descriptive for the given tasks. It is common practice to also add additional information to a custom event to provide additional information. In this post, I will show you how to create a custom event handler to broadcast Database Errors throughout an application. This example allows you to have data processing methods that go through a large number of operations but internally handle errors, raise the event, and then keep processing.
This example is broken into the following parts.
- Creating a custom EventArgs class.
- Declaring a delegate to handle your custom event.
- Declaring and raising a public event for consumption.
- Subscribing to the event from another process.
Creating a custom EventArgs Class
First, we need to create a custom class to handle the information for our event, and we will want to have this class derive from the EventArgs class. Below is an example of a DatabaseErrorEventArgs class that I have created; below the code sample is a summary of what is in the code.
public class DatabaseErrorEventArgs : EventArgs
{
#region Private Data Members
System.Data.SqlClient.SqlException mException;
string mSectionDetail;
bool mIsCritical;
#endregion
#region Constructor
/// <summary>
/// Class constructor, all parameters are required all the time!
/// </summary>
/// <param name="ex">The SQL Exception raised by the application.</param>
/// <param name="section">The section of the process that caused the error.</param>
/// <param name="critical">An indicator if the error was a critical error.</param>
public DatabaseErrorEventArgs(System.Data.SqlClient.SqlException ex, string section, bool critical)
{
this.mException = ex;
this.mSectionDetail = section;
this.mIsCritical = critical;
}
#endregion
#region Public Properties
/// <summary>
/// Returns the passed SQL Exception detailing the error.
/// </summary>
public System.Data.SqlClient.SqlException DatabaseError
{
get { return mException; }
}
/// <summary>
/// Returns information regarding the section of the application that returned the error.
/// </summary>
public string ErrorSection
{
get { return mSectionDetail; }
}
/// <summary>
/// Returns an indicator designating if the failure was critical.
/// </summary>
public bool IsCritical
{
get { return mIsCritical; }
}
#endregion
}
If you notice in the constructor, I require that all three parameters be sent. I also provided three public properties to expose the values of the internal members. In addition to the methods I have listed here you might also want to override the "ToString" method of the EventArgs class to provide custom text when calling "ToString".
Declaring a delegate to handle your custom event
I think everyone at times has been confused about delegates and what they are; I have found the following "description" very helpful. "A delegate serves as a special event handler, identifying the signature of our event. This ensures that ALL handlers of the event will have all desired event information." With this in mind for our DatabaseErrorEvent, we want to ensure that the listener to the event knows the following: Who created the error and what the details were? Therefore we will have 2 parameters in our delegate. (Sender and e to follow the event standards). A delegate is declared as a void method with no body and the delegate keyword, an example of our delegate is below.
public delegate void DatabaseErrorEvent(object sender, DatabaseErrorEventArgs e);
This creates a special event handler, so now we can raise events elsewhere in our application and state that the event is a DatabaseErrorEvent. By doing this, we will ensure that the DatabaseErrorEventArgs is provided. Since the delegate and the EventArgs class are so closely related, I typically include them both in the same .cs file in my solution, as it makes it much easier to find the declaration later.
Declaring and raising a public event for consumption
Now that we have an EventArgs class and a delegate, it is time to create and raise an event from our code for users to subscribe to. First, we need to declare the event; this event declaration must be within a class and should be formed like the following.
public event DatabaseErrorEvent CriticalFailure;
This creates an event called CriticalFailure which is of the type DatabaseErrorEvent, which, when raised, will provide information regarding the sender and a populated instance of DatabaseErrorEventArgs. To raise this event, it is fairly simple, you will want to create a protected virtual void method to accomplish this. The reason for using "protected virtual" instead of private or internal is to ensure that if the class was ever extended/inherited in the future, the new implementation could override and call the method.
This method to raise the event is traditionally called an OnError event. Therefore, for ease of maintaining your code, you should name it consistently. In the case of our example, the event is CriticialFailure, therefore, the method should be OnCriticalFailure. Below you will find the code for the OnCriticalFailure method; I will describe a few things below the code sample.
protected virtual void OnCriticalFailure(DatabaseErrorEventArgs e)
{
if (this.CriticalFailure != null)
{
this.CriticalFailure(this, e);
}
}
In this statement, you will notice that we check to ensure that CriticalError is not equal to null. This simply ensures that we have at least one subscriber to the method. If we have no subscribers, it is not necessary to raise the event. In your code, when you want to raise a "CriticalError," you simply call OnCriticalError, passing it a valid DatabaseErrorEventArgs instance. You can view this code in the linked file below.
Subscribing to the event from another process
Now that you have the EventArgs class created, the delegate created, and all of the methods in place to raise the event, your final step is to subscribe and respond to the event from elsewhere in your code. This part is fairly simple. Simply instantiate your worker class, and add the handler, which points to the method that will respond to the raised event. Below is an example. (Also included in the code sample).
private void btnGo_Click(object sender, EventArgs e)
{
// Clear results
lblResult.Text = "";
// Instantiate the worker
DemonstrationWorker oWorker = new DemonstrationWorker();
// Subscribe to the critical error event
oWorker.CriticalFailure += new DatabaseErrorEvent(oWorker_CriticalFailure);
// Call its do work method
oWorker.DoWorkAndRaiseEvent();
// Mark as done
lblResult.Text = "Finished";
}
/// <summary>
/// Responds to the raising of the critical failure notice.
/// </summary>
void oWorker_CriticalFailure(object sender, DatabaseErrorEventArgs e)
{
// Show it was done
MessageBox.Show("Critical Failure Indicated!! \n" + e.DatabaseError.Message);
}
Note. the line that starts with "oWorker.CriticalFailure +=" This is the line that allows your calling class to listen for the CriticalFailureEvent. Notice the handler that we are adding is declared as a new DatabaseErrorEvent, which is what our delegate was. The value inside the ()'s is the name of the method to call when the event is raised.
If you run the included sample application, when you click on the "Perform Demo" button, you should notice a 2-second delay, then you should get a message box, then after about 6 more seconds, you will see the text "Finished" in the display label. This was a fairly simple example of how you can create and generate your own events. If there are any questions or if you have ideas for other topics, please let me know.