Click here to Skip to main content
15,893,644 members
Articles / Web Development / ASP.NET

Life of Exception in ASP.NET

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
19 Aug 2013CPOL3 min read 11.3K   5   1
How ASP.NET (MVC) handles exceptions that occur in web applications
This post gives a better understanding of the exception flow in ASP.NET. If, in future, Elmah does not report any exceptions in ASP.NET MVC application, you will know where to look for the fault.

In this post, I’m going to show you the way ASP.NET (MVC) handles exceptions that occur in web applications. We will also examine different places where we can hook our own loggers. Our application will be a very basic ASP.NET MVC project with one controller and one view:

C#
using System;
using System.Web;
using System.Web.Mvc;

[HandleError]
public class HomeController : Controller
{
    //
    // GET: /Home/
    public ActionResult Index()
    {
        return View();
    }
    
    public ActionResult Exception() {
        throw new Exception("test exception");
    }
}
XML
@{
    ViewBag.Title = "test title";
}

<h2>Index page</h2>

<a href=&quot;@Url.Action(&quot;Exception&quot;)&quot;>throw exception</a>

Let’s attach windbg to the IIS Express server hosting our sample application, configure it to stop on all first chance CLR exception and break when HandleError filter is called:

0:030> sxe clr
0:027> !Name2EE System.Web.Mvc.dll System.Web.Mvc.HandleErrorAttribute.OnException
Module:      645c1000
Assembly:    System.Web.Mvc.dll
Token:       060009a3
MethodDesc:  645f13d4
Name:        System.Web.Mvc.HandleErrorAttribute.OnException(System.Web.Mvc.ExceptionContext)
JITTED Code Address: 646c94e4
0:027> bp 646c94e4
*** WARNING: Unable to verify checksum for 
C:\Windows\assembly\NativeImages_v4.0.30319_32\System.Web.Mvc\
1a2d7693f4ae1edffeb277679caefefe\System.Web.Mvc.ni.dll

ASP.NET MVC (HandleError)

After clicking the throw exception link, windbg should notify us of the exception that was thrown. We may verify that it’s our exception and press go. We should now stop at our breakpoint. Quick look at the stack and we can find that the HandleError filter is called by ControllerActionInvoker:

