Dealing with Page Errors

 

Dealing with Page Errors

Just like other .NET applications, ASP.NET applications can take advantage of exceptions to catch and handle run-time errors that occur in the code. Exceptions, though, should be just what their name suggests exceptional events in the life of the application, raised when something happens that violates an assumption. A typical bad programming practice is to rely on exceptions to catch any possible error resulting from an operation. Admittedly, wrapping a piece of code with a try/catch block makes programming much simpler while offering a single point of control for errors. However, employing this technique on a large scale can result in a dramatic loss of performance. Exceptions are meant to target exceptional events that aren't predictable in other ways. Exceptions should not be used to control the normal flow of the program. If there is a way to detect possible inconsistent situations, by all means use that other method and keep exceptions as the last resort.

This said, bear in mind that exceptions are the official tool to handle errors in .NET applications. They're not lightweight and should not be overused, but they provide a solid, modern, and effective way of catching errors and recovering from them.

When an exception occurs in an ASP.NET application, the common language runtime (CLR) tries to find a block of code willing to catch it. Exceptions walk their way up the stack until the root of the current application is reached. If no proper handler shows up along the way, the exception gains the rank of unhandled exception and causes the CLR to throw a system-level exception. Users are shown a standard error page that some developers familiarly call YSOD (Yellow Screen of Death), which is a spin-off of the just as illustrious BSOD (Blue Screen of Death) that we all have come to know after years of experience with the Microsoft Windows operating system.

An unhandled exception originates an error and stops the application. As a developer, how should you deal with unhandled exceptions in ASP.NET applications?

Basics of Error Handling

Any ASP.NET application can incur various types of errors. There are configuration errors caused by some invalid syntax or structure in one of the application's web.config files and parser errors that occur when the syntax on a page is malformed. In addition, you can run into compilation errors when statements in the page's code-behind class are incorrect. Finally, there are run-time errors that show up during the page's execution.

Default Error Pages

When an unrecoverable error occurs in an ASP.NET page, the user always receives a page that (more or less) nicely informs him or her that something went wrong at a certain point. ASP.NET catches any unhandled exception and transforms it into a page for the user, as shown in Figure 5-3.

image from book
Figure 5-3: The error page generated by an unhandled exception.

The typical error page differs for local and remote users. By default, local users namely, any user accessing the application through the local host receive the page shown in Figure 5-3. The page includes the call stack the chain of method calls leading up to the exception and a brief description of the error. Additional source code information is added if the page runs in debug mode. For security reasons, remote users receive a less detailed page, like the one shown in Figure 5-4.

image from book
Figure 5-4: A run-time error occurred on the server. The page does not provide information about the error, but it still can't be called a user-friendly page!

Exception handling is a powerful mechanism used to trap code anomalies and recover or degrade gracefully. By design, though, exception handling requires you to know exactly the points in the code where a given exception, or a given set of exceptions, can occur. Exceptions raised outside any interception points you might have arranged become unhandled exceptions and originate YSOD.

ASP.NET provides a couple of global interception points for you to handle errors programmatically, at either the page level or the application level. As mentioned in Chapter 3, the Page base class exposes an Error event, which you can override in your pages to catch any unhandled exceptions raised during the execution of the page. Likewise, an Error event exists on the HttpApplication class, too, to catch any unhandled exception thrown within the application.

Page-Level Error Handling

To catch any unhandled exceptions wandering around a particular page, you define a handler for the Error event. Here's an example:

