Cancel HttpClient request on connection lost - c#

I have the following method, on a windows-store project, to upload a file
public async Task<Boolean> UploadFileStreamService(Stream binaries, String fileName, String filePath)
{
try
{
filePath = Uri.EscapeDataString(filePath);
using (var httpClient = new HttpClient { BaseAddress = Constants.baseAddress })
{
var content = new StreamContent(binaries);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", App.Current.Resources["token"] as string);
App.Current.Resources["TaskUpload"] = true;
using (var response = await httpClient.PostAsync("file?fileName=" + filePath, content))
{
string responseData = await response.Content.ReadAsStringAsync();
if (responseData.Contains("errorCode"))
throw new Exception("Exception: " + responseData);
else
{
JsonObject jObj = new JsonObject();
JsonObject.TryParse(responseData, out jObj);
if (jObj.ContainsKey("fileId"))
{
if (jObj["fileId"].ValueType != JsonValueType.Null)
{
App.Current.Resources["NewVersionDoc"] = jObj["fileId"].GetString();
}
}
}
return true;
}
}
}
catch (Exception e)
{
...
}
}
And on the app.xaml.cs i have on the constructor:
NetworkInformation.NetworkStatusChanged +=
NetworkInformation_NetworkStatusChanged; // Listen to connectivity changes
And on that method i check for the connection changes.
What i would like to know is how to stop a upload task when i detect that network change ( from having internet to not having).

You can use cancellation tokens. You need CancellationTokenSource:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
Then pass token to your UploadFileStreamService method (use _cts.Token to get token):
public async Task<Boolean> UploadFileStreamService(Stream binaries, String fileName, String filePath, CancellationToken ct)
And use another overload of PostAsync which accepts token (note - also use overloads that accept tokens for all other async methods where possible, for example for ReadAsStringAsync):
using (var response = await httpClient.PostAsync("file?fileName=" + filePath, content, ct))
Then when you found network connection is lost, cancel with:
_cts.Cancel();
Note that this will throw OperationCancelledException on PostAsync call, which you may (or may not) want to handle somehow.

Related

Does these HttpClient call be called in parallel?

