Professional ASP.NET 1.1 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Professional ASP.NET 1.1 [Electronic resources] - نسخه متنی

Alex Homeret

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید






Error Handling

Error handling has always been a bit of a juggling act – that fine line between what you need to protect your code against and what you don't. While it would be nice to assume that all data handled by your code is correct (it was validated wasn't it?) and that all resources will be available, that's not something you can take for granted. As applications become more complex, they rely on other programs, components, external resources, and so on, and you have to assume that at some point something might go wrong. Taking this defensive attitude will lead to more robust applications, as well as making development easier in the future.

In previous versions of ASP you probably relied upon the VBScript

On Error statement (or

Try...Catch if you are one of the few who used JScript server-side). While acceptable, it was never a good solution, as it was difficult to build centralized error routines and provide a neat way to manage the errors. The CLR has solved this by providing support for structured exception handling.


Structured Exception Handling


Structured exception handling is a fundamental part of the CLR, and provides .NET programmers with a great way of managing errors, and has a good set of features:



It is cross-language; therefore exceptions can be raised in one language and caught in another.



It is cross-process and cross-machine, so even when remote .NET components raise exceptions, they can be caught locally.



It is a hierarchical system, allowing exceptions to be layered, with each exception able to encompass another. This means that components can trap exceptions from underlying objects (such as data access layers), and raise their own exception, including the original as part of it. This allows programs to trap exceptions at a high-level, but drill-down through the exception list to find more fine-grained exception information.



It obviates the need to check for return values from every function or method call – errors that are raised as exceptions will never be missed.



There is no performance downside unless an exception is raised.



All exceptions in the CLR are derived from a single base class, and many of the classes in the framework extend from this to provide finer-grained errors.

The Exception Class


The

Exception class provides the following properties to detail the problem:































Property


Description


HelpLink


A URN or URL indicating the help file associated with this error.


HResult


For COM interoperability, the Windows 32-bit

HRESULT .


InnerException


For nested exceptions, an exception object representing the inner exception.


Message


The textual error message.


Source


The name of the application, or object, that raised the error. This will be the assembly name if left blank by the application throwing the exception.


StackTrace


A string containing the stack trace.


TargetSite


A

MethodBase object detailing the method that raised the exception.


There is also one method,

GetBaseException (for use with nested exceptions), that returns the original exception that was raised.

Try...Catch


You can manage exceptions by use of the

Try...Catch statement. In Visual Basic .NET, the general syntax is:


Try

' code block to run

[Catch [exception [As type]] [When expression]

' code to run if the exception generated matches

' the exception and expression defined above

[Exit Try]

]

Catch [exception [As type]] [When expression]

' code to run if the exception generated matches

' the exception and expression defined above

[Exit Try]

[Finally

' code that always runs, whether or not an exception

' was caught, unless Exit Try is called

]

End Try


There can be multiple

Catch blocks to allow for fine-grained control over exceptions. For example, imagine a function that returns the contents of a file:


Public Function GetFileContents(fileName As String) As String

Dim fileContents As String = "

Try

Dim sr As New StreamReader(fileName)

fileContents = sr.ReadToEnd()

sr.Close()

Catch exArg As ArgumentException

' argument not supplied

Catch exFNF As FileNotFoundException

' file wasn't found – handle error

Finally

Return fileContents

End Try

End Function


When using multiple

Catch blocks, you should put the finest-grain exception first, and the widest (the base class

Exception , for example) last. This is because these blocks are tried in the order of declaration. So, putting the widest exception first could hide a narrower one.

The

Finally block will always be run, except when you use

Exit Try to exit from the block, whether or not an exception is thrown. Even if used within a procedure, and the procedure is exited from within a

Catch block, the

Finally block still runs.

Raising Exceptions


To raise exceptions use the

Throw statement. For example, if you forget the file name to the constructor of the

StreamReader you normally get the message shown in Figure 22-6:


Figure 22-6:





Note

In this example, the error is being thrown again – back to the calling procedure. This is why the

Try...Catch block is visible in the code.


If you wanted to provide your own error message, you could throw a new error containing the new details:


Public Function GetFileContents(fileName As String) As String

Dim fileContents As String = "

Try

Dim sr As New StreamReader(fileName)

fileContents = sr.ReadToEnd()

sr.Close()

Catch exArg As ArgumentException

Throw New ArgumentException("Doh – you forgot the filename!")

Finally

Return fileContents

End Try

End Function


This gives you the results shown in Figure 22-7:


Figure 22-7:

This gives you the new error message, but the source error is shown on the line where you raised the error, rather than where the error was actually generated. If this isn't acceptable, you can pass in the original exception as an argument when you throw the new exception. For example:


Catch exArg As ArgumentException

