Blazor で IndexedDB と memory access out of bounds エラー
Blazor WebAssembly アプリ アプリから IndexedDB を使う方法です。
Blazor から使えるようにしたライブラリはいくつかありますが、更新が止まっているなど、現時点では良さそうなものがありませんでした。汎用的な IndexedDB をラップしたライブラリを使うのではなく、JavaScript 相互運用 で直接 JavaScript コードを書いて、必要なデータのやりとりだけ行う方法で、解決するのが良さそうです。
- wtulloch/Blazor.IndexedDB: A Blazor library for accessing IndexedDB
- Reshiru/Blazor.IndexedDB.Framework: A framework for blazor which acts as an interface to IndexedDB
- amuste/DnetIndexedDb: Blazor Library for IndexedDB DOM API
- nwestfall/BlazorDB: Use IndexedDB in Blazor WebAssembly (WASM)
Blazor + Dexie.js
IndexDB を扱う JavaScript ライブラリとして、Dexie.js を使います。
index.html に script を追加します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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 のコードです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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# のコードです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 データを少し小さくする方法も有効です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Friend | |
{ | |
[JsonPropertyName("n")] | |
public string Name { get; set; } | |
[JsonPropertyName("s")] | |
public int ShoeSize { get; set; } | |
} |