The reason why ELMAH doesn't get notified is that ASP.NET AJAX terminates the request abruptly using Response.End. It does this when it sees an unhandled exception during a background post-back resulting from the use of UpdatePanel. Even though HTTP transaction is short-circuited, it does nonetheless send the message from the exception as part of the response.
The problem with Response.End is that it short-circuits the HTTP pipeline and skips a lot of key events. As a consequence, the Error event does not get raised and which ELMAH relies on to log the exception. The good news is, however, that there is a way out. The ScriptManager itself relies on the Page's Error event. So while the application Error event is not fired (the one ELMAH needs), the one from the Page is. So what one could do is write a separate HTTP module that sits and listens to the Error event of a Page and then logs the unhandled exception when the event gets fired. The advantage of using an HTTP module here is that you don't have to touch a single line of your code or that of ELMAH. I've attached a sample that shows how this can be done. Just throw it into the App_Code directory of your web site, add the line below to your web.config and then sit back and watch unhandled exceptions being logged during roundtrips from use of UpdatePanel!
<httpModules>
...
<add name="AjaxDeltaErrorLogModule" type="AjaxDeltaErrorLogModule"/>
...
</httpModules>
Here's a dissection of this module and it's very straightforward. The module subscribes to the PostMapRequestHandler event during Initialization:
public void Init(HttpApplication context)
{
context.PostMapRequestHandler += new EventHandler(OnPostMapRequestHandler);
}
The PostMapRequestHandler event is fired when the handler for the request is determined. At this point we sneak in and check if the request handler is a Page, and if so, then subscribe to its Error event. That's what's happening here:
private static void OnPostMapRequestHandler(object sender, EventArgs args)
{
HttpContext context = ((HttpApplication) sender).Context;
if (!IsAsyncPostBackRequest(context.Request.Headers))
return;
Page page = context.Handler as Page;
if (page == null)
return;
page.Error += new EventHandler(OnPageError);
}
You'll notice one odd check in there carried out by IsAsyncPostBackRequest, but I'll return to this shortly. The page error handler does nothing but simply get the last error that occurred (the unhandled exception) and simply sends it to ELMAH to be logged:
private static void OnPageError(object sender, EventArgs args)
{
Page page = (Page) sender;
Exception exception = page.Server.GetLastError();
if (exception == null)
return;
ErrorLog.Default.Log(new Error(exception, HttpContext.Current));
}
That's it really. Now about IsAsyncPostBackRequest. This check makes sure that we only listen to the page's Error event if the request is indeed being issued by the PageRequestManager from the client-side JavaScript to get a delta response. You can identify such a request by checking for a special HTTP header named "X-MicrosoftAjax" and whose value will read, "Delta=true". You can see this being done in line 929 of MicrosoftAjaxWebForms.debug.js (the file can be found in the ASP.NET AJAX installation directory). This check does two things. It makes sure that we don't subscribe to the Error event unnecessarily in order to avoid side effects on other types requests we're not interested in. Second, and more importantly, it avoids duplicate entries being logged. You see, there are now two modules looking for unhandled exceptions. The original one from ELMAH and now the new AjaxDeltaErrorLogModule. If an exception goes uncaught in the normal (non-AJAX) case then both modules would end up logging the exception and you'd get duplicate entries. Not good. Consequently, AjaxDeltaErrorLogModule only subscribes to the Error event of a Page when it knows it is safe enough to assume that the current request was issued by the client-side PageRequestManager for a delta response. The check for the special HTTP header and its value is therefore embodied in the private IsAsyncPostBackRequest method.
Hope this clears up things and solves the problem. I have not bothered to do the same for the e-mail bit, but I guess you can imagine how that will look like now based on the attached code.
- Atif
-----Original Message-----
From: elmah@googlegroups.com [mailto:elmah@googlegroups.com] On Behalf Of Chris
Sent: Tuesday, April 24, 2007 6:39 PM
To: ELMAH
Subject: [ELMAH] Catching Errors within in UpdatePanel
I just discovered errors that are thrown by code running in an ASP.Net
AJAX UpdatePanel always gets sent to the client (and thus pops up as a
javascript alert) and ELMAH doesn't handle/intercept it, and thus I'm
not getting emails about those errors. Is there any way to correct or
modify this behavior?
Thanks