Currently, I download byte arrays as files using JsInterop.
Here is my JS file:
function downloadFile(fileName, base64String) {
const url = "data:application/octet-stream;base64," + base64String;
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
}
And here is a method in my razor component:
async Task DownloadFile(byte[] file)
{
ShowMessage("Start");
await JSRuntime.InvokeVoidAsync("downloadFile", "FileName", Convert.ToBase64String(file));
ShowMessage("End");
}
This code works, and I am able to download files. My issue is that I cannot implement the progress bar, or even show the loading spinner because await JSRuntime has no idea about an actual file download size and its progress. JSRuntime only launches the process of downloading and immediately continues to the next line of code.
In the code above ShowMessage("Start") and ShowMessage("End") are both shown one after another as soon as I click the download button, but the file in the browser downloads much later (depending on the file size).
How may I await the download process and execute relevant code only when the file has been downloaded? And it would be even better if I could read downloaded bytes to show a progress bar with percentages.
Update: for test purposes, I upload the file from the browser and store it in a byte[] variable. Then I download the same file from the variable using JS. Even though I store the file in the memory, it still takes time to download the file. I suppose that when I store a file in memory, it is already on my PC (client), and should download immediately. But instead, my window gets frozen for the duration of downloading the file. Tested on 6 - 11- 20 MB files. The bigger file, the more I have to wait for it to download.
I suggest you should be show message ShowMessage("Start") and ShowMessage("End"); in function downloadFile at JS
Related
I have logic that downloads a group of files as a zip. The issue is there is no progress so the user does not know how far along the download is.
This Zip file doesn't exist before hand, the user selects the files they want to download and then I use the SharpZipLib nuget package to create a zip
and stream it to the response.
It seems I need to set the Content-Length header for the browser to show a total size progress indicator. The issue I'm having is it seems
this value has to be exact, if its too low or too high by 1 byte the file does not get downloaded properly. I can get an approximate
end value size by adding all the files size together and setting there to be no compressions level but I don't see a way I can calculate the final zip size exactly.
I hoped I could of just overesitmated the final size a bit and the browser would allow that but that doesn't work, the file isn't downloaded properly so you cant access it.
Here are some possible solution I've come up with but they have there own issues.
1 - I can create the zip on the server first and then stream it, therefore knowing the exact size I can set the Content-length. Issue with this
is the user will have to wait for all the files to be streamed to the web server, the zip to be created and then I can start streaming it to the user. While this is going on the user wont even see the file download as being started. This also results in more memory usage of the web server as it has to persist the entire zip file in memory.
2 - I can come up with my own progress UI, I will use the combined file sizes to get a rough final size estimation and then as the files are streamed I push updates to the user via signalR indicating the progress.
3- I show the user the total file size before download begins, this way they will at least have a way to assess themselves how far along it is. But the browser has no indication of how far along it is so if they may forget and when they look at the browser download progress there will be no indication how far along it is
These all have their own drawbacks. Is there a better way do this, ideally so its all handled by the browser?
Below is my ZipFilesToRepsonse method. It uses some objects that aren't shown here for simplicity sake. It also streams the files from azure blob storage
public void ZipFilesToResponse(HttpResponseBase response, IEnumerable<Tuple<string,string>> filePathNames, string zipFileName)
{
using (var zipOutputStream = new ZipOutputStream(response.OutputStream))
{
zipOutputStream.SetLevel(0); // 0 - store only to 9 - means best compression
response.BufferOutput = false;
response.AddHeader("Content-Disposition", "attachment; filename=" + zipFileName);
response.ContentType = "application/octet-stream";
Dictionary<string,long> sizeDictionary = new Dictionary<string, long>();
long totalSize = 0;
foreach (var file in filePathNames)
{
long size = GetBlobProperties(file.Item1).Length;
totalSize += size;
sizeDictionary.Add(file.Item1,size);
}
//Zip files breaks if we dont have exact content length
//and it isn't nesccarily the total lengths of the contents
//dont see a simple way to get it set correctly without downloading entire file to server first
//so for now we wont include a content length
//response.AddHeader("Content-Length",totalSize.ToString());
foreach (var file in filePathNames)
{
long size = sizeDictionary[file.Item1];
var entry = new ZipEntry(file.Item2)
{
DateTime = DateTime.Now,
Size = size
};
zipOutputStream.PutNextEntry(entry);
Container.GetBlockBlobReference(file.Item1).DownloadToStream(zipOutputStream);
response.Flush();
if (!response.IsClientConnected)
{
break;
}
}
zipOutputStream.Finish();
zipOutputStream.Close();
}
response.End();
}
I'm working on a simple blazor application that receives a file upload and stores it. I am using BlazorInputFile and I can't work out why copying the stream to MemoryStream is causing the browser to freeze.
The details of how to use (and how it's implemented) BlazorInputFile are explained in this blog post: Uploading Files in Blazor.
var ms = new MemoryStream();
await file.Data.CopyToAsync(ms); // With a 1MB file, this line took 3 seconds, and froze the browser
status = $"Finished loading {file.Size} bytes from {file.Name}";
Sample project/repo: https://github.com/paulallington/BlazorInputFileIssue
(this is just the default Blazor app, with BlazorInputFile implemented as per the article)
Use await Task.Delay(1); as mentioned on Zhi Lv's comment in this post blazor-webassembly upload file can't show progress?
var buffer = new byte[imageFile.Size];
await Task.Delay(1);
await imageFile.OpenReadStream(Int64.MaxValue).ReadAsync(buffer);
pratica.Files.Add(new FilePraticaRequest()
{
Contenuto = buffer,
Nome = imageFile.Name,
});
StateHasChanged();
I've experienced the same issue. I've tried both predefined components such as Steve Sanderssons file upload and MatBlazor fileupload and also my own component to handle fileuploads. Small files are not a problem. Once the files are a bit larger in size the UI will hang itself. MemoryOutOfBoundsException (or similar). So no, async/await can't help you release the UI.
I have put so much effort into this issue, one solution, that I am currently using, is to do all fileuploads with javascript instead of blazor. Just use javascript to get the file and post it up to the server. No JSInterop..
However, it seems like it is a memory issue in webassembly mono.
Read more here: https://github.com/dotnet/aspnetcore/issues/15777
Note: I haven't tried this on the latest Blazor version. So I'm not sure it's fixed or not.
You wait for the copy result, so the app freeze, you can refactor your code like this;
var ms = new MemoryStream();
file.Data.CopyToAsync(ms).ContinueWith(async task =>
{
if (task.Exception != null)
{
throw task.Exception; // Update this by your convenience
}
status = $"Finished loading {file.Size} bytes from {file.Name}";
await InvokeAsync(StateHasChanged).ConfigureAwait(false); // informs the component the status changed
}; // With a 1MB file, this line took 3 seconds, and should not froze the browser
I have an app that I want to download & upload a simple .txt file with a URL inside. I have downloaded Live Connect SDK V5.4, referenced the documentation, but it appears that the documentation is incorrect. The sample code uses event handlers for when a download/upload is complete, but that no longer can be used in V5.4.
I have two methods, downURL & upURL. I have started working on downURL:
private async void downURL()
{
try
{
LiveDownloadOperationResult download = await client.DownloadAsync("URL.txt");
}
catch { }
}
I am not sure what I am suppose to use for the path, I put "URL.txt" for now, I've seen some examples with "/me/". Do I need this? The file does not need to be visible to the user, as the user can't really do anything with it, but it is vital for the app to work.
My question is how do I use the LiveDownloadOperationResult download to save the file to Isolated Storage Settings, get the text contents, and put that in a string? Also, if you know how to upload the file back up, the upload event handler looks the same (but without the Result variable).
This code help you download content a file which you want. It get content have format OpenXML
Here, "item.id" is Id of "URL.txt".
private async void downURL()
{
try
{
LiveDownloadOperationResult operationResult = await client.DownloadAsync(item.id + "/Content?type=notebook");
StreamReader reader = new StreamReader(operationResult.Stream);
string Content = await reader.ReadToEndAsync();
}
catch { }
}
In one of our project we need the functionality to download a file from server to client location.
For this we are using an ashx handler to do the operation. Its working perfectly and we are able to download files.
Now we need a requirement like we need to update a field when a download is started and completed. Is there any way to do this.
Once we click the download link the Save as dialog box will appear and after that i think we don't have any control to check the progress. I think we even don't know which button is clicked ie we don't know whether the user is clicked a 'Yes' or 'No'.
Can anyone please suggest a method to know when the download is started and when it has been completed? We are using Asp.Net 2.0 with c#.
The handler used for download is given below
string fileUrl = string.Empty;
if (context.Request["fileUrl"] != null)
{
fileUrl = context.Request["fileUrl"].ToString();
}
string filename = System.IO.Path.GetFileName(fileUrl);
context.Response.ClearContent();
context.Response.ContentType = "application/exe";
context.Response.AddHeader("content-disposition", String.Format("attachment; filename={0}", filename));
context.Response.TransmitFile(fileUrl);
context.Response.Flush();
The file is downloaded from an aspx page method like
private void DownloadExe()
{
string downloadUrl = "Test.exe");
Response.Redirect("Test.ashx?fileUrl=" + downloadUrl, false);
}
Your ASHX handler knwos if download started (since it is actually get called) and when download is completed (end of handler is reached). You may even get some progress server side if you are writing response manually in chunks, this way you also may be able to detect some cases when user cancels download (if writing to response stream fails at some point).
Depending on your needs you may be able to transfer this information to other pages (i.e. via session state) or simply store in some database.
How about this:
Response.BufferOutput = false;
Response.TransmitFile(fileUrl);
//download complete code
If you disable response output buffering then it won't move past the line of code that sends the file to the client until the client has finished receiving it. If they cancel the download half way through it throws a HttpException so the download complete code doesn't get run.
You could also place your download complete code after your call to flush the buffer. But it's better not to enable buffering when sending large binary files to save on server memory.
Ok I had the same problem and jumped over this site:
Check over coockies
This works great for me.
I have run into some problems using C# WebClient DownloadFileAsync and hope that you would help me.
Here's my scenario:
I am downloading many files at the same time (http://example.com/abc/1.jpg, http://example.com/abc/efg/2.jpg, etc.) using WebClient DownloadFileAsync.
My current example code is:
while (theres still files in http://example.com/abc/) {
// filename will be something like /abc/1.jpg, /abc/efg/2.jpg
if (!file.exists(directory.getcurrentdirectory()+filename.Replace('/', '\\'))) {
WebClient webClient = new WebClient();
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(downloadProgressChanged);
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(downloadCompleted);
webClient.DownloadFileAsync(new Uri("http://example.com"+filename), Directory.GetCurrentDirectory()+filename.Replace('/', '\\'));
}
How do I make it so that all the files being downloaded is shown in one progress bar?
eg. 1.jpg is 50kb, 2.jpg is 50kb, when 1.jpg completes download the progress bar will show 50%, and 2.jpg will be from 51% to 100% in the progress bar.
Also if the filename is /abc/1.jpg, if my current directory do not have a folder called abc, the download will not work. How do I make it create the folder automatically depending on filename? (eg. /random123/3.jpg, /anotherrandom/4.jpg, etc.)
To give an overall progress counter for you will need a centralised routine that gathers the amount-completed-so-far and total-amount-to-download for each of the in-progress transfers and sums them to calculate the overall totals. Because the transfers are running in parallel you can't just assign a chunk of the progress bar (e.g 51-100%) to each file.
With most APIs you will need to create folders for yourself before writing files into them. Just do this to create all missing folders on the path before you start writing the file:
Directory.CreateDirectory(Path.GetDirectoryName(filename));
To do the progress bar thing, you can use a queue. When something finishes downloading, update the progress bar, and start the next download.
However, I don't think the WebClient class can tell you how much of the download finished, only if it is finished or not.
To verify if the directory exists, use:
if (!System.IO.Directory.Exists(folder_name)) System.IO.Directory.Create(folder_name);
You can get the directory from a path, using the System.IO.Path class.