protected void Page_Error(object sender, EventArgs e) {     // Capture the error     Exception ex = Server.GetLastError();     // Resolve the error page based on the exception that occurred     // and redirect to the appropriate page     if (ex is NotImplementedException)         Server.Transfer("/errorpages/notimplementedexception.aspx");     else         Server.Transfer("/errorpages/apperror.aspx");     // Clear the error     Server.ClearError(); } 

You know about the raised exception through the GetLastError method of the Server object. In the Error handler, you can transfer control to a particular page and show a personalized and exception-specific message to the user. The control is transferred to the error page, and the URL in the address bar of the browser doesn't change. If you use Server.Transfer to pass control, the exception information is maintained and the error page itself can call into GetLastError and display more detailed information. Finally, once the exception is fully handled, you clear the error by calling ClearError.

Important 

When displaying error messages, take care not to hand out sensitive information that a malicious user might use against your system. Sensitive data includes user names, file system paths, connection strings, and password-related information. You can make error pages smart enough to determine whether the user is local, or whether a custom header is defined, and display more details that can be helpful to diagnose errors:

if (Request.UserHostAddress == "127.0.0.1") {     ... } 

You can also use the Request.Headers collection to check for custom headers added only by a particular Web server machine. To add a custom header, you open the Properties dialog box of the application's Internet Information Server (IIS) virtual folder and click the HTTP Headers tab.

Global Error Handling

A page Error handler catches only errors that occur within a particular page. This means that each page that requires error handling must point to a common piece of code or define its own handler. Such a fine-grained approach is not desirable when you want to share the same generic error handler for all the pages that make up the application. In this case, you can create a global error handler at the application level that catches all unhandled exceptions and routes them to the specified error page.

The implementation is nearly identical to page-level error handlers except that you will be handling the Error event on the HttpApplication object that represents your application. To do that, you add a global.asax file to your application and write code in the predefined Application_Error stub:

void Application_Error(Object sender, EventArgs e) {     ... } 

In Microsoft Visual Studio .NET 2005, to generate the global.asax file, you select Add New Item and then pick up a Global Application Class item.

Note 

You can have at most one global.asax file per ASP.NET application. In Visual Studio .NET 2003, an empty global.asax file is generated when you create a Web application project. In Visual Studio .NET 2005, if your Web site project already contains a global.asax file, the corresponding selection is removed from the list of available items when you click the Add New Item menu.

You could do something useful in this event handler, such as sending an e-mail to the site administrator or writing to the Windows event log to say that the page failed to execute properly. ASP.NET 2.0 provides a set of classes in the System.Net.Mail namespace for just this purpose. (Note that a similar set of classes exist for ASP.NET 1.x in the System.Web.Mail namespace, which is obsolete in ASP.NET 2.0.)

MailMessage mail = new MailMessage(); mail.From = new MailAddress("automated@contoso.com"); mail.To.Add(new MailAddress("administrator@contoso.com")); mail.Subject = "Site Error at " + DateTime.Now; mail.Body = "Error Description: " + ex.Message; SmtpClient server = new SmtpClient(); server.Host = outgoingMailServerHost; server.Send(mail); 

The code to use for ASP.NET 1.x is slightly different, doesn't require you to explicitly set the host, and uses the SmtpMail class.

Robust Error Handling

A good strategy for a robust and effective error handling is based on the following three guidelines:

  1. Anticipate problems by wrapping all blocks of code that might fail in try/catch/finally blocks. This alone doesn't guarantee that no exceptions will ever show up, but at least you'll correctly handle the most common ones.

  2. Don't leave any exceptions unhandled. By following this guideline, even if you did not anticipate a problem, at least users won't see an exception page. You can do this both at the page and application level. Needless to say, an application-level error handler takes precedence over page-level handlers.

  3. Make sure that error pages don't give away any sensitive information. If necessary, distinguish between local and remote users and show detailed messages only to the former. A local user is defined as the user that accesses the application from the Web server machine.

Outlined in this way, error handling is mostly a matter of writing the right code in the right place. However, ASP.NET provides developers with a built-in mechanism to automatically redirect users to error-specific pages. This mechanism is entirely declarative and can be controlled through the web.config file.

Warning 

As we delve deeper into the various topics of ASP.NET, we'll be making extensive use of the information stored in configuration files. Configuration files were briefly introduced in Chapter 1, but you won't find in this book a chapter expressly dedicated to them. For appropriate coverage, you might want to take a look at Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics (Microsoft Press, 2005).

Mapping Errors to Pages

When an unhandled exception reaches the root of the stack, ASP.NET renders a default page, displaying the previously mentioned yellow screen of death. Developers can customize this aspect of ASP.NET to a large extent through the <customErrors> section in the application's web.config file.

The <customErrors> Section

You turn on custom error messages for an ASP.NET application acting on the <customErrors> section. Here's an example:

<configuration>     <system.web>         ...         <customErrors mode="RemoteOnly" />     </system.web> </configuration> 

The mode attribute specifies whether custom errors are enabled, disabled, or shown only to remote clients. The attribute is required. When the mode attribute is set to RemoteOnly (the default setting) remote users receive a generic error page that informs them that something went wrong on the server. (See Figure 5-4.) Local users, on the other hand, receive pages that show lots of details about the ASP.NET error. (See Figure 5-3.)

The error handling policy can be changed at will. In particular, ASP.NET can be instructed to display detailed pages to all local and remote users. To activate this functionality, you change the value of the mode attribute to Off. For obvious security reasons, Off should not be used in production environments it might reveal critical information to potential attackers.

Using Custom Error Pages

Overall, whatever your choice is for the mode attribute, all users have a good chance to be served a rather inexpressive and uninformative error page. To display a more professional, friendly, and apologetic page that has a look and feel consistent with the site, you set web.config as follows. Figure 5-5 gives an idea of the results you can get.

<configuration>     <system.web>         <customErrors mode="On"             defaultRedirect="/GenericError.aspx" />     </system.web> </configuration> 

image from book
Figure 5-5: A nicer-looking, friendlier error page.

Whatever the error is, ASP.NET now redirects the user to the GenericError.aspx page, whose contents and layout are completely under your control. This look is obtained by adding an optional attribute such as defaultRedirect, which indicates the error page to use to notify users. If mode is set to On, the default redirect takes on the standard error pages for all local and remote users. If mode is set to RemoteOnly, remote users will receive the custom error page while local users (typically, the developers) still receive the default page with the ASP.NET error information.

In most cases, the custom error page is made of plain HTML so that no error could recursively be raised. However, should the error page, in turn, originate another error, the default generic page of ASP.NET will be shown.

Note 

When a default redirect is used, the browser receives an HTTP 302 status code and is invited to issue a new request to the specified error page. This fact has a key consequence: any information about the original exception is lost and GetLastError, which is called from within the custom error page, returns null.

Handling Common HTTP Errors

A generic error page invoked for each unhandled exception can hardly be context-sensitive especially if you consider that there's no immediate way for the page author to access the original exception. We'll return to this point in a moment.

In addition to redirecting users to a common page for all errors, ASP.NET enables you to customize pages to show when certain HTTP errors occur. The mapping between error pages and specific HTTP status codes is defined in the web.config file. The <customErrors> section supports an inner <error> tag, which you can use to associate HTTP status codes with custom error pages:

<configuration>   <system.web>     <customErrors mode="RemoteOnly" defaultRedirect="/GenericError.aspx">         <error statusCode="404" redirect="/ErrorPages/Error404.aspx" />         <error statusCode="500" redirect="/ErrorPages/Error500.aspx" />     </customErrors>   </system.web> </configuration> 

The <error> element indicates the page to redirect the user to when the specified HTTP error occurs. The attribute statusCode denotes the HTTP error. Figure 5-6 shows what happens when the user mistypes the name of the URL and the error HTTP 404 (resource not found) is generated.

image from book
Figure 5-6: A custom page for the popular HTTP 404 error.

When invoked by the ASP.NET infrastructure, pages are passed the URL that caused the error on the query string. The following code shows the code-behind of a sample HTTP 404 error page:

public partial class Error404 : System.Web.UI.Page {     protected void Page_Load(object sender, EventArgs e)     {         string errPath = "<i>No error path information is available.</i>";         object o = Request.QueryString["AspxErrorPath"];         if (o != null)             errPath = (string) o;         // Update the UI         ErrorPath.InnerHtml = errPath;     } } 

Getting Information About the Exception

As mentioned, when you configure ASP.NET to redirect to a particular set of error pages, you lose any information about the internal exception that might have caused the error. Needless to say, no internal exception is involved in an HTTP 404 or HTTP 302 error. Unhandled exceptions are the typical cause of HTTP 500 internal errors. How do you make the page show context-sensitive information, at least to local users?

You get access to the exception in the Error event both at the page and application level. One thing you can do is this: write a page-level error handler, capture the exception, and store the exception (or only the properties you're interested in) to the session state. The default redirect will then retrieve any context information from the session state.

protected void Page_Error(object sender, EventArgs e) {     // Capture the error and stores exception data     Exception ex = Server.GetLastError();     // Distinguish local and remote users     if (Request.UserHostAddress == "127.0.0.1")         Session["LastErrorMessage"] = ex.Message;     else         Session["LastErrorMessage"] = "Internal error.";     // Clear the error     Server.ClearError(); } 

The preceding code checks the host address and stores exception-related information (limited to the message for simplicity) only for local users. The following code should be added to the Page_Load method of the page that handles the HTTP 500 error:

string msg = "No additional information available."; object extraInfo = Session["LastErrorMessage"]; if (extraInfo != null)     msg = (string) extraInfo; ExtraInfo.InnerHtml = msg; Session["LastErrorMessage"] = null; 

Figure 5-7 shows the final result.

image from book
Figure 5-7: A context-sensitive error page for the HTTP 500 status code.

Writing context-sensitive error pages requires a page-level Error handler to cache the original exception. This means that you should write the same handler for every page that requires context-sensitive errors. You can either resort to a global error handler or write a new Page-derived class that incorporates the default Error handler. All the pages that require that functionality will derive their code file from this class instead of Page.

Note 

What takes precedence if you have an application-level error handler, a page-level handler, and a bunch of redirects? The application-level handler takes precedence over the others. The page-level code runs later, followed by any code that handles HTTP 500 internal errors. Note that HTTP errors other than 500 are not caught by Error page and application events because they're handled by the Web server and don't go through the ASP.NET errorhandling mechanism described here.

image from book
Debugging Options

Debugging an ASP.NET page is possible only if the page is compiled in debug mode. An assembly compiled in debug mode incorporates additional information for a debugger tool to step through the code. You can enable debug mode on individual pages as well as for all the pages in a given application. The <compilation> section in the web.config file controls this setting. In particular, you set the Debug attribute to true to enable debug activity for all pages in the application. The default is false. To enable debug on a single page, you add the Debug attribute to the @Page directive:

<% @Page Debug="true" %> 

ASP.NET compiles the contents of any .aspx resource before execution. The contents of the .aspx resource is parsed to obtain a C# (or Microsoft Visual Basic .NET) class file, which is then handed out to the language compiler. When a page is flagged with the Debug attribute, ASP.NET doesn't delete the temporary class file used to generate the page assembly. This file is available on the Web server for you to peruse and investigate. The file is located under the Windows folder at the following path: Microsoft.NET\Framework\[version]\Temporary ASP.NET Files.

Debug mode is important for testing applications and diagnosing their problems. Note, though, that running applications in debug mode has a significant performance overhead. You should make sure that an application has debugging disabled before deploying it on a production server.

 


Programming Microsoft ASP. Net 2.0 Core Reference
Programming Microsoft ASP.NET 2.0 Core Reference
ISBN: 0735621764
EAN: 2147483647
Year: 2004
Pages: 112
Authors: Dino Esposito
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net