
Beginning ASP.NET 2.0 With CSharp (2006) [eng]
.pdf
Chapter 15
You could use the e-mail code instead of, or in conjunction with, the logging to a file. For example, you could have the following:
Tools.Log(“My error message”, SqlEx);
Tools.SendMail(“My error message”, SqlEx);
This would perform both actions, but it includes repeated code — the error message. An alternative would be to add the e-mail code into the Log method, but that would always send the e-mail, which might not be required. A better option might be to only send an e-mail if required, perhaps adding another parameter to the Log method to indicate if the e-mail is to be sent:
Tools.Log(“My error message”, SqlEx, true);
The Log method could then have the following code:
public static void Log(string Message, Exception Ex, bool SendEmailMessage)
{
if (SendEmailMessage)
SendEmail(Message, Ex);
// rest of logging code
}
This gives a combination of the passive reporting to a log file, and the active, which lets the administrator know of problems as they occur.
Raising Exceptions
You’ve seen that you can trap exceptions, but you can also raise your own exceptions. One use for this is that you can use the same exception handling mechanism to deal with custom errors as you use for
.NET errors. However, this comes with a warning, in that you should still adhere to the rule of only using exceptions for exceptional circumstances. If you can handle the problem gracefully without using exceptions, you should do so.
To raise an exception, you use the Throw statement. For example:
throw new Exception(“exception description”);
You can also pass in an underlying exception:
throw new exception(“exception description”, ex);
If you are within a Catch block, you can also re-throw the existing exception by just calling the Throw statement on its own. You’ll see how this can be used a little later when handling exceptions globally is discussed.
578

Dealing with Errors
Exceptions Best Practices
Using exceptions is good practice, but the following rules should be adhered to when dealing with exceptions:
You should only catch an exception if you actually expect the exception. This doesn’t mean that it will happen, but that it could. A database failure is a good example, because these aren’t unheard of. If you can understand why an exception would occur and you know how to deal with it, then that’s a good case for catching it.
Dealing with the exception doesn’t mean that you know how to cure it, but that something can be done. For example, in the Checkout page modified earlier in the chapter, catching SqlException was necessary to allow the use of transactions so the database wouldn’t be left in an inconsistent state. That is dealing with the exception, even if there is nothing that can be done about the underlying problem.
As a general rule, it’s a good idea to avoid catching only the base Exception. Because this is the base class for all exceptions, it’s not narrow enough in its focus.
If you are performing database work, catch the appropriate exception (SqlException for SQL Server).
Global Exception Handling
Handling exceptions where they happen is both good and bad. In the Checkout page, the exception had to be dealt with locally because of the transaction, but in many cases, you might want some form of centralized exception handling. You also might want some way to handle exceptions not trapped elsewhere. The Checkout page is again a good example, because there is handling for SqlException, but not for anything else. What happens if some other exception occurs? This is an important point because it really isn’t sensible to put Try Catch around every piece of code just in case an exception might occur. In fact, that would be bad practice because it would make the code hard to read and isn’t required.
The way global exception handling is managed is with the Global.asax file, which contains code for the application. In this case, the term application has a special meaning, because code in Global.asax responds to application-level events. These are events that are raised at specific points during the running of the application, events that are raised by ASP.NET. Global.asax is a code-only page, and has no user interaction.
Several events are contained in the Global.asax page:
Application_Start: Raised when the application first starts. This will be when the first user accesses the site and should be used to set any initial start conditions.
Application_End: Raised when the application stops.
Session_Start: Raised when a user starts a session. This will be when the user starts accessing the site for the first time, and will include the time when a user closes the browser window and opens it again.
579

