您的位置:首页 > 编程语言 > ASP

ASP.NET Custom Error Pages (asp.net自定义错误页)

2006-05-19 19:48 501 查看
By Milan Negovan

Download
Asp.Net provides a simple yet powerful way to deal with errors that occur in your web applications. We will look at several ways to trap errors and display friendly meaningful messages to users. We will then take the discussion a step further and learn how to be instantly notified about problems so you can cope with them right away. As a geek touch we will also track the path 404's travel.
in the days of "classic" ASP you used to get cryptic—an often times downright misleading—error messages. "White pages" leave your visitors in the dark who don't know what happened and what to do next besides closing the browser or navigating away.

it's obvious that "white pages" are ugly but there's another angle to this—ASP.NET displays an exception stack trace which may reveal what your code does and how it works. These days, when the word "security" is in vogue, this could be a dangerous side effect.

custom error pages are not luxury any more, they are a must-have. You have several ways to implement them.

Trapping Errors On Page Level

every time you create a web form in Visual Studio .NET you see that your page class derives from
System.Web.UI.Page
. The Page object helps you trap page-level errors. For this to happen you need to override its
OnError
method as follows:

protected override void OnError(EventArgs e)
{
// At this point we have information about the error
HttpContext ctx = HttpContext.Current;
Exception exception = ctx.Server.GetLastError ();
string errorInfo =
"<br>Offending URL: " + ctx.Request.Url.ToString () +
"<br>Source: " + exception.Source +
"<br>Message: " + exception.Message +
"<br>Stack trace: " + exception.StackTrace;
ctx.Response.Write (errorInfo);
// --------------------------------------------------
// To let the page finish running we clear the error
// --------------------------------------------------
ctx.Server.ClearError ();
base.OnError (e);
}

this works for one page only, you may say. To have every page benefit from this kind of error handing we need to take advantage of the Page Controller pattern. You define a base class and have every page inherit from it. Download sample code for this article and see the CustomError1 project for an example.

later on in this article you will learn why may need to collect exception information in this manner. Stay tuned.

Trapping Errors On Application Level

the idea of capturing errors on the application level is somewhat similar. At this point we need to rehash our understanding of the
Global.asax
file.

from the moment you request a page in your browser to the moment you see a response on your screen a complex process takes place on the server. Your request travels through the Asp.Net pipeline.

in the eyes of IIS each virtual directory is an application. When a request within a certain virtual directory is placed, the pipeline creates an instance of
HttpApplication
to process the request. The runtime maintains a pool of
HttpApplication
objects. The same instance of
HttpApplication
will service a request it is responsible for. This instance can be pooled and reused only after it is done processing a request.

Global.asax
is optional which means if you are not interested in any session or application events you can live without it. Otherwise the Asp.Net runtime parses your global.asax, compiles a class derived from
HttpApplication
and hands it a request for your web application.

httpapplication fires a number of events. One of them is
Error
. To implement your own handler for application-level errors your
global.asax
file needs to have code similar to this:

protected void Application_Error(object sender, EventArgs e)
{
}

when any exception is thrown now—be it a general exception or a 404—it will end up in
Application_Error
. The following implementation of this handler is similar to the one above:

protected void Application_Error(Object sender, EventArgs e)
{
// At this point we have information about the error
HttpContext ctx = HttpContext.Current;
Exception exception = ctx.Server.GetLastError ();
string errorInfo =
"<br>Offending URL: " + ctx.Request.Url.ToString () +
"<br>Source: " + exception.Source +
"<br>Message: " + exception.Message +
"<br>Stack trace: " + exception.StackTrace;
ctx.Response.Write (errorInfo);
// --------------------------------------------------
// To let the page finish running we clear the error
// --------------------------------------------------
ctx.Server.ClearError ();
}

Be careful when modifying global.asax. The Asp.Net framework detects that you changed it, flushes all session state and closed all browser sessions and—in essence—reboots your application. When a new page request arrives, the framework will parse global.asax and compile a new object derived from HttpApplication again.

Setting Custom Error Pages In web.config

if an exception has not been handed by the
Page
object, or the
HttpApplication
object and has not been cleared through
Server.ClearError()
it will be dealt with according to the settings of
web.config
.

