IndexedDB is a powerful NoSQL database embedded in the modern browsers. Using IndexedDB can improve your website performance vastly. In this tutorial, you will discover:
You can download the example code used in this topic on GitHub.
IndexedDB as its name suggested, uses indexes to enable high performance queries. IndexedDB also allows you to store various data type as key and value. IndexedDB provides many useful features:
IndexedDB can also help you to improve your website performance by caching files, large set of data and so on.
IndexedDB isolates data by the domain. That being said, one domain cannot access another domain's resources. For example, data created by blazorschool.com cannot be accessed by google.com and vice versa.
IndexedDB manages data like folders, but with a fixed level. There are 3 levels in IndexedDB:
In this tutorial, we only provide a basic example on how to store and get the value from the IndexedDB and the example will not demonstrate all the possibilities of the IndexedDB.
You can access the IndexedDB storage in the browser by opening the Developer Tools (F12 for most browsers).
For Firefox, the IndexedDB storage is located at the Storage tab as the following image:
For Chrome and Edge, the IndexedDB storage is located at the Application tab as the following images:
To use the IndexedDB storage, you first need to create a JavaScript module, then you will use C# code to call the exported functions of this module.
export function initialize() { let blazorSchoolIndexedDb = indexedDB.open(DATABASE_NAME, CURRENT_VERSION); blazorSchoolIndexedDb.onupgradeneeded = function () { let db = blazorSchoolIndexedDb.result; db.createObjectStore("books", { keyPath: "id" }); } } let CURRENT_VERSION = 1; let DATABASE_NAME = "Blazor School";
You can change the value ofDATABASE_NAME
, you need to increase theCURRENT_VERSION
whenever you change your data structure, you can create one or more Object Store with different names than "books" as in the example.
public class IndexedDbAccessor : IAsyncDisposable { private Lazy<IJSObjectReference> _accessorJsRef = new(); private readonly IJSRuntime _jsRuntime; public IndexedDbAccessor(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } public async Task InitializeAsync() { await WaitForReference(); await _accessorJsRef.Value.InvokeVoidAsync("initialize"); } private async Task WaitForReference() { if (_accessorJsRef.IsValueCreated is false) { _accessorJsRef = new(await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "/js/IndexedDbAccessor.js")); } } public async ValueTask DisposeAsync() { if (_accessorJsRef.IsValueCreated) { await _accessorJsRef.Value.DisposeAsync(); } } }
Always remember to dispose the JavaScript module.
Program.cs
builder.Services.AddScoped<IndexedDbAccessor>();
App.razor
@inject IndexedDbAccessor IndexedDbAccessor ... @code { protected override async Task OnAfterRenderAsync(bool firstRender) { await IndexedDbAccessor.InitializeAsync(); } }
In this section, we will demonstrate how to add some basic operations with the IndexedDB storage. You can also add your own operations with this method too.
export function set(collectionName, value) { let blazorSchoolIndexedDb = indexedDB.open(DATABASE_NAME, CURRENT_VERSION); blazorSchoolIndexedDb.onsuccess = function () { let transaction = blazorSchoolIndexedDb.result.transaction(collectionName, "readwrite"); let collection = transaction.objectStore(collectionName) collection.put(value); } } export async function get(collectionName, id) { let request = new Promise((resolve) => { let blazorSchoolIndexedDb = indexedDB.open(DATABASE_NAME, CURRENT_VERSION); blazorSchoolIndexedDb.onsuccess = function () { let transaction = blazorSchoolIndexedDb.result.transaction(collectionName, "readonly"); let collection = transaction.objectStore(collectionName); let result = collection.get(id); result.onsuccess = function (e) { resolve(result.result); } } }); let result = await request; return result; }
WaitForReference()
in all methods.public class IndexedDbAccessor : IAsyncDisposable { ... public async Task<T> GetValueAsync<T>(string collectionName, int id) { await WaitForReference(); var result = await _accessorJsRef.Value.InvokeAsync<T>("get", collectionName, id); return result; } public async Task SetValueAsync<T>(string collectionName, T value) { await WaitForReference(); await _accessorJsRef.Value.InvokeVoidAsync("set", collectionName, value); } }
Once you have the complete all the previous steps, you can use it as follows:
@using System.Text.Json @inject IndexedDbAccessor IndexedDbAccessor <form> <label class="form-label"> Book ID: <input class="form-control" type="text" @bind-value="BookId" /> </label> <label class="form-label"> Book Name: <input class="form-control" type="text" @bind-value="BookName" /> </label> <button class="btn btn-primary" type="button" @onclick="SetValueAsync">Set Value</button> </form> <div>Stored Value: @StoredValue</div> <button class="btn btn-primary" type="button" @onclick="GetValueAsync">Get Value</button> @code { public string BookId { get; set; } = ""; public string BookName { get; set; } = ""; public string StoredValue { get; set; } = ""; public async Task SetValueAsync() { await IndexedDbAccessor.SetValueAsync("books", new { Id = Convert.ToInt32(BookId), Name = BookName }); } public async Task GetValueAsync() { JsonDocument storedBook = await IndexedDbAccessor.GetValueAsync<JsonDocument>("books", Convert.ToInt32(BookId)); StoredValue = storedBook.RootElement.GetProperty("name").GetString() ?? ""; } }