Chapter 15
Session_End: Raised when a user session ends. This isn’t when the browser window is closed, because sessions have a timeout — if there is no user activity within that time, the session ends.
Application_Error: Raised when an unhandled error occurs.
Profile_OnMigrateAnonymous: Raised when an anonymous user logs in, and allows migration of any Profile properties. (The Profile was covered in Chapter 11.)
As you can see, the event you’re interested in is the add code to centrally handle untrapped exceptions. used in the following Try It Out.
Application_Error event, which is where you can You see how the Application_Error event can be
Try It Out |
Handling Global Errors |
1.Using Windows Explorer, navigate to the web site directory, C:\BegASPNET2\Chapters\ Begin\Chapter15\WroxUnited, and have a look at WroxUnited.log.
2.In the Wrox United application, open the global.asax file.
3.Add the following code to the Application_Error event procedure:
Exception ex = Server.GetLastError();
Tools.Log(“An unhandled error was caught by Application_Error”, ex);
4.Save the file.
5.Open checkout.aspx.cs and move to the Wizard1_FinishButtonClick event.
6.Comment out the code that logs the error and replace it with the following:
throw;
7.To ensure that the exception is seen in practice, there actually needs to be some error, so you’ll force one by making the SQL statement incorrect. Change the SQL statement that inserts into the Orders table to insert into no_Orders:
cmd.CommandText = “INSERT INTO no_ORDERS(MemberName ...”;
8.Save the file and run the application.
9.If there are no items in your cart, go to the Wrox United shop and add some items. Otherwise, proceed to the Checkout page. Step through the checkout wizard. After clicking the Finish button, you’ll be switched back to VWD stating that an error has occurred. Press F5 to continue.
10.Using Windows Explorer, navigate to the web site directory, C:\BegASPNET2\Chapters\ Begin\Chapter15\WroxUnited, and check the WroxUnited.log file (or you can press the refresh button in the Solution Explorer, and the new file will appear). You’ll notice that the exception has been logged. Check the last error and see that it states that an error was caught by
Application_Error. Also notice that the next line states that an HttpUnhandledException was thrown, followed by details of the SqlException.
11.Delete WroxUnited.log and switch back to the Checkout.aspx.cs code.
12.Change the throw statement to this:
throw new Exception(“An error occurred while creating the order”, SqlEx);
580

Dealing with Errors
13.Run the application again and follow the same procedure to generate the error.
14.Open WroxUnited.log again and look at the details. First there is the HttpUnhandledException, then Exception with the text you added, and then the
SqlException.
How It Works
The first thing to understand is that the Application_Error in global.asax is raised when any unhandled error occurs. In this event, in your code you need to find out what the error was that caused the event to be raised, and for that you use the GetLastError method of the Server object. This returns an exception object, which is passed into the Log method to log the error. So if you had a Try Catch block around your code, how was it that you got to the Application_Error event? It’s because you rethrew the error using the Throw statement — although you handled the initial SqlException, the rethrown exception wasn’t handled.
The important thing to note about what happened is that the exception is wrapped in another exception — an HttpUnhandledException. All exceptions you get from within Application_Error will be like this. The actual SqlException is shown because in the Log method, you used ToString to write out all of the details of the exception. If you didn’t want the HttpUnhandledException shown, you could use the InnerException property of the exception:
Exception ex = Server.GetLastError().InnerException;
When the exception is logged, now it would only show the actual exception that was unhandled.
In the second case, you used the following:
throw new Exception(“An error occurred while creating the order”, SqlEx);
This throws a new Exception, but passes into that the actual exception that caused the problem. So you’ve wrapped the original exception within your own — a useful technique if you need to store more details than are available in the original exception. Here you are just detailing that the problem arose when an order was being created.
Don’t correct the SQL statement. You’ll need the incorrect statement in a later exercise when you look at debugging.
In general, using this simple code in Application_Error and logging exceptions to a file means that you always have details of problems that occur during the normal running of a site. You can use the stack trace (more on this later) to see exactly where the problem was, and you don’t have to rely on users telling you what they think the problem was (the two very rarely match).
Custom Error Pages
One problem with the error handling code shown so far is that the user still sees a confusing message. Ideally, you’d like to present the user with something less shocking than a stack trace (a list of the methods called so far, which you’ll look at later), for two very good reasons. First, a stack trace is not what
581

Chapter 15
users need to see — if something has gone wrong, then they need to see a clear description, explaining that it wasn’t their problem, and that something is being done about it. Second, showing a stack trace gives away a lot of information about your site, details that can be used by hackers. Even if your site is secure, they could cause unnecessary slowdowns as they try to hack the site.
In addition to exceptions, there are other types of errors that aren’t nice for a user to see. For example, what if you rename a page but don’t update the links to it, or perhaps the user types in the wrong name for a page? In these cases, you’d see the 404 — the error number that indicates a page could not be found. ASP.NET applications can be configured to redirect the user to other pages, depending on the type of error.
Configuring Custom Error Pages
Configuration of custom error pages is done in the Web.config file using the customErrors section. For example:
<customErrors mode=”On” defaultRedirect=”customError.aspx”> <error statusCode=”404” redirect=”missingPage.aspx” />
</customErrors>
The mode attribute can be one of the following:
Off: The ASP.NET error details are always shown, even if a custom error page exists.
On: The custom error is always shown, and the ASP.NET error details are never shown.
RemoteOnly: The ASP.NET error details are only shown to local users, meaning users logged on locally to the machine. For remote users (everyone else using the site), one of two things is shown: a default page telling the user that an error has occurred, but without showing any error details, or a custom error page if one exists.
The values of On or RemoteOnly should be used for a live site, whereas Off can be used for debugging purposes.
The defaultRedirect attribute defines the page that is shown if an unhandled error occurs.
The error element details specific errors and the page to redirect to if that error occurs. In this case, the statusCode is 404, which means a missing page, so the user will be redirected to missingPage.aspx if the page they are looking for cannot be found. This enables you to have detailed pages for individual errors, so you can help the user correct the problem. The missing page example could explain that the page cannot be found and perhaps get users to check that they typed the correct URL.
In the following Try It Out, you create your own custom error page.
Try It Out |
Custom Error Pages |
1.In the Wrox United application for the chapter, open Web.config and add the following within the <system.web> section:
<customErrors mode=”On”>
<error statusCode=”404” redirect=”missingPage.aspx” /> </customErrors>
582

Dealing with Errors
2.Save the file.
3.Create a new Web Form called missingPage.aspx, making sure that the code isn’t placed in a separate file, but that you pick the site.master file for the Master page.
4.Within the <asp:Content> controls, add the following text:
We’re sorry but the page you were looking |
for cannot |
be found. It’s probably hiding behind the |
couch. We’ll |
tell the Web site administrator to go and |
fetch it. |
|
|
5.Save the file and run the application.
6.Navigate to a page that doesn’t exist — perhaps abc.aspx. You’ll need to type this page into the address bar of the browser. Notice that the text you entered is displayed rather than the normal message for a missing page.
How It Works
The working of this is quite simple, because when custom errors are enabled, ASP.NET intercepts the errors. What it does depends on how you’ve configured the customErrors section. In this case, the mode has been set to On, which means that custom errors will always be shown — this is required because you are logged on to the machine locally, so remoteOnly wouldn’t work.
You’ve also configured a custom page for the statusCode of 404, so whenever a page cannot be found, ASP.NET doesn’t show the normal error message but instead redirects to the custom error page. This technique makes your site friendlier to use, which means that if an error does occur, the user isn’t left with a frightening error message, but is presented with something more reassuring. Also, because this is an ASP.NET page, you could make the error message more helpful. For example, you could check the name of the file the user was looking for and see if something similar exists on the site, perhaps by looking up the pages in the SiteMap or from a database. You could then either take the user to the nearest matching page, or present them with a list of possible matches.
Error pages can be combined with logging to give very proactive feedback of problems within the site. For example, sites often expand or change, and the names of pages sometimes change, but you might forget to change a link to the changed page. If a user clicks a link on your site and that page isn’t found, then sending an e-mail to the site administrator is very useful — the page in error can be quickly corrected so that no other users see the problem.
Debugging and Tracing
Controlling how errors are shown to the user is only part of the story when developing web sites, and tracking down those errors is just as important. Ideally, errors should be found during development and testing, and in many ways the job of testing is just as development. Many companies, Microsoft included, have teams of testers running through development projects, shaking out those errors.
As a developer, you are bound to make mistakes, ranging from simple typing errors to more complex coding problems. The typing problems are usually easy to track down because they often cause compilation errors, but runtime errors can be more problematic. Tracing and debugging are the two main techniques used to find errors.
583

Chapter 15
Using ASP.NET Tracing
You first looked at tracing in Chapter 14. It is the technique of adding code to your pages, for a few reasons: for debugging purposes, to output values of variables, or simply to find out where in your code certain things happen. The great thing about ASP.NET tracing is that not only is it extremely simple to do, but it’s also easily configurable and doesn’t require tracing code to be removed if you don’t want the trace information shown. What’s also great is that you get a wealth of additional information about the page, which can be useful for both debugging purposes and for learning about ASP.NET.
Tracing Individual Pages
Tracing can be turned on for individual pages by adding the Trace attribute to the Page directive:
<%@ Page Trace=”true” %>
On its own, this outputs a great deal of information about the page, but you can also add your own output using the Trace class, which has methods to write output to the trace log:
Trace.Write(“my information”)
The following Try It Out shows tracing in action.
Try It Out |
Page-Level Tracing |
1.In the Wrox United application for the chapter, open Checkout.aspx in Source View.
2.Add the Trace attribute to the Page directive:
<%@ Page Trace=”True” ... %>
3.Save the file and run the application.
4.Add some items from the shop to your shopping cart and navigate to the Checkout page, where you’ll see that the bottom of the page has lots of information added. You might have to scroll down the page to see all of the information.
5.Switch back to VWD and open the code file for the Checkout page.
6.At the top of the Page_Load event, add the following line of code:
Trace.Write(“In Page_Load”);
7.Before the check to see if the user is authenticated, add the following:
Trace.Write(“In page_Load”, User.Identity.IsAuthenticated.ToString());
if (User.Identity.IsAuthenticated)
...
8.Save the page and run the application, again navigating to the Checkout page.
9.Scroll the page down so you can see the Trace Information section, as shown in Figure 15-6.
10.Here you can see that the output from the Trace.Write statements is mixed with the output that ASP.NET puts into the trace. Take a look at how this works and what information the trace output produces.
584

Dealing with Errors
Figure 15-6
11.Edit Checkout.aspx again and set the Trace attribute to False:
<%@ Page Trace=”False” %>
12.Save the page and run the application, again navigating to the Checkout page. Notice that the trace information is gone from the page, even though the Trace.Write statements are still in the code.
How It Works
The first thing to look at is what all of this trace information is, and what it is useful for. There are many sections, as detailed in the following table.
Section |
Contains |
|
|
Request Details |
Details of the request, such as the status code. |
Trace Information |
The flow of page events, showing the category, message, and |
|
time from the first to last byte of information sent to the browser. |
Control Tree |
The hierarchy of controls in the page. |
Session State |
Any session variables in use. |
Application State |
Any application variables in use. |
Request Cookies Collection |
The cookies stored for the current site. |
Response Cookies Collection |
Any cookies set during the page processing. |
Headers Collection |
The HTTP headers. |
Response Headers Collection |
Any headers set during the page processing. |
Form Collection |
Contents of the form. |
QueryString Collection |
Any querystring parameters for the request. |
Server Variables |
The HTTP server variables. |
|
|
585

Chapter 15
All of this information is useful, although some sections are more useful than others. The Control Tree, for example, clearly shows the hierarchy of controls. You saw this in Chapter 14 when you looked at performance, but it’s also useful for understanding how the page is made up from the hierarchy of controls. At the top is the Page object, beneath that the Master page, and then controls within the Master page. This continues with all of the page objects, and shows the unique name of the control as well as its type.
The Trace Information section shows the events in the order in which they are raised, so it is great for seeing exactly when things happen. Without any trace information of your own, the standard page events are shown, and anything you write is slotted into its correct space. So take a look what you actually did:
protected void Page_Load(object sender, System.EventArgs e)
{
Trace.Write(“In Page_Load”);
if (!Page.IsPostBack)
{
if (Profile.Cart == null)
{
NoCartlabel.Visible = true; Wizard1.Visible = false;
}
Trace.Write(“In Page_Load”, User.Identity.IsAuthenticated.ToString()); if (User.Identity.IsAuthenticated)
Wizard1.ActiveStepIndex = 1; else
Wizard1.ActiveStepIndex = 0;
}
In the first statement, you used Trace.Write to output a single string, which is displayed in the Message column. With the second Trace.Write, you passed in two parameters, and in this case, the first becomes the Category and the second becomes the Message. You can put trace statements anywhere within your code, and the output will be displayed in the Trace Information section, so it’s a great way to simply see what’s happening in your code. There is a also a Warn method of the Trace class, which outputs in the same way as Write, but the content is in red. This is useful for picking out statements within the trace output.
The other thing you may have noticed is that by changing the value of the Trace attribute at the top of page to False, no trace output is displayed. You didn’t have to remove the Trace.Write statements from the code, because these are simply ignored if tracing isn’t enabled. This is great during development, because you can liberally sprinkle Trace.Write statements throughout your code to give you a good understanding of the program flow, and you can turn on or off the tracing without having to remove or comment out these statements.
Tracing All Pages
Although tracing in individual pages is useful, what’s great is being able to control tracing for the entire application. This is done with a configuration setting in Web.config, within the <system.web> section:
<trace enabled=”True” />
586

Dealing with Errors
When the enabled attribute is set to True, tracing is enabled for the application, but the output isn’t shown in the pages. Instead, it is stored for viewing by way of a special URL — Trace.axd — which doesn’t point to a physical file, but is instead interpreted by ASP.NET. You give application tracing a go in the next Try It Out.
Try It Out |
Application-Level Tracing |
1.In Source View in Checkout.aspx, remove the Trace attribute from the Page directive at the top of the page.
2.Open Web.config and add the following within the <system.web> section:
<trace enabled=”True” />
3.Save the file and run the application, navigating to the Checkout page.
4.Within Internet Explorer, select File New Window (or press Control+N) to launch a new window from the same site.
5.In the address bar, change Checkout.aspx to Trace.axd and press Enter. You should see something like Figure 15-7.
Figure 15-7
6.Click the View Details link on the Checkout.aspx line, and you will see the trace page you’ve already seen. Notice that it contains the two pieces of data that were added to Checkout.aspx in the Page_Load event.
587