Web API MultipartAsync Task no result - c#

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.

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;
}
}

Cancel HttpClient request on connection lost

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.

How get upload progress?

Sorry for my english. I have a method in which I send the StorageFile to the server. I tried using Windows.Web.Http.HttpClient, but does not work ( getting an invalid response from the server ) , so I use System.Net.Http.HttpClient.
Here is my code :
public static async void Upload(string uri, StorageFile data, Action<double> progressCallback = null)
{
try
{
byte[] fileBytes =await ReadFile(data);
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
MultipartContent content = new System.Net.Http.MultipartFormDataContent();
var file1 = new ByteArrayContent(fileBytes);
file1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "file1",
FileName = data.Name,
};
content.Add(file1);
System.Net.Http.HttpResponseMessage response = await client.PostAsync(uri, content);
response.EnsureSuccessStatusCode();
var raw_response = await response.Content.ReadAsByteArrayAsync();
var r2 = Encoding.UTF8.GetString(raw_response, 0, raw_response.Length);
if (r2[0] == '\uFEFF')
{
r2 = r2.Substring(1);
}
Logger.Info(r2);
}
catch (Exception exc)
{
Logger.Error( exc);
}
}
Whether it is possible to change the code to receive progress about downloading a file in callback function?
On Windows Runtime you can try to switch to Windows.Web.HttpClient class. Its PostAsync method returns IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> interface. This interface has Progress event to which you can simply subscribe before awaiting the result.
Simplest way to upload file with progress
I had the same issue, and after some tries found out that you can easily get byte-accurate progress by tracking the Position of the FileStream of the file that you are going to upload.
This is a sample code that shows that.
FileStream fileToUpload = File.OpenRead(#"C:\test.mp3");
HttpContent content = new StreamContent(fileToUpload);
HttpRequestMessage msg = new HttpRequestMessage{
Content=content,
RequestUri = new Uri(--yourUploadURL--)
}
bool keepTracking = true; //to start and stop the tracking thread
new Task(new Action(() => { progressTracker(fileToUpload, ref keepTracking); })).Start();
var result = httpClient.SendAsync(msg).Result;
keepTracking = false; //stops the tracking thread
And define progressTracker() as
void progressTracker(FileStream streamToTrack, ref bool keepTracking)
{
int prevPos = -1;
while (keepTracking)
{
int pos = (int)Math.Round(100 * (streamToTrack.Position / (double)streamToTrack.Length));
if (pos != prevPos)
{
Console.WriteLine(pos + "%");
}
prevPos = pos;
Thread.Sleep(100); //only update progress every 100ms
}
}
And this solved my problem.

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
};
}
}

await httpClient.SendAsync(httpContent) is non responsive

await httpClient.SendAsync(httpContent) is not responding though I found no error in code/url its still getting hang. Please suggest/help.
My code as follows:
public async Task<string> Get_API_Result_String(string url, List<KeyValuePair<string, string>> parameters)
{
string res = "";
try
{
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
//Prepare url
Uri mainurl = new Uri(settings[FSAPARAM.UserSettingsParam.SERVERNAME].ToString());
Uri requesturl = new Uri(mainurl, url);
var httpClient = new HttpClient();
var httpContent = new HttpRequestMessage(HttpMethod.Post, requesturl);
// httpContent.Headers.ExpectContinue = false;
httpContent.Content = new FormUrlEncodedContent(parameters);
HttpResponseMessage response = await httpClient.SendAsync(httpContent);
var result = await response.Content.ReadAsStringAsync();
res = result.ToString();
response.Dispose();
httpClient.Dispose();
httpContent.Dispose();
}
catch (Exception ex)
{
Logger l = new Logger();
l.LogInfo("Get_API_Result_String: "+ url + ex.Message.ToString());
ex = null;
l = null;
}
return res;
}
Calling it in another class as follows:
NetUtil u = new NetUtil();
string result = await u.Get_API_Result_String(Register_API, values);
u = null;
I predict that further up your call stack, you are calling Wait or Result on a returned Task. This will cause a deadlock that I explain fully on my blog.
To summarize, await will capture a context and use that to resume the async method; on a UI application this is a UI thread. However, if the UI thread is blocked (in a call to Wait or Result), then that thread is not available to resume the async method.
this worked for me:
httpClient.SendAsync(httpContent).ConfigureAwait(false);
I just removed await and just used as below and it worked:
var result = httpClient.SendAsync(httpContent).Result;
But that is not a good practice.
As
Nikola mentioned, we shouldn't mix sync and async calls.
I changed the calling method to async and problem got resolved.
This is OK on mine
var response = httpClient.SendAsync(request);
var responseResult = response.Result;
if (responseResult.IsSuccessStatusCode)
{
var result = responseResult.Content.ReadAsStringAsync().Result;
return result;
}
Just got this bug when returning compressed gzip data. It's a very rare case as 99% times with different input data everything is fine. Had to switch to HttpWebRequest.

Categories