Should I re-throw exceptions within a method, or is there a better way to handle it? When handling errors in C#, is it acceptable/appropriate to re-throw exceptions that are caught in a try/catch?

My scenario is that I am making an HTTP request and then checking the content of the response for a specific key (access_token). If that key is missing, I throw an error.

However, as all of the code within the method is in a try/catch block (just in case HTTP requests go awry), the exception that I throw is being caught. The solution that I've settled on for now is to re-throw the error that is caught, to ensure that I don't lose additional detail that was included with the custom exception. You can see a full example of my code at the bottom of this question.

I've found a number of other possibilities though, and I'd be interested to know what the C# community thinks is the best way to handle such scenarios (even if it turns out that none of them are appropriate!). There are of course other questions/resources that are similar to this, but some of the information is ambiguous and being a novice at C#, I'd like get a bit of extra context with a real-life example.

Possibilities


A separate catch block to re-throw the known error (see example 1).
Check for the type of exception in a single catch block (see example 2).
Return an exception should 'access_token' not be present (as opposed to throwing it).
Don't wrap all the code in a try\catch.


Example 1

catch (WhException ex)
{
    // Capture and rethrow a 'WhException', ensurig that the detail is not lost.
    throw ex;
}
catch (Exception ex)
{
    // Capture any generic exception and retrhow it as a 'WhException'.
    throw new WhException("An error occurred whilst attempting to generate a bearer access token.", ex.Message, ex);
}


Example 2

catch (Exception ex)
{
    if (ex is WhException)
        // Capture and rethrow a 'WhException', ensurig that the detail is not lost.
        throw ex;
    else
        // Capture any generic exception and retrhow it as a 'WhException'.
        throw new WhException("An error occurred whilst attempting to generate a bearer access token.", ex.Message, ex);
}


Full working example

public static async Task&lt;string&gt; GetAzureAccessTokenAsync(this HttpClient http, WhConfigRaw config)
{
    try
    {
        Dictionary&lt;string, string&gt; form = new Dictionary&lt;string, string&gt;()
        {
            { "grant_type", "client_credentials" },
            { "client_id", config.ServicePrincipalId },
            { "client_secret", config.ServicePrincipalKey },
            { "resource", AppSettings.Get("AzureCoreUri") }
        };
        FormUrlEncodedContent requestContent = new FormUrlEncodedContent(form);
        string uri = string.Format(AppSettings.Get("ClientCredentialsUri"), config.TenantId);

        // Query the Azure management API to get client credentials.
        HttpResponseMessage response = await http.PostAsync(uri, requestContent);

        // Read the content of the response for the Azure management API.
        JObject content = await response.Content.ReadAsJsonAsync&lt;JObject&gt;();

        // Check whether or not the content of the response contains and 'access_token'.
        if (!content.ContainsKey("access_token"))
        {
            throw new WhException("An error occurred whilst attempting to generate a bearer access token.", content);
        }

        return content["access_token"].ToString();
    }
    catch (WhException ex)
    {
        // Capture and rethrow a 'WhException', ensurig that the detail is not lost.
        throw ex;
    }
    catch (Exception ex)
    {
        // Capture any generic exception and retrhow it as a 'WhException'.
        throw new WhException("An error occurred whilst attempting to generate a bearer access token.", ex.Message, ex);
    }
}
Deciding which exceptions to handle, rethrow, wrap in other exceptions is a design decision. Some exceptions might be caught to branch to a different flow. Naively catching all exceptions is a bad call. Guidelines for handling and throwing exceptions



Some exceptions should be propagated all the way up, because of the integrity of the application or even your machine cannot be preserved. Be careful when encountering OutOfMemoryException, BadImageFormatException, StackOverflowException.

Would you really want to wrap/hide the aforementioned exceptions in a custom exception?


catch (Exception ex)
{
    // Capture any generic exception and retrhow it as a 'WhException'.
    throw new WhException("An error occurred whilst ..", ex.Message, ex);
}





Keep in mind that detail is lost when you catch and throw ex;. A new stacktrace is created.


catch (WhException ex)
{
    // Capture and rethrow a 'WhException', ensurig that the detail is not lost.
    throw ex;
}



Use throw; to preserve stacktrace.

catch (WhException ex)
{
    throw; // preserves stacktrace
}


If you want to rethrow outside the catch block you could use ExceptionDispatchInfo. Suppose Exception error is declared before the try:

catch (WhException ex)
{
    error = ExceptionDispatchInfo.Capture(ex);
}

error?.Throw(); // preserves stacktrace




Consider throwing these well-known exceptions for common situations:


ArgumentNullException : when a mandatory argument is not provided
ArgumentException: when an argument does not meet the preconditions to run
AggregateException: when packing multiple exceptions together