BlazorEssentials.IndexedDb 3.0.0-CI-20241025-225746

This is a prerelease version of BlazorEssentials.IndexedDb.
dotnet add package BlazorEssentials.IndexedDb --version 3.0.0-CI-20241025-225746                
NuGet\Install-Package BlazorEssentials.IndexedDb -Version 3.0.0-CI-20241025-225746                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BlazorEssentials.IndexedDb" Version="3.0.0-CI-20241025-225746" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add BlazorEssentials.IndexedDb --version 3.0.0-CI-20241025-225746                
#r "nuget: BlazorEssentials.IndexedDb, 3.0.0-CI-20241025-225746"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install BlazorEssentials.IndexedDb as a Cake Addin
#addin nuget:?package=BlazorEssentials.IndexedDb&version=3.0.0-CI-20241025-225746&prerelease

// Install BlazorEssentials.IndexedDb as a Cake Tool
#tool nuget:?package=BlazorEssentials.IndexedDb&version=3.0.0-CI-20241025-225746&prerelease                

Build status

Nuget

BlazorEssentials.IndexedDb

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

It maps as closely to the browser IndexedDB API as possible, but in a .NET way, so you can use public documentation.

Features

  • A clean, simple, intuitive async-first .NET API for IndexedDB
  • Create in-browser databases that feel more like the Entity Framework DbContext
  • Support for multiple databases in the same application
  • Uses Blazor JavaScript Isolation, no script installation required
  • Supports .NET 8.0 using idb 8.0.0

Code Provenance

This library is a fork of BlazorIndexedDbJs, which in turn is a fork of Blazor.IndexedDB.

The original library was licensed under the MIT License, and this library is as well.

Differences from BlazorIndexedDbJs

  • Refactored to be .NET-first
    • Objects prefixed with IndexedDb instead of IDB (the later conflicts with C# interface naming conventions)
    • Async method names now end in Async
  • More automagic
    • Constructor-based, attribute-based, or reflection-based initialization (see examples below)
    • The default codepath sets the name of your Database and ObjectStores based on the class or property name
  • Redesigned JSInterop
    • Uses JS Isolation (Blazor dynamically loads the JS as modules)
    • Multiple database instance support
    • Better tracking for database open state for fewer runtime errors
  • Re-engineered build process
    • Uses MSBuild-based TypeScript compilation
    • Eliminates WebPack
    • Uses SkyPack to load third-party modules like idb remotely, resulting in smaller package sizes

Demo

You can see a demo of using IndexedDbDatabase and ViewModels together in our Sample App.

Using the library

requires

NET 8.0 or newer

Step 1: Install NuGet package

Install-Package BlazorEssentials.IndexedDb

or

dotnet add package BlazorEssentials.IndexedDb

Step 2: Make the necessary classes available in your Razor files

Add the following to your _Imports.razor file:

@using CloudNimble.BlazorEssentials.IndexedDb

Step 3: Create an IndexedDBDatabase class

This file should feel very similar to a DbContext class. Here is a basic implementation, using one of my favorite childhood restaurants as an example:

Data/TheSpaghettiFactoryDb.cs

using Microsoft.JSInterop;
using CloudNimble.BlazorEssentials.IndexedDb;

namespace BlazorEssentials.IndexedDb.Demo.Data
{

    public class TheFactoryDb: IndexedDbDatabase
    {

        public IndexedDbObjectStore Employees { get; }

        public TheSpaghettiFactoryDb(IJSRuntime jsRuntime): base(jsRuntime)
        {
            Name = "TheSpaghettiFactory";
            Version = 1;
        }

    }

}

Or you can customize it with attributes. In the below example:

  • the database name will be "TheSpaghettiFactoryDb"
  • the table name will be "FiredEmployees"
  • the ID for the table is the "id" property
  • you wll be expected to manage your own keys
  • there will be an index on the firstName property for the FiredEmployees IndexedDbObjectStore (table)

Data/TheSpaghettiFactoryDb.cs

using Microsoft.JSInterop;
using CloudNimble.BlazorEssentials.IndexedDb;

namespace BlazorEssentials.IndexedDb.Demo.Data
{

    public class TheSpaghettiFactoryDb: IndexedDbDatabase
    {

        [ObjectStore(Name = "FiredEmployees", AutoIncrementKeys = false)]
        [Index(Name = "FirstName", KeyPath = "firstName")]]
        public IndexedDbObjectStore Employees { get; }

        public TheSpaghettiFactoryDb(IJSRuntime jsRuntime): base(jsRuntime)
        {
            Version = 1;
        }

    }

}

