This section demonstrates how to perform ASP .NET debugging and error handling with the help of the AspNetDebugDemo application. Figure 9-6 shows the application's main user interface Web page. After you load the application into Visual Studio, open the source file MainForm.aspx to view the application's main page and then use the code view to see the source code behind this page.
If you run the AspNetDebugDemo application within Visual Studio with debugging (by pressing F5), you can see that your default browser displays the application's main page. If you switch back to Visual Studio, go to the Debug menu, and select the Processes menu item, you can see what the Visual Studio debugger does after you press F5. The Processes dialog window (see Figure 9-7) demonstrates that the debugger has attached itself to the aspnet_wp.exe process, which is the ASP .NET worker process that I discussed earlier in this chapter. It's also attached itself to your Web browser's process ”in my case iexplore.exe, because I'm using Internet Explorer.
Although this process attachment happens automatically when you start your ASP .NET application with debugging, you can also do ASP .NET debugging by attaching to these two processes manually using the Processes dialog window, as discussed in the "Attaching to a Process" section in Chapter 3. Manually attaching in this manner is most useful when you want to debug an ASP .NET application that's already running, for instance on a remote test or production Web server.
One error that you may see when you start an ASP .NET application with debugging is the message "Error while trying to run project: Unable to start debugging on the web server". This error is commonly caused by a defective Web.config file ”for example, when one of the XML elements is written using the wrong case. To test whether the Web.config file is indeed the source of the problem, try starting your application without debugging (using Ctrl+F5). If the Web.config file is at fault, you'll see a page describing which setting ASP .NET believes is incorrect.
The buttons on the main page demonstrate how you can perform error handling at various levels of your application. When an error occurs, you can handle it using one or more of the following methods :
Don't handle the error at all ”let it bubble up through your application's call stack until it becomes a completely unhandled error.
Use Try Catch Finally to catch and deal with an error in a procedure within the call stack, as described in Chapter 13.
Use page-level error handling to catch any error within a specific page that isn't caught at the procedure level.
Use application-level error handling to catch any error within your application that isn't caught at the procedure level or the page level.
Of course, you can and should use a mixture of these error-handling methods if you want to catch and handle all errors properly. The next four sections of this chapter look at ASP .NET debugging and error-handling with the help of the AspNetDebugDemo application.
The top button on the main page of the AspNetDebugDemo application demonstrates what happens when you don't handle exceptions that occur within your ASP .NET application. To demonstrate this, you first need to switch off page-level and application-level error handling within the program. To switch off page-level error handling, make sure that the MainForm.ErrorPage property is blank in the form's Properties window. To switch off application-level error handling, you should ensure that the customErrors setting in Web.config is set to RemoteOnly , as discussed in the previous section "Setting CustomErrors in Web.config."
Having switched off page-level and application-level error handling, you can restart the application and add a breakpoint to the btnNoLevel_Click procedure on line 42, the line marked in bold in Listing 9-1.
Private Sub btnNoLevel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnNoLevel.Click 'Doesn't catch or deal with error at all 'Displays error using ASP .NET default error page Dim Test As Object Test.ToString() End Sub
Then launch the application with debugging (by using F5) and click the No Error Handling button. The breakpoint should be hit and you can step through the code shown in Listing 9-1. This code triggers an unhandled exception. Because the exception isn't handled within the application, the ASP .NET worker process displays the error page shown in Figure 9-8. This page shows a description of the error, the source code that triggered it, and the error's stack trace.
As you can see, there's a fair amount of detail shown on this page. This is ideal when you're testing and debugging your application, but you probably wouldn't want to have your end users seeing this page, let alone any hackers who might be interested in doing some digging. So ideally , you want to have some error handling in place to prevent this page from appearing.
To demonstrate procedure-level error handling, add a breakpoint on line 49, the line marked in bold in Listing 9-2. This code triggers an exception that is caught and displayed using the Try Catch Finally construct.
Private Sub btnProcLevel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnProcLevel.Click 'Catches error at procedure-level 'Deals with error in Catch clause 'Displays error in Catch clause Try Dim Test As Object Test.ToString() Catch ex As Exception Me.lblException.Text = ex.Message Finally Server.ClearError() End Try End Sub
Then click your browser's Back button to return to the application's main page, and this time click the button marked "Procedure-level error handling". When the exception is triggered as shown in Listing 9-2, the exception is caught and the exception message is displayed next to the button.
For exceptions that you can anticipate, handling them at the procedure level like this is usually the best way of dealing with them. If you want to see more information about how Try Catch Finally works, it's discussed in detail in Chapter 13.
The button marked "Page-level error handling" demonstrates how you can catch an error at the page level if it's not dealt with at the procedure level, how you can clean up after a page-level error by adding code to the Page_Error event of a Web Form, and how you can then display a custom error page using the ErrorPage property of a Web Form.
To demonstrate page-level error handling, you first need to configure the application to use this level of error handling. First, change the customErrors setting in Web.config to On as discussed in the section "Setting CustomErrors in Web.config." Next, enable the Page_Error event handler for MainForm.aspx by removing the comment symbol from line 77 in this procedure. Finally, add a breakpoint to line 63 in the btnPageLevel_Click procedure, as marked in bold in Listing 9-3, and restart the application.
Private Sub btnPageLevel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnPageLevel.Click 'Catches error at page level 'Deals with error in Me.Page_Error event 'Displays error using Me.ErrorPage property Me.ErrorPage = "CustomErrorPage.aspx" Dim Test As Object Test.ToString() End Sub
Now click the button marked "Page-level error handling". This executes the code shown in Listing 9-3, which first sets the ErrorPage property of the main page and then triggers an exception. Because this exception isn't handled within the procedure, it bubbles up to the Web page's Page_Error event, the code for which is shown in Listing 9-4. The demonstration code simply writes the exception message to any attached trace listeners, but you could place any cleanup or logging code that you wish in this procedure. Note that the Server.ClearError statement is commented-out in this code ”if you don't want to display a custom page, you can use this statement to suppress the exception and then redirect your user back to one of your application's pages after you've logged and dealt with the error.
Private Sub Page_Error(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Error Trace.Write(Server.GetLastError.Message) 'Server.ClearError End Sub
After the code in this procedure has been executed, the exception is redirected for display to the page specified in the ErrorPage property, in this case the CustomErrorPage page. This page is shown in Figure 9-9. It does very little except allow you to return to the page that produced the exception, but you can modify your custom error pages to be as flashy as you want.
The button marked "App-level error handling" demonstrates how you can catch an error at the application level if it's not dealt with at the procedure or page level. It shows how you can clean up after an application-level error by adding code to the Application_Error event in Global.asax and how you can then display a custom error page using the Redirect facility in Web.config.
To demonstrate application-level error handling, you first need to configure the application to use this level of error handling. Then change the customErrors setting in Web.config to On as discussed in the section "Setting CustomErrors in Web.config," and add a defaultRedirect attribute to the customErrors setting, as shown in Listing 9-5. A line with this setting and attribute already exists in the AspNetDebugDemo application, but you should check that the line isn't commented-out. This attribute redirects any application-level exception to the specified custom error page, in a similar fashion to the way that the ErrorPage property of a Web Form contains a custom error page for redirecting page-level exceptions.
<customErrors mode="On" defaultRedirect = "DefaultErrorPage.aspx" />
Finally, add a breakpoint to line 73 in MainForm.aspx.vb as marked in bold in Listing 9-6 and another breakpoint to line 49 in Global.asax.vb as marked in bold in Listing 9-7. Now you can restart the application with debugging (using F5).
Private Sub btnAppLevel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnAppLevel.Click 'Error will be caught at application level 'Deals with error in Global.asax.Application_Error event 'Displays error using redirect in Web.config Dim Test As Object Test.ToString() End Sub
Click the button marked "App-level error handling" to execute the code shown in Listing 9-6, which triggers an exception. This exception isn't handled within the procedure, but it's handled at the page level because there's code present in the MainForm.Page_Error event handler. Because the code in the Page_Error procedure doesn't call Server.ClearError , the exception continues to bubble up until it reaches the Application_Error event in Global.asax, the code for which is shown in Listing 9-7. This demonstration code simply writes the exception message to any attached trace listeners, but you could place any cleanup or logging code that you wish in this procedure.
Sub Application_Error(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Context.Trace.Write(Server.GetLastError.InnerException.Message) End Sub
Notice the requirement to look at the InnerException property of Server.GetLastError , rather than the page-level technique of looking at GetLastError directly. This is necessary because by the time an unhandled exception reaches the application level, it will be an HttpUnhandledException type. To retrieve the original exception, you need to use the InnerException property.
After the code in this procedure has been executed, the exception is redirected for display to the page specified in the defaultRedirect attribute of the customErrors setting, in this case the DefaultErrorPage page shown in Figure 9-10. This page does very little except allow you to return to the page that produced the exception, but you can modify your custom error pages to be as glitzy as you wish.
In addition to having a default error page for application-level unhandled exceptions, you can use the customErrors setting in Web.config to add a specific page redirection for each type of error. This is shown in Listing 9-8.
<customErrors mode="On" defaultRedirect = "DefaultErrorPage.aspx" > <error statusCode="403" redirect = "/AccessForbidden.aspx" /> <error statusCode="404" redirect = "/MissingPage.aspx" /> <error statusCode="500" redirect = "/Support.aspx" /> </customErrors>
This ability to combine procedural, page-level, and application-level ASP .NET error handling gives you a lot of flexibility in the way that you log, recover from, and display your application's exceptions.