/BlazorIndexedDbJs

A Blazor library for accessing IndexedDB

Primary LanguageC#

build Actions Status

Nuget

This is a Blazor library for accessing IndexedDB, it uses Jake Archibald's idb library for handling access to IndexedDB API.

It tries to implement IndexedDB API with same classes and function names when possible, so you can use public documentation

This library was originally a fork from William Tulloch library Blazor.IndexedDB.

ATTENTION Current 2.3.x versions are development versions, it can suffer major changes between minor version, 2.4.x versions will be considered stable.

API

Properties

public string Name
public int Version
public List<string> ObjectStoreNames
public IList<IDBObjectStore> ObjectStores

Constructor

public IDBDatabase(IJSRuntime jsRuntime)

Methods

public async Task Open();
public async Task DeleteDatabase();
public IDBObjectStore ObjectStore(string storeName);

Properties

public string Name
public string? KeyPath
public bool AutoIncrement
public IList<IDBIndex> Indexes
public IDBDatabase IDBDatabase

Constructor

public IDBObjectStore(IDBDatabase idbDatabase);

Methods

public async Task Add<TData>(TData data);
public async Task Add<TData, TKey>(TData data, TKey key);
public async Task Put<TData>(TData data);
public async Task Put<TData, TKey>(TData data, TKey key);
public async Task Delete<TKey>(TKey key);
public async Task ClearStore();
public async Task BatchAdd<TData>(TData[] data);
public async Task BatchPut<TData>(TData[] data);
public async Task BatchDelete<TKey>(TKey[] key);
public async Task<int> Count();
public async Task<int> Count<TKey>(TKey key);
public async Task<int> Count<TKey>(IDBKeyRange<TKey> key);
public async Task<TResult?> Get<TKey, TResult>(TKey key);
public async Task<List<TResult>> GetAll<TResult>(int? count = null);
public async Task<List<TResult>> GetAll<TKey, TResult>(TKey key, int? count = null);
public async Task<List<TResult>> GetAll<TKey, TResult>(IDBKeyRange<TKey> key, int? count = null);
public async Task<List<TResult>> GetAll<TKey, TResult>(TKey[] key);
public async Task<List<TResult>> GetAllKeys<TResult>(int? count = null);
public async Task<List<TResult>> GetAllKeys<TKey, TResult>(TKey key, int? count = null);
public async Task<List<TResult>> GetAllKeys<TKey, TResult>(IDBKeyRange<TKey> key, int? count = null);
public async Task<List<TResult>> GetAllKeys<TKey, TResult>(TKey[] key);
public async Task<List<TResult>> Query<TResult>(string filter, int? count = null, int? skip = null);
public async Task<List<TResult>> Query<TKey, TResult>(string filter, TKey key, int? count = null, int? skip = null);
public async Task<List<TResult>> Query<TKey, TResult>(string filter, IDBKeyRange<TKey> key, int? count = null, int? skip = null)

Properties

public string Name
public string KeyPath
public bool MultiEntry
public bool Unique
public IDBObjectStore ObjectStore

Constructor

public IDBIndex(IDBObjectStore idbStore, string name, string keyPath, bool multiEntry = false, bool unique = false);

Methods

public async Task<int> Count(string indexName);
public async Task<int> Count<TKey>(TKey key);
public async Task<int> Count<TKey>(IDBKeyRange<TKey> key);
public async Task<TResult> Get<TKey, TResult>(TKey queryValue);
public async Task<List<TResult>> GetAll<TResult>(int? count = null);
public async Task<List<TResult>> GetAll<TKey, TResult>(TKey key, int? count = null);
public async Task<List<TResult>> GetAll<TKey, TResult>(IDBKeyRange<TKey> key, int? count = null);
public async Task<List<TResult>> GetAll<TKey, TResult>(TKey[] key);
public async Task<TResult> GetKey<TKey, TResult>(TKey queryValue);
public async Task<List<TResult>> GetAllKeys<TResult>(int? count = null);
public async Task<List<TResult>> GetAllKeys<TKey, TResult>(TKey key, int? count = null);
public async Task<List<TResult>> GetAllKeys<TKey, TResult>(IDBKeyRange<TKey> key, int? count = null);
public async Task<List<TResult>> GetAllKeys<TKey, TResult>(TKey[] key);
public async Task<List<TResult>> Query<TResult>(string filter, int? count = null, int? skip = null);
public async Task<List<TResult>> Query<TKey, TResult>(string filter, TKey key, int? count = null, int? skip = null);
public async Task<List<TResult>> Query<TKey, TResult>(string filter, IDBKeyRange<TKey> key, int? count = null, int? skip = null)