I've a .NET Core web app that, once a web method (i.e. Test()) is invoked, call another remote api.
Basically, I do it this way (here's a POST example, called within a web method Test()):
public T PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
T returnValue = default(T);
succeded = false;
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
HttpResponseMessage res = null;
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var task = Task.Run(() => client.PostAsync($"{baseAddress}/{url}", body));
task.Wait();
res = task.Result;
succeded = res.IsSuccessStatusCode;
if (succeded)
{
returnValue = JsonConvert.DeserializeObject<T>(res.Content.ReadAsStringAsync().Result);
}
else
{
Log($"PostRead failed, error: {JsonConvert.SerializeObject(res)}");
}
}
}
catch (Exception ex)
{
Log($"PostRead error: {baseAddress}/{url} entity: {JsonConvert.SerializeObject(entity)}");
}
return returnValue;
}
Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?
Because if they will be called in serial, the last one will take the time of the previous ones, and that's not what I want.
In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?
They will be called in parallel. Nothing in the code would make it in a blocking way.
In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
There are at least 2 things that need to be fixed here:
Properly using async/await
Properly using HttpClient
The first one is easy:
public async Task<T> PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
succeded = false;
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var responseMessage = await client.PostAsync($"{baseAddress}/{url}", body);
var responseContent = await responseMessage.Content.ReadAsStringAsync();
if (responseMessage.IsSuccessStatusCode)
{
succeeded = true;
return JsonConvert.DeserializeObject<T>();
}
else
{
// log...
}
}
}
catch (Exception ex)
{
// log...
}
return default; // or default(T) depending on the c# version
}
The second one is a bit more tricky and can quite possibly be the cause of the problems you're seeing. Generally, unless you have some very specific scenario, new HttpClient() is plain wrong. It wastes resources, hides dependencies and prevents mocking. The correct way to do this is by using IHttpClientFactory, which can be very easily abstracted by using the AddHttpClient extension methods when registering the service.
Basically, this would look like this:
services.AddHttpClient<YourClassName>(x =>
{
x.BaseAddress = new Uri(baseAddress);
x.DefaultRequestHeaders.Add("Authorization", MyApiKey);
}
Then it's a matter of getting rid of all using HttpClient in the class and just:
private readonly HttpClient _client;
public MyClassName(HttpClient client) { _client = client; }
public async Task<T> PostRead<T>(string url, out bool succeded, object entity = null)
{
succeded = false;
try
{
string json = JsonConvert.SerializeObject(entity);
var responseMessage = await _client.PostAsync($"{baseAddress}/{url}", body);
//...
}
catch (Exception ex)
{
// log...
}
return default; // or default(T) depending on the c# version
}
[HttpClient call using parallel and solve JSON Parse error]
You can use task. when for parallel call . but when you use N number of calls and try to parse as JSON this may throw an error because result concatenation change the Json format so we can use Jarray. merge for combining the result .
Code
public async Task < string > (string baseAddress, string url, out bool succeded, object entity = null) {
using(HttpClient client = new HttpClient()) {
string responseContent = string.Empty;
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var tasks = new List < Task < string >> ();
var jArrayResponse = new JArray();
int count = 10; // number of call required
for (int i = 0; i <= count; i++) {
async Task < string > RemoteCall() {
var response = await client.PostAsync($"{baseAddress}/{url}", body);
return await response.Content.ReadAsStringAsync();
}
tasks.Add(RemoteCall());
}
await Task.WhenAll(tasks);
foreach(var task in tasks) {
string postResponse = await task;
if (postResponse != "[]") {
var userJArray = JArray.Parse(postResponse);
jArrayResponse.Merge(userJArray);
}
}
responseContent = jArrayResponse.ToString();
return responseContent;
}
}

Web API MultipartAsync Task no result

The goal is to upload a single file to my webserver and then store it to mssql database by using multipartcontent. While the file is uploading, a progress bar should be displayed in the client (WPF application). The following code sample shows only the upload to memorystream (no database connection).
The connection from client to server works, the upload to the MemoryStream at server-side works and receiving receiving the percentage to client side works (section ContinueWith in my sample code). The problem is, the client doesn't receive the final CreateResponse request - like a timeout or lost connection, I am not sure because i doesn't get an error/exception. The client never receives the task final result.
WebApi:
public class AttachmentsController : ApiController
{
[HttpPost]
[Route("api/Attachments/Upload")]
public async Task<HttpResponseMessage> Upload()
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
try
{
AttachmentUpload _resultAttachmentUpload = null;
var _provider = new InMemoryMultipartFormDataStreamProvider();
await Request.Content.ReadAsMultipartAsync(_provider)
.ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
throw new HttpResponseException(
HttpStatusCode.InternalServerError);
}
return new AttachmentUpload()
{
FileName = _provider.Files[0].Headers.ContentDisposition
.FileName.Trim('\"'),
Size = _provider.Files[0].ReadAsStringAsync().Result
.Length / 1024
};
});
return Request.CreateResponse<AttachmentUpload>(HttpStatusCode.Accepted,
_resultAttachmentUpload);
}
catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError,
e.ToString());
}
}
}
WPF Client UploadService.cs:
private async Task<Attachment> UploadAttachment(AttachmentUpload uploadData,
string filePath)
{
try
{
var _encoding = Encoding.UTF8;
MultipartFormDataContent _multipartFormDataContent =
new MultipartFormDataContent();
_multipartFormDataContent.Add(new StreamContent(new MemoryStream(
File.ReadAllBytes(filePath))), uploadData.FileName, uploadData.FileName);
_multipartFormDataContent.Add(new StringContent(uploadData.Id.ToString()),
"AttachmentId", "AttachmentId");
_multipartFormDataContent.Add(new StringContent(
uploadData.Upload.ToString(CultureInfo.CurrentCulture)), "AttachmentUpload",
"AttachmentUpload");
_multipartFormDataContent.Add(new StringContent(
uploadData.DocumentId.ToString()), "DocumentId", "DocumentId");
_multipartFormDataContent.Add(new StringContent(
uploadData.User, _encoding), "User", "User");
//ProgressMessageHandler is instantiate in ctor to show progressbar
var _client = new HttpClient(ProgressMessageHandler);
_client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("multipart/form-data"));
_client.Timeout = TimeSpan.FromMinutes(5);
var _requestUri = new Uri(BaseAddress + "api/Attachments/Upload");
var _httpRequestTask = await _client.PostAsync(_requestUri,
_multipartFormDataContent)
.ContinueWith<AttachmentUpload>(request =>
{
var _httpResponse = request.Result;
if (!_httpResponse.IsSuccessStatusCode)
{
throw new Exception();
}
var _response = _httpResponse.Content.ReadAsStringAsync();
AttachmentUpload _upload =
JsonConvert.DeserializeObject<AttachmentUpload>(_response.Result);
return _upload;
});
var _resultAttachment = _httpRequestTask;
return new Attachment()
{
Id = _resultAttachment.Id,
FileName = _resultAttachment.FileName,
Comment = _resultAttachment.Comment,
Upload = _resultAttachment.Upload,
};
}
catch (Exception e)
{
//Handle exceptions
//file not found, access denied, no internet connection etc etc
var tmp = e;
throw e;
}
}
The program lacks at var _httpRequestTask = await _client.PostAsync(...).
The debugger never reaches the line var _resultAttachment = _httpRequestTask;.
Thank you very much for your help.
First of all, don't mix await and ContinueWith, the introduction of async / await effectively renders ContinueWith obsolete.
The reason that var _resultAttachment = _httpRequestTask; is never hit is because you have created a deadlock.
WPF has a synchronisation context, that ensures continuations resume on the UI thread.
In the line AttachmentUpload _upload = JsonConvert.DeserializeObject<AttachmentUpload>(_response.Result);, _response.Result is a blocking call; it blocks the current thread, until the Task referenced by _response has completed.
The ReadAsStringAsync method, which generated the Task, will attempt to resume once the asynchronous work has completed, and the WPF synchronisation context will force it to use the UI thread, which has been blocked by _response.Result, hence the deadlock.
To remedy this, use the await keyword for every asynchronous call:
var _httpResponse = await _client.PostAsync(_requestUri, _multipartFormDataContent);
if (!_httpResponse.IsSuccessStatusCode)
{
throw new Exception();
}
var _response = await _httpResponse.Content.ReadAsStringAsync();
var _resultAttachment = JsonConvert.DeserializeObject<AttachmentUpload>(_response);
You should also notice that the code is much more readable without the ContinueWith.

Null message on hTTPResponse.Content.ReadAsStringAsync().Result after 1st foreach loop

I have issue with null result messages when calling a HttpClient getAsync within a foreach loop.
I need to iterate with a list of objects for values to call an API via HttpClient. After the first loop, the HttpResponseMessage's result() comes up with null message.
I tried CacheControl.NoCache = true. It's not working.
public async Task<List<Common.ProgressResponse>> RunAsync()
{
List<Response> ListOfResponses = new List<Responses>();
try
{
_client.BaseAddress = new Uri([URL]);
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true };
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add([KEY], [VALUE]);
ListOfResponses = await SomeFunction();
}
catch (Exception ex)
{
//Exceptions here...
}
return ListOfResponses;
}
private async Task<List<ListOfResponses>> SomeFunction()
{
List<Response> responses = new List<Response>();
string aPISuffix = string.Format("{0}/{1}", [APISUFFIX1], [APISUFFIX2]);
foreach (Object obj in ListOfObject)
{
_client.DefaultRequestHeaders.Add("Param1", obj.Param1);
if (!string.IsNullOrEmpty(obj.Param2))
_client.DefaultRequestHeaders.Add("Param2", obj.Param2);
Response response = new Response();
/*This is where the issue is .begin.*/
HttpResponseMessage hTTPResponse = await _client.GetAsync(aPISuffix).ConfigureAwait(false);
string result = hTTPResponse.Content.ReadAsStringAsync().Result;
/*This is where the issue is .end.*/
if (hTTPResponse.IsSuccessStatusCode)
{
response = [Code here...]
//Codes here...
}
responses.Add(response);
}
return responses;
}
On: 'string result = hTTPResponse.Content.ReadAsStringAsync().Result;'
I would expect the result message would have values as it loops through ListOfObjects.
Update: Per comment
First off, I generally try to avoid using .Result with tasks. I also don't think adding the cache headers is the issue since you are making a server to server call.
One thing I tend to always do is evaluate the response before I try to read it. I also prefer to use the HttpRequestMessage so I can manage the disposal of it.
Here is a quick example demonstrating how call out:
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Testing
{
class Program
{
static void Main(string[] args)
{
TestAsync().Wait();
}
static async Task TestAsync()
{
var urls = new string[]
{
"https://stackoverflow.com/questions/57084989/null-message-on-httpresponse-content-readasstringasync-result-after-1st-foreac",
"https://stackoverflow.com/users/2025711/rogala"
};
using (var httpClient = new HttpClient())
{
foreach (var url in urls)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
Console.WriteLine($"Request: {url}".PadLeft(5,'*').PadRight(5, '*'));
var response = await httpClient.SendAsync(request)
.ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
Console.WriteLine($"{body.Length}{Environment.NewLine}");
}
else
{
Console.WriteLine($"*Bad request: {response.StatusCode}");
}
}
}
}
Console.ReadKey();
}
}
}
One additional thing to keep in mind is socket exhaustion. If this code is running on a server, then you will run into socket exhaustion once your service gets some load. I would highly recommend using an HttpClientFactory to handle the HttpClients.
With respect to why the content string is empty, it could be because the server didn't return content which could be a server issue. The server may have also throttled you, which resulted in no content. I would recommend looking into Polly to handle certain response codes.
Solved it... I moved the .DefaultRequestHeaders values inside the foreach() loop.
I suspected that they were pilling up at every loop iteration. I had to clear them and set them up before the GetAsync()
Updated code here:
public async Task<List<Common.ProgressResponse>> RunAsync()
{
List<Response> ListOfResponses = new List<Responses>();
try
{
_client.BaseAddress = new Uri([URL]);
ListOfResponses = await SomeFunction();
}
catch (Exception ex)
{
//Exceptions here...
}
return ListOfResponses;
}
private async Task<List<ListOfResponses>> SomeFunction()
{
List<Response> responses = new List<Response>();
string aPISuffix = string.Format("{0}/{1}", [APISUFFIX1], [APISUFFIX2]);
foreach (Object obj in ListOfObject)
{
/*Moved Code .begin.*/
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true };
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add([KEY], [VALUE]);
/*Moved Code .end.*/
_client.DefaultRequestHeaders.Add("Param1", obj.Param1);
if (!string.IsNullOrEmpty(obj.Param2))
_client.DefaultRequestHeaders.Add("Param2", obj.Param2);
Response response = new Response();
HttpResponseMessage hTTPResponse = await _client.GetAsync(aPISuffix).ConfigureAwait(false);
if (hTTPResponse.IsSuccessStatusCode)
{
string result = hTTPResponse.Content.ReadAsStringAsync().Result;
response = [Code here...]
//Codes here...
}
responses.Add(response);
}
return responses;
}

