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
That sounds like a great idea.
Let's start with a helper method to do exactly that:
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 testingMemberService
.