Performance comparison of the solutions for passing byte arrays from .NET runtime to JavaScript in Blazor wasm
Overview
Passing data from and to the JavaScript environment is one of the common tasks developers need to solve while working on a WebAssembly application with Blazor. Although typical use cases are well-covered by the JSInterop classes (e.g. IJSRuntime), less common problems require putting some effort into finding the right technical solution.
One of the common problems of JS interoperability is marshalling data between dotnet and JS runtimes. Simple types (e.g. strings and numbers) can be transferred easily, just by supplying them to a called JS function through parameters argument of one of the Invoke methods. On a contrary, there is no single recommended technique for arrays of simple types.
This article presents several techniques for transferring large byte arrays from dotnet to JS, a problem that may arise while dealing with arbitrary files, generated images, or encrypted data.
Problem
There is a byte array defined in the Blazor application. This array should be converted to a typed UInt8Array JS array.
@inject IJSRuntime js
...
private static byte[] GetArray(int length) =>
Enumerable.Range(0, length)
.Select(value => (byte)(value & 0xFF))
.ToArray();
private ... Method() {
var data = GetArray(1024);
...
// the following line should print "Uint8Array(1024) [0, 1, 2, 3, 4,..."
await js.InvokeVoidAsync("console.log", bytes);
}
Solutions
Solution 1: The trivial solution is to create an array, populate it, and then convert it to Uint8Array.
var array = await js.InvokeAsync<IJSObjectReference>("Array");
foreach (var value in data)
await array.InvokeVoidAsync("push", value);
var bytes = await js.InvokeAsync<IJSObjectReference>("Uint8Array.of", array);
This code works with both server and client Blazor environments and it is also memory efficient. But it also the slowest among the methods reviewed.

Solution 2: Solution 1 with synchronous calls.
var jsr = (IJSInProcessRuntime)js;
var array = jsr.Invoke<IJSInProcessObjectReference>("Array");
foreach (var value in data)
array.InvokeVoid("push", value);
var bytes = jsr.Invoke<IJSInProcessObjectReference>("Uint8Array.from", array);
Although it can be used only in wasm Blazor applications, it improves the performance by 60%.

Solution 3: transfer all data at once in JavaScript code string and parse it with eval
.
var jsr = (IJSInProcessRuntime)js;
var values = string.Join(',', data.Select(value => Convert.ToString(value)));
var code = "Uint8Array.from([" + values + "])";
var bytes = jsr.Invoke<IJSInProcessObjectReference>("eval", code);
According to the previous tests, the difference between synchronous and asynchronous call is less than 1ms. This code has a single JSInterop call so whether it is asynchronous or synchronous has very little impact. It uses significantly more memory, at least 4 times bigger than the data.
The performance, however, is more than 10 times better.

Open the rest of the article, subscribe.