How to block caller thread after await operator - c#

I'm using new HttpClient class to upload some data to the server, so I was forced to use new operators like await/async. I have following issue: how can I block a caller thread to wait until first file will be uploaded and after that move to the next one.
public async Task Upload(string filename)
{
HttpRequestMessage message = new HttpRequestMessage();
StreamContent streamContent = new StreamContent(new FileStream(filename, FileMode.Open));
message.Method = HttpMethod.Put;
message.Content = streamContent;
message.RequestUri = new Uri(webURI);
HttpResponseMessage response = await httpClient.SendAsync(message);
//I want to reach this point untill I will start to upload next file
if (response.IsSuccessStatusCode)
{
//do something
}
}
void uploadFiles()
{
foreach (string filename in filenames)
{
Upload(filename);
}
}
Thanks for any attention about my problem.

await that the upload has finished before you upload the next file:
async void uploadFiles()
{
foreach (string filename in filenames)
{
await Upload(filename);
}
}

Related

Bad Request when trying POST in ASP .NET

I have quite simple system: ASP .NET Core server which is hosted on domain.ru. In API controller there I have 2 methods:
[HttpGet("{id}")]
public string Get(int id)
{
try
{
using (FileStream fstream = new FileStream(string.Format(#"{0}\data{1}.txt", _path, id.ToString()), FileMode.OpenOrCreate))
{
byte[] array = System.Text.Encoding.Default.GetBytes(id.ToString());
fstream.Write(array, 0, array.Length);
return "It's ok!";
}
}
catch
{
return "Something went wrong";
}
}
[HttpPost]
public string Post(string resolvedString)
{
try
{
using (FileStream fstream = new FileStream(string.Format(#"{0}\dataPost.txt", _path), FileMode.OpenOrCreate))
{
byte[] array = System.Text.Encoding.Default.GetBytes(resolvedString);
fstream.Write(array, 0, array.Length);
return "It's ok!";
}
}
catch
{
return "Something went wrong";
}
}
So basically both of them are just creating text files in the _path directory. The part that i can't understand is when I try to call Get method by url domain.ru/api/values/1 I can see the file which was created in _path directory and I have response "It's ok!". That's how I call Get:
var client = new HttpClient();
client.BaseAddress = new Uri(uri);
HttpResponseMessage response = await client.GetAsync("api/values/1");
string result = await response.Content.ReadAsStringAsync();
textBox1.Text = result.ToString();
But when I try the same with Post I get either Bad Request when I do it with C# or "Something went wrong" when I do it with Postman.
That's the way how I call Post
var client = new HttpClient();
client.BaseAddress = new Uri(uri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
MultipartFormDataContent content = new MultipartFormDataContent();
StringContent str = new StringContent("1");
content.Add(str);
HttpResponseMessage response = await client.PostAsync("api/values/", content);
string returnString = await response.Content.ReadAsStringAsync();
MessageBox.Show(returnString);
Here's what the request shows when I try to manually debug this
And the most fun part. When I try to do all the same actions when my server is hosted on IIS (localserver) it works just fine! I' really don't know what I am doing wrong. Please, help.
UPD. Thanks to Jonathan, I asked my hoster to disable ModSecurity in Plesk and the above code started to work after replacing [HttpPost] by [HttpPost("{resolvedString}")]. So far so good!
Then I tried to send a zip archive to the server. Here is the server's controller code:
[HttpPost]
public string ImportZip(IFormFile file)
{
DirectoryInfo dirInfo = new DirectoryInfo(_extractPath);
try
{
foreach (FileInfo myfile in dirInfo.GetFiles())
{
myfile.Delete();
}
string path = _path + "tmp.zip";
if (Request.HasFormContentType)
{
var form = Request.Form;
foreach (var formFile in form.Files)
{
using (var fileStream = new FileStream(path, FileMode.Create))
{
formFile.CopyTo(fileStream);
}
ZipFile.ExtractToDirectory(_path + "tmp.zip", _extractPath);
}
}
return "It's OK! At least we've entered the method.";
}
catch
{
return "Oh no no no...";
}
}
And that's how I call it from the client:
string filepath = _zipFile;
string filename = _fileName;
var client = new HttpClient();
client.BaseAddress = new Uri(uri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
MultipartFormDataContent content = new MultipartFormDataContent();
ByteArrayContent fileContent = new ByteArrayContent(File.ReadAllBytes(filepath));
content.Add(fileContent, "file", filename);
HttpResponseMessage response = await client.PostAsync("File/ImportZip/", content);
string result = await response.Content.ReadAsStringAsync();
textBox1.Text = result;
Once again, it works as it should when I run both server and client on my computer. I can see downloaded archive and extracted files in destination directories.
But when I upload my server to hosting and try to execute my query once again, I get the same error:
an example of an error
Well, seems like I found an answer myself. Will leave it here so it can help someone (maybe me in the future).
Code of the client's send method:
string uri = "https://example.com/controller/action/";
string zipFile = #"C:\Path\To\Your\File.txt";
string response;
using (WebClient client = new WebClient())
{
response = Encoding.Default.GetString(client.UploadFile(uri, zipFile));
}
MessageBox.Show(response);
Here we just composing a request and sending a file. The path and url are hardcoded for ex.
Code of the server's save method:
[HttpPost]
public string ImportZip(IFormFile file)
{
try
{
string path = _path + "tmp.zip";
if (Request.HasFormContentType)
{
var form = Request.Form;
foreach (var formFile in form.Files)
{
using (var fileStream = new FileStream(path, FileMode.Create))
{
formFile.CopyTo(fileStream);
}
}
return "Done";
}
return "Empty request";
}
catch
{
return "No access";
}
}
As long as I send only one file and also I know its extension and I want it to be called "tmp", I hardcode it's name and extension. You can take file's default name/extension to save it as is.
Then I save all the files in request into a chosen _path directory.
Basically, that's it.

use async /await with synchronous methods

How to make such piece of code run asynchronously with synchronous System.Xml.Serialization.XmlSerializer.Deserialize method which forces me to use Result all the way long?
...
await GetContent(url)
...
private async Task<Node> GetContent (string url)
{
var response = _httpClient.GetAsync(url).Result;
var ser = new XmlSerializer(typeof(Node));
retVal = (Node)ser.Deserialize(response.Content.ReadAsStreamAsync().Result);
}
The method is already async so there's no reason to use .Result. Just use await and remember to close the stream, otherwise the server connection remains open:
private async Task<Node> GetContent (string url)
{
var response = await _httpClient.GetAsync(url);
//**IMPORTANT** Ensure the stream is closed
using(var stream= await response.Content.ReadAsStreamAsync())
{
var ser = new XmlSerializer(typeof(Node));
var retVal = (Node)ser.Deserialize(stream);
return retVal;
}
}

Upload file to Pushbullet in Windows 10 app c#

I'm currently using Pushbullet API and need to upload a file.
I can successfully get an upload url as specified in the docs using this method:
public static async Task<Uploads> GetUploadUrl(string file_name, string file_type)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Access-Token", AccessToken);
var json = new JObject
{
["file_name"] = file_name,
["file_type"] = file_type
};
var result = await client.PostAsync(new Uri(_uploadUrl, UriKind.RelativeOrAbsolute), new HttpStringContent(json.ToString(), UnicodeEncoding.Utf8, "application/json"));
if (result.IsSuccessStatusCode)
{
var textresult = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Uploads>(textresult);
}
}
return null;
}
The problem is when I try to upload the file. I'm currently using this method:
public static async Task<bool> UploadFile(StorageFile file, string upload_url)
{
try
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
var content = new MultipartFormDataContent();
if (file != null)
{
var streamData = await file.OpenReadAsync();
var bytes = new byte[streamData.Size];
using (var dataReader = new DataReader(streamData))
{
await dataReader.LoadAsync((uint)streamData.Size);
dataReader.ReadBytes(bytes);
}
var streamContent = new ByteArrayContent(bytes);
content.Add(streamContent);
}
client.DefaultRequestHeaders.Add("Access-Token", AccessToken);
var response = await client.PostAsync(new Uri(upload_url, UriKind.Absolute), content);
if (response.IsSuccessStatusCode)
return true;
}
catch { return false; }
return false;
}
but I get a Http 400 error. What's the right way to upload a file using multipart/form-data in a UWP app?
HTTP 400 error indicates Bad Request, it means the request could not be understood by the server due to malformed syntax. In the other word, the request sent by the client doesn't follow server's rules.
Let's look at the document, and we can find in the example request it uses following parameter:
-F file=#cat.jpg
So in the request, we need to set the name for the uploaded file and the name should be "file". Besides, in this request, there is no need to use access token. So you can change your code like following:
public static async Task<bool> UploadFile(StorageFile file, string upload_url)
{
try
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
var content = new MultipartFormDataContent();
if (file != null)
{
var streamData = await file.OpenReadAsync();
var bytes = new byte[streamData.Size];
using (var dataReader = new DataReader(streamData))
{
await dataReader.LoadAsync((uint)streamData.Size);
dataReader.ReadBytes(bytes);
}
var streamContent = new ByteArrayContent(bytes);
content.Add(streamContent, "file");
}
//client.DefaultRequestHeaders.Add("Access-Token", AccessToken);
var response = await client.PostAsync(new Uri(upload_url, UriKind.Absolute), content);
if (response.IsSuccessStatusCode)
return true;
}
catch { return false; }
return false;
}
Then your code should be able to work. You will get a 204 No Content response and UploadFile method will return true.

WriteToStreamAsync cancel does not work

I am running a Task, which copies from one stream to another. This works without problems, including progress reporting. But i cant cancel the task. If i fire the CancellationToken, the copy progress runs till its completion, then the task is cancelled, but this is of course to late. Here is my actual code
private async Task Download(Uri uriToWork, CancellationToken cts)
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage requestAction = new HttpRequestMessage();
requestAction.Method = new HttpMethod("GET");
requestAction.RequestUri = uriToWork;
HttpResponseMessage httpResponseContent = await httpClient.SendRequestAsync(requestAction, HttpCompletionOption.ResponseHeadersRead);
using (Stream streamToRead = (await httpResponseContent.Content.ReadAsInputStreamAsync()).AsStreamForRead())
{
string fileToWrite = Path.GetTempFileName();
using (Stream streamToWrite = File.Open(fileToWrite, FileMode.Create))
{
await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, progressDownload);
await streamToWrite.FlushAsync();
//streamToWrite.Dispose();
}
await streamToRead.FlushAsync();
//streamToRead.Dispose();
}
httpClient.Dispose();
}
Can someone help me please, or can explain, why it does not work?
Is it this operation that continues until it completes ?
await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, progressDownload);
Or is it this one ?
await streamToWrite.FlushAsync();
I think the latter needs probably to have the CancellationToken as well:
await streamToWrite.FlushAsync(cts);
Unfortunately I cannot answer why this cancel does not occur. However, a solution that consists in writing the Stream in chunks may help.
Here is something very dirty that works:
private async Task Download(Uri uriToWork, CancellationToken cts) {
using(HttpClient httpClient = new HttpClient()) {
HttpRequestMessage requestAction = new HttpRequestMessage();
requestAction.Method = new HttpMethod("GET");
requestAction.RequestUri = uriToWork;
HttpResponseMessage httpResponseContent = await httpClient.SendRequestAsync(requestAction, HttpCompletionOption.ResponseHeadersRead);
string fileToWrite = Path.GetTempFileName();
using(Stream streamToWrite = File.Open(fileToWrite, FileMode.Create)) {
// Disposes streamToWrite to force any write operation to fail
cts.Register(() => streamToWrite.Dispose());
try {
await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, p);
}
catch(TaskCanceledException) {
return; // "gracefully" exit when the token is cancelled
}
await streamToWrite.FlushAsync();
}
}
}
I enclosed the httpClient in a using so a return disposes it properly.
I removed the streamToRead which was not used at all
Now here is the horror: I added a delegate that executes when the token is cancelled: it disposes streamToWrite while it is written to (ughhhh), which triggers an TaskCancelledException when WriteToStreamAsync cannot longer write in this disposed stream.
Please dont throw a puke bag at me yet, I am not experienced enough in this "Universal" Framework which looks very different as the usual one.
Here is a chunked stream solution that looks more acceptable. I shortened a bit the original code and added the IProgress as a parameter.
async Task Download(Uri uriToWork, CancellationToken cts, IProgress<int> progress) {
using(HttpClient httpClient = new HttpClient()) {
var chunkSize = 1024;
var buffer = new byte[chunkSize];
int count = 0;
string fileToWrite = Path.GetTempFileName();
using(var inputStream = await httpClient.GetInputStreamAsync(uriToWork)) {
using(var streamToRead = inputStream.AsStreamForRead()) {
using(Stream streamToWrite = File.OpenWrite(fileToWrite)) {
int size;
while((size = await streamToRead.ReadAsync(buffer, 0, chunkSize, cts).ConfigureAwait(false)) > 0) {
count += size;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => progress.Report(count));
// progress.Report(count);
await streamToWrite.WriteAsync(buffer, 0, size, cts).ConfigureAwait(false);
}
}
}
}
}
}
The blocking operation is most probably not WriteToStreamAsync() but FlushAsync(), so #Larry's assumption should be right, the FlushAsync method needs the cancellation token as well.

ASYNC error on WebApi Download

I have the following code, used to download multiple files, create a zip file, and return the file to the user:
//In a WebAPI GET Handler
public async Task<HttpResponseMessage> Get(string id)
{
try
{
var urlList = CacheDictionary<String, List<String>>.Instance[id];
var helper = new Helper();
var zipFile = await helper.CreateZipFormUrls(urlList);
var response = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new MemoryStream();
zipFile.Save(stream);
response.Content = new ByteArrayContent(stream.ToArray());
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
response.Content.Headers.ContentLength = stream.Length;
response.Content.Headers.ContentDisposition.FileName = "download.zip";
return response;
}
catch (Exception)
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
//In a Helper Class
public async Task<ZipFile> CreateZipFromUrls(List<string> urlList)
{
using (var zip = new ZipFile())
{
var files = await ReturnFileData(urlList);
foreach (var file in files)
{
var e = zip.AddEntry(GetFileNameFromUrlString(file.Key), file.Value);
}
return zip;
}
}
static Task<Dictionary<string, byte[]>> ReturnFileData(IEnumerable<string> urls)
{
Dictionary<Uri, Task<byte[]>> dictionary;
using (var client = new WebClient())
{
dictionary = urls.Select(url => new Uri(url)).ToDictionary(
uri => uri, uri => client.DownloadDataTaskAsync(uri));
await Task.WhenAll(dictionary.Values);
}
return dictionary.ToDictionary(pair => Path.GetFileName(pair.Key.ToString()),
pair => pair.Value.Result);
}
private string GetFileNameFromUrlString(string url)
{
var uri = new Uri(url);
return System.IO.Path.GetFileName(uri.LocalPath);
}
I always get:
An asynchronous module or handler completed while an asynchronous operation was still pending
And cannot reach any breakpoints after the download method is called. What am I doing wrong? Where do I look?
Try await on this
dictionary = urls.Select(url => new Uri(url)).ToDictionary(
uri => uri, uri => client.DownloadDataTaskAsync(uri));
The problem might be that
client.DownloadDataTaskAsync(uri));
is probably still running when the rest of your code is done.

Categories