call REST API in xamarin

I design a login form in xamarin Form PCL and I want to call a my webservice that will return JSON. For this, I created two functions for the same but both are n is not returning values.
Can you please tell me what I am doing wrong?
async void OnLoginButtonClick(object sender, EventArgs e)
{
if (usernameEntry.Text != "" && passwordEntry.Text != "")
{
var response = GetLoginDetails(usernameEntry.Text,passwordEntry.Text);
var getData = await getDataFromService(usernameEntry.Text, passwordEntry.Text);
}
}
public static async Task<HttpResponseMessage> GetLoginDetails(string username, string password)
{
try
{
var httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://mywebserverIP/api/Users?Username=" + username + "&Password=" + password);
var response = await httpClient.SendAsync(request);
return response;
}
catch (Exception ex)
{
throw;
}
}
public static async Task<dynamic> getDataFromService(string username, string password)
{
using (var client = new HttpClient())
{
var responseText = await client.GetStringAsync("http://mywebserverIP/api/Users?Username=" + username + "&Password=" + password);
dynamic data = JsonConvert.DeserializeObject(responseText);
return data;
}
}
Thanks for your comment in Advance.
As you not awaiting the first method , request thread will not wait till it returns the value so , 1st change you have to make is to
var response = await GetLoginDetails()
For the second method
var getData = await getDataFromService()
I do not see any issue. I am not sure how you know that this method is not returning any values. Better to log the response of the both the method call and check.
Use await.
First in the
var response = await GetLoginDetails(...
then maybe in the deserializeobject method too (this one i'm not sure)
dynamic data = await Task.Run(() => JsonConvert.DeserializeObject(responseText));

Task method returns TaskCanceledException was caught

I am trying to make a call to a service, the service is supposed to return all the records but i am getting a TaskCancelledException with a message A Task was cancelled and then the results are null. I know there is lots of data (tried with small data and works fine). How can I make it so it continues working (keep grabbing records) without throwing the "A Task was cancelled". Here is my method
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
Result<string> result = new Result<string>();
try
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
HttpResponseMessage response = await httpClient.SendAsync(request); //HttpClient
string jsonresult = await HandleReceipt(response, type);
result.Result = jsonresult;
}
catch (Exception ex)
{
result.Message = "Problem obtaining data.";
}
return result;
}
I see there is a cancellation token being passed, is there a way to handle the cancellation?. Also, this method is being called from here:
public async void GetJsonData()
{
string json = await ObtainData();
}
I would like it to complete getting the request without cancelling.
It sounds from your question and comments that your request is timing out. Try setting the HttpClient.Timeout property
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
Result<string> result = new Result<string>();
try
{
var request = (HttpWebRequest) WebRequest.Create(url);
request.Method = "GET";
HttpResponseMessage response = await httpClient.SendAsync(request); //HttpClient
httpClient.TimeOut = TimeSpan.MaxValue // change this as you like
string jsonresult = await HandleReceipt(response, type);
result.Result = jsonresult;
}
catch (Exception ex)
{
result.Message = "Problem obtaining data.";
}
return result;
}
Edit
Regarding you comment about the exception, this is from MSDN:
The Timeout property must be set before the GetRequestStream or GetResponse method is called.
Create a new instance of HttpClient before making the request, or set it inside the code where you initialize the client (constructor i assume).
As Yuval Itzchakov mentioned you should set the Timeout property to a larger value (but it is a TimeSpan not an integer) and you should use the cancellation token in the HTTP request call if you are using with the async pattern.
And there is no need to use a global HttpClient (as Yuval pointed out) esspecially in this heavy async method calling nightmare (additionally i don't see the reason why should call anything inside the ObtainData async).
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
try
{
using (var httpClient = new HttpClient
{
Timeout = TimeSpan.MaxValue
})
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
using (var response = await httpClient.SendAsync(request, ct))
{
return new ActionResult<string>()
{
Result = await HandleReceipt(response, type)
};
}
}
}
}
catch (Exception ex)
{
return new ActionResult<string>
{
Message = "Problem obtaining data. " + ex
};
}
}

Categories