This blog post is built off another blog post found Here. In that post it goes over the steps of making efficent api calls. I won't repeat what was written, please read the orignal source to get a bit of background.
In this post I am going to take what was learnt from the blog post linked above and use it within an ApiClient class where I can resue a generic get call. In the example ApiClient is a static class, but if your using DI in your code then you can easily refactor to make it injectable.
If you want to play with a working demo you can find the source code of this example on Github Here. Example usage is within a .Net core (2.2) console app.
The ApiClient code.
public static class ApiClient
{
/// <summary>
/// Gets the async.
/// </summary>
/// <returns>The async.</returns>
/// <param name="url">URL.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public static async Task<T> GetAsync<T>(string url, CancellationToken cancellationToken)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
using (var client = new HttpClient())
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
var stream = await response.Content.ReadAsStreamAsync();
if (response.IsSuccessStatusCode)
return DeserializeJsonFromStream<T>(stream);
var content = await StreamToStringAsync(stream);
throw new ApiException { StatusCode = (int)response.StatusCode, Content = content };
}
}
/// <summary>
/// Deserializes the json from stream.
/// </summary>
/// <returns>The json from stream.</returns>
/// <param name="stream">Stream.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
private static T DeserializeJsonFromStream<T>(Stream stream)
{
if (stream == null || stream.CanRead == false)
return default(T);
using (var sr = new StreamReader(stream))
using (var jtr = new JsonTextReader(sr))
{
var jr = new JsonSerializer();
var searchResult = jr.Deserialize<T>(jtr);
return searchResult;
}
}
/// <summary>
/// Streams to string async.
/// </summary>
/// <returns>The to string async.</returns>
/// <param name="stream">Stream.</param>
private static async Task<string> StreamToStringAsync(Stream stream)
{
string content = null;
if (stream != null)
{
using (var sr = new StreamReader(stream))
{
content = await sr.ReadToEndAsync();
}
}
return content;
}
}
To use use ApiClient, you would do as follows:
try
{
using (var cancelSrc = new CancellationTokenSource())
{
var response = await ApiClient.GetAsync<Todo>($"https://jsonplaceholder.typicode.com/todos/3", cancelSrc.Token);
Console.WriteLine($"response: {response}");
}
}
catch (ApiException apiEx)
{
Console.WriteLine(apiEx.StackTrace);
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
finally
{
Console.ReadLine();
}
Other classes used in the previous code.
public class ApiException : Exception
{
/// <summary>
/// Gets or sets the status code.
/// </summary>
/// <value>The status code.</value>
public int StatusCode { get; set; }
/// <summary>
/// Gets or sets the content.
/// </summary>
/// <value>The content.</value>
public string Content { get; set; }
}
public class Todo
{
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
[JsonProperty("userId")]
public int UserId { get; set; }
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
[JsonProperty("id")]
public int Id { get; set; }
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
[JsonProperty("title")]
public string Title { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="T:Example.ApiClient.Todo"/> is completed.
/// </summary>
/// <value><c>true</c> if completed; otherwise, <c>false</c>.</value>
[JsonProperty("completed")]
public bool Completed { get; set; }
public override string ToString()
{
return $"\nuserId:{UserId}\nid:{Id}\ntitle:{Title}\ncompleted:{Completed}";
}
}
The main bulk of the code is within ApiClient. By using a Generic class I can now pass a type within <T>
. This means I don't have to repeat the api calling code, I can just create my class that maps to the Json object I expect and voila.