I am testing an HTTP Request to my API while the server is down.
It should receive an error response, but instead, it returns null and it gives me this exception:
System.ObjectDisposedException: Cannot access a closed Stream.
This happens in Android only, iOS I get an error response. this is my code:
using (HttpClient client = new HttpClient())
{
try
{
//pedido de token
var loginInfo = new StringContent(JsonConvert.SerializeObject(userAuth).ToString(), Encoding.UTF8, "application/json");
var requestToken = await client.PostAsync(URLs.url + URLs.getToken, loginInfo);
var receiveToken = await requestToken.Content.ReadAsStringAsync();
It doesn't reach the ReadAsString, throws the exception in the PostAsync.
Don't dispose of HttpClient. It is designed to be reused and handle multiple simultaneous requests.
Here's more info about how HttpClient works: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Here is a generic implementation that I use for all HttpClient services in my Xamarin.Forms apps:
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Net.Http;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using Xamarin.Forms;
namespace NameSpace
{
public abstract class BaseHttpClientService
{
#region Constant Fields
static readonly Lazy<JsonSerializer> _serializerHolder = new Lazy<JsonSerializer>();
static readonly Lazy<HttpClient> _clientHolder = new Lazy<HttpClient>(() => CreateHttpClient(TimeSpan.FromSeconds(30)));
#endregion
#region Fields
static int _networkIndicatorCount = 0;
#endregion
#region Events
public static event EventHandler<string> HttpRequestFailed;
#endregion
#region Properties
static HttpClient Client => _clientHolder.Value;
static JsonSerializer Serializer => _serializerHolder.Value;
#endregion
#region Methods
protected static async Task<T> GetObjectFromAPI<T>(string apiUrl)
{
using (var responseMessage = await GetObjectFromAPI(apiUrl).ConfigureAwait(false))
return await DeserializeResponse<T>(responseMessage).ConfigureAwait(false);
}
protected static async Task<HttpResponseMessage> GetObjectFromAPI(string apiUrl)
{
try
{
UpdateActivityIndicatorStatus(true);
return await Client.GetAsync(apiUrl).ConfigureAwait(false);
}
catch (Exception e)
{
OnHttpRequestFailed(e.Message);
Report(e);
throw;
}
finally
{
UpdateActivityIndicatorStatus(false);
}
}
protected static async Task<TResponse> PostObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PostObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PostObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(HttpMethod.Post, apiUrl, requestData);
protected static async Task<TResponse> PutObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PutObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PutObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(HttpMethod.Put, apiUrl, requestData);
protected static async Task<TResponse> PatchObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PatchObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PatchObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(new HttpMethod("PATCH"), apiUrl, requestData);
protected static async Task<TResponse> DeleteObjectFromAPI<TResponse>(string apiUrl)
{
using (var responseMessage = await DeleteObjectFromAPI(apiUrl).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> DeleteObjectFromAPI(string apiUrl) => SendAsync<object>(HttpMethod.Delete, apiUrl);
static HttpClient CreateHttpClient(TimeSpan timeout)
{
HttpClient client;
switch (Device.RuntimePlatform)
{
case Device.iOS:
case Device.Android:
client = new HttpClient();
break;
default:
client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip });
break;
}
client.Timeout = timeout;
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
return client;
}
static async Task<HttpResponseMessage> SendAsync<T>(HttpMethod httpMethod, string apiUrl, T requestData = default)
{
using (var httpRequestMessage = await GetHttpRequestMessage(httpMethod, apiUrl, requestData).ConfigureAwait(false))
{
try
{
UpdateActivityIndicatorStatus(true);
return await Client.SendAsync(httpRequestMessage).ConfigureAwait(false);
}
catch (Exception e)
{
OnHttpRequestFailed(e.Message);
Report(e);
throw;
}
finally
{
UpdateActivityIndicatorStatus(false);
}
}
}
protected static void UpdateActivityIndicatorStatus(bool isActivityIndicatorDisplayed)
{
if (isActivityIndicatorDisplayed)
{
Device.BeginInvokeOnMainThread(() => Application.Current.MainPage.IsBusy = true);
_networkIndicatorCount++;
}
else if (--_networkIndicatorCount <= 0)
{
Device.BeginInvokeOnMainThread(() => Application.Current.MainPage.IsBusy = false);
_networkIndicatorCount = 0;
}
}
static async ValueTask<HttpRequestMessage> GetHttpRequestMessage<T>(HttpMethod method, string apiUrl, T requestData = default)
{
var httpRequestMessage = new HttpRequestMessage(method, apiUrl);
switch (requestData)
{
case T data when data.Equals(default(T)):
break;
case Stream stream:
httpRequestMessage.Content = new StreamContent(stream);
httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
break;
default:
var stringPayload = await Task.Run(() => JsonConvert.SerializeObject(requestData)).ConfigureAwait(false);
httpRequestMessage.Content = new StringContent(stringPayload, Encoding.UTF8, "application/json");
break;
}
return httpRequestMessage;
}
static async Task<T> DeserializeResponse<T>(HttpResponseMessage httpResponseMessage)
{
httpResponseMessage.EnsureSuccessStatusCode();
try
{
using (var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var reader = new StreamReader(contentStream))
using (var json = new JsonTextReader(reader))
{
if (json is null)
return default;
return await Task.Run(() => Serializer.Deserialize<T>(json)).ConfigureAwait(false);
}
}
catch (Exception e)
{
Report(e);
throw;
}
}
static void OnHttpRequestFailed(string message) => HttpRequestFailed?.Invoke(null, message);
static void Report(Exception e, [CallerMemberName]string callerMemberName = "") => Debug.WriteLine(e.Message);
#endregion
}
}
I had the same problem (it worked fine in UWP but this error on Android).
Please see this linked question for what fixed it for me: HttpClient.SendAsync throws ObjectDisposedException on Xamarin.Forms Android but not on UWP
Related
On my Xamarin Forms project I'm trying to logout when the token is no longer valid and it returns 401 response.
For that I'm trying to use a DelegatingHandler but it will stop at SendAsync method without giving any errors.
Here is the DelegatingHandler class
public class HttpDelegatingHandler : DelegatingHandler
{
public HttpDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// before request
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// after request
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await Logout();
}
return response;
}
private async Task Logout()
{
CurrentPropertiesService.Logout();
CurrentPropertiesService.RemoveCart();
await Shell.Current.GoToAsync($"//main");
}
And here is my class AzureApiService where GetAsync stops the application
public class AzureApiService
{
HttpClient httpClient;
public AzureApiService()
{
#if DEBUG
var httpHandler = new HttpDelegatingHandler(new HttpClientHandler());
#else
var httpHandler = HttpDelegatingHandler(new HttpClientHandler());
#endif
httpClient = new HttpClient(httpHandler);
httpClient.Timeout = TimeSpan.FromSeconds(15);
httpClient.MaxResponseContentBufferSize = 256000;
}
public async Task<string> LoginAsync(string url, AuthUser data)
{
var user = await HttpLoginPostAsync(url, data);
if (user != null)
{
//Save data on constants
CurrentPropertiesService.SaveUser(user);
return user.Token;
}
else
{
return string.Empty;
}
}
// Generic Get Method
public async Task<T> HttpGetAsync<T>(string url, string token)
{
T result = default(T);
try
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.GetAsync(url);
HttpContent content = response.Content;
var jsonResponse = await content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
}
catch (Exception ex)
{
OnError(ex.ToString());
}
return result;
}
Please help I don't know where the issue is. Thanks.
In ASP.NET WebAPi 2 code, I have a delegate handler
public class RequestHandler1 : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var formData2 = await ReadContent(request);
return await base.SendAsync(request, cancellationToken);
}
private static async Task<string> ReadContent(HttpRequestMessage request)
{
using (var ms = new MemoryStream())
{
await request.Content.CopyToAsync(ms);
ms.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(ms, Encoding.UTF8, true, 100, true))
{
return sr.ReadToEnd();
}
}
}
private static async Task<string> ReadContent3(HttpRequestMessage request)
{
var text = await request.Content.ReadAsStringAsync();
return text;
}
}
The issue is related to HttpContent.ReadAsStringAsync causes request to hang (or other strange behaviours) but it was never properly answered in that thread.
By the time I call return await base.SendAsync(request, cancellationToken); it just hangs. it does not matter if I call ReadContent or ReadContent3
Any more suggestions?
try this code
public class CustomLogHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var logMetadata = await BuildRequestMetadata(request);
var response = await base.SendAsync(request, cancellationToken);
logMetadata = await BuildResponseMetadata(logMetadata, response);
await SendToLog(logMetadata);
return response;
}
private async Task<LogMetadata> BuildRequestMetadata(HttpRequestMessage request)
{
LogMetadata log = new LogMetadata
{
RequestMethod = request.Method.Method,
RequestTimestamp = DateTime.Now,
RequestUri = request.RequestUri.ToString(),
RequestContent = await request.Content.ReadAsStringAsync(),
};
return log;
}
private async Task<LogMetadata> BuildResponseMetadata(LogMetadata logMetadata, HttpResponseMessage response)
{
logMetadata.ResponseStatusCode = response.StatusCode;
logMetadata.ResponseTimestamp = DateTime.Now;
logMetadata.ResponseContentType = response.Content == null ? string.Empty : response.Content.Headers.ContentType.MediaType;
logMetadata.Response = await response.Content.ReadAsStringAsync();
return logMetadata;
}
private async Task<bool> SendToLog(LogMetadata logMetadata)
{
try
{
//write your code
}
catch
{
return false;
}
return true;
}
}
Using the Template Studio extension for Visual Studio, I generated a project solution base and am now attempting to interject the app load process with a HTTP request before proceeding the render the page view.
App.xaml.cs
using System;
using Braytech_3.Services;
using Windows.ApplicationModel.Activation;
using Windows.Storage;
using Windows.UI.Xaml;
namespace Braytech_3
{
public sealed partial class App : Application
{
private Lazy<ActivationService> _activationService;
private ActivationService ActivationService
{
get { return _activationService.Value; }
}
public App()
{
InitializeComponent();
APIRequest();
// Deferred execution until used. Check https://msdn.microsoft.com/library/dd642331(v=vs.110).aspx for further info on Lazy<T> class.
_activationService = new Lazy<ActivationService>(CreateActivationService);
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
if (!args.PrelaunchActivated)
{
await ActivationService.ActivateAsync(args);
}
}
protected override async void OnActivated(IActivatedEventArgs args)
{
await ActivationService.ActivateAsync(args);
}
private async void APIRequest()
{
//Create an HTTP client object
Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient();
//Add a user-agent header to the GET request.
var headers = httpClient.DefaultRequestHeaders;
Uri requestUri = new Uri("https://json_url");
//Send the GET request asynchronously and retrieve the response as a string.
Windows.Web.Http.HttpResponseMessage httpResponse = new Windows.Web.Http.HttpResponseMessage();
string httpResponseBody = "";
try
{
//Send the GET request
httpResponse = await httpClient.GetAsync(requestUri);
httpResponse.EnsureSuccessStatusCode();
httpResponseBody = await httpResponse.Content.ReadAsStringAsync();
APITempSave(httpResponseBody);
}
catch (Exception ex)
{
}
}
private async void APITempSave(string json)
{
StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
if (await tempFolder.TryGetItemAsync("APIData.json") != null)
{
StorageFile APIData = await tempFolder.GetFileAsync("APIData.json");
await FileIO.WriteTextAsync(APIData, json);
}
else
{
StorageFile APIData = await tempFolder.CreateFileAsync("APIData.json");
await FileIO.WriteTextAsync(APIData, json);
}
}
private ActivationService CreateActivationService()
{
return new ActivationService(this, typeof(Views.VendorsPage), new Lazy<UIElement>(CreateShell));
}
private UIElement CreateShell()
{
return new Views.ShellPage();
}
}
}
I think what I need to do is call _activationService = new Lazy<ActivationService>(CreateActivationService); once APITempSave() has been called but I am unsure of how to do so and what best practices are.
Any guidance would be greatly appreciated!
After further investigation and familiarisation with the generated solution, as well as additional Googling of await, async, and Tasks<>, I was able to implement the request as a service alongside items such as ThemeSelector, and ToastNotifications.
The ThemeSelector is one of the first things to be called in order to determine light and dark theme mode for the current user, so I was able to model my service around it and call it at the same time.
This is obviously very specific to the code that template studio generates, but some concepts are shared and should anyone else look for similar answers in the future maybe they'll find this.
APIRequest.cs (Service)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
namespace Braytech_3.Services
{
public static class APIRequest
{
internal static async Task Request()
{
//Create an HTTP client object
Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient();
//Add a user-agent header to the GET request.
var headers = httpClient.DefaultRequestHeaders;
Uri requestUri = new Uri("https://json_url");
//Send the GET request asynchronously and retrieve the response as a string.
Windows.Web.Http.HttpResponseMessage httpResponse = new Windows.Web.Http.HttpResponseMessage();
string httpResponseBody = "";
try
{
//Send the GET request
httpResponse = await httpClient.GetAsync(requestUri);
httpResponse.EnsureSuccessStatusCode();
httpResponseBody = await httpResponse.Content.ReadAsStringAsync();
await APITempSave(httpResponseBody);
}
catch (Exception ex)
{
}
}
internal static async Task APITempSave(string json)
{
StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
if (await tempFolder.TryGetItemAsync("APIData.json") != null)
{
StorageFile APIData = await tempFolder.GetFileAsync("APIData.json");
await FileIO.WriteTextAsync(APIData, json);
}
else
{
StorageFile APIData = await tempFolder.CreateFileAsync("APIData.json");
await FileIO.WriteTextAsync(APIData, json);
}
}
}
}
ActiviationService.cs (originally called by App.xaml.cs)
private async Task InitializeAsync()
{
await ThemeSelectorService.InitializeAsync();
await APIRequest.Request();
}
We have a few classes in our C# project that make calls out to 3rd party APIs. We're using HttpClient objects for the calls. We've set up our classes where we do these calls to accept an HttpClient so that when testing, we can use a custom/fake DelegatingHandler with the client.
We've set up our classes like this:
public class CallingService : ApiService
{
private readonly ISomeOtherService _someOtherService;
public CallingService (ILogger logger,
IConfigurationManager configurationManager,
ISomeOtherService someOtherService) : base(logger, configurationManager)
{
_someOtherService = someOtherService;
}
public CallingService (ILogger logger,
HttpClient client,
IConfigurationManager configurationManager,
ISomeOtherService someOtherService) : base(logger, configurationManager, client)
{
_someOtherService = someOtherService;
}
private async Task<XmlNodeList> TransmitToApi(string xml_string)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12;
//..
string type = "application/xml";
var content = new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes(xml_string)));
var targetUri = new Uri(ConfigurationManager.GetAppSetting("ApiUrl"));
var message = new HttpRequestMessage
{
RequestUri = targetUri ,
Method = HttpMethod.Post,
Content = content
};
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
message.Content.Headers.Add("Content-Type", type);
message.Headers.Add("someHeader", someData);
HttpResponseMessage response = null;
try
{
// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
response = await Client.SendAsync(message, token);
}
catch (Exception ex)
{
throw ex;
}
//...
return someData;
}
The base ApiService class defines a generic HttpClient object if one is not provided.
We're currently using SendAsync so we can define the message headers. (We have more headers than are listed here.)
The test defines the DelegatingHandler like this:
public class FakeResponseHandler : DelegatingHandler
{
private readonly Dictionary<Uri, HttpResponseMessage> _fakeResponses = new Dictionary<Uri, HttpResponseMessage>();
public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage, string content = "", bool asXml = false)
{
if (!string.IsNullOrWhiteSpace(content))
{
if (asXml)
{
responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/xml");
}
else
{
responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
}
}
_fakeResponses.Add(uri, responseMessage);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_fakeResponses.ContainsKey(request.RequestUri))
{
return _fakeResponses[request.RequestUri];
}
return new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request };
}
}
And then:
[Fact]
public async Task ItWillDoStuffAndCallApi()
{
using (var mock = AutoMock.GetLoose())
{
mock.Mock<IConfigurationManager>()
.Setup(cm => cm.GetAppSetting("ApiUrl"))
.Returns("http://example.org/test/");
string testReturnData = GetFileContents("IntegrationTests.SampleData.SampleApiResponseXML.txt");
FakeResponseHandler fakeResponseHandler = new FakeResponseHandler();
fakeResponseHandler.AddFakeResponse(new Uri("http://example.org/test/"),
new HttpResponseMessage(HttpStatusCode.OK),
testReturnData,
true);
//HttpClient httpClient = new HttpClient(fakeResponseHandler);
HttpClient httpClient = HttpClientFactory.Create(fakeResponseHandler);
mock.Provide(httpClient);
var ourService = new CallingService();
ourService.TransmitToApi(someXmlString);
}
}
When we run the test, we receive the message:
Handler did not return a response message.
And we never seem to get into DelegatingHandler.SendAsync method.
We have other classes calling APIs using HttpClient.PostAsync or GetAsync, and these do call the DelegatingHandler.SendAsync method and work as expected.
We've tried:
HttpClient httpClient = new HttpClient(fakeResponseHandler);
and
HttpClient httpClient = HttpClientFactory.Create(fakeResponseHandler);
We've also tried Client.SendAsync with and without the cancellation token.
Why is this not working?
Should we re-write this to use PostAsync?
I'd need to see the implementation of HttpClientFactory.Create and what Client.SendAsync actually does internally but nevertheless I was able to use the sample code you provide and fill in the blanks where I could to get the following to work:
public class FakeResponseHandler : DelegatingHandler
{
private readonly Dictionary<Uri, HttpResponseMessage> _fakeResponses = new Dictionary<Uri, HttpResponseMessage>();
public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage, string content = "", bool asXml = false)
{
if (!string.IsNullOrWhiteSpace(content))
{
if (asXml)
{
responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/xml");
}
else
{
responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
}
}
_fakeResponses.Add(uri, responseMessage);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var emptyContent = string.Empty;
if (request.Content.Headers.ContentType.MediaType == "application/xml")
emptyContent = "<empty />";
return Task.FromResult(_fakeResponses.ContainsKey(request.RequestUri) ?
_fakeResponses[request.RequestUri] :
new HttpResponseMessage(HttpStatusCode.NotFound)
{
RequestMessage = request,
Content = new StringContent(emptyContent)
});
}
}
Just to make things clean use Task.FromResult to return a task in SendAsync and also provide an empty content to avoid null reference exceptions.
public class CallingService
{
private readonly HttpClient _httpClient;
private readonly IConfigurationManager _configurationManager;
public CallingService(HttpClient httpClient,
IConfigurationManager configurationManager)
{
_httpClient = httpClient;
_configurationManager = configurationManager;
}
public async Task<XmlNodeList> TransmitToApi(string xml_string)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12;
//..
string type = "application/xml";
var content = new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes(xml_string)));
var targetUri = new Uri(_configurationManager.GetAppSetting("ApiUrl"));
var message = new HttpRequestMessage
{
RequestUri = targetUri,
Method = HttpMethod.Post,
Content = content
};
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
message.Content.Headers.Add("Content-Type", type);
string somedata;
try
{
// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
var response = await _httpClient.SendAsync(message, token);
somedata = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
throw ex;
}
//...
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(somedata);
return xmlDoc.SelectNodes("*");
}
}
And then the test passes the instance of HttpClient to CallingService:
[TestMethod]
public async Task TestMethod1()
{
const string content = #"<root><test>1243</test></root>";
const string httpExample = "http://example.org/test/";
var configurationManager = new Mock<IConfigurationManager>();
configurationManager
.Setup(cm => cm.GetAppSetting("ApiUrl"))
.Returns(httpExample);
var fakeResponseHandler = new FakeResponseHandler();
fakeResponseHandler.AddFakeResponse(new Uri(httpExample),
new HttpResponseMessage(HttpStatusCode.OK), content, true);
using (var httpClient = new HttpClient(fakeResponseHandler))
{
var ourService = new CallingService(httpClient, configurationManager.Object);
var result = await ourService.TransmitToApi(content);
Assert.AreEqual(content, result.Item(0)?.OuterXml);
}
}
This all works so if I had to guess - the issue would be somewhere in your HttpClientFacotry.
Hope that helps!! Cheers, :)
I am having difficulties to understand on how the bellow code could handle occasional internet connection loss. Ideally I would like to pause the app, once the connection is lost, and resume when it is up again. Is there any guideline on how to do it?
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.UseDefaultCredentials = true;
HttpClient client = new HttpClient(clientHandler) { MaxResponseContentBufferSize = 1000000 };
HttpResponseMessage response = await client.GetAsync(Url, ct);
The following example is not a direct solution, but it is an example I built to show how to return "pre-canned" content to requests whilst offline and then return back online when connectivity is restored. If you can get what I'm doing here, building what you want should be fairly easy.
[Fact]
public async Task Getting_a_response_when_offline()
{
var offlineHandler = new OfflineHandler(new HttpClientHandler(), new Uri("http://oak:1001/status"));
offlineHandler.AddOfflineResponse(new Uri("http://oak:1001/ServerNotRunning"),
new HttpResponseMessage(HttpStatusCode.NonAuthoritativeInformation)
{
Content = new StringContent("Here's an old copy of the information while we are offline.")
});
var httpClient = new HttpClient(offlineHandler);
var retry = true;
while (retry)
{
var response = await httpClient.GetAsync(new Uri("http://oak:1001/ServerNotRunning"));
if (response.StatusCode == HttpStatusCode.OK) retry = false;
Thread.Sleep(10000);
}
}
public class OfflineHandler : DelegatingHandler
{
private readonly Uri _statusMonitorUri;
private readonly Dictionary<Uri, HttpResponseMessage> _offlineResponses = new Dictionary<Uri, HttpResponseMessage>();
private bool _isOffline = false;
private Timer _timer;
public OfflineHandler(HttpMessageHandler innerHandler, Uri statusMonitorUri)
{
_statusMonitorUri = statusMonitorUri;
InnerHandler = innerHandler;
}
public void AddOfflineResponse(Uri uri, HttpResponseMessage response)
{
_offlineResponses.Add(uri,response);
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_isOffline == true) return OfflineResponse(request);
try
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.ServiceUnavailable || response.StatusCode == HttpStatusCode.BadGateway)
{
MonitorOfflineState();
return OfflineResponse(request);
}
return response;
}
catch (WebException ex)
{
MonitorOfflineState();
return OfflineResponse(request);
}
}
private void MonitorOfflineState()
{
_isOffline = true;
_timer = new Timer( async state =>
{
var request = new HttpRequestMessage() {RequestUri = _statusMonitorUri};
try
{
var response = await base.SendAsync(request, new CancellationToken());
if (response.StatusCode == HttpStatusCode.OK)
{
_isOffline = false;
_timer.Dispose();
}
}
catch
{
}
}, null, new TimeSpan(0,0,0),new TimeSpan(0,1,0));
}
private HttpResponseMessage OfflineResponse(HttpRequestMessage request)
{
if (_offlineResponses.ContainsKey(request.RequestUri))
{
return _offlineResponses[request.RequestUri];
}
return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
}
}
}