IndexedDB storage

🟨 Slightly different to Blazor Server

IndexedDB is indeed a powerful NoSQL database that is embedded in modern web browsers. It is designed to provide a client-side storage solution for web applications, allowing developers to store and retrieve large amounts of structured data in a flexible and efficient manner. Unlike traditional relational databases, IndexedDB is a key-value store that allows developers to store data in object stores and access it using indexes. Using IndexedDB can certainly improve website performance, especially when working with large amounts of data.

  • What is IndexedDB storage?
  • Set up the base code for interacting with IndexedDB storage.
  • Add common operations to your IndexedDB storage code.
  • Use IndexedDB storage.
  • Key differences between Blazor WebAssembly and Blazor Server.
You can download the example code used in this topic on GitHub.

What is IndexedDB storage?

IndexedDB, as its name suggests, uses indexes to enable high-performance queries. This client-side storage technology allows developers to store various data types as key-value pairs, providing a flexible and versatile way to work with structured data.

IndexedDB offers several useful features, including:

  • Transaction.
  • Data migration.
  • Data versioning.
  • Indexing.
  • Asynchronous processing.

These features make it possible for developers to create efficient, scalable, and reliable web applications that can work with large amounts of data.

In addition to these features, IndexedDB can also help to improve website performance by caching files and large sets of data. By storing data locally, web applications can reduce network latency and improve overall performance, particularly in situations where a network connection may not be reliable or available.

Understanding IndexedDB

IndexedDB is a client-side storage technology that isolates data by domain, meaning that each domain can only access its own resources. For example, data created by blazorschool.com cannot be accessed by google.com, and vice versa.

IndexedDB organizes data like folders, but with a fixed hierarchy. There are 3 levels in IndexedDB:

  • Domain: A domain can contain one or more databases.
  • Database: A database can contain one or more object stores.
  • Object Store: An object store is where data is actually stored under key-value pairs.

In this tutorial, we will provide a basic example of how to store and retrieve values from IndexedDB. Please note that this example will not demonstrate all the possibilities of IndexedDB, but rather serve as an introduction to the basic concepts of this powerful client-side storage technology.

How to access the IndexedDB storage in the browser?

In Firefox, you can find the IndexedDB storage under the Storage tab, as shown in the image below:

firefox-indexeddb-storage.png

In Chrome and Edge, you can find the IndexedDB storage under the Application tab, as shown in the images below:

chrome-indexeddb-storage.png

edge-indexeddb-storage.png


Set up the base code for interacting with IndexedDB storage

The IndexedDB storage is exclusively accessible through the JavaScript API. This means that in order to access it, you will need to create a JavaScript module. You can refer to the JavaScript Interaction tutorial and choose between IJSRuntime or InteropServices.JavaScript to interact with your module. Below is an example of how to access the IndexedDB storage using IJSRuntime.

  1. Create a new JavaScript file under the wwwroot folder. For example:

/js/IndexedDbStorageAccessor.js

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";
Customize the name of the database by changing the value of DATABASE_NAME. You should also increase the CURRENT_VERSION whenever you change the structure of your data. Additionally, you can create one or more object stores with different names than "books" as shown in the example.
  1. Create a C# class with the following base implementation:
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();
        }
    }
}
  1. Register the IndexedDbAccessor class in the Program.cs file:
builder.Services.AddScoped<IndexedDbAccessor>();
var host = builder.Build();
using var scope = host.Services.CreateScope();
await using var indexedDB = scope.ServiceProvider.GetService<IndexedDbAccessor>();

if (indexedDB is not null)
{
    await indexedDB.InitializeAsync();
}

Add common operations to your IndexedDB storage code

In this section, we will demonstrate how to perform basic IndexedDB storage operations in JavaScript and C#. With the provided code, you can easily store, get, and delete data from the IndexedDB. Additionally, you can customize the code to add your own operations as well.

  1. To store and get data, add the following functions to your JavaScript module:
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;
}
  1. To use these operations in your C# class, add a method for each operation, and call WaitForReference() in all of them. Here is an example implementation:
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);
    }
}

Use IndexedDB storage

Once you have completed all the previous steps, you can use the IndexedDB Storage as follows:

@using System.Text.Json
@inject IndexedDbAccessor IndexedDbAccessor

<form>
    <label>
        Book ID:
        <input type="text" @bind-value="BookId" />
    </label>
    <label>
        Book Name:
        <input type="text" @bind-value="BookName" />
    </label>
    <button type="button" @onclick="SetValueAsync">Set Value</button>
</form>
<div>Stored Value: @StoredValue</div>
<button 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() ?? "";
    }
}

Key differences between Blazor WebAssembly and Blazor Server

In Blazor Server, you can only use IJSRuntime to interact with the JavaScript.

In Blazor WebAssembly, you initialize the IndexedDB at the Program.cs instead of App.razor as with Blazor Server.

BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2024 Blazor School
An unhandled error has occurred. Reload 🗙