A Blazor library that provides seamless interaction with IndexedDB via JavaScript interop. This library simplifies data storage and retrieval in Blazor applications by offering a straightforward API for IndexedDB operations.
- Features
- Installation
- Getting Started
- API Reference
- Examples
- Demo Application
- Contributing
- License
- Acknowledgments
- Easy Setup: Simplify the configuration and use of IndexedDB in Blazor applications.
- Type Safety: Utilize generics for strong typing and compile-time checks.
- Async Operations: All methods are asynchronous, providing non-blocking data operations.
- Transactions Support: Execute multiple operations within a single transaction.
- Indexing: Create and query indexes for efficient data retrieval.
- Schema Migration: Upgrade database schemas with versioning and store schemas.
- Comprehensive API: Provides methods for CRUD operations, filtering, counting, and clearing data.
Install the package via NuGet Package Manager:
dotnet add package IdxDb --version 1.0.0-beta.1Or via the NuGet Package Manager UI in Visual Studio by searching for IndexedDb.
Before using the library, you need to define your database schema and initialize it. This is typically done in the OnInitializedAsync method of your Blazor component or in the startup configuration.
@page "/"
@using IndexedDb
@using IndexedDb.BlazorApp.Services
@inject IndexedDbInterop IndexedDbInterop
<PageTitle>Home</PageTitle>
<h1>IndexedDB Demo</h1>
<!-- Your UI Components Here -->
@code {
private IndexedDbRepository<Person> _personRepository;
protected override async Task OnInitializedAsync()
{
// Initialize IndexedDbInterop
_personRepository = new IndexedDbRepository<Person>(IndexedDbInterop, "demo", "persons");
// Define the store schema with indexes
var storeSchemas = new[]
{
new
{
name = "persons",
options = new { keyPath = "id", autoIncrement = false },
indexes = new[]
{
new { name = "ageIndex", keyPath = "age", unique = false }
}
}
};
// Upgrade or initialize the database
await _personRepository.UpgradeDatabaseAsync("demo", 1, storeSchemas);
// Retrieve all persons
await RetrieveAllPersonsAsync();
}
public async ValueTask DisposeAsync()
{
if (_personRepository != null)
{
await _personRepository.DisposeAsync();
}
}
// Other methods will be detailed in the "Using IndexedDbInterop" section
}The IndexedDbInterop class provides direct methods to interact with IndexedDB. For higher-level operations, the IndexedDbRepository<TItem> class offers a more convenient API.
-
Register Services in DI Container
Ensure that
IndexedDbInteropis registered in your dependency injection (DI) container. Typically, this is done in theProgram.csorStartup.csfile.builder.Services.AddScoped<IndexedDbInterop>(); builder.Services.AddScoped(typeof(IndexedDbRepository<>));
-
Inject the Repository into Your Component
@inject IndexedDbRepository<Person> PersonRepository
-
Initialize the Database and Perform Operations
Here's an example of how to add, retrieve, update, delete, filter, count, and clear records using the
IndexedDbRepository.@page "/" @using IndexedDb @using IndexedDb.BlazorApp.Models @using IndexedDb.BlazorApp.Services @inject IndexedDbRepository<Person> PersonRepository @inject IJSRuntime JsRuntime <PageTitle>Home</PageTitle> <h1>IndexedDB Demo</h1> <!-- Your UI Components Here --> @code { // Component code as shown in the [Database Initialization] section }
The IndexedDbInterop class provides methods to interact directly with IndexedDB.
AddOneAsync(string dbName, string storeName, object item)AddManyAsync(string dbName, string storeName, object[] items)GetAllAsync<T>(string dbName, string storeName)GetOneAsync<TRecord, TKey>(string dbName, string storeName, TKey id)UpdateOneAsync(string dbName, string storeName, object item)DeleteOneAsync<TKey>(string dbName, string storeName, TKey id)UpgradeDatabaseAsync(string dbName, int newVersion, object[] storeSchemas)CreateIndexAsync(string dbName, string storeName, string indexName, string keyPath, bool unique = false)GetAllByIndexAsync<T>(string dbName, string storeName, string indexName, object query)ExecuteTransactionAsync(string dbName, string[] storeNames, string mode, Func<Task> transactionBody)CountAsync(string dbName, string storeName)ClearStoreAsync(string dbName, string storeName)DisposeAsync()
The IndexedDbRepository<TItem> class simplifies data operations by setting the database name, store name, and item type.
public IndexedDbRepository(IndexedDbInterop indexedDbInterop, string dbName, string storeName)AddOneAsync(TItem item)AddManyAsync(TItem[] items)GetAllAsync()GetOneAsync<TKey>(TKey id)UpdateOneAsync(TItem item)DeleteOneAsync<TKey>(TKey id)GetAllByIndexAsync<TIndex>(string indexName, object query)CountAsync()ClearStoreAsync()UpgradeDatabaseAsync(string dbName, int newVersion, object[] storeSchemas)DisposeAsync()
private async Task AddPersonAsync()
{
if (string.IsNullOrWhiteSpace(_editPerson.Name))
{
_message = "Name is required.";
return;
}
if (_editPerson.Age <= 0)
{
_message = "Age must be a positive number.";
return;
}
try
{
// Create a new person
var personToAdd = new Person
{
Id = Guid.NewGuid(),
Name = _editPerson.Name,
Age = _editPerson.Age
};
// Add the person to the database
await _personRepository.AddOneAsync(personToAdd);
// Update the list of people
_people = _people.Append(personToAdd).ToArray();
// Clear the form
_editPerson = new Person();
_message = "Person added successfully.";
}
catch (Exception ex)
{
_message = $"Error adding person: {ex.Message}";
}
}private async Task RetrieveAllPeopleAsync()
{
try
{
_people = await _personRepository.GetAllAsync();
_message = "People retrieved successfully.";
}
catch (Exception ex)
{
_message = $"Error retrieving people: {ex.Message}";
}
}private async Task UpdatePersonAsync()
{
if (string.IsNullOrWhiteSpace(_editPerson.Name))
{
_message = "Name is required.";
return;
}
if (_editPerson.Age <= 0)
{
_message = "Age must be a positive number.";
return;
}
try
{
// Update the person in the database
await _personRepository.UpdateOneAsync(_editPerson);
// Update the local list
var index = Array.FindIndex(_people, p => p.Id == _editPerson.Id);
if (index != -1)
{
_people[index] = _editPerson;
}
// Clear the edit form
_editPerson = new Person();
_message = "Person updated successfully.";
}
catch (Exception ex)
{
_message = $"Error updating person: {ex.Message}";
}
}private async Task DeletePersonAsync(Guid id)
{
try
{
// Delete the person from the database
await _personRepository.DeleteOneAsync(id);
// Remove from the local list
_people = _people.Where(p => p.Id != id).ToArray();
_message = "Person deleted successfully.";
}
catch (Exception ex)
{
_message = $"Error deleting person: {ex.Message}";
}
}Retrieve all people of a certain age using the ageIndex.
private async Task FilterPeopleByAgeAsync()
{
if (_filterAge <= 0)
{
_message = "Please enter a valid age to filter.";
return;
}
try
{
// Use the index to filter people by age
_people = await _personRepository.GetAllByIndexAsync<Person>("ageIndex", _filterAge);
_message = $"Filtered people by age: {_filterAge}";
}
catch (Exception ex)
{
_message = $"Error filtering people: {ex.Message}";
}
}
private async Task ResetFilter()
{
_filterAge = 0;
await RetrieveAllPeopleAsync();
_message = "Filter reset.";
}Get the total number of person records.
private async Task GetPeopleCountAsync()
{
try
{
_peopleCount = await _personRepository.CountAsync();
}
catch (Exception ex)
{
_message = $"Error counting people: {ex.Message}";
}
}Remove all person records from the database.
private async Task ClearAllRecordsAsync()
{
bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure you want to delete all records?");
if (!confirmed)
{
_message = "Clear store operation canceled.";
return;
}
try
{
await _personRepository.ClearStoreAsync();
_people = Array.Empty<Person>();
_peopleCount = 0;
_message = "All records have been cleared.";
}
catch (Exception ex)
{
_message = $"Error clearing records: {ex.Message}";
}
}While the IndexedDb Blazor Library provides the foundational tools for interacting with IndexedDB, a demo application is available to showcase practical implementations and advanced usage scenarios.
- CRUD Operations: Demonstrates adding, retrieving, updating, and deleting records.
- Filtering with Indexes: Shows how to filter data using IndexedDB indexes for efficient queries.
- Counting Records: Displays the total number of records in the object store.
- Clearing the Object Store: Provides functionality to clear all records.
- User Feedback: Implements error handling and user notifications for better UX.
-
Navigate to the Demo Directory
cd IndexedDb.DemoApp -
Restore Dependencies
dotnet restore
-
Run the Application
dotnet run
-
Access the Demo
Open your browser and navigate to
https://localhost:8843(or the specified URL) to interact with the demo application.
The demo application includes a PersonRepository that extends the library's capabilities for managing Person entities. It showcases how to utilize the library's methods in a real-world scenario.
Note: The PersonRepository is not part of the IndexedDb Blazor Library but serves as an example in the demo application.
Contributions are welcome! Please follow these steps:
-
Fork the Repository
Click the "Fork" button at the top-right corner of the repository page to create a personal copy of the repository.
-
Create a New Branch
git checkout -b feature/your-feature-name
-
Make Your Changes
Implement your feature or fix in the new branch.
-
Commit Your Changes
git commit -am 'feat: add new feature' -
Push to the Branch
git push origin feature/your-feature-name
-
Submit a Pull Request
Navigate to the original repository and click "Compare & pull request" to submit your changes for review.
Please ensure all new code is covered by unit tests and adheres to the existing coding standards.
This project is licensed under the MIT License - see the LICENSE file for details.
- Nuke Build: For build automation.
- MinVer: For versioning based on Git tags.
- IndexedDB API: The underlying technology enabling this library.
- fake-indexeddb: For facilitating testing of IndexedDB in Jest.
- Blazor: For the powerful framework enabling interactive web UIs with C#.