Throw New ArgumentException("Doh – you forgot the filename.", exArg)


The result is shown in Figure 22-8:


Figure 22-8:

Hmm, but wait a minute – where has the new error message gone? You have re-thrown the error using the original exception details, which is why you are now on the correct line, but your error message isn't shown. Well, if you look at the stack trace you will see it as in Figure 22-9:


Figure 22-9:

The original exception is shown first, and your error is shown next. It is probably not a scenario you would use that often, but the reason for generating exceptions is so that you can catch them and take some appropriate action, such as displaying a message to the user (who doesn't need to see the stack trace or the errors).

Custom Exceptions


Taking the custom error message one step further allows you to create your own exception class, derived not from the

Exception class, but from

ApplicationException . For example, consider a parser for a text file of a specific format, and you want to have parsing errors integrated with the exception handling system. You could create a new exception like this:


Imports System

Namespace Wrox

Public Class InvalidContentException

Inherits ApplicationException

Public Sub New()

MyBase.New()

End Sub

Public Sub New(message As String)

MyBase.New(message)

End Sub

Public Sub New(message As String, inner As Exception)

MyBase.New(message, inner)

End Sub

End Class

End Namespace


This simply defines a new class derived from

ApplicationException . The class can be any name, but by convention it should end in

Exception . Within this class the three standard constructors are implemented to call the base class constructors. Now, when you throw this exception you get the results shown in Figure 22-10:


Figure 22-10:

Notice this is the same layout as standard exceptions – the only difference is that your exception name is used. The advantage of this system is that you can just trap your custom exception. For example:


Try

' run custom parser

Catch ex As InvalidContentException

' notify user of error

End Try


Within the new exception class you can do more than just call the base class methods. For example, you could add extra tracing information, or even overload the properties to provide further information.

Exception Types


Many of the internal classes define their own exceptions that you can catch. For example, when connecting to SQL Server, a data access problem might raise a

SQLException . Although the documentation details what exceptions will be raised, the simplest way to find a list of the exceptions available is to use a tool like WinCV, and search for exception. This gives you a list as shown in Figure 22-11:


Figure 22-11:

Here you can see exactly what exceptions are available, and to which namespace they belong. WinCV is in the

bin directory of the SDK install.

Exceptions and COM


The exception handling system in .NET integrates well with the error handling of COM components, whatever type of interoperability you are performing. If you are using .NET components from COM, then exceptions raised in the .NET component will lead to a Windows

HRESULT being passed to the COM component. If you are using COM components from .NET, and the COM component returns an

HRESULT , this will be mapped to a .NET exception. The type of exception depends on the

HRESULT – for example, the

HRESULT E_ACCESSDENIED error will be mapped to

UnauthorizedAccessException , while unknown instances of

HRESULTs will be mapped to

COMException . Additionally, COM components that support

IErrorInfo will have these details mapped to the properties of the exception.

This topic is covered in more detail in Chapter 23 where you look at migration and interoperability.


ASP.NET Error Handling


In addition to the CLR exception system, ASP.NET also provides ways of handling errors. There are three ways in which this can be done:



At the page-level error event for errors on an individual page



At the application-level error event for errors in an application



In the application configuration file to perform declarative error handling for an application



Which method you use depends on what you want to do when handling errors and what sort of structure you wish your application to have.


Page_Error Event


At the page level you can use the

Page_Error event to trap errors on a page. For example:


<%@ Import Namespace="System.Data.SqlClient" %>

<script runat="server">

Sub Page_Load(Sender As Object, E As EventArgs)

Dim conn As SqlConnection

Response.Write(conn.ToString())

End Sub

Sub Page_Error(Sender As Object, E As EventArgs)

Dim err As String = "Error in: " & Request.Url.ToString() & "<p/>" & _

"Stack Trace Below:<br/>" & _

Server.GetLastError().ToString()

Response.Write(err)

Server.ClearError()

End Sub

</script>


This uses the

Page_Load event to generate a runtime error (by trying to display the connection string details when the connection hasn't been opened), which can be caught by the

Page_Error event. The error details are available by calling the

GetLastError method to return the exception object generated by the error. In this case, just output the details, but you could log the error or notify the web site administrator (see the Notifying Administrators of Errors section a little later in this chapter for details).

Application_Error Event


Like the

Page_Error event, there is also an

Application_Error event, which is declared in the

global.asax file. For example, the following block uses similar code as seen in the previous section:


Sub Application_Error(Sender As Object, E As EventArgs)


Dim err As String = "<h1>Application Error</h1>" & _

"Error in: " & Request.Url.ToString() & "<p/>" & _

"Stack Trace Below:<br/>" & _

Server.GetLastError().ToString()

Response.Write(err)

Server.ClearError()

End Sub


This event will be run if no

Page_Error event traps the error. This event is one place where you could put application-wide error logging information.

Application Error Configuration


As well as programmatic handling of errors, there is also the declarative method utilizing the

web.config file. This method is for handling HTTP errors or errors not handled elsewhere in the application, and providing a simple way to return custom error pages. It is configured with the

customErrors element of the

system.web section in the configuration file:


<system.web>

<customErrors defaultRedirect="url" mode="On|Off|RemoteOnly">

<error statusCode="code" redirect="url"/>

</customErrors>

</system.web>


The

defaultRedirect attribute indicates the page that should be shown if no other errors are trapped. It should be the last resort page for completely unexpected errors. The mode can be:



On :To specify that custom errors be enabled



Off :To specify that custom errors be disabled



RemoteOnly :To specify that custom errors are only enabled for remote clients



The

RemoteOnly option allows you to define custom errors for all users, except those accessing the page locally. This allows users to see a nice error page while you can log onto the server and see the exception details and stack trace.

The

error sub-element allows individual pages to be shown for individual errors. For example:


<system.web>

<customErrors defaultRedirect="DefaultErrorPage.aspx" mode="RemoteOnly">

<error statusCode="404" redirect="FileNotFound.aspx"/>

</customErrors>

</system.web>


Here, the 404 (not found) error will redirect to

FileNotFound.aspx , while all other unhandled errors will go to

DefaultErrorPage.aspx . You can have multiple

error elements to cater to multiple HTTP errors.


Notifying Administrators of Errors


The rich set of error handling that ASP.NET provides is a great way to trap errors, but the users aren't the only people who need to know that something has gone wrong. A user will see the error page, but administrators and developers also need to know when things go wrong. There are many ways in which you can do this, but by far the simplest is to automatically write an event to the event log or to mail the administrator. Both of these are techniques you could add to the error pages so that whenever an error occurs, the details are logged somehow.

One important point to note about the event log is that you require special permissions to create new logs. In Chapter 14, we discussed the ASPNET account that, by default, all ASP.NET pages run under, and this account doesn't have permissions to create new event logs. This is because creation of new logs requires write permission in the registry, and by default, the ASPNET account doesn't require this. By and large you shouldn't need to create new logs within your ASP.NET pages, since this should really be an installation task. Once the log is created, then you don't need any special permission to write to the log.

If you do need to create a new log from within ASP.NET then you have two options, both of which involve the account under which ASP.NET runs. This first option is to modify the configuration file so that ASP.NET runs under a different account – perhaps the system account. This account allows the creation of event logs, but means that your entire application is now running in a less secure environment, which isn't recommended. Alternatively, you can move just the event log pages to a new directory and create a

web.config file to allow impersonation for just this task, adding the following lines:


<configuration>

<system.web>

<identity impersonate="true" userName="RegWriter" password="iRule" />

</system.web>

<configuration>


The user name should be an account with permissions to write to the registry.

Writing to the Event Log


Writing to the event log is extremely simple, as there is an

EventLog class in the

System.Diagnostics namespace. Consider the following function:


<%@ Import Namespace="System.Diagnostics" %>

... ' rest of page here

Sub WriteToEventLog(LogName As String, Message As String)

' Create event log if it doesn't exist

If (Not EventLog.SourceExists(LogName)) Then

EventLog.CreateEventSource(LogName, LogName)

End if

' Fire off to event log

Dim Log as New EventLog(LogName)

Log.Source = LogName

Log.WriteEntry(Message, EventLogEntryType.Error)

End Sub


This first creates a custom event log if it doesn't already exist, and then writes a new entry into the log. You could call this function like this:


Try

' do something here

Catch ex As Exception

WriteToEventLog("Wrox", ex.ToString())

End Try


The result appears in the event log like any other event (Figure 22-12). Notice that this is in a custom log.


Figure 22-12:

Sending Email Error Notifications


An alternative approach would be to send an automatic mail message, using the

MailMessage class of the

System.Web.Mail namespace. For example:


<%@ Import Namespace="System.Web.Mail" %>

... ' rest of page here

Public Sub SendMail(message As String)

Dim MyMessage as New MailMessage

MyMessage.To = "webmaster@yourcompany.com"

MyMessage.From = "ASPApplication@yourcompany.com"

MyMessage.Subject = "Unhandled ASP.NET Error"

MyMessage.BodyFormat = MailFormat.Text

MyMessage.Body = message

SmtpMail.SmtpServer = "YourSMTPServer"

SmtpMail.Send(MyMessage)

End Sub


Notice that the

Send method is a static method, and therefore you don't need to create an instance of the

SmtpMail component.

You could call this like so:


Try

' do something here

Catch ex As Exception

SendMail(ex.ToString())

End Try


/ 244