Step 4. Add each database to your Blazor application's Dependency Injection container

For Blazor WebAssembly, 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) });

            // RWM: You can add this as a Singleton because a WebAssembly app runs in the browser and only has one "user".
            builder.Services.AddSingleton<TheSpaghettiFactoryDb>();

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

For Blazor Web, in startup.cs

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

            // RWM: Here the database is scoped because each user has their own session.
            services.AddScoped<TheSpaghettiFactoryDb>();
        }

Step 5: Use the database in your Blazor components

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; }

    }

Mote that you DO NOT have to decorate your objects with any attributes. As IndexedDb is a NoSQL database, it is schema-less, so your object will be serialized and deserialized using the default JSON serializer.

You can also mix types in the same ObjectStore (table), but be careful is you use numbers for keys or objects may collide.

Accessing the IndexedDbDatabase

To use IndexedDB in a component or page, first inject the IndexedDbDatabase instance, in this case the TheSpaghettifactoryDb class.

@inject TheSpaghettiFactoryDb database

Open database

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

NOTE: Query calls will do this automatically if the database is not already open.

await database.Open()

Getting all records from a store

var people = await database.Employees.GetAllAsync<Person>();

Get one record by Id

var person = await database.Employees.GetAsync<long, Person>(id);

Getting one record using an index

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

Getting all records from an index

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

Adding a record to an IDBObjectStore

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

await database.Employees.AddAsync(newPerson);

Updating a record

await database.Employees.PutAsync<Person>(recordToUpdate)

Deleting a record

await database.Employees.DeleteAsync<int>(id)

Clear all records from a store

await database.Employees.ClearAsync()

Deleting the database

await database.DeleteDatabaseAsync()

API

IndexedDbDatabase

Properties

name
public string Name
version
public int Version
objectStores
public IList<IndexedDBObjectStore> ObjectStores

Constructor

public IndexedDBDatabase(IJSRuntime jsRuntime)

Methods

open()
public async Task OpenAsync();
deleteDatabase()
public async Task DeleteDatabaseAsync();
IndexedDbObjectStore

Properties

name
public string Name
keyPath
public string? KeyPath
autoIncrement
public bool AutoIncrement
Indexes
public IList<IndexedDbIndex> Indexes
IndexedDbDatabase
public IndexedDbDatabase IndexedDbDatabase

Constructors

public IndexedDbObjectStore(IndexedDbDatabase database, ObjectStoreAttribute attribute = null);
public IndexedDbObjectStore(IndexedDbDatabase database, string name, string keyPath = "id", bool autoIncrement = false)

Methods

add()
public async Task AddAsync<TData>(TData data);
public async Task AddAsync<TData, TKey>(TData data, TKey key);
put()
public async Task PutAsync<TData>(TData data);
public async Task PutAsync<TData, TKey>(TData data, TKey key);
delete()
public async Task DeleteAsync<TKey>(TKey key);
clear()
public async Task ClearStoreAsync();
Batch (add/put/delete) functions
public async Task BatchAddAsync<TData>(TData[] data);
public async Task BatchPutAsync<TData>(TData[] data);
public async Task BatchDeleteAsync<TKey>(TKey[] key);
count()
public async Task<int> CountAsync();
public async Task<int> CountAsync<TKey>(TKey key);
public async Task<int> CountAsync<TKey>(IDBKeyRange<TKey> key);
get()
public async Task<TResult?> GetAsync<TKey, TResult>(TKey key);
getAll()
public async Task<List<TResult>> GetAllAsync<TResult>(int? count = null);
public async Task<List<TResult>> GetAllAsync<TKey, TResult>(TKey key, int? count = null);
public async Task<List<TResult>> GetAllAsync<TKey, TResult>(IDBKeyRange<TKey> key, int? count = null);
public async Task<List<TResult>> GetAllAsync<TKey, TResult>(TKey[] key);
getAllKeys()
public async Task<List<TResult>> GetAllKeysAsync<TResult>(int? count = null);
public async Task<List<TResult>> GetAllKeysAsync<TKey, TResult>(TKey key, int? count = null);
public async Task<List<TResult>> GetAllKeysAsync<TKey, TResult>(IDBKeyRange<TKey> key, int? count = null);
public async Task<List<TResult>> GetAllKeysAsync<TKey, TResult>(TKey[] key);
Query
public async Task<List<TResult>> QueryAsync<TResult>(string filter, int? count = null, int? skip = null);
public async Task<List<TResult>> QueryAsync<TKey, TResult>(string filter, TKey key, int? count = null, int? skip = null);
public async Task<List<TResult>> QueryAsync<TKey, TResult>(string filter, IDBKeyRange<TKey> key, int? count = null, int? skip = null)
IndexedDbIndex

Properties

name
public string Name
keyPath
public string KeyPath
multiEntry
public bool MultiEntry
unique
public bool Unique
objectStore
public IndexedDbObjectStore ObjectStore

Constructor

public IndexedDbIndex(IndexedDbObjectStore idbStore, string name, string keyPath, bool multiEntry = false, bool unique = false);

Methods

count()
public async Task<int> CountAsync(string indexName);
public async Task<int> CountAsync<TKey>(TKey key);
public async Task<int> CountAsync<TKey>(IDBKeyRange<TKey> key);
get()
public async Task<TResult> GetAsync<TKey, TResult>(TKey queryValue);
getAll()
public async Task<List<TResult>> GetAllAsync<TResult>(int? count = null);
public async Task<List<TResult>> GetAllAsync<TKey, TResult>(TKey key, int? count = null);
public async Task<List<TResult>> GetAllAsync<TKey, TResult>(IDBKeyRange<TKey> key, int? count = null);
public async Task<List<TResult>> GetAllAsync<TKey, TResult>(TKey[] key);
getKey()
public async Task<TResult> GetKeyAsync<TKey, TResult>(TKey queryValue);
getAllKeys()
public async Task<List<TResult>> GetAllKeysAsync<TResult>(int? count = null);
public async Task<List<TResult>> GetAllKeysAsync<TKey, TResult>(TKey key, int? count = null);
public async Task<List<TResult>> GetAllKeysAsync<TKey, TResult>(IDBKeyRange<TKey> key, int? count = null);
public async Task<List<TResult>> GetAllKeysAsync<TKey, TResult>(TKey[] key);
Query
public async Task<List<TResult>> QueryAsync<TResult>(string filter, int? count = null, int? skip = null);
public async Task<List<TResult>> QueryAsync<TKey, TResult>(string filter, TKey key, int? count = null, int? skip = null);
public async Task<List<TResult>> QueryAsync<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;"
);
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on BlazorEssentials.IndexedDb:

Package Downloads
SimpleMessageBus.IndexedDb.Core

SimpleMessageBus is a system for making applications more reliable and responsive to users by processing potentially long-running tasks out-of-band from the user's main workflow. It is designed to run either on-prem, or in the Microsoft Cloud, making it suitable for any application, and able to grow as your needs do.

GitHub repositories

This package is not used by any popular GitHub repositories.