0:027> !pe
Exception object: 03e03a68
Exception type:   System.Exception
Message:          test exception
InnerException:   <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80131500
0:027> g
Breakpoint 0 hit
0:027> !DumpStack
OS Thread Id: 0x16b0 (27)
Current frame: (MethodDesc 645f13d4 +0 System.Web.Mvc.HandleErrorAttribute.OnException
(System.Web.Mvc.ExceptionContext))
ChildEBP RetAddr  Caller, Callee
1058ec68 646ab72e (MethodDesc 645eb840 +0x6e 
System.Web.Mvc.ControllerActionInvoker.InvokeExceptionFilters
(System.Web.Mvc.ControllerContext, System.Collections.Generic.IList`1
<System.Web.Mvc.IExceptionFilter>, System.Exception)), calling clr!VSD_FixupAsmStub
1058ec94 646aae06 (MethodDesc 645eb7e8 +0x13a 
System.Web.Mvc.ControllerActionInvoker.InvokeAction
(System.Web.Mvc.ControllerContext, System.String))
1058ecc0 646aac1c (MethodDesc 645eb7f8 +0xb4 
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter
(System.Web.Mvc.IActionFilter, System.Web.Mvc.ActionExecutingContext, 
System.Func`1<System.Web.Mvc.ActionExecutedContext>)), calling clr!IL_Rethrow
1058ed84 646ab3b5 (MethodDesc 645eb744 +0x61 System.Web.Mvc.ControllerActionInvoker..ctor()), 
calling clr!JIT_WriteBarrierEAX
1058ed98 646b10ab (MethodDesc 645ecb44 +0x6b System.Web.Mvc.Controller.ExecuteCore()), 
calling 03859182
1058edc0 646ab9fc (MethodDesc 645eb97c +0x5c 
System.Web.Mvc.ControllerBase.Execute(System.Web.Routing.RequestContext))
1058ede8 646abc6b (MethodDesc 645eb9b0 +0xb 
System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute
(System.Web.Routing.RequestContext))
1058edf0 646b735b (MethodDesc 645edca0 +0x23 
System.Web.Mvc.MvcHandler+<>c__DisplayClass6+<>c__DisplayClassb.<BeginProcessRequest>b__5()), 
calling 0385f75a
1058ee10 646afb44 (MethodDesc 645ec504 +0x14 
System.Web.Mvc.Async.AsyncResultWrapper+<>c__DisplayClass1.<MakeVoidDelegate>b__0())
1058ee1c 646cf5cb (MethodDesc 645f262c +0xb 
System.Web.Mvc.Async.AsyncResultWrapper+<>c__DisplayClass8`1[[System.Web.Mvc.Async.AsyncVoid, 
System.Web.Mvc]].<BeginSynchronous>b__7(System.IAsyncResult))
1058ee20 646afdf7 (MethodDesc 645ec57c +0x3f 
System.Web.Mvc.Async.AsyncResultWrapper+WrappedAsyncResult`1[[System.Web.Mvc.Async.AsyncVoid, 
System.Web.Mvc]].End())
1058ee34 646b7231 (MethodDesc 645edc40 +0x31 
System.Web.Mvc.MvcHandler+<>c__DisplayClasse.<EndProcessRequest>b__d()), 
calling (MethodDesc 645ec57c +0 
System.Web.Mvc.Async.AsyncResultWrapper+WrappedAsyncResult`1[[System.Web.Mvc.Async.AsyncVoid, 
System.Web.Mvc]].End())
1058ee44 6469d398 (MethodDesc 645e9248 +0x8 
System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(System.Action))
1058ee48 6469d317 (MethodDesc 645e923c +0x17 
System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(System.Action))
1058ee54 646b6cd5 (MethodDesc 645edb1c +0x3d 
System.Web.Mvc.MvcHandler.EndProcessRequest(System.IAsyncResult)), 
calling (MethodDesc 645e923c +0 System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust
(System.Action))
1058ee64 646b698a (MethodDesc 645edba0 +0xa 
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(System.IAsyncResult))
1058ee6c 65bec82d (MethodDesc 6511b544 
System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.
IExecutionStep.Execute()), calling 0385ae7e
1058eeb0 65298aec (MethodDesc 65113f2c +0x9c 
System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)), calling 03858d22
1058eef0 652ac310 (MethodDesc 6511b4f4 +0x474 
System.Web.HttpApplication+PipelineStepManager.ResumeSteps(System.Exception)), 
calling (MethodDesc 65113f2c +0 System.Web.HttpApplication.ExecuteStep
(IExecutionStep, Boolean ByRef))
1058ef74 65298d50 (MethodDesc 65113f8c +0x60 
System.Web.HttpApplication.BeginProcessRequestNotification
(System.Web.HttpContext, System.AsyncCallback))
1058ef88 65294e2b (MethodDesc 6510de78 +0xbb 
System.Web.HttpRuntime.ProcessRequestNotificationPrivate
(System.Web.Hosting.IIS7WorkerRequest, System.Web.HttpContext)), 
calling (MethodDesc 65113f8c +0 System.Web.HttpApplication.BeginProcessRequestNotification
(System.Web.HttpContext, System.AsyncCallback))
1058efd8 6529ad2d (MethodDesc 6510fad4 +0x245 
System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper
(IntPtr, IntPtr, IntPtr, Int32)), calling (MethodDesc 6510de78 +0 
System.Web.HttpRuntime.ProcessRequestNotificationPrivate
(System.Web.Hosting.IIS7WorkerRequest, System.Web.HttpContext))
1058f06c 6529aabf (MethodDesc 6510fac8 +0x1f 
System.Web.Hosting.PipelineRuntime.ProcessRequestNotification
(IntPtr, IntPtr, IntPtr, Int32)), calling (MethodDesc 6510fad4 +0 
System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper
(IntPtr, IntPtr, IntPtr, Int32))
1058f094 0382a98a 0382a98a
...

As we can read from the stack trace, InvokeExceptionFilter is called from the InvokeAction method. Here is the decompiled code to check how the handling is defined:

C#
FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor);
try
{
  AuthorizationContext authorizationContext = this.InvokeAuthorizationFilters
               (controllerContext, filters.AuthorizationFilters, actionDescriptor);
  if (authorizationContext.Result != null)
  {
    this.InvokeActionResult(controllerContext, authorizationContext.Result);
  }
  else
  {
    if (controllerContext.Controller.ValidateRequest)
    {
      ControllerActionInvoker.ValidateRequest(controllerContext);
    }
    IDictionary<string, object> parameterValues = this.GetParameterValues
                                (controllerContext, actionDescriptor);
    ActionExecutedContext actionExecutedContext = this.InvokeActionMethodWithFilters
          (controllerContext, filters.ActionFilters, actionDescriptor, parameterValues);
    this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, 
                                       actionExecutedContext.Result);
  }
}
catch (ThreadAbortException)
{
  throw;
}
catch (Exception exception)
{
  ExceptionContext exceptionContext = this.InvokeExceptionFilters
                             (controllerContext, filters.ExceptionFilters, exception);
  if (!exceptionContext.ExceptionHandled)
  {
    throw;
  }
  this.InvokeActionResult(controllerContext, exceptionContext.Result);
}
return true;

The default HandleErrorAttribute swallows exceptions inheriting from the exception type specified in its constructor (by default, it’s System.Exception so all exceptions are included). After setting the exceptionContext.ExceptionHandled to true, the ControllerActionInvoker happily continues using ActionResult provided by the HandleErrorAttribute (by default, it’s an action of rendering the Error.cshtml view). This also means that no other ASP.NET components are notified about the problem that occurred. You may ask what other components I am talking about. We can find it out by examining the stack. .NET exception handling is based on SEH (Structure Exception Handling). I won’t go too deep into this matter as it’s quite complicated and differs among architectures (x64, x86), but what’s important for us is that by checking the stack from top to bottom, we are able to identify all exception handles (catch blocks) awaiting for exceptions that happen “above them”. Imagine that our HandleErrorAttribute hasn’t caught the exception. In that case, framework starts looking for another catch block which will be able to handle the exception. We can emulate this behavior using !EHInfo command from the SOS extension on all method descriptors found in the stack trace presented at the beginning of this post (I dotted methods that do not have any catch clauses defined):

0:027> !EHInfo 645eb7e8
MethodDesc:   645eb7e8
Method Name:  System.Web.Mvc.ControllerActionInvoker.InvokeAction
              (System.Web.Mvc.ControllerContext, System.String)
Class:        645dd248
MethodTable:  646f1b1c
mdToken:      06000269
Module:       645c1000
IsJitted:     yes
CodeAddr:     646aaccc
Transparency: Transparent

EHHandler 0: TYPED 
Clause:  [646aad4b, 646aadeb] [7f, 11f]
Handler: [646aadeb, 646aadf0] [11f, 124]

EHHandler 1: TYPED 
Clause:  [646aad4b, 646aadeb] [7f, 11f]
Handler: [646aadf0, 646aae44] [124, 178]
...
0:027> !EHInfo 6511b544
MethodDesc:   6511b544
Method Name:  System.Web.HttpApplication+CallHandlerExecutionStep.
              System.Web.HttpApplication.IExecutionStep.Execute()
Class:        65102264
MethodTable:  6533f124
mdToken:      06002441
Module:       650e1000
IsJitted:     yes
CodeAddr:     652bdc10
Transparency: Safe critical

EHHandler 0: TYPED 
Clause:  [652bde25, 652bde3f] [215, 22f]
Handler: [652bdd07, 652bdd3e] [f7, 12e]

EHHandler 1: FINALLY 
Clause:  [652bde3f, 652bde74] [22f, 264]
Handler: [652bdd3e, 652bdd5c] [12e, 14c]

EHHandler 2: FINALLY 
Clause:  [652bdd5c, 652bdd7b] [14c, 16b]
Handler: [652bdd7b, 652bde14] [16b, 204]
...
0:027> !EHInfo 65113f2c
MethodDesc:   65113f2c
Method Name:  System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
Class:        65100b00
MethodTable:  6533721c
mdToken:      060023e0
Module:       650e1000
IsJitted:     yes
CodeAddr:     65298a50
Transparency: Safe critical

EHHandler 0: FINALLY 
Clause:  [65298a8d, 65298aab] [3d, 5b]
Handler: [65298aab, 65298acb] [5b, 7b]

EHHandler 1: TYPED 
Clause:  [65298a73, 65298b08] [23, b8]
Handler: [65298b08, 65298b8e] [b8, 13e]

EHHandler 2: TYPED 
Clause:  [65298a73, 65298b08] [23, b8]
Handler: [65298b8e, 65298b98] [13e, 148]

EHHandler 3: TYPED 
Clause:  [65298a73, 65298b98] [23, 148]
Handler: [65298b98, 65298c39] [148, 1e9]
... 
0:027> !EHInfo 6510de78
MethodDesc:   6510de78
Method Name:  System.Web.HttpRuntime.ProcessRequestNotificationPrivate
              (System.Web.Hosting.IIS7WorkerRequest, System.Web.HttpContext)
Class:        650ff9d8
MethodTable:  6533220c
mdToken:      0600294b
Module:       650e1000
IsJitted:     yes
CodeAddr:     65294d70
Transparency: Safe critical

EHHandler 0: TYPED 
Clause:  [65294e45, 65294e52] [d5, e2]
Handler: [65294e52, 65294e74] [e2, 104]

EHHandler 1: TYPED 
Clause:  [65294d95, 65294f9d] [25, 22d]
Handler: [65294f9d, 65294ff5] [22d, 285]
...
0:027> !EHInfo 6510fac8
MethodDesc:   6510fac8
Method Name:  System.Web.Hosting.PipelineRuntime.ProcessRequestNotification
              (IntPtr, IntPtr, IntPtr, Int32)
Class:        650e15d0
MethodTable:  65332ca8
mdToken:      06002169
Module:       650e1000
IsJitted:     yes
CodeAddr:     6529aaa0
Transparency: Safe critical

EHHandler 0: TYPED 
Clause:  [6529aab4, 6529aac1] [14, 21]
Handler: [6529aac1, 6529aad8] [21, 38]

ASP.NET (http modules, customerrors)

We’ve already checked the first method from the above list. Now it’s time for System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute(). This method posts a request to the handler and rethrows exception if any occurs. Next on the list is System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef). After decompiling, we can see that it gracefully handles any exception, sending it as a result to the caller:

C#
internal Exception ExecuteStep(HttpApplication.IExecutionStep step, 
                               ref bool completedSynchronously)
{
	Exception result = null;
  ...
  try
  {
    ...
    step.Execute();
    ...
  }
  catch (Exception ex)
  {
    result = ex;
    ...
  }
  catch
  {
  }
  ....
  return result;
}

Now, the caller which happens to be System.Web.HttpApplication+PipelineStepManager.ResumeSteps(System.Exception)

C#
internal override void ResumeSteps(Exception error)
{
  ...
  while (true)
  {
    if (syncContext.Error != null)
    {
      error = syncContext.Error;
      syncContext.ClearError();
    }
    if (error != null)
    {
      this._application.RecordError(error);
      error = null;
    }
    if (!this._validateInputCalled || !this._validatePathCalled)
    {
      error = this.ValidateHelper(context);
      if (error != null)
      {
        continue;
      }
    }
    if (syncContext.PendingCompletion(this._resumeStepsWaitCallback))
    {
      break;
    }
    ...
    error = this._application.ExecuteStep(nextEvent, ref flag4);
    ...
  }
  ...
}

checks if an error occurred while processing the request (any of the executed steps returns something different than null). If so, the PipelineStepManager records it and notifies all error event handlers (this._application.RecordError(error)), including http modules which subscribed to these type of events (httpContext.Error += new EventHandler(httpModule_Error)). If event handlers do not clear the error information in Http context (httpContext.Server.ClearError()), the error will be eventually handled by HttpRuntime (more exactly System.Web.HttpContext.ReportRuntimeErrorIfExists(System.Web.RequestNotificationStatus ByRef)). HttpRuntime creates an ASP.NET Health Monitoring event (by default, it logs the exception to the Application event log) and prepares a Yellow Screen Of Death, taking into consideration customErrors settings from the web.config file.

I hope that after having read this post, you have a better understanding of the exception flow in ASP.NET and, next time if Elmah does not report any exceptions in ASP.NET MVC application, you will know where to look for the fault :).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Poland Poland
Interested in tracing, debugging and performance tuning of the .NET applications.

My twitter: @lowleveldesign
My website: http://www.lowleveldesign.org

Comments and Discussions

 
Questionwindbg and ASP.NET Health Monitoring Pin
kiquenet.com28-Oct-16 11:01
professionalkiquenet.com28-Oct-16 11:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.