2

I am working on a weekend project that aggregates downloads from other sites in a zip file with Blazor (webassmbly).

The zip file is created in c# code and need to be saved on the local computer. I am using a modified version of this answer because the file size can easily extend over 400 mb.

My Version partitions the js call to not imminently overflow the webassembly VM but after a certain filesize it happens and i get the folloring exception:

WASM: Try saving 213776418bytes --my message
blazor.webassembly.js:1 WASM: GC_MAJOR_SWEEP: major size: 1808K in use: 94964K
blazor.webassembly.js:1 WASM: GC_MAJOR: (LOS overflow) time 71.98ms, stw 72.00ms los size: 486832K in use: 481083K
WASM: Error: Garbage collector could not allocate 16384 bytes of memory for major heap section.
Uncaught (in promise) ExitStatus {name: "ExitStatus", message: "Program terminated with exit(1)", status: 1}

Is there currently any other way to save a file from WebAssembly other then transferring the file content to js and then save them with Blob?

Venson
  • 1,772
  • 17
  • 37
  • Quick read up suggests not, the only other option that comes to mind is to break the zip up into multiple files. You could also split the file into multiple base 64 strings, but the problem is going to be piecing it back together on the client side... Those strings are going to eat memory and you can't exactly stream it to the browser. It would have to be chunks that download one by one, make a call back to blazor from js interop to get the next chunk, but the stitching together would have to be manual – Charleh Dec 15 '19 at 12:44
  • @Charleh in fact, thats exactly what i have done. https://gist.github.com/JPVenson/8eb2686b2f07d28014d0f4098d0e04c4 The stiching of the parts included. That is my "Modified" version of the mentioned post. But still its sucks up that much memory in the process (and afaik) does not free the Memory from the interopt in a certain time even if i produce multiple files, there is a point (around 400mb) where the VM just throws this exception – Venson Dec 15 '19 at 13:25
  • Yeah but the point is, you can't stitch the files together in the vm because that's what's causing your memory issues. There's no option to stitch the files on disk because it has to go through the browser, and the whole thing is a hack in the first place where the bytes are just sent to the vm. That's why I mentioned the manual stitching (i.e. download 4 files and manually concatenate them with another program external from the blazor app). Until there's support for streaming directly from the blazor app I think it will be very tricky to make work – Charleh Dec 15 '19 at 14:14
  • @Charleh After i did some further research, there is no obvious "blazor" way how to handle this case. I guess i have to wait for https://developer.mozilla.org/en-US/docs/Web/API/File_and_Directory_Entries_API to become standard, or any blazor improvements in the interopt with the js vm. – Venson Jan 01 '20 at 13:02
  • I am facing the same problem. Did you find a good solution for this in the end? Im thinking maybe just save these as an asset in the wwwRoot and navigate to the url and then make sure the file is cleaned up after its downloaded. The problem I expect to face with this is knowing if the download has completed – Paul Aug 14 '20 at 09:40
  • @paul Are you talking about server side blazor? My problem involves only WebAssembly Blazor. And no, not yet. – Venson Aug 15 '20 at 10:25
  • Yes I haven't tried web assembly yet. Is it not possible to create a new asset to download from a Web assembly project from the server side services? My idea is to just do this and set up a hangfire task to clean up after an expiry date and redirect to a download link to the phisical file – Paul Aug 16 '20 at 11:23
  • @paul sure, there is no (Not literally) limit in what you can download from a site, but this post in particular asks for the problem in webassembly when you create a file in webassembly code and then "move" the data through the Js Interopt. – Venson Aug 17 '20 at 12:22

1 Answers1

0

I found a solution. With the latest update to .Net5 it is now possible to use the IJSUnmarshalledRuntime. This makes everything a lot easier. Beware there seems to be still limits or other restrictions in place I was forced to partition my files to around 200mb but the solution is very fast and does seem to work "fine" enough. Warning: Works only in Webassembly Blazor!

First write use a FileUtil:

public static class FileUtil
{
    public static void SaveAs(this IJSRuntime js, string filename, MemoryStream data)
    {
        var rt = js as IJSUnmarshalledRuntime;
        var dataArray = data.ToArray();
        rt.InvokeUnmarshalled<string, byte[], object>(
            "saveAsFile",
            filename,
            dataArray);
    }
}

and then use this js function:

function saveAsFile(filenamePointer, contentPointer) {
    var parts = Blazor.platform.toUint8Array(contentPointer);
    var filename = BINDING.conv_string(filenamePointer);

    var blob = new Blob([parts], { type: "application/octet-stream" });
    if (navigator.msSaveBlob) {
        //Download document in Edge browser
        navigator.msSaveBlob(blob, filename);
    }
    else {
        var link = document.createElement('a');
        link.download = filename;
        link.href = URL.createObjectURL(blob);
        document.body.appendChild(link); // Needed for Firefox
        link.click();
        document.body.removeChild(link);
    }
}
Venson
  • 1,772
  • 17
  • 37