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[].
Steps to reproduce the behavior:
dotnet new razorcomponents -o UploadTest, then cd UploadTest and explorer .\, and open the generated UploadTest.sln with Visual Studio 2019 Preview 3.0.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>
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);
}
}
Console.WriteLine(data); line within the @functions block and run the app.The line Console.WriteLine(data); line within the functions block should be executed.
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
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]"
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
Uint8Arraydoes not serialize as a regular array via standard JSON serialization. Example:Result:
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 convertUint8Arrayto a regular array usingArray.from(yourUint8Array). To see this serializes correctly:Result: