Aspnetcore: File Upload Via Byte Array Transfer

Created on 17 Feb 2019  路  3Comments  路  Source: dotnet/aspnetcore

Describe the bug

I am currently trying to get file uploading functionality working via using the JavaScript FileReader to generate an ArrayBuffer from a file, and then converting the data to a Uint8Array and sending it back up to Razor Components on the server. For some reason, the awaited call never finishes. The C# equivalent of Uint8Array is byte[], which is what I used as the type parameter for IJSRuntime.InvokeAsync<T>(string, TModel[]), but it would seem that no matter what, even in a simpler case, InvokeAsync doesn't want to deal with byte[].

To Reproduce

Steps to reproduce the behavior:

  1. Use ASP.NET Core 3.0 Preview 2 on Visual Studio 2019 Preview 3.0.
  2. Run dotnet new razorcomponents -o UploadTest, then cd UploadTest and explorer .\, and open the generated UploadTest.sln with Visual Studio 2019 Preview 3.0.
  3. Add code for file uploading to index.html.
<script>
    window.uploadUtilities = {
        readUploadedFile: function (file) {
            const temporaryFileReader = new FileReader();
            return new Promise((resolve, reject) => {
                temporaryFileReader.onerror = () => {
                    temporaryFileReader.abort();
                    reject(new DOMException("Problem parsing input file."));
                };
                temporaryFileReader.addEventListener("load", () => {
                    resolve(new Uint8Array(temporaryFileReader.result));
                }, false);
                temporaryFileReader.readAsArrayBuffer(file.files[0]);
            });
        }
    }
</script>
  1. Add a file upload input to Index.cshtml and wire it up to the JavaScript uploading code.
@* Other Directives *@
@inject IJSRuntime Runtime
@* Markup *@
<input type="file" onchange=@ProcessUpload ref=FileInput />
@* More Markup *@
@functions
{
    ElementRef FileInput { get; set; }

    async Task ProcessUpload()
    {
        byte[] data = await Runtime.InvokeAsync<byte[]>("uploadUtilities.readUploadedFile", FileInput);
        Console.WriteLine(data);
    }
}
  1. Set a breakpoint on the Console.WriteLine(data); line within the @functions block and run the app.
  2. See that even once a file is uploaded via the UI, the breakpoint is never hit.

Expected behavior

The line Console.WriteLine(data); line within the functions block should be executed.

Additional context

I am going to try binding the value of the <input/> tag to a string to see if that will work, but it is concerning that not only is Uint8Array transfer not working, the remaining code is not executing and the whole thing is failing silently. There should be an exception here if Razor Components simply does not support transferring bytes.
dotnetinfo.txt

area-blazor investigate

Most helpful comment

JSInterop uses JSON as the interchange format, since it has well-established and widely understood type conversion rules.

Your issue is that Uint8Array does not serialize as a regular array via standard JSON serialization. Example:

JSON.stringify(new Uint8Array([1,2,3]))

Result:

"{\"0\":1,\"1\":2,\"2\":3}"

As you can see, the .NET side would only be able to interpret this as a Dictionary<string, byte>, which is not at all what you want.

To resolve this, you need to pass a regular JS array, not a Uint8Array. You can convert Uint8Array to a regular array using Array.from(yourUint8Array). To see this serializes correctly:

JSON.stringify(Array.from(new Uint8Array([1,2,3])))

Result:

"[1,2,3]"

All 3 comments

Just to update, if the temporaryFileReader is used to extract the data URL-encoded, as Text, or as a binary string, it works perfectly, as long as InovkeAsync is called with string as the type parameter.

@SteveSandersonMS Any idea what's going on here?

JSInterop uses JSON as the interchange format, since it has well-established and widely understood type conversion rules.

Your issue is that Uint8Array does not serialize as a regular array via standard JSON serialization. Example:

JSON.stringify(new Uint8Array([1,2,3]))

Result:

"{\"0\":1,\"1\":2,\"2\":3}"

As you can see, the .NET side would only be able to interpret this as a Dictionary<string, byte>, which is not at all what you want.

To resolve this, you need to pass a regular JS array, not a Uint8Array. You can convert Uint8Array to a regular array using Array.from(yourUint8Array). To see this serializes correctly:

JSON.stringify(Array.from(new Uint8Array([1,2,3])))

Result:

"[1,2,3]"
Was this page helpful?
0 / 5 - 0 ratings