您的位置:首页 > 移动开发

Exception Handling Advice for ASP.NET Web Applications

2010-02-08 22:22 501 查看
Introduction
Exceptions are a construct in the .NET Framework that are (ideally) used to indicate an unexpected state in executing code. For example, when working with a database the underlying ADO.NET code that communicates with the database raises an exception if the database is offline or if the database reports an error when executing a query. Similarly, if you attempt to cast user input from one type to another - say from a string to an integer - but the user's input is not valid, an exception will be thrown. You can also raise exceptions from your own code by using the
Throw
keyword.
When an exception is thrown it is passed up the call stack. That is, if
MethodA
calls
MethodB
, and then
MethodB
raises an exception,
MethodA
is given the opportunity to execute code in response to the exception. Specifically,
MethodA
can do one of two things: it can catch the exception (using a
Try
...
Catch
block) and execute code in response to the exception being throw; or it can ignore the exception and let it percolate up the call stack. If the exception is percolated up the call stack - either by
MethodA
not catching the exception or by
MethodA
re-throwing the exception - then the exception information will be passed up to the method that called
MethodA
. If no method in the call stack handles the exception then it will eventually reach the ASP.NET runtime, which will display the configured error page (the Yellow Screen of Death, by default).
In my experience as a consultant and trainer I have worked with dozens of companies and hundreds of developers and have seen a variety of techniques used for handling exceptions in ASP.NET applications. Some have never used
Try
...
Catch
blocks; others surrounded the code in every method with one. Some logged exception details while others simply swallowed them. This article presents my views and advice on how best to handle exceptions in an ASP.NET application. Read on to learn more!

The Crib Notes
My advice for handling exceptions in an ASP.NET application can be boiled down to the following guidelines:
Create and use a meaningful custom error page.
In general, do not catch exceptions. Let them bubble up to the ASP.NET runtime. Some cases where catching an exception makes sense include:
When there is a plausible way to recover from the exception by performing some alternative logic,
When a peripheral part of the application's workflow throws and exception and that exception should not derail the entire application, and
When you need to include additional information with the exception by throwing a new exception that has the original exception as its inner exception.

Log all exceptions to some persistent store and use email (or some other medium) to notify developers when an exception occurs in production. Consider using ELMAH or ASP.NET's built-in Health Monitoring system to facilitate this process.
Read on for a more in-depth look at these suggestions.

First Things First: Create a Custom Error Page
Whenever an unhandled exception occurs, the ASP.NET runtime displays its configured error page. (An unhandled exception is an exception that is not caught and handled by some method in the call stack when the exception is raised. If no method in the call stack catches the exception then it percolates all the way up to the ASP.NET runtime, which then displays the error page.)
The error page displayed by the ASP.NET runtime depends on two factors:
Whether the website is being visited locally or remotely, and
The
<customErrors>
configuration in
Web.config
.

ASP.NET's default error page is affectionately referred to as the Yellow Screen of Death. The screen shot on the right shows the default Yellow Screen of Death error page shown when an unhandled exception occurs for a remote visitor. When visiting locally, the Yellow Screen of Death page includes additional information about the exception that was raised.

While the Yellow Screen of Death error page is acceptable in the development environment, displaying such an error page in production to real users smacks of an unprofessional website. Instead, your ASP.NET application should use a custom error page. A custom error page is a user-friendly error page that you create in your project. Unlike the Yellow Screen of Death error page, a custom error page can match the look and feel of your existing website and explain to the user that there was a problem and provide suggestions or steps for the user to take.
Janko Jovanovic's article Exception Handling Best Practices in ASP.NET Web Applications offers advice on the information to display in a custom error page. At minimum, you need to inform the user that something went awry, but as Janko points out:
[In the custom error page] you can provide a user with a meaningful set of messages that will explain:
what happened
what will be affected
what the user can do from there
and any valuable support information
By doing this you are eliminating the confusion in users and allowing them to react properly. [The] image below shows an example of a well designed error screen.



To use a custom error page first create the error page in your website. Next, go to the
Web.config
file and set the
defaultRedirect
attribute in the
<customErrors>
section to the URL of your custom error page. Finally, make sure that the
mode
attribute of the
<customErrors>
section is set to either
On
or
RemoteOnly
. That's all there is to it!

<customErrors mode="On"
defaultRedirect="~/YourCustomErrorPage.aspx" />
For more information on creating a custom error page and configuring
Web.config
, see Gracefully Responding to Unhandled Exceptions - Displaying User-Friendly Error Pages and Displaying a Custom Error Page.
Only Handle Exceptions When...
I've reviewed a number of applications where the developer used
Try
...
Catch
blocks like they were going out of style. They would surround each and every logical block of code with a
Try
...
Catch
. When an exception was caught, the developer would then do one of the following:
Re-throw the exception, as in:

Try
...
Catch
Throw
End Try
Or:

Try
...
Catch ex As Exception
Throw ex
End Try
Swallow the exception, as in:

Try
...
Catch
' Do nothing, swallowing the exception
End Try
Use the
Catch
block to return a status code, as in:

Try
...
Catch
Return -1
End Try
All of these approaches are unpalatable. In fact, it is my opinion that
Try
...
Catch
blocks should rarely be used
. Typically when an exception is raised it indicates an abnormal stoppage of the application. If the database is offline, the data-driven web application is rendered useless. If the user's input is converted from a string to an integer, but that conversion fails, we can't move forward. In the majority of cases it's best to let the exception percolate up to the ASP.NET runtime and have the custom error page displayed.

Try
...
Finally
a Different Matter Altogether
Try
...
Finally
blocks
are useful for executing cleanup code regardless of whether an exception occurs or not, and should be used as needed. Keep in mind that you can have a
Try
...
Finally
block without a
Catch
statement. Moreover, any sort of cleanup logic that needs to occur should always be put in the
Finally
rather than in the
Catch
.
However, there are times when there are two (or more) paths to a happy resolution, and an exception simply indicates that one path has a roadblock and that an alternative path must be tried. Consider an e-Commerce application that automatically sends an e-mail receipt to the customer once their purchase has been completed. What should happen if the SMTP server used to send the e-mail is offline? If there are two (or more) known SMTP servers, the code could try to send from one of the alternative SMTP servers should the primary one be unavailable. This logic could be handled via
Try
...
Catch
blocks in the following manner:
Try to send the email message via the primary SMTP server...
If an exception is thrown from Step 1 then first log the exception and try to resend the email, but this time use the secondary SMTP server...
If an exception is thrown from Step 2 then let this exception percolate up the call stack

This workflow could be implemented with the following pseudocode:

Try

... Try Sending Email Using Primary Email Server ...

Catch smtpEx As SmtpException

... Log Failure Sending to Primary Email Server ...

... Try Sending Email Using Secondary Email Server ...

End Try
The above scenario is a good use of
Try
...
Catch
blocks because we are trying to recover from the exception. If all you are doing is re-throwing the exception (and nothing else) then the
Try
...
Catch
block is superfluous. But if you can possibly recover from the exception then it makes sense to have a
Try
...
Catch
block.
Taking the above example a step further, there are scenarios where an exception does not mean that the application should terminate. If a receipt e-mail cannot be sent to the user we should still process the order. To extend the steps outlined above a bit further we would have:
Try to send the email message via the primary SMTP server...
If an exception is thrown from Step 1 then first log the exception and try to resend the email, but this time use the secondary SMTP server...
If an exception is thrown from Step 2 then log it and complete the order processing

This updated workflow could be implemented with the following pseudocode:

Try

... Try Sending Email Using Primary Email Server ...

Catch smtpEx As SmtpException

Try

... Log Failure Sending to Primary Email Server ...

... Try Sending Email Using Secondary Email Server ...

Catch smtpEx2 As SmtpException
... Log the exception and continue on ...
End Catch
End Try
Finally,
Try
...
Catch
blocks are useful in situations where you need to let the exception percolate up the call stack, but before doing so you want to add additional information about the exception. This is accomplished by catching the exception and then throwing a new exception with the original exception as an inner exception.
To recap, an ASP.NET application should have a very limited number of
Try
...
Catch
blocks. For most cases if an exception occurs it should be allowed to bubble up to the ASP.NET runtime. There are three general cases when
Try
...
Catch
blocks make sense:
When there is an opportunity to recover from the application by taking some alternative course of action.
When the exception indicates a periphery problem that should not impede the current workflow. Keep in mind that all exceptions should be logged; do not swallow an exception, but rather log it before continuing on. (We'll talk about exception logging and notification momentarily.)
When additional information needs to be included with the exception. In this case it is useful to catch the exception and then throw a new exception with the original exception as an inner exception.

Logging Exceptions and Exception Notification
The most important aspect of exception handling is logging and notification. Whenever an exception happens it needs to be logged to some persistent store (such as a file or database or Windows Event Log) and a developer (or set of developers) should be notified of the exception via e-mail or some other medium. The good news is that implementing such logging and notification is actually quite easy.

Earlier I noted that when an exception percolates to the top of the call stack and reaches the ASP.NET runtime, the configured error page is displayed. In addition to displaying the error page, the ASP.NET runtime also raises its
Error
event. It is possible to create an event handler for this event, either through
Global.asax
or via an HTTP Module. In the event handler the exception's details can be logged and developers notified.
There's no reason to write your own code to log exceptions as there are existing libraries that handle this for you. My personal favorite error logging and notification library is ELMAH, an open source project created by Atif Aziz. In a nutshell, ELMAH contains an HTTP Module that defines an event handler for the
Error
event. When an unhandled exception percolates to the ASP.NET runtime, ELMAH logs its details to a log source provider that you specify in
Web.config
. This log source can be a Microsoft SQL Server database, an XML file, a Microsoft Access database, or an Oracle database, among many other options. You can also configure ELMAH to e-mail the error details to one or more recipients. Perhaps ELMAH's most distinctive feature is its built-in, web-based error log viewing page, which enables developers to view a history of errors and error details from a web page on the site. See Simone Busoli's article ELMAH - Error Logging Modules And Handlers for more information on ELMAH.
Another option is Microsoft's own health monitoring, which was added starting with ASP.NET version 2.0. Unlike ELMAH, which focuses on logging errors, ASP.NET's health monitoring can log all sorts of events to any number of log sources. In addition to logging errors, health monitoring can also log application life cycle events (startups, shutdowns, restarts), security-related events, and more. These events can be logged to a Microsoft SQL Server database, to an e-mail message, or to the Windows Event Log. And like with ELMAH, health monitoring can be configured entirely through settings in
Web.config
- you don't need to write a line of code. For a good overview of health monitoring check out my article Health Monitoring in ASP.NET, along with Erik Reitan's Health Monitoring FAQ.
How exactly you log errors and notify developers of these errors is not that important. I encourage you to give both ELMAH and the health monitoring system a go. (If you want a high-level overview of the two system, check out the Log and Review Web Application Errors review from my Toolbox column in MSDN Magazine - Toolbox: Logging Web App Errors, Learning LINQ, and More.) What is vitally important, though, is that you have some system in place to log errors and notify developers. Otherwise you'll have no idea if a user encounters an error on the website, let alone how to go about fixing that error should the user take the time to report the error.
Happy Programming!

By Scott Mitchell
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