Created
May 6, 2020 23:32
-
-
Save zola-25/5d0a75b6092cdf78488ff5d1a2c41892 to your computer and use it in GitHub Desktop.
A wrapper for C# HttpClient to consume Asynchronous REST resources
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
{ | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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