when you first create an Asp.Net web project in Visual Studio .NET you get a
web.config
for free with a small <customErrors> section:

<customErrors mode="RemoteOnly" />

with this setting your visitors will see a canned error page much like the one from ASP days. To save your face you can have Asp.Net display a nice page with an apology and a suggested plan of action.

the
mode
attribute can be one of the following:

On – error details are not shown to anybody, even local users. If you specified a custom error page it will be always used.
Off – everyone will see error details, both local and remote users. If you specified a custom error page it will NOT be used.
RemoteOnly – local users will see detailed error pages with a stack trace and compilation details, while remote users with be presented with a concise page notifying them that an error occurred. If a custom error page is available, it will be shown to the remote users only.

displaying a concise yet not-so-pretty error page to visitors is still not good enough, so you need to put together a custom error page and specify it this way:

<customErrors
mode="RemoteOnly"
defaultRedirect="~/errors/GeneralError.aspx"
/>

should anything happen now, you will see a detailed stack trace and remote users will be automatically redirected to the custom error page,
GeneralError.aspx
. How you apologize to users for the inconvenience is up to you. Ian Lloyd gives a couple of suggestions as to the look and feel of a custom 404 page.

the
<customErrors>
tag may also contain several
<error>
(see MSDN) subtags for more granular error handling. Each
<error>
tag allows you to set a custom condition based upon an HTTP status code. For example, you may display a custom 404 for missing pages and a general error page for all other exceptions:

<customErrors mode="On" defaultRedirect="~/errors/GeneralError.aspx">
<error statusCode="404" redirect="~/errors/PageNotFound.aspx" />
</customErrors>

The URL to a custom error page may be relative (
~/error/PageNotFound.aspx
) or absolute (
http://www.yoursite.com/errors/PageNotFound.aspx
). The tilde (~) in front of URLs means that these URLs point to the root of your web application. Please download sample code for this article and see the CustomErrors3 project.

that's really all there's to it. Before we move on to the next (and last approach) a few words about clearing errors.

Clearing Errors

you probably noticed I chose to call
Server.ClearError()
in both
OnError
and
Application_Error
above. I call it to let the page run its course. What happens if you comment it out? The exception will leave
Application_Error
and continue to crawl up the stack until it's handled and put to rest. If you set custom error pages in
web.config
the runtime will act accordingly—you get to collect exception information AND see a friendly error page. We'll talk about utilizing this information a little later.

Handling Errors In An HttpModule

much is written about HTTP modules. They are an integral part of the Asp.Net pipeline model. Suffice it to say that they act as content filters. An HTTP module class implements the
IHttpModule
interface (see MSDN). With the help of
HttpModule
s you can pre- and post-process a request and modify its content.
IHttpModule
is a simple interface:

public interface IHttpModule
{
void Dispose();
void Init(HttpApplication context);
}

as you see the context parameter is of type
HttpApplication
. It will come in very handy when we write out own
HttpModule
. Implementation of a simple
HttpModule
may look as follows:

using System;
using System.Web;
namespace AspNetResources.CustomErrors4
{
public class MyErrorModule : IHttpModule
{
public void Init (HttpApplication app)
{
app.Error += new System.EventHandler (OnError);
}
public void OnError (object obj, EventArgs args)
{
// At this point we have information about the error
HttpContext ctx = HttpContext.Current;
Exception exception = ctx.Server.GetLastError ();
string errorInfo =
"<br>Offending URL: " + ctx.Request.Url.ToString () +
"<br>Source: " + exception.Source +
"<br>Message: " + exception.Message +
"<br>Stack trace: " + exception.StackTrace;
ctx.Response.Write (errorInfo);
// --------------------------------------------------
// To let the page finish running we clear the error
// --------------------------------------------------
ctx.Server.ClearError ();
}
public void Dispose () {}
}
}

the Init method is where you wire events exposed by
HttpApplication
. Wait, is it the same
HttpApplication
we talked about before? Yes, it is. You've already learned how to add handlers for various evens to
Global.asax
. What you do here is essentially the same. Personally, I think writing an
HttpModule
to trap errors is a much more elegant solution than customizing your
Global.asax
file. Again, if you comment out the line with
Server.ClearError()
the exception will travel back up the stack and—provided your
web.config
is properly configured—a custom error page will be served.

to plug our
HttpModule
into the pipeline we need to add a few lines to
web.config
:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<httpModules>
<add type="AspNetResources.CustomErrors4.MyErrorModule,«
CustomErrors4"
name="MyErrorModule" />
</httpModules>
</system.web>
</configuration>

as MSDN states the
type
attribute specifies a comma-separated class/assembly combination. In our case
MyErrorModule
is the name of a class from the
AspNetResources.CustomErrors4
assembly. Tim Ewald and Keith Brown wrote an excellent article for MSDN Magazine on this subject.

You will find a full-fledged sample in the CustomErrors4 project in code download.

to gain deeper understanding of
HttpModules
and their place in the HTTP pipeline I encourage you to search the web since the nuts and bolts are not relevant to the topic of error handling.

What about HTML pages?

what happens if you request a non-existent HTML page? This question comes up in news groups very often.

by default you will get a canned "white page". When you install the .NET Framework is maps certain file extensions to the Asp.Net ISAPI,
aspnet_isapi.dll
. Neither HTML nor HTM files are mapped to it (because they are not Asp.Net pages). However, you can configure IIS to treat them as Asp.Net pages and serve our custom error pages.

Run the IIS Manager
Select a web application
Right click and go to Properties
On the Virtual Directory tab click Configuration
In the Extension column find
.aspx
, double click and copy the full path to
aspnet_isapi.dll
. It should be something like
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\«
aspnet_isapi.dll

Click Add and paste the path in the Executable box
In the Extension box type
.html

Make sure Check that file exists is NOT checked
Dismiss all dialogs

if you type a wrong URL to an HTML page now you should get our user-friendly error page.

Analyze That

the reason we went this far with error trapping is the insight we gain about the problem. As strange as it may sound, exceptions are your friends because once you trap one you can alert responsible people right away. There are several ways to go about it:

Write it to the system event log. You could have WMI monitor events in the log and act on them, too. See MSDN for more information
Write it to a file
Email alerts

Please refer to an excellent whitepaper, Exception Management Architecture Guide from Microsoft for a comprehensive discussion of different aspects of error handling.

i've implemented the last option—email alerts—in a production environment and it worked great. Once someone pulls up a (custom) error page we get an email and jump right on it. Given the fact that users grow impatient with faulty sites and web applications, it's critical to be notified of errors right away.

The Path of 404

as I was researching the topic of custom error pages I couldn't help wondering where 404s originate from and how we end up seeing custom 404 pages. To follow this exercise you will need MSDN and Lutz Roeder's Reflector.



when IIS receives a resource request it first figures out if it will process it directly or match against an ISAPI. If it is one of the Asp.Net resources, IIS hands the request to the Asp.Net ISAPI,
aspnet_isapi.dll
.

for example, when a request for an .aspx page comes the runtime creates a whole pipeline of objects. At about this time an object of type
HttpApplication
(which we already talked about) is instantiated. This object represents your web application. By tapping into the various events of
HttpApplication
you can follow request execution every step of the way (the image on the left shows the sequence of these events).

next,
HttpApplication
calls its
MapHttpHandler
method which returns an instance of
IHttpHandler
(an object that implements
IHttpHandler
, to be more precise). The
IHttpHandler
interface is a very simple one:

public interface IHttpHandler
{
void ProcessRequest(HttpContext context);
bool IsReusable { get; }
}

the
IsReusable
property specifies if the same instance of the handler can be pooled and reused repeatedly. Each request gets its own instance of
HttpHandler
which is dedicated to it throughout the lifetime of the request itself. Once the request is processed its HttpHandler is returned to a pool and later reused for another request.

the
ProcessRequest
method is where magic happens. Ultimately, this method processes a request and generates a response stream which travels back up the pipeline, leaves the web server and is delivered to the client. How does
HttpApplication
know which
HttpHandler
to instantiate? It's all pre-configured in
machine.config
:

