Now that you have your error handling classes and can save errors in either the database or the Windows Event Log, it is time to implement the functionality in your application. In a typical application you might have to put an error handling routine, like you are going to create, in every form of your application. Thanks to .NET and visual inheritance, you have a convenient place to put your code—our base classes. For the purposes of this application, you will add your error handling routine to the frmListBase form and the frmEditBase form. You will also add it to the main form, frmMain. Add the method in Listing 4-10 to the frmListBase form.
Listing 4-10: The LogException Method
#Region " Error Logger" Protected Sub LogException(ByVal exc As Exception) Dim objLogErr As New NorthwindUC.ErrorLogging.LogError() Try objLogErr.LogException(exc) MessageBox.Show("The NorthwindTrader application generated " _ & "the following error:" & ControlChars.CrLf & exc.Message, _ "NorthwindTrader Error", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Catch excNew As Exception Dim objErrorEvent As NorthwindUC.ErrorLogging.LogErrorEvent objErrorEvent = objErrorEvent.getInstance objErrorEvent.LogErr(exc) objErrorEvent.LogErr(excNew) objErrorEvent = Nothing MessageBox.Show("The NorthwindTrader application generated " _ & "the following critical error: " & excNew.Message, _ "NorthwindTrader Error", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Finally objLogErr = Nothing End Try End Sub #End Region
The method is declared as protected so that you can access it from the forms that inherit from your base form. This routine tries to log the exception that was generated to the database. If an error occurs during the logging of the exception to the database, you can usually be guaranteed that it is a network connection or database connection issue (or some other issue that involves calling remote components). However, you do not want to lose the original error, so you log both errors to the event log and inform the user that a more serious error occurred. It is simple and elegant. Any Windows application can use this code (at least the code that logs to the event log).
Note | In an actual production environment, it would make more sense to split the database logging mechanism and the event log logging mechanism into two classes in different files, but for these purposes it will work fine. This is because it makes the code more reusable; not every application will have a database with this table in it. |
Add the code in Listing 4-10 to the frmEditBase and the frmMain form as well.
Next you are going to add a little interface so that the users can send the errors that are in the event log to technical support.
Caution | It is easy to automatically send the errors via e-mail to technical support whenever an error occurs; however, with all of the issues surrounding privacy and information being sent with or without the user's permission, this could be problematic. Even though this is an enterprise application, you still do not want to violate that privacy. This is a personal choice, but if in doubt, have your company define these policies explicitly so you do not violate any of their privacy guidelines. |
At this point, you can go back into the user interface and add error handling code in your application. I recommend that every routine (except the constructors) have a Try..Catch block if that routine is in the user interface (this includes the base forms). To add the Try..Catch blocks, enter the following code in every routine:
Try 'Your routines code Catch exc As Exception LogException(exc) End Try
Note | If, as in the MenuItem1_Click method in frmMain, you turn the cursor into an hourglass, it makes sense to include a Finally block and in the Finally block turn the cursor back into the default cursor. This saves you the embarrassment of having the cursor looking like an hour-glass after an error occurred when there is no processing occurring. |
Fortunately there is not a lot to retrofit right now so this process is fairly painless. Make sure to avoid adding error handling code in the txtRegionDescription_Validated event in the frmRegionEdit form. Until the next chapter, you are going to leave any error that occurs here unhandled because you will be performing some special error handling for the validated events.
Note | Some developers claim you do not have to include error handling in every routine. They may want to put an error handler only in code that is directly executed by the user. Further, if a method was called by another method, only the original method needs the error handler. However, how do you know all of the methods called by all of the other methods in the application? And what happens if the error occurred in an event raised by a class to which you did not have access? So, why take the chance? If there is an error handler in every routine, there is almost no chance of the application crashing without warning and without reporting the error that caused it to terminate. |
Now that you have a way to retrieve the application errors from the Windows Event Log, you need to give the users a way to send you those errors when they occur. To start with, add a new form to the NorthwindTraders project called frmReportErrors. When you are done adding controls to the form, the form should look like the form in Figure 4-5.
Figure 4-5: Report Error Wizard form
Add the controls in Table 4-2 to the form.
Control | Control Name |
---|---|
Groupbox | |
Label | |
Label | |
Label | |
Listbox | lstErrors |
Button | btnSend |
Button | btnCancel |
Set the properties for these controls according to Table 4-3.
Control Name | Property | Value |
---|---|---|
grpHeader | Backcolor | White |
lblWizardLabel | Text | Report Error Wizard |
lblWizardLabel | Font | Bold |
lblInfo | Text | Send the error log to technical support for diagnosis. |
lblOperation | Text | The following errors will be sent to the technical support personnel: |
btnSend | Text | &Send |
btnCancel | Text | &Cancel |
frmReportErrors | FormBorderStyle | FixedSingle |
frmReportErrors | MaximizeBox | False |
frmReportErrors | MinimizeBox | False |
frmReportErrors | Text | Northwind Wizard |
Import the ErrorLogging namespace using the following line:
Imports NorthwindTraders.NorthwindUC.ErrorLogging
Next, add the following module-level variables:
Private mobjLogErr As LogErrorEvent Private mobjErrArray() As structLoggedError
Before adding any other code into this form, let's copy and paste the LogException method from one of the base classes. It would probably be embarrassing for your error reporting form to cause the application to fail because of an unhandled error!
In the form load event, you will retrieve your errors and place them in the listbox. To do this, add the code in Listing 4-11 to frmReportErrors.
Listing 4-11: The frmReportErrors_Load Method
Private Sub frmReportErrors_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim i As Integer Try mobjLogErr = mobjLogErr.getInstance mobjErrArray = mobjLogErr.RetrieveErrors For i = 0 To mobjErrArray.Length - 1 lstErrors.Items.Add(mobjErrArray(i).Message) Next If lstErrors.Items.Count = 0 Then btnSend.Enabled = False End If Catch exc As Exception LogException(exc) End Try End Sub
This code simply retrieves the array of errors and loops through them to add them to the listbox. If there are no items in the array, it means there were no errors, so just disable the Send button.
Next, add the code in Listing 4-12 to implement the btnSend_Click method so that technical support can actually get the errors.
Listing 4-12: The btnSend_Click Method
Private Sub btnSend_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnSend.Click Try Cursor = Cursors.WaitCursor mobjLogErr.SendErrors(mobjErrArray) Catch exc As Exception LogException(exc) Finally Cursor = Cursors.Default Me.Close() End Try End Sub
This code simply calls the SendErrors method of the LogErrorEvent. Finally, to finish the form, add the following two methods:
Private Sub btnCancel_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnCancel.Click Try Close() Catch exc As Exception LogException(exc) End Try End Sub Private Sub frmReportErrors_Closed(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Closed Try mobjLogErr = Nothing mobjErrArray = Nothing Catch exc As Exception LogException(exc) End Try End Sub
The Cancel button click event simply closes the form, and the Closed event cleans up any of your variables. That is it for the errors form.
Note | This form only displays a list of error messages, with no other information. Extending this form to be able to show the actual information about the exception that is being sent to technical support should be easy if you desire to let them see this information. Remember, the more information an attacker has about vulnerabilities in your system, the easier it will be to take advantage of them. Of course, if an attacker is actually on the user's system, they probably already have access to the event log! |
To implement the form, let's hard-code a menu item (you will create a dynamic menu structure in Chapter 5, "Building Business Objects," when you clean up the user interface). Go back into frmMain and add a new menu item called Report Errors. Double-click the new Report Errors menu item to create the MenuItem.Click event in the code module and alter the method so that it looks like the method in Listing 4-13.
Listing 4-13: Method to Display the Application Errors
Private Sub MenuItem2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem2.Click Dim objReportErrors As frmReportErrors Try objReportErrors = New frmReportErrors() objReportErrors.ShowDialog() Catch exc As Exception LogException(exc) Finally objReportErrors = Nothing End Try End Sub
Note | Your menu control name may not be the same, but it does not matter. As you will notice, I am not following any particular naming convention at this point because you are going to fix most of this work in Chapter 5, "Building Business Objects." |
Because you are showing this form using the ShowDialog() method, you do not need to declare this form as a module-level variable.
Now that you have all of your error handling capabilities in place, it is time to see what happens when you throw an exception that gets stored to the database. But before you can do anything, you need to rebuild the data-centric and shared assemblies and redeploy them to the IIS server.
For the purposes of this test only, modify the LoadList method of the frmRegionList form by adding the following line immediately after the Try statement:
Throw New Exception("My Error Test")
After you have redeployed the remote assemblies, run the application. When you try to open up the Region List form, you will receive the error shown in Figure 4-6.
Figure 4-6: Error test
Assuming that there were no other errors, this will be the only error that is displayed and the form will then be displayed with an empty list. So now that you have an error, let's look at what it stored in the database. Figure 4-7 shows the exception as stored in the application_errors table.
Figure 4-7: The exception as stored in application_errors table
Remember to remove the exception you added for this test!