Get the value of download progress when using HttpClient in WPF [duplicate] - c#

This question already has answers here:
Stream.CopyToAsync with progress reporting - progress is reported even after copying finish
(2 answers)
Progress bar with HttpClient
(11 answers)
Closed last month.
How would you get the current progress as a interger for example when downloading a file?
I've been trying to implementing it into the code below with no luck at all
public static async Task HttpDownload(string url, string OutputLocation)
{
using (HttpClient client = new HttpClient())
using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
{
var contentLength = response.Content.Headers.ContentLength;
using (Stream streamToReadFrom = await response.Content.ReadAsStreamAsync())
{
string fileToWriteTo = OutputLocation;
using (Stream streamToWriteTo = File.Open(fileToWriteTo, FileMode.Create))
{
await streamToReadFrom.CopyToAsync(streamToWriteTo);
}
}
}
}
I've tried things like Progress<T>, but it i could never covert it to a usage value
(Edit, I found something thats actually works for me, Progress bar with HttpClient)

I wrote my own method to achieve this. Its complettely stream based. So almost no load on your system memory. But feel free to test it out.
public static async Task<T> DownloadAsync<T>(
this HttpClient client,
Uri requestUri,
Func<HttpResponseMessage, T> destinationStreamFactory,
IProgress<HttpDownloadProgress> progress = null,
CancellationToken cancellationToken = default)
where T : Stream
{
// Get the http headers first to examine the content length
using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
var contentLength = response.Content.Headers.ContentLength;
using (var download = await response.Content.ReadAsStreamAsync())
{
var destinationStream = destinationStreamFactory(response);
// Ignore progress reporting when no progress reporter was
// passed or when the content length is unknown
if (progress == null)
{
await download.CopyToAsync(destinationStream, 81920, cancellationToken);
return destinationStream;
}
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
var relativeProgress = new Progress<long>(totalBytes => progress.Report(
new HttpDownloadProgress()
{
ContentLength = contentLength ?? 0,
RelativeProgress = contentLength.HasValue ? Convert.ToInt32(100 * (float)totalBytes / contentLength.Value) : 0,
ReadBytes = totalBytes
}));
// Use extension method to report progress while downloading
await download.CopyToAsync(destinationStream, 81920, relativeProgress, cancellationToken);
progress.Report(new HttpDownloadProgress()
{
ContentLength = contentLength ?? 0,
RelativeProgress = 1,
ReadBytes = contentLength ?? 0
});
return destinationStream;
}
}
Example:
var downloadProgress = new Progress<HttpDownloadProgress>(
f =>
{
var readMbs = f.ReadBytes / 1024 / 1024;
var contentMbs = f.ContentLength / 1024 / 1024;
Progress = f.RelativeProgress;
Message = $"Downloading {pluginVersion.PluginName} ({readMbs}MB/{contentMbs}MB)";
});
using (var fileStream = await client.DownloadAsync(
serviceEndPoint,
response => CreateFileStream(response, outputPath),
downloadProgress,
token))
{
Console.WriteLine($"File name {fileStream.Name}";
}
with the FileStreamFactory, which creates the output folder and a filestream with the extracted file name of the httpreponse:
private FileStream CreateFileStream(HttpResponseMessage response, string outputPath)
{
// get the actual content stream
var fileName = response.Content.Headers.ContentDisposition?.FileName;
if (fileName == null)
{
throw new InvalidOperationException("No filename available");
}
var outputFile = Path.Combine(outputPath, fileName);
var folder = Directory.GetParent(outputFile)?.FullName;
if (folder != null && !Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
else if (File.Exists(outputFile))
{
File.Delete(outputFile);
}
Console.WriteLine($"Downloading file to '{outputFile} ...");
return File.Open(outputFile, FileMode.Create);
}
public class HttpDownloadProgress
{
public int RelativeProgress { get; set; }
public long ContentLength { get; set; }
public long ReadBytes { get; set; }
}

Related

Web API service hangs on reading the stream

Description: I am modifying the ASP.NET Core Web API service (hosted in Windows Service) that supports resumable file uploads. This works fine and resumes file uploads in many failure conditions except one described below.
Problem: When the service is on ther other computer and the client is on mine and I unplug the cable on my computer, the client detects the absence of network while the service hangs on fileSection.FileStream.Read(). Sometimes the service detects the failure in 8 min, sometimes in 20, sometimes never.
I also noticed that after I unplug cable and stop the client, the service becomes stuck at Read() function and the file size is x KB, but when the service finally detects the exception some time later, it writes additional 4 KB to the file. This is weird because I turned off buffering and the buffer size is 2 KB.
Question: How to properly detect the absence of network on the service, or timeout properly, or cancel the request
The service code:
public static async Task<List<(Guid, string)>> StreamFileAsync(
this HttpRequest request, DeviceId deviceId, FileTransferInfo transferInfo)
{
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType), DefaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var section = await reader.ReadNextSectionAsync(_cancellationToken);
if (section != null)
{
var fileSection = section.AsFileSection();
var targetPath = transferInfo.FileTempPath;
try
{
using (var outfile = new FileStream(transferInfo.FileTempPath, FileMode.Append, FileAccess.Write, FileShare.None))
{
var buffer = new byte[DefaultCopyBufferSize];
int read;
while ((read = fileSection.FileStream.Read(buffer, 0, buffer.Length)) > 0) // HANGS HERE
{
outfile.Write(buffer, 0, read);
transferInfo.BytesSaved = read + transferInfo.BytesSaved;
}
}
}
catch (Exception e)
{
...
}
}
}
The client code:
var request = CreateRequest(fileTransferId, boundary, header, footer, filePath, offset, headers, null);
using (Stream formDataStream = request.GetRequestStream())
{
formDataStream.ReadTimeout = 60000;
formDataStream.Write(Encoding.UTF8.GetBytes(header), 0, header.Length);
byte[] buffer = new byte[2048];
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
fs.Seek(offset, SeekOrigin.Begin);
for (int i = 0; i < fs.Length - offset;)
{
int k = await fs.ReadAsync(buffer, 0, buffer.Length);
if (k > 0)
{
await Task.Delay(100);
await formDataStream.WriteAsync(buffer, 0, k);
}
i = i + k;
}
}
formDataStream.Write(footer, 0, footer.Length);
}
var uploadingResult = request.GetResponse() as HttpWebResponse;
private static HttpWebRequest CreateRequest(
Guid fileTransferId,
string boundary,
string header,
byte[] footer,
string filePath,
long offset,
NameValueCollection headers,
Dictionary<string, string> postParameters)
{
var url = $"{_BaseAddress}v1/ResumableUpload?fileTransferId={fileTransferId}";
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "multipart/form-data; boundary=\"" + boundary + "\"";
request.UserAgent = "Agent 1.0";
request.Headers.Add(headers); // custom headers
request.Timeout = 120000;
request.KeepAlive = true;
request.AllowReadStreamBuffering = false;
request.ReadWriteTimeout = 120000;
request.AllowWriteStreamBuffering = false;
request.ContentLength = CalculateContentLength(filePath, offset, header, footer, postParameters, boundary);
return request;
}
What I tried:
I added these in into config files:
Tried to set timeout on the server
var host = new WebHostBuilder().UseKestrel(o => { o.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);})
Used async and non-async Read()
Tried with keep alive and without
Tried to abort the request when network was restored: request?.Abort();
Tried to set formDataStream.ReadTimeout = 60000;
Since I did not find a better way, I decided to add a timeout to the reading stream and saving it to the file. The good example was posted here: https://blogs.msdn.microsoft.com/pfxteam/2012/10/05/how-do-i-cancel-non-cancelable-async-operations/
public static async Task<List<(Guid, string)>> StreamFileAsync(this HttpRequest request, DeviceId deviceId, FileTransferInfo transferInfo)
{
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType), DefaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var section = await reader.ReadNextSectionAsync(_cancellationToken);
if (section != null)
{
var fileSection = section.AsFileSection();
var targetPath = transferInfo.FileTempPath;
try
{
await SaveMyFile(...);
}
catch (OperationCanceledException){...}
catch (Exception){...}
}
}
private static async Task SaveMyFile(...)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(myOtherCancellationToken);
cts.CancelAfter(streamReadTimeoutInMs);
var myReadTask = StreamFile(transferInfo, fileSection, cts.Token);
await ExecuteMyTaskWithCancellation(myReadTask, cts.Token);
}
private static async Task<T> ExecuteMyTaskWithCancellation<T>(Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
{
throw new OperationCanceledException(cancellationToken);
}
}
return await task;
}
private static async Task<bool> StreamFile(...)
{
using (var outfile = new FileStream(transferInfo.FileTempPath, FileMode.Append, FileAccess.Write, FileShare.None))
{
var buffer = new byte[DefaultCopyBufferSize];
int read;
while ((read = await fileSection.FileStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
{
if (token.IsCancellationRequested)
{
break;
}
await outfile.WriteAsync(buffer, 0, read);
transferInfo.BytesSaved = read + transferInfo.BytesSaved;
}
return true;
}
}

Send large file from WebAPI.Content Length is 0

I am trying to send large file (GB) from one WebAPI (.NET Core) to another WebApi (.Net Core).
I already managed to send smaller file as part of Multipart Request like in last post here: link
To send bigger file I need (I think) send this file as StreamContent, however i am getting Content length = 0 in API which receives request.
Problem occurs even when I am sending (for test) smaller files (10 Mb).
Clientside code:
[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(IFormFile file)
{
var filePath = Path.GetTempFileName();
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
await file.CopyToAsync(stream);
using (var formDataContent = new MultipartFormDataContent())
{
using (var httpClient = new HttpClient())
{
formDataContent.Add(CreateFileContent(stream, "myfile.test", "application/octet-stream"));
var response = await httpClient.PostAsync(
"http://localhost:56595/home/upload",
formDataContent);
return Json(response);
}
}
}
}
internal static StreamContent CreateFileContent(Stream stream, string fileName, string contentType)
{
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data")
{
Name = "\"file\"",
FileName = "\"" + fileName + "\"",
};
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
return fileContent;
}
Serverside code:
[HttpPost]
public ActionResult Upload()
{
IFormFile fileFromRequest = Request.Form.Files.First();
string myFileName = fileFromRequest.Name;
// some code
return Ok();
}
Where is the problem?
To create Multipart request I used advices from:
HttpClient StreamContent append filename twice
POST StreamContent with Multiple Files
Finally I figured it out:
There were 2 problems:
1. Stream pointer position
In client side code, change this:
await file.CopyToAsync(stream);
to that:
await file.CopyToAsync(stream);
stream.Position = 0;
Problem was that file from request was copied to stream and left position of pointer in the end of the stream. That is why request send from client had stream with proper length, but actually when it started to read it, it could not (read 0 bytes).
2. Wrong way of handling request on server.
I used code from dotnetcoretutorials.com
Working code below:
Client side:
[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(IFormFile file)
{
var filePath = Path.GetTempFileName();
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
await file.CopyToAsync(stream);
stream.Position = 0;
using (var formDataContent = new MultipartFormDataContent())
{
using (var httpClient = new HttpClient())
{
formDataContent.Add(CreateFileContent(stream, "myfile.test", "application/octet-stream"));
var response = await httpClient.PostAsync(
"http://localhost:56595/home/upload",
formDataContent);
return Json(response);
}
}
}
}
internal static StreamContent CreateFileContent(Stream stream, string fileName, string contentType)
{
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data")
{
Name = "\"file\"",
FileName = "\"" + fileName + "\"",
};
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
return fileContent;
}
Server side:
Controller:
[HttpPost]
[DisableFormValueModelBinding]
public async Task<IActionResult> Upload()
{
var viewModel = new MyViewModel();
try
{
FormValueProvider formModel;
using (var stream = System.IO.File.Create("c:\\temp\\myfile.temp"))
{
formModel = await Request.StreamFile(stream);
}
var bindingSuccessful = await TryUpdateModelAsync(viewModel, prefix: "",
valueProvider: formModel);
if (!bindingSuccessful)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
}
catch(Exception exception)
{
throw;
}
return Ok(viewModel);
}
Helper classes for methods from controller:
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec says 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
if (string.IsNullOrWhiteSpace(boundary.ToString()))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary.ToString();
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.ToString())
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.ToString());
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.ToString())
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.ToString()));
}
}
public static class FileStreamingHelper
{
private static readonly FormOptions _defaultFormOptions = new FormOptions();
public static async Task<FormValueProvider> StreamFile(this HttpRequest request, Stream targetStream)
{
if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
{
throw new Exception($"Expected a multipart request, but got {request.ContentType}");
}
// Used to accumulate all the form url encoded key value pairs in the
// request.
var formAccumulator = new KeyValueAccumulator();
string targetFilePath = null;
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
ContentDispositionHeaderValue contentDisposition;
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
await section.Body.CopyToAsync(targetStream);
}
else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
{
// Content-Disposition: form-data; name="key"
//
// value
// Do not limit the key name length here because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
var encoding = GetEncoding(section);
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
{
value = String.Empty;
}
formAccumulator.Append(key.ToString(), value);
if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
{
throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
}
}
}
}
// Drains any remaining section body that has not been consumed and
// reads the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to a model
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
return formValueProvider;
}
private static Encoding GetEncoding(MultipartSection section)
{
MediaTypeHeaderValue mediaType;
var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
// UTF-7 is insecure and should not be honored. UTF-8 will succeed in
// most cases.
if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
{
return Encoding.UTF8;
}
return mediaType.Encoding;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var formValueProviderFactory = context.ValueProviderFactories
.OfType<FormValueProviderFactory>()
.FirstOrDefault();
if (formValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(formValueProviderFactory);
}
var jqueryFormValueProviderFactory = context.ValueProviderFactories
.OfType<JQueryFormValueProviderFactory>()
.FirstOrDefault();
if (jqueryFormValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
Additional thoughts:
(on clientside) line:
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
is not necessary to send the file.
(on clientside) file is send when MediaTypeHeaderValue is one of these:
application/x-msdownload
application/json
application/octet-stream
(on serverside) to use lines with contentDisposition.FileNameStar on serverside you need to change them to contentDisposition.FileNameStar.ToString()
(on serverside) code used in question for serverside will work with smaller files (Mb's) but to send GB file we need code which is pasted in the answer.
some parts of code are taken from aspnet core docs

Trouble writing zip content to device. Writing as hexa data not xml

I'm trying to download a zip file that contains an xml file and save the xml file to the device.
I'm able to save the file inside the zip but when I open the file it contains hexa data and not xml.
What I'm doing:
I'm using http client to make a get call. I get stream content from the response and using dependency injection, I pass the stream to a method that writes to the device.
In share code:
public static async Task<bool> Download()
{
var url = "some url";
HttpClientHandler handler = new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
var httpClient = new HttpClient(handler);
try
{
var response = await httpClient.GetAsync(url);
var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
var stream = await response.Content.ReadAsStreamAsync();
DependencyService.Get<IFileHelper>().WriteToFilePath("download", stream, total);
}
catch (Exception e)
{
//Handle exception
}
return true;
}
in iOS:
public async void WriteToFilePath(string filename, Stream responseStream, long contentSize)
{
var downloadFilePath = GetLocalFilePath(filename);
int receivedBytes = 0;
using (FileStream fileOut = File.Create(downloadFilePath))
{
using (Stream stream = responseStream)
{
byte[] buffer = new byte[4096];
for (;;)
{
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
await Task.Yield();
break;
}
else
{
System.Diagnostics.Debug.WriteLine(bytesRead);
fileOut.Write(buffer, 0, bytesRead);
}
receivedBytes += bytesRead;
System.Diagnostics.Debug.WriteLine("receivedBytes: " + receivedBytes + " contentSize: " + contentSize);
}
}
}

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.

Downloading files not affecting UI

We are currently downloading files using HttpClient because our backend requires certificate.
I have a control - FileRowwhich is an UI element with some code-behind methods for file downloading, like this one:
if (FileIsDownloaded == false)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low, () =>
{
DataManager.Instance.DownloadFile(this);
});
}
if (ThumbnailIsDownloaded == false)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low, () =>
{
DataManager.Instance.DownloadThumbnail(this);
});
}
Downloading single item is fine but when i click download all ( about 50 items ) the whole UI starts to freeze.
As you can see, i have tried to give requests low priority - but still same result.
Answers to common questions:
1) Yes the files should be downloadable all at one time, not one after another.
2) DataManager.Instance.DownloadThumbnail(this) - i do this to give refference to current control so that i could report a progress in a progress bar.
Any suggestions?
EDIT:
Downloading looks like this:
public async void DownloadFile(FileRow fileRow)
{
//Lot of checking for if file exist, if version is the same
string LocalFilename = await DownloadManager.DownloadFile(fileRow.MyFile.file.id, fileRow.MyFile.file.version, fileRow.MyFile.file.filename,fileRow);
// next is just using the filename string
}
And finally my download:
public static async Task<string> DownloadFileOfCustomerAssetRow(int? id, int? version, string filename, FileRow fileRow)
{
try
{
HttpClientHandler aHandler = new HttpClientHandler();
aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
HttpClient aClient = new HttpClient(aHandler);
customerAssetRow.CurrentFileDownload = aClient;
aClient.DefaultRequestHeaders.ExpectContinue = false;
HttpResponseMessage response = await aClient.GetAsync(WebServices.BackendStartUrl + "getFileData?id=" + id + "&version=" + version, HttpCompletionOption.ResponseHeadersRead);
var file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, Windows.Storage.CreationCollisionOption.GenerateUniqueName);
fileRow.FileName = file.Name;
using (var fs = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
{
Stream stream = await response.Content.ReadAsStreamAsync();
IInputStream inputStream = stream.AsInputStream();
ulong totalBytesRead = 0;
while (true)
{
IBuffer buffer = new Windows.Storage.Streams.Buffer(1024);
buffer = await inputStream.ReadAsync(
buffer,
buffer.Capacity,
InputStreamOptions.None);
if (buffer.Length == 0)
{
break;
}
totalBytesRead += buffer.Length;
fileRow.Progress.Value = fileRow.Progress.Value + 1024;
await fs.WriteAsync(buffer);
}
inputStream.Dispose();
}
fileRow.Progress.Visibility = Visibility.Collapsed;
return file.Name;
}
catch (Exception e)
{
ErrorReporter.ReportError("Error in DownloadManager.cs in function DownloadFile.", e);
return "";
}
}
It's possible that your async method continuations are interrupting the UI thread too much. Try creating a stronger separation between your background logic (DownloadFileOfCustomerAssetRow) and your UI (FileRow) by introducing an IProgress<T> reporter. Then ensure that every await in your background logic has a ConfigureAwait(false) on it.
public static async Task<string> DownloadFileOfCustomerAssetRow(int? id, int? version, string filename, IProgress<int> progress)
{
HttpClientHandler aHandler = new HttpClientHandler();
aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
HttpClient aClient = new HttpClient(aHandler);
customerAssetRow.CurrentFileDownload = aClient;
aClient.DefaultRequestHeaders.ExpectContinue = false;
HttpResponseMessage response = await aClient.GetAsync(WebServices.BackendStartUrl + "getFileData?id=" + id + "&version=" + version, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
var file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, Windows.Storage.CreationCollisionOption.GenerateUniqueName).ConfigureAwait(false);
fileRow.FileName = file.Name;
using (var fs = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite).ConfigureAwait(false))
{
Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
IInputStream inputStream = stream.AsInputStream();
ulong totalBytesRead = 0;
while (true)
{
IBuffer buffer = new Windows.Storage.Streams.Buffer(1024);
buffer = await inputStream.ReadAsync(
buffer,
buffer.Capacity,
InputStreamOptions.None).ConfigureAwait(false);
if (buffer.Length == 0)
{
break;
}
totalBytesRead += buffer.Length;
if (progress != null)
progress.Report(totalBytesRead);
await fs.WriteAsync(buffer).ConfigureAwait(false);
}
inputStream.Dispose();
}
return file.Name;
}

Categories