Skip to content

Instantly share code, notes, and snippets.

@zola-25
Created May 6, 2020 23:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zola-25/5d0a75b6092cdf78488ff5d1a2c41892 to your computer and use it in GitHub Desktop.
Save zola-25/5d0a75b6092cdf78488ff5d1a2c41892 to your computer and use it in GitHub Desktop.
A wrapper for C# HttpClient to consume Asynchronous REST resources
public interface IAnalyticsHttpWrapper
{
Task<T> GetAsynchronous<T>(string path);
}
public class AnalyticsHttpWrapper : IAnalyticsHttpWrapper
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly string _httpClientName;
private readonly HttpClient _httpClient;
private readonly int _defaultPollingInterval;
private readonly int _defaultPollingTimeout;
public AnalyticsHttpWrapper(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
_httpClientName = "AnalyticsAPI";
_httpClient = _httpClientFactory.CreateClient(_httpClientName);
_defaultPollingInterval = 2000; // 2 seconds
_defaultPollingTimeout = 1000*60*45; // 45 mins
}
public async Task<T> GetAsynchronous<T>(string path)
{
var httpResponse = await PollUntilResourceCreated(path);
await CheckResourceCreatedResponseOk(httpResponse);
var jsonContent = await httpResponse.Content.ReadAsStringAsync();
var responseContent = JsonConvert.DeserializeObject<T>(jsonContent);
return responseContent;
}
private async Task<HttpResponseMessage> PollUntilResourceCreated(string path)
{
var createResourceResponse = await _httpClient.PostAsync(path, null);
if (createResourceResponse.StatusCode != System.Net.HttpStatusCode.Accepted)
{
var content = await createResourceResponse.Content.ReadAsStringAsync();
throw new AnalyticsHttpException(
$"Create resource did not respond with (202): {(int)createResourceResponse.StatusCode}",
content);
}
var tokenSource = new CancellationTokenSource();
var cancellationToken = tokenSource.Token;
var timeoutTask = Task.Delay(_defaultPollingTimeout, cancellationToken);
var statusLocation = createResourceResponse.Headers.Location;
HttpResponseMessage httpResponse;
do
{
TimeOutCheck(timeoutTask, statusLocation);
httpResponse = await _httpClient.GetAsync(statusLocation); // should auto redirect to resource location when resource creation completed
await Task.Delay(_defaultPollingInterval);
}
while (httpResponse.StatusCode == System.Net.HttpStatusCode.Accepted);
tokenSource.Cancel(); // Cancel timeout timer
return httpResponse;
}
private void TimeOutCheck(Task timeoutTask, Uri location)
{
if (timeoutTask.IsCompleted)
{
throw new AnalyticsHttpException(
@$"Resource queued at {location.ToString()} did not finish after {TimeSpan.FromMilliseconds(_defaultPollingTimeout).TotalMinutes}; aborting");
}
}
private async Task CheckResourceCreatedResponseOk(HttpResponseMessage httpResponse)
{
if (httpResponse.StatusCode == System.Net.HttpStatusCode.OK)
{
return;
}
else
{
var content = await httpResponse.Content.ReadAsStringAsync();
throw new AnalyticsHttpException(
$"Create resource accepted, but retrieving results responded: {(int)httpResponse.StatusCode}",
content);
}
}
}
public class AnalyticsHttpException : Exception
{
public string HttpResponseContent { get; }
public AnalyticsHttpException(string message, string httpResponseContent) : base(message)
{
HttpResponseContent = httpResponseContent;
}
public AnalyticsHttpException(string message) : base(message)
{
}
}
public class MyClientClass
{
private readonly IAnalyticsHttpWrapper _analyticsHttpWrapper;
public MyClientClass(IAnalyticsHttpWrapper analyticsHttpWrapper)
{
_analyticsHttpWrapper = analyticsHttpWrapper;
}
public async Task<MyAnalyticsResults> GetAnalyticsReults(int analysisId)
{
return await _analyticsHttpWrapper.GetAsynchronous<MyAnalyticsResults>($"/analysis/{analysisId}/result");
}
}
// in Startup.cs
//...
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
public void ConfigureServices(IServiceCollection services)
{
string analyticsApiUrl = "https://my-R-analytics-api/"
services.AddHttpClient("AnalyticsAPI", client =>
{
client.BaseAddress = new Uri(analyticsApiUrl);
client.DefaultRequestHeaders.Add("Accept", "application/json");
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = true
};
});;
services.AddTransient<IAnalyticsHttpWrapper, AnalyticsHttpWrapper >();
//...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment