The not so clear Error event
Note: this entry has moved.
The abstract TemplateControl class defines an Error event which –by the docs– “…occurs when an unhandled exception is thrown…”. Currently there are only two subclasses of TemplateControl, they’re the famous Page and UserControl. Based on this you can easily infer that any unhandled exception will cause the Error event to fire and you would have a chance to do whatever you want. Up to here everything seems fine so let begin to complicate things a bit…
You already know that to catch an exception you need to explicitly write a try/catch block; but you are not writing one in your Page or UserControl-derived class and the Error event is still firing properly when an exception goes unhandled. So, where is the little magic to make this happen hidden?
We’ll begin by tracing the execution of a Page starting at its implementation of IHttpHandler.ProcessRequest. What interests us most about this method is that depending if transactions are supported or not on the page it will call ProcessRequestTransacted or ProcessRequestMain respectively.
ProcessRequestMain is actually the one in charge for directing the execution of our Page; it will initialize, load viewstate, process postdata, etc, for every control in our page and all this code will be enclosed in a try/catch block (can you see the light now?). A simplified pseudo-code of ProcessRequestMain would look like this:
// For every child control:
// Call Init
// Call LoadViewState
// Call ProcessPostData
// Call Load
// and the list goes on…
// Call Unload for every child control
Note that the code enclosed in the try block consists of what is usually referred as the control execution lifecycle. Following that, we have three catch blocks:
- the first one is just there to ensure that Unload (and Dispose) get properly called even when a page is prematurely ending its execution (ThreadAbortException is a special type of exception that would actually deserve its own post).
- the second one is there to… well I’m not sure. I believe the reasoning behind this may be that a ConfigurationException is more of an application-related exception than a page-related exception so it may make some sense for this exception not to be handled at the page level. Anyway, it should be good to properly document this behaviour in the docs.
Now, if none of the two previous catch blocks got executed, our code will –unfailingly– reach the third one. Inside this last catch block the private Page.HandlerError method is called and depending on its results, the caught exception may be re-thrown thus giving an application-level error handler a chance to catch it later on. The basic idea here is that you could do whatever you want plus having the chance to decide if you want your application-level error handler to still be able to “see” this exception later on.
Getting our event handler code called
It is Page.HandlerError method the one that will call TemplateControl.OnError which in turns will fire any delegates attached to the Error event (does this pattern sound familiar to you?). The code looks something like this:
private bool HandlerError(Exception e)
Context.TempError = e;
if(Context.TempError == null)
TempError is an internal HttpContext property that wraps a private member variable that holds the exception that was thrown. If after calling OnError (which in turn will fire all attached delegates) TempError was not cleared (set to null), it means we want to let an application-level error handler “see” the exception. But being TempError an internal property how are we supposed to clear it in our event handler code? We can do this by calling the public HttpContext.ClearError method. Note that to make traditional ASP developers feel right at home, the ASP.NET team added an HttpServerUtility.ClearError method which is just a wrapper for HttpContext.ClearError, so you can write the more familiar:
Instead of the not-so-popular-but-equally-effective:
Error event don’t firing at all
We saw that any exceptions thrown by code enclosed in the try block in ProcessRequestMain will be caught by the catch block and the Error event will fire. This is true for example for any event handler we attach to a control’s event, any other method that these may call, etc. But what happens for code that is actually not enclosed by the above mentioned try block? Take for instance the instantiation of a declaratively added custom control that takes place in the class generated by the parser; that will not be enclosed by the try block shown previously thus any exception that the type constructor (or any other method called by it) may raise won’t be caught by the catch block and the Error event won’t fire. If the exception goes unhandled the application-level error handler still can “see” it, but this is not really what we want, after all it was an exception thrown by a control living on a page, it is an exception related to a page. Be well aware of this as the docs won’t help here.
What’s the story with User Controls?
All the explanation above seems to work well for Page but what happens if we attach a delegate to a UserControl’s Error event? If any exception thrown by code in our UserControl goes unhandled the Error event for the UserControl will not be called. Why? Simply because there is no try/catch logic coded anywhere to make this happen (like ProcessRequestMain does for Page). The documentation doesn’t state this properly which makes people think it can still be done.
Lastly, if you’re already wondering if the Error event at the application-level (HttpApplication.Error) works the same way as what we just saw, the short answer is: yes; the long answer would actually require its own post J