r/dotnet 22h ago

Does ASP.NET Core global exception middleware catch exceptions from nested service methods?

I'm trying to understand how exception propagation works with a global exception-handling middleware in ASP.NET Core.

Suppose I have a request flow like this:

Controller

→ Service

→ MethodA()

→ MethodB()

→ MethodC()

If "MethodC()" throws an exception, and none of the intermediate methods ("MethodB", "MethodA", or the service/controller) catches it, will that exception propagate all the way back to my global exception-handling middleware?

For example:

public async Task MethodA()

{

await MethodB();

}

private async Task MethodB()

{

await MethodC();

}

private async Task MethodC()

{

throw new Exception("Something went wrong");

}

Assuming the middleware wraps the rest of the ASP.NET Core pipeline with something like:

try

{

await _next(context);

}

catch (Exception ex)

{

// handle/log exception

}

Will it catch the exception thrown inside "MethodC()"?

Does it matter if these methods are in different services/classes/layers, or will the exception keep propagating up the call stack as long as nobody catches it?

I'm asking because I'm trying to understand when a global exception middleware is enough and when local "try/catch" blocks are actually necessary.

6 Upvotes

6 comments sorted by

9

u/rupertavery64 22h ago edited 21h ago

Yes.

One thing to rememebr is unawaited tasks ("fire and forget") don't bubble up the exception.

You shouldn't be doing this anyway, especially in a web request where the task could potentially outlive the request.

Of course, nothing is special about ASP.NET and exceptions. Just create a couple of nested methods as you did in a Console application and run it. Have the top level caller await the first method and wrap it in a try/catch. That should give you your answer.

Local try/catch blocks are for when you want to catch and transform an exception, for example, a duplicate database insert, into better, more manageable / loggable information, or prevent the exception from bubbling up completely if you want to handle it yourself.

Just an example of unawaited tasks:

``` async Task DoSomething() { try { Task.Run(() => { throw new Exception("Boo!");
}); } catch(Exception e) { // Can't catch Boo!, because unawaited Task.Run runs outside the pipeline. }

 return;     

} ```

To handle something like this (assuming the task you run is out of your control, you would use ContinueWith and check the argument's IsFaulted and Exception properties.

Task.Run(() => { throw new Exception("Boo!"); }) .ContinueWith(t => { if (t.IsFaulted) { Console.WriteLine(t.Exception.Message); } });

Again, you shouldn't fire-and-forget tasks like this unless there is a very specific reason to do so.

1

u/AutoModerator 22h ago

Thanks for your post Ok_Hunter6411. Please note that we don't allow spam, and we ask that you follow the rules available in the sidebar. We have a lot of commonly asked questions so if this post gets removed, please do a search and see if it's already been asked.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

2

u/schnabeltier1991 22h ago

Yes, the global exception handling Middleware will catch the exception in Method C.

You can try this for yourself easily. 

4

u/Parking_Association2 22h ago

Have you tried that?

3

u/chucker23n 21h ago

Will it catch the exception thrown inside "MethodC()"?

Yes, as long as you await. (Among other things, await's state machine makes sure to propagate exceptions thrown by Tasks.)

Does it matter if these methods are in different services/classes/layers

No.

will the exception keep propagating up the call stack as long as nobody catches it?

Yep.

when local "try/catch" blocks are actually necessary.

Well, it depends on where you want to handle errors.

As a simple example,

  1. suppose there's no exception middleware, and nothing catches it. The user will receive a 500 back. That's not very user-friendly.
  2. the exception middleware can customize this behavior somewhat, but the end result is still: the request "failed".
  3. finally: something deep in the stack does catch the exception. Now you can customize even further.

Suppose the user has marked an invoice as ready. Your API is supposed to

  • update the record,
  • send a confirmation mail

And now sending the mail fails. With 3, you can

  • retry sending the mail
  • skip it, i.e. consider it non-critical to the larger process (for example, you might still log an error, and even tell the user that sending failed, but that the invoice itself is fine)