Development

Blazor で IndexedDB と memory access out of bounds エラー

Blazor WebAssembly アプリ アプリから IndexedDB を使う方法です。

Blazor から使えるようにしたライブラリはいくつかありますが、更新が止まっているなど、現時点では良さそうなものがありませんでした。汎用的な IndexedDB をラップしたライブラリを使うのではなく、JavaScript 相互運用 で直接 JavaScript コードを書いて、必要なデータのやりとりだけ行う方法で、解決するのが良さそうです。

Blazor + Dexie.js

IndexDB を扱う JavaScript ライブラリとして、Dexie.js を使います。

index.html に script を追加します。

index.html
<script src="_framework/blazor.webassembly.js"></script>
<script src="https://unpkg.com/dexie@latest/dist/dexie.js"></script>
<script src="js/db.js"></script>

IndexDB を扱う JavaScript のコードです。

db.js
var db = new Dexie("friend_database");
db.version(1).stores({
    friends: 'name,shoeSize'
});

window.putFriend = (friend) =>
    db.friends.put(friend);

window.getFriends = () =>
    db.friends.toArray();

JavaScript の function を呼び出す C# のコードです。

Index.razor
@code
{
    public class Friend
    {
        public string Name { get; set; }
        public int ShoeSize { get; set; }
    }

    private async Task GetFriends()
    {
        var friends =
            await JsRuntime.InvokeAsync<List<Friend>>("getFriends");
    }

    private async Task PutFriend()
    {
          var friend = new Friend
          {
              Name = "Nicolas",
              ShoeSize = 8
          };
          var key = await JsRuntime.InvokeAsync<string>("putFriend", friend);
    }
}

memory access out of bounds

IndexedDB も Blazor も直接は関係のないエラーですが、JavaScript 相互運用で、大きなサイズのデータを JavaScript 側から C# 側に受け取ろうとすると(逆方向も起きるかは未確認)、次のエラーが発生します。

RuntimeError: memory access out of bounds

単純な文字列を返す JavaScript の関数で、1MB を少し超えると起きました。IndexDB で大量のデータを扱うと発生する場合があります。

原因は Mono 由来のようです(関連 issue: Mono WebAssembly)。

回避策としては、やり取りを複数に分割する以外に、C# の byte[] と JavaScript の ArrayBuffer でやりとりするテクニックがあるようです(関連: .net core - Blazor preview 9/mono-wasm memory access out of bounds: max string size for DotNet.invokeMethod? - Stack Overflow)。BlazorFileReader はこの問題を解決しているみたいですね。

単純に、JsonPropertyName 属性を使って JSON データを少し小さくする方法も有効です。

Index.razor
public class Friend
{
    [JsonPropertyName("n")]
    public string Name { get; set; }
    [JsonPropertyName("s")]
    public int ShoeSize { get; set; }
}