Advanced query functions

The filter expression is the body of a function that receives de parameter obj than handle each record of ObjectStore. The function must return an Object of type TResult, that will be included in the List<TResult> result and can be one of the following options:

  • the same object
  • a new object
  • an array of new objects (unwind)
  • undefined (record is not included in result)

for example, return a list of objects that contains the world "per" in property firstName ordered using index lastName.

List<Person> result = await theFactoryDb.Store("people").Index("lastName").Query<Person>(
    "if (obj.firstName.toLowerCase().includes('per')) return obj;"
);

Demo

Check simple Blazor WASM PWA demo in Demos BlazorIndexedDbJsClientDemo

Using the library

requires

NET 5.0 or newer

1. Install NuGet package

Install-Package BlazorIndexedDbJs

or

dotnet add package BlazorIndexedDbJs

2. Refence to BlazorIndexedDb.js library

For blazor wasm, in wwwroot\index.html

...
<body>
    ...
    <script src="_framework/blazor.webassembly.js"></script>

    <script src="_content/BlazorIndexedDbJs/BlazorIndexedDb.js"></script>
</body>

For blazor server, in Pages/_Host.cshtml

...
<body>
    ...
    <script src="_framework/blazor.server.js"></script>

    <script src="_content/BlazorIndexedDbJs/BlazorIndexedDb.js"></script>
</body>

3. create a database definition

Although it is not mandatory, because ObjectStores and Indexes can be accessed by name, it is preferable to define the database schema using classes, this facilitates refactoring and detecting errors at compile time.

Without classes

var people = await theFactoryDb.Store("Employees").Index("firstName").GetAll<Person>();

With classes

var people = await theFactoryDb.Employees.FirstName.GetAll<Person>();

Example of schema using classes:

Data/TheFactoryDb.cs

using System.Collections.Generic;
using Microsoft.JSInterop;
using BlazorIndexedDbJs;

namespace BlazorIndexedDbJsClientDemo.Data
{
    public class Employees: IDBObjectStore
    {
        public IDBIndex FirstName { get; }
        public IDBIndex LastName { get; }
        public IDBIndex FullName { get; }

        public Employees(IDBDatabase database): base(database)
        {
            Name = "Employees";
            KeyPath = "id";
            AutoIncrement = true;

            FirstName = new IDBIndex(this, "firstName", "firstName");
            LastName = new IDBIndex(this, "lastName", "lastName");
            FullName = new IDBIndex(this, "fullName", "firstName,lastName");
        }
    }

    public class TheFactoryDb: IDBDatabase
    {
        public Employees Employees { get; }

        public TheFactoryDb(IJSRuntime jsRuntime): base(jsRuntime)
        {
            Name = "TheFactory";
            Version = 1;

            Employees = new Employees(this);
        }
    }
}

4. Add a scoped service for each IDBDatabase

For blazor wasm, in program.cs

    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

            builder.Services.AddScoped<TheFactoryDb>();

            await builder.Build().RunAsync();
        }
    }

For blazor server, in startup.cs

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();

            services.AddScoped<TheFactoryDb>();
        }

Examples

For the following examples we are going to assume that we have Person class which is defined as follows:

    public class Person
    {
        public long? Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

    }

And the data store name is "Employees"

Accessing IDBDatabase

To use IndexedDB in a component or page, first inject the IDBDatabase instance.

@inject TheFactoryDb theFactoryDb

Open database

This will create the database if it not exists and will upgrade schema to new version if it is older.

await theFactoryDb.Open()

Getting all records from a store

var people = await theFactoryDb.Employees.GetAll<Person>();

Get one record by Id

var person = await theFactoryDb.Employees.Get<long, Person>(id);

Getting one record using an index

var person = await theFactoryDb.Employees.FirstName.Get<string, Person>("John");

Getting all records from an index

var people = await theFactoryDb.Employees.FirstName.GetAll<string, Person>("John");

Adding a record to an IDBObjectStore

var newPerson = new Person() {
    FirstName = "John",
    LastName = "Doe"
};

await theFactoryDb.Employees.Add(newPerson);

Updating a record

await theFactoryDb.Employees.Put<Person>(recordToUpdate)

Deleting a record

await theFactoryDb.Employees.Delete<int>(id)

Clear all records from a store

await theFactoryDb.Employees.Clear()

Deleting the database

await theFactoryDb.DeleteDb()