Introduction
This article describes an approach to providing users with the means to submit error information directly back to the software developers as errors are encountered during the use of a targeted application. The approach described in this article uses a custom error dialog to display fault information to the user and further describes an approach by which the users can submit the error information directly back to the application developer's by means of a web service.
The approach is intended to be similar to the approach used within the Microsoft Windows operating system where the operating system provides the users with the means to send application error related information back to Microsoft for analysis.
Use of the approach as described in this article should yield information about the performance of a fielded or beta application. The approach demonstrated uses the exception message and stack trace as the source of information regarding specific exceptions encountered by the users; such information will identify the class, method, and the line of code where the exception occurred. Periodic review of the messages and stack traces encountered should provide sufficient information to the development staff to determine what if any problems are being encountered by users of the application and should clearly define specific failure points that may be targeted for repair. While user narrative type reports are necessary for conditions occurring within application that do not trigger an exception, in most instances, a stack trace would be far more useful than a user's description of any given problem.
Since the approach relies upon a web service as the vehicle for making the report it is obvious that the application will require an internet connection in order to enable this feature. Without a current internet connection, the application will not be able to report on an error. Further, the approach demonstrated herein relies upon the user providing consent as a precursor to sending the error information; if the user does not provide such consent, the error will not be reported. Of course it would be possible to send the message without the user's consent, however, when the application attempts to connect to the web service, the user will likely be warned of the connection attempt which may cause some suspicion about the intent of the application.
Figure 1. Generating, Displaying, and Reporting Errors
Figure 2. Viewing Defects Online
Getting Started
There are two solutions included with this download, one is win forms application that demonstrates the construction and use of the custom error dialog, the other is a web solution that demonstrates the construction of the bug reporting web service.
Figure 3 (below) shows the solution explorer for the project. The win forms application is shown at the top and contains a web reference to the bug reporting web service. It also contains two forms: Form1 is just a test container used to generate errors and evoke the display of the custom error dialog, the second form (ErrorReport) is used to display the error information to the user and to provide the user with option of viewing the stack trace and sending the error report to the web service.
Aside from these items, the win forms application ("BugReport") includes a resource file which contains three icons. These icons are used with the custom error dialog to show the appropriate icon on the form when it is displayed (e.g., an exclamation point icon, a denied icon, and an information icon).
The web solution contains a single web service called "ReportBug" as a well as a web page (Default) used to display all of the current reports contained in the defect database (which in this demo is an SQL server database called "BugTracker" and in a table called "BugReports". The database can of course be any type of database desired (e.g., Oracle, SQL Server, etc.). Even Microsoft Access could be used however Microsoft cautions against the use of OLEDB connections when working with a web services due mainly to security and permissions issues.
Figure 3. Solution Explorer with Both Projects Visible
In this example, the web.config file was set up to run with Windows Authentication and the database users and permissions were configured to support this type of authentication. Depending upon your needs this may or may not be a satisfactory solution so feel free to set the users and permissions up according to your requirements should you want to implement the approach; the code in the demo is not dependent upon this part of the configuration so long as the set up allows the web service to connect to the database and perform an insert when the request is originated from the win forms application.
Database Setup.
For the purposes of this demonstration, an SQL service database was set up and called "BugTracker"; within this database, a single table was added ("BugReports"). That table included the following columns:
Figure 4. The Bug Reports Table Definition
Naturally you may create your own table and add and remove columns from it as you see fit. Further, you may wish to add a stored procedure to handle the insert command; for the purposes of this demo, I did not add a stored procedure and I performed the inserts using dynamic SQL. Certainly using a stored procedure would make for a more robust and safer design.
The Web Project
Code: ReportBug Web Service.
The web service is fairly straight forward, there is a single web service contained in ReportBug.asmx and using the ReportBug.vb code behind file. The imports and declaration of this class are as follows:
Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlClient
<WebService(Namespace:="http://localhost/BugTracker")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class ReportBug
Inherits System.Web.Services.WebService
In addition to the default imports, I've added System.Data, System.Data.SQL, and System.Data.SQLClient. These imports are necessary to support performing the insert to the SQL Server database. The class declaration is per the default created when the web service was added to the project.
The web service contains a single web method which is shown in the following; the section is annotated with comments and should be easy enough to understand.
<WebMethod()> _
Public Function Report(ByVal sDomain As String, _
ByVal sUser As String, _
ByVal sComputer As String, _
ByVal sApplication As String, _
ByVal sErrorMessage As String, _
ByVal sErrorStackTrace As String) As Boolean
Dim sConnectionString As String
sConnectionString = "Data Source=(local);Initial Catalog=BugTracker;" & _
"Integrated Security=True"
Dim conn As SqlConnection = New SqlConnection(sConnectionString)
Try
Dim strBuilder As New System.Text.StringBuilder
strBuilder.Append("INSERT INTO [BugReports] ")
strBuilder.Append("([UserDomain], [UserName], [UserComputer], " & _
"[UserApplication], [ErrorMessage], " & _
"[ErrorStackTrace]) VALUES(")
strBuilder.Append("'" & sDomain & "', ")
strBuilder.Append("'" & sUser & "', ")
strBuilder.Append("'" & sComputer & "', ")
strBuilder.Append("'" & sApplication & "', ")
strBuilder.Append("'" & sErrorMessage & "', ")
strBuilder.Append("'" & sErrorStackTrace & "')")
Dim sSQL As String = strBuilder.ToString()
Dim cmd As SqlCommand = New SqlCommand(sSQL, conn)
Try
conn.Open()
cmd.ExecuteNonQuery()
conn.Close()
Catch ex As Exception
conn.Close()
Return False
End Try
Catch ex As Exception
conn.Close()
Return False
End Try
conn.Close()
Return True
End Function
The web method accepts all of the required information to perform an insert as arguments, these values are passed to an insert statement formatted using a string builder. The code establishes a database connection and executes the insert statement through the connection. The rest of the method is used to capture and errors in execution and to close the connection following the insert.
Code: Default Page
The default page included in the web project is merely provided as a means for reviewing the fault information stored in the database. The default page was shown earlier in Figure 2 of this document. There is nothing particularly interesting about the default page; it contains a single data grid which is sourced back to the table containing the error report information. All of the information contained in the table is shown in the data grid with the exception of the time stamp.
Code: The web.config File
The web.config file is set up per the default; it calls for Windows Authentication. Depending upon your needs, you may opt to select an alternative form of authentication.
<authentication mode="Windows" />
The Win Forms Project
Code: ErrorReport Form
The error report form is used as a custom error dialog; the form's design is simple, it contains a picture box used to display the error type icon, a text box with no border set to have a background color matching the form which is used to display the error message, a text box used to display the message details (in the form of the stack trace), a button which allows the user to show or hide the details, a button used to close the form, and a button used to send the error information collected to the database through the web service.
The form's code behind page is set up into regions: declarations, constructors, methods, and properties. Each of these sections is used to contain the relevant sections of the code.
The class imports and declaration are simple enough:
Imports System
Imports System.Windows.Forms.SystemInformation
Public Class ErrorReport
Aside from the System import, System.Windows.Forms.SystemInformation has been added to support capturing some of the information used to make the bug report.
The declarations section contains the variable declarations used to support this form:
#Region "Declarations"
Public Enum MessageImage
Denied
Exclaim
Information
End Enum
Private mMessage As String
Private mTitle As String
Private mStackTrace As String
Private mMessageImage As MessageImage
Private mApplicationName As String
#End Region
The enumeration declared at the top of the region is used to define what type of image will be displayed as the form icon; in the demo, three alternatives are presented and included in this enumeration: Denied, Exclaim, and Information.
The next four variables are private local member variables used to contain the error message, the message title, the stack trace, and the message image; all of these values are supported through properties with public accessor methods used to set or retrieve the values of variables.
The constructors region contains two constructors, one default, empty constructor, and one overloaded constructor that accepts all of the necessary values used to generate an error report as a set of arguments:
#Region "Constructors"
Public Sub New()
InitializeComponent()
Me.Height = 160
End Sub
Public Sub New(ByVal message As String, _
ByVal title As String, _
ByVal stack As String, _
ByVal app As String, _
ByVal msgImage As MessageImage)
InitializeComponent()
Me.Height = 160
mMessage = message
mTitle = title
mStackTrace = stack
txtErrorMsg.Text = mMessage
txtDetails.Text = mStackTrace
Me.Text = mTitle
Select Case (msgImage)
Case MessageImage.Denied
Me.picMsgPicture.Image = My.Resources.explorer_exe_Ico64_ico_Ico1
Case MessageImage.Exclaim
Me.picMsgPicture.Image = My.Resources.netplwiz_dll_Ico8_ico_Ico1
Case MessageImage.Information
Me.picMsgPicture.Image = My.Resources.explorer_exe_Ico52_ico_Ico1
Case Else
Me.picMsgPicture.Image = My.Resources.netplwiz_dll_Ico8_ico_Ico1
End Select
End Sub
#End Region
The details section of the error report is hidden by default, this is accomplished in both constructors merely by setting the height of the form to 160 on initialization. In the overloaded constructor, the arguments are processed and used to set each of the properties used in the dialog. A select case statement as the bottom of the overloaded constructor is used to set the image used on the form based upon the Message Image value passed into the constructor as a argument.
If the class is initialized using the default constructor, the user may set the properties for the form directly prior to showing the form.
The methods region of the class is used to handle the button clicks used to close the form, view the details, and send the error report. The click event handler for the close button is as follows:
#Region "Methods"
Private Sub btnClose_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnClose.Click
Me.Dispose()
End Sub
The click event handler for the view details button is as follows:
Private Sub btnViewDetails_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnViewDetails.Click
If btnViewDetails.Text = "View Details" Then
Me.Height = 300
btnViewDetails.Text = "Hide Details"
Else
Me.Height = 160
btnViewDetails.Text = "View Details"
End If
End Sub
This handler checks the label of the button to determine the appropriate action; the label is set to "View Details", it changes the height of the form from 160 to 300 which in turn exposes the details and the button used to send the details to the web service. It also changes the button label to read "Hide Details". If the button lable reads "Hide Details" when the button is clicked, the height of the form is set back 160 and the button label is reset to read "View Details".
The last button click event handler is used to make the error report. The code for that handler is:
Private Sub btnSendBugReport_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnSendBugReport.Click
Dim strUser As String
strUser = System.Windows.Forms.SystemInformation.UserName.ToString()
Dim strDomain As String
strDomain _
= System.Windows.Forms.SystemInformation.UserDomainName.ToString()
Dim strComputerName As String
strComputerName =
System.Windows.Forms.SystemInformation.ComputerName.ToString()
Dim ws As New BugTracker.ReportBug()
ws.Credentials = System.Net.CredentialCache.DefaultCredentials
Dim blnTest As Boolean = False
Try
blnTest = ws.Report(strDomain, strUser, strComputerName, _
ApplicationName, ErrorMessage, ErrorStack)
Catch
MessageBox.Show("The error message was not sent.", _
"Submittal Failed", _
MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End Try
If blnTest = True Then
MessageBox.Show("The error information was successfully sent.",
"Submittal Complete", _
MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
End Sub
The code is annotated and should be easy to follow; at the beginning, the code obtains the domain name, user name, and computer name from the user's machine and uses the values obtained to populate some variables which will in turn be passed to the web service.
Next, an instance of the web service is created. Following that and since the web project is using Windows Authenication, I am passing the user's default credentials to the service. A boolean variable is then declared and used to capture the results returned by the web service when the error report information is submitted.
Within the try-catch block, the web service's Report web method is evoked and passed the system, user, and error related information. If the information is successfully loaded into the database, the web method returns a true, if the operation fails, a false is returned.
After the web method executes, the boolean is evaluated and the user is notified as to whether or not the data was transferred to the database.
The remaining properties section of the class contains the public accessor methods used to set each of the local member variables and to update the form:
#Region "Properties"
Public Property ErrorMessage() As String
Get
Return mMessage
End Get
Set(ByVal value As String)
mMessage = value
txtErrorMsg.Text = mMessage
End Set
End Property
Public Property ErrorStack() As String
Get
Return mStackTrace
End Get
Set(ByVal value As String)
mStackTrace = value
txtDetails.Text = "An error occurred at " & Trim(mStackTrace)
End Set
End Property
Public Property ErrorTitle() As String
Get
Return mTitle
End Get
Set(ByVal value As String)
mTitle = value
Me.Text = mTitle
End Set
End Property
Public Property ErrorImage() As MessageImage
Get
Return mMessageImage
End Get
Set(ByVal value As MessageImage)
mMessageImage = value
Select Case (mMessageImage)
Case MessageImage.Denied
Me.picMsgPicture.Image = My.Resources.explorer_exe_Ico64_ico_Ico1
Case MessageImage.Exclaim
Me.picMsgPicture.Image = My.Resources.netplwiz_dll_Ico8_ico_Ico1
Case MessageImage.Information
Me.picMsgPicture.Image = My.Resources.explorer_exe_Ico52_ico_Ico1
Case Else
Me.picMsgPicture.Image = My.Resources.netplwiz_dll_Ico8_ico_Ico1
End Select
End Set
End Property
Public Property ApplicationName() As String
Get
Return mApplicationName
End Get
Set(ByVal value As String)
mApplicationName = value
End Set
End Property
#End Region
Code: Form1
This form is used as a test bed for the error report dialog. It is set up to force a divide by zero exception and an index out of bounds exception. When the exceptions are encountered, the catch block is used to create an instance of the error report dialog and to populate and display it. The form also contains a button used to open the default page of the web project and to display all of the reported errors contained in the database. The code for this form is annotated and easy to follow:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Try
Dim intA As Integer = 12
Dim intB As Integer = 0
Dim result As Integer = intA / intB
Catch ex As Exception
Dim f As New ErrorReport()
f.ErrorMessage = ex.Message.ToString()
f.ErrorStack = ex.StackTrace.ToString()
f.ErrorTitle = "An Error Has Occurred"
f.ErrorImage = ErrorReport.MessageImage.Denied
f.ApplicationName = f.ProductName & " " & f.ProductVersion
f.Show()
End Try
End Sub
Private Sub btnIndexError_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnIndexError.Click
Try
Dim sJunk As String = "Test"
Dim cArr() As Char = sJunk.ToCharArray()
Dim i As Integer
For i = 0 To 5
sJunk += cArr(i)
Next
Catch ex As Exception
Dim f As New ErrorReport()
f.ErrorMessage = ex.Message.ToString()
f.ErrorStack = ex.StackTrace.ToString()
f.ErrorTitle = "An Error Has Occurred"
f.ErrorImage = ErrorReport.MessageImage.Denied
f.ApplicationName = f.ProductName & " " & f.ProductVersion
f.Show()
End Try
End Sub
Private Sub btnErrorView_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnErrorView.Click
System.Diagnostics.Process.Start( _
"http://localhost/BugTracker/Default.aspx")
End Sub
Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnExit.Click
Application.Exit()
End Sub
End Class
Summary
This article demonstrates an approach that may be used to gather fault related information about an application directly from the users of the application. This approach could easily be modified to include other types of information in addition to that shown. In deploying this sort of feature you will need to attend to the authentication used by the web project as well as the users and permissions applicable to the database side of the project.