...
<httpHandlers>
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
<add verb="*" path="*.ashx"
type="System.Web.UI.SimpleHandlerFactory"/>
<add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler"/>
...
</httpHandlers>

this is only an excerpt. You have more
HttHandlers
configured for you. See how .aspx files are mapped to the
System.Web.UI.PageHandlerFactory
class?

to instantiate the right handler
HttpApplication
calls its
MapHttpHandler
method:

internal IHttpHandler MapHttpHandler (
HttpContext context, string requestType,
string path, string pathTranslated,
bool useAppConfig);

if you follow the assembly code of this method you will also see a call to the
PageHandlerFactory.GetHandler
method which returns an instance of
HttpHandler
:

L_0022: call HttpApplication.GetHandlerMapping
L_0027: stloc.2
L_0028: ldloc.2
L_0029: brtrue.s L_004a
L_002b: ldc.i4.s 42
L_002d: call PerfCounters.IncrementCounter
L_0032: ldc.i4.s 41
L_0034: call PerfCounters.IncrementCounter
L_0039: ldstr "Http_handler_not_found_for_request_type"
L_003e: ldarg.2
L_003f: call HttpRuntime.FormatResourceString
L_0044: newobj HttpException..ctor
L_0049: throw
L_004a: ldarg.0
L_004b: ldloc.2
L_004c: call HttpApplication.GetFactory
L_0051: stloc.3
L_0052: ldloc.3
L_0053: ldarg.1
L_0054: ldarg.2
L_0055: ldarg.3
L_0056: ldarg.s pathTranslated
L_0058: callvirt IHttpHandlerFactory.GetHandler

every Asp.Net page you write, whether you insert a base class in-between or not, ultimately derives from the
System.Web.UI.Page
class. It's interesting to note that the Page class inherits the
IHttpHandler
interface and is an
HttpHandler
itself! What that means is the runtime will at some point call
Page.ProcessRequest
!

Page.ProcessRequest
request delegates all work to its internal method,
ProcessRequestMain
:

if (this.IsTransacted)
{ this.ProcessRequestTransacted(); }
else
{ this.ProcessRequestMain(); }

finally,
ProcessRequestMain
is where all the fun stuff happens. Among all the many things it does, it defines an exception handler as follows:

Try
{
// code skipped
catch (Exception exception4)
{
exception1 = exception4;
PerfCounters.IncrementCounter(34);
PerfCounters.IncrementCounter(36);
if (!this.HandleError(exception1)) { throw; }
}
}

if you follow
HandleError
further you'll notice that it will try to look up the name of your custom error page and redirect you to it:

if ((this._errorPage != null) &&
CustomErrors.GetSettings(base.Context).«
CustomErrorsEnabled(this._request))
{
this._response.RedirectToErrorPage(this._errorPage);
return true;
}
internal bool RedirectToErrorPage(string url)
{
bool flag1;
try
{
if (url == null)
{
flag1 = false;
goto L_0062;
}
if (this._headersWritten)
{
flag1 = false;
goto L_0062;
}
if (this.Request.QueryString["aspxerrorpath"] != null)
{
flag1 = false;
goto L_0062;
}
if (url.IndexOf(63) < 0)
{
url = string.Concat(url, "?aspxerrorpath=", this.Request.Path);
}
this.Redirect(url, 0);
}
catch (Exception exception1)
{
flag1 = false;
goto L_0062;
}
return true;
L_0062:
return flag1;
}

this method does you a favor by appending the offending URL. You can see
?aspxerrorpath=
in the URL each time a custom 404 page is displayed.

if everything goes smooth—no exceptions thrown, no redirects to custom error pages—
ProcessRequest
finishes its job, and the response travels back through the pipeline.

Conclusion

this article gave a detailed overview of Asp.Net custom error pages and several different approaches of setting them up. It's important that users see meaningful, friendly error pages. By the same token it's important that people on the other end are alerted about problems right away. Asp.Net provides a powerful and flexible framework to achieve both goals.

Discuss

liked it? Hated it? Discuss this article

if (dotIR != null)
{
dotIR.replaceElement ("h1", "#900", "#e9e9df", 490, "left", 36, "CaflischScriptWebPro.ttf");
}

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