C# API – How to Handle Failed API Calls in C#

apicerror handling

I built my API service and now I want to consume the information from a WPF application.
So far I created the class ApiHelper which initializes and provides the HttpClient used to call the API endpoints and the class MemberService which exposes the methods to get the data that it retrieves from the API.

public class ApiHelper : IApiHelper
{
    private readonly IConfiguration _config;

    public HttpClient ApiClient { get; private set; }

    public ApiHelper(IConfiguration config)
    {
        _config = config;
        InitializeClient();

    }

    private void InitializeClient()
    {
        string apiBase = _config["api"];

        ApiClient = new HttpClient
        {
            BaseAddress = new Uri(apiBase)
        };

        ApiClient.DefaultRequestHeaders.Clear();
        ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

This is the service:

public class MemberService : IMemberService
{
    private readonly IApiHelper _apiHelper;

    public MemberService(IApiHelper apiHelper)
    {
        _apiHelper = apiHelper;
    }

    public async Task<List<Member>> GetAllMembersAsync()
    {
        using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync("/api/Members"))
        {
            // If the response has a successful code...
            if (response.IsSuccessStatusCode)
            {
                // Read the response
                var result = await response.Content.ReadAsAsync<List<MemberResponse>>();
                var output = AutoMapping.Mapper.Map<List<Member>>(result);
                return output;
            }
            else
            // If the response is not successful
            {
                // Error handling here
            }
        }

        return null;
    }

My question is not about handling errors such as HttpRequestException that I could handle with a try catch block, but how to handle responses with a status code not in the 2xx range.
In particular, the endpoints of the API are token-protected so a user has to authenticate before making a request and after the token expires, it needs to be refreshed.
What I'd like to do is when the response has a status of 401 - Unauthorized, instead of throwing an exception or returning an object with an error message, the method should try to refresh the token (calling the appropriate method in another service) because it could be expired and just if the call fails again, throw the exception. I'd like to have this behavior for all the API calls.
It's the first time I work with API calls, so this is what I thought to do, but I'd like to have some tips about how to do it.
What is the best approach to achive this? Or do you suggest another way to make API calls and manage its responses?

Best Answer

What I'd like to do is when the response has a status of 401 - Unauthorized, instead of throwing an exception or returning an object with an error message, the method should try to refresh the token (calling the appropriate method in another service) because it could be expired and just if the call fails again, throw the exception. I'd like to have this behavior for all the API calls.

That sounds like a great idea.

Let's start with a helper method to do exactly that:

public class ApiHelper : IApiHelper
{
    ...
    public async HttpContent GetAuthenticatedAsync(string requestUri)
    {
        var response = await ApiClient.GetAsync(requestUri);

        // Pseudo code:
        //
        // if (200): return response.Content
        // if (401): 
        //   refresh token
        //   try GetAsync again
        //   if (200): return response
        //   else: throw meaningful exception - something is wrong with our credentials
        // else: throw meaningful exception - unexpected return value
        ...
    }
}

Then I'd use encapsulation to ensure that your business logic can't accidentally bypass your helper method: Make ApiClient private and add new low-level helper methods as needed.

You have now taken the first step to abstract away the transport mechanism. This improves readability of your business logic, since the code there can now concentrate on solving problems in your problem domain, rather than dealing with issues on the transport layer. If you always use the same deserialization logic, it might make sense to put that into ApiHelper as well (e.g. some public async TResult GetAuthenticatedAsync<TResult, TResultMessage>(string requestUri)).

It will also make unit testing easier: Authentication issues should be tested by testing ApiHelper, not by testing MemberService.

Related Topic