Search code examples
c#exceptionasp.net-coreazure-service-fabric

Handling Aggregate Exceptions in Service Fabric


Let's say I have a Web API service that calls my user Service to return user profile information etc..

UserProfileService can throw UserNotFoundException. When thrown, it is serialized and sent as an inner exception in an AggregateException which can be caught in the calling method. This service uses Service Fabric's service remoting for RPCing.

My WebAPI is calling my service like this:

[HttpGet]
public async Task<IActionResult> Get(int id)
{
    try
    {
        var profile = await _userService.GetProfileAsync(int id);
        return Json(profile);
    } catch (AggregateException ae)
    {
        // Here I want to call NotFound() if `UserNotFoundException`
        //   was thrown, otherwise...
        return StatusCode(StatusCodes.Status500InternalServerError);
    }
}

So a couple of questions here:

  1. What do I do to handle expected exceptions?

Naively I'd do something like this:

try { /* ... */ } catch (AggregateException ae)
{
    foreach(var e in ae.InnerExceptions)
    {
        if (e is UserNotFoundException)
        {
            return NotFound();
        }
    }

    return errorResponse ?? StatusCode(StatusCodes.Status500InternalServerError);
}

But the trouble with this is, if there are multiple exceptions only one will "win". And, I believe - although there is no guarantee, that the earliest added Exceptions will have priority, as they'll have a lower index in InnerExceptions. Am I over thinking this, and would this solution be fine? The only time my custom exceptions will be thrown are when I know they should be thrown, surely?

This leads me to my other question:

  1. Under what circumstances would you retrieve several exceptions in an AggregateException.

Is it when you have Task a calling Task b calling a Task c, c throws, b doesn't throw, a throws, you'd get aggregate exception containing a and c's exceptions?


Solution

  • I'll answer your questions backwards:

    2) AggregateException has a contructor that allows an IEnumerable<Exception> as parameter. This is how it can contain multiple inner exceptions. This means that your aggregate exception won't contain more than one inner exception unless you explicitly throw an AggregateException with multiple inner exceptions. Say you have a Task a calling Task b calling Task c. If c throws an exception, which is not caught in a or b, a would throw an AggregateException with an inner AggregateException with an inner exception thrown by c.

    1) Your example works just fine. If you want it a bit shorter you could catch it by inner exception:

    try
    {
        // ...
    }
    catch (AggregateException ex) when (ex.InnerException is UserNotFoundException)
    {
        // ...
    }
    catch (AggregateException ex) when (ex.InnerException is SomeOtherException)
    {
        // ...
    }
    

    You could also catch them with some if-statements like you did in your example:

    try
    {
        // ...
    }
    catch (AggregateException ex)
    {
        if (ex.InnerException is UserNotFoundException)
        {
            // ...
        }
        else if (ex.InnerException is SomeOtherExeption)
        {
            // ...
        }
    }