Storing local data in a .NET MAUI Blazor Hybrid App using IndexedDB - Part 1
Tags: dotnet, dotnetmaui, blazor, indexeddb
Published: 2023-12-01 11:01:37

_This publication is part of the **.NET MAUI Advent Calendar 2023**, an initiative led by Héctor Pérez, Alex Rostan, Pablo Piovano, and Luis Beltrán. Check **[this link](https://devs.school/net-maui-advent-calendar-23/)** for more interesting articles about .NET MAUI created by the community._

The most commonly used option for storing structured data locally in a .NET MAUI app is SQLite database. However, because it is possible to create a .NET MAUI Blazor Hybrid app, a new option can be considered: **IndexedDB**, a database that is built into a browser,

## Definition
Taken from [here](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB):

> IndexedDB is a way to persistently store data inside the browser. Because it lets you create web applications with rich query abilities regardless of network availability, your applications can work both online and offline.

Before writing this post, I didn't know if it was possible or not to use IndexedDB in a Blazor Hybrid app. In theory, it is possible, so I thought it would be an interesting case to explore. I'm not sure if there are advantages or disadvantages over SQLite, but I do know that there is the typical benefit around Blazor Hybrid: _If you already have a web application which stores local data using IndexedDB, then you can bring your code to a mobile app using Blazor Hybrid_.

Let's demonstrate this. Moreover, I am going to use the experimental [**MauiHybridWebView**](https://github.com/Eilon/MauiHybridWebView) component that was [presented a few months ago](https://www.youtube.com/watch?v=NxFunRzi-tc) and [highlighted during the .NET Conf 2023](https://www.youtube.com/watch?v=u30XwO9-10Q). This component allows you to use JavaScript in your .NET MAUI app, it also enables communication between the code in the WebView (JavaScript) and the code that hosts the WebView (C#/.NET), so you can have a React JS app hosted in a .NET MAUI native app. It's amazing!

## Step 1. Create & configure the project

First of all, create a Blazor Hybrid .NET MAUI app. _You must use .NET 7 at minimum for the MauiHybridWebView component to work._

![Creating a project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/57tvxfep8qvc7yx3ie1s.png)

Add the `EJL.MauiHybridWebView` NuGet package in your application:


![Adding the NuGet package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b34o3qir0ybgzzh2mfkj.png)


Using the `Solution Explorer`, open the `Resources` folder of your project. Inside the `raw` folder, create a new folder named `hybrid_root`. Then, create two new files there: `index.html` and `dbscript.js`:

![Web files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rt589h89atokmfp2ei60.png)

## Step 2. Add the .NET MAUI C#/XAML mobile code:

Open `MauiProgram.cs`. Add the `HybridWebView` support in the `CreateMauiApp` method, just before returning the `MauiApp` built instance:

```
...
public static MauiApp CreateMauiApp()
{
...
  builder.Services.AddHybridWebView();
  return builder.Build();
}
```

Now, open `MainPage.xaml` and delete the controls (just keep the top `ContentPage` definitions). Then, modify it according to the following instructions:

* Add a reference to the `HybridWebView` assembly.
* Add a `HybridWebView` component in the `Content` section of the `ContentPage`. Set the following properties and values:
  * Set `Name` to `hwv`.
  * Set `HybridAssetRoot` to the `hybrid_root` folder created in Step 1.
  * Set `MainFile` to the `index.html` file created in Step 1.
  * Set `RawMessageReceived` to an `OnJsRawMessageReceived` method that will be created in the C# code.

This is a sample implementation code:

```
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
  ... 
  xmlns:ejl="clr-namespace:HybridWebView;assembly=HybridWebView"
  ...>

    <ejl:HybridWebView x:Name="hwv" 
                       HybridAssetRoot="hybrid_root" 
                       MainFile="index.html" 
                       RawMessageReceived="OnJsRawMessageReceived" />

</ContentPage>
```

Then, open `MainPage.xaml.cs`. Here:

* Add the `HybridWebView` namespace.
* Enable the Web Dev Tools from the `HybridWebView` component in the constructor after `InitializeComponent` method.
* Create an async method called `OnJsRawMessageReceived` which displays a message sent by JavaScript code. It uses a `Dispatcher` for a safe interaction with the UI. The message is included in the `HybridWebViewRawMessageReceivedEventArgs` argument from the `HybridWebView` component.

This is the code:

```
using HybridWebView;

namespace NetMauiIndexedDb
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            hwv.EnableWebDevTools = true;
        }

        private async void OnJsRawMessageReceived(object sender, HybridWebView.HybridWebViewRawMessageReceivedEventArgs e)
        {
            await Dispatcher.DispatchAsync(async () =>
            {
                await DisplayAlert("JavaScript message", e.Message, "OK");
            });
        }
    }
}
```

## Step 3. Add the HTML/JS web code:

For the `index.html` page, you can: 

* Define a basic HTML5 page that references two scripts: `HybridWebView.js` (from the NuGet package) and `dbscript.js` (which includes the code to handle the IndexedDB database). 
* Add a button. When it is pressed, it will execute a `load_data` method defined in `dbscript.js`.
* Add an ordered list. This one will dynamically show the data stored in a `student` table from the IndexedDB database.

Please note, the `dbscript.js` reference is added before the body ends, because we need the HTML elements to be created first. 

This is the code:

```
<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
    <script src="_hwv/HybridWebView.js"></script>
</head>
<body>
    <div>
        <button onclick="load_data()">Load Data</button>
    </div>

    <br />

    <div>
        <h2>Students</h2>
        <ol></ol>
    </div>

    <script src="dbscript.js"></script>
</body>
</html>
```

Finally, for `dbscript.js`, two local variables are created: `students_table` (for the name of the table that will exist in our database) and `students` (the ordered list that will display the table content). 

Moreover, some methods are defined:

* `load_data`: It simply invokes `init_database` method.
* `init_database`: First of all, it checks if indexedDB is supported in the browser. If so, then it tries to set up a connection to a `schoolDB` IndexedDB database. If it doesn't exist yet, it is created. The `onupgradeneeded` event is invoked once the database is created for the first time, and here we create the `students` table (object store). The `onsuccess` event is fired once we have successfully connected to the database, and here the `insert_student` method is called twice. 
* `insert_student`: As you can expect, this method adds an entry in the `students` table.
* `show_students`: This method firstly removes all the content of the ordered list. Then, it gets all entries from the `students` table. A `Cursor` object iterates over each record in the table to create dynamic HTML content to display each entry.

By the way, the `SendRawMessageToDotNet` method (from the `HybridWebView` component) is used to send a message from JavaScript to .NET code. 

```
let students_table = 'Students';
let students = document.querySelector("ol");

function load_data() {
    init_database();
}

function init_database() {
    if (!window.indexedDB) {
        HybridWebView.SendRawMessageToDotNet("Your browser doesn't support IndexedDB");
        return;
    }

    let db;
    const request = indexedDB.open('schoolDB', 1);

    request.onerror = (event) => {
        HybridWebView.SendRawMessageToDotNet("Database error: " + event.target.errorCode);
    };

    request.onsuccess = (event) => {
        db = event.target.result;

        insert_student(db, {
            name: 'John Doe',
            faculty: 'FAI'
        });

        insert_student(db, {
            name: 'Jane Doe',
            faculty: 'FAME'
        });

        show_students(db);
    };

    request.onupgradeneeded = (event) => {
        db = event.target.result;

        let store = db.createObjectStore(students_table, {
            autoIncrement: true,
            keyPath: 'id' 
        });
    };
}

function insert_student(db, student) {
    const txn = db.transaction(students_table, 'readwrite');
    const store = txn.objectStore(students_table);

    let query = store.put(student);

    query.onsuccess = function (event) {
        console.log(event);
    };

    query.onerror = function (event) {
        console.log(event.target.errorCode);
    }

    txn.oncomplete = function () {
        db.close();
    };
}

function show_students(db) {
    while (students.firstChild) {
        students.removeChild(students.firstChild);
    }

    const txn = db.transaction(students_table, 'readwrite');

    const store = txn.objectStore(students_table);
    store.openCursor().addEventListener('success', e => {
        const pointer = e.target.result;

        if (pointer) {
            const listItem = document.createElement('li');
            const h3 = document.createElement('h3');
            const pg = document.createElement('p');
            listItem.appendChild(h3);
            listItem.appendChild(pg);
            students.appendChild(listItem);

            h3.textContent = pointer.value.name;
            pg.textContent = pointer.value.faculty;
            listItem.setAttribute('data-id', pointer.value.id);

            pointer.continue();
        } else {
            if (!students.firstChild) {
                const listItem = document.createElement('li');
                listItem.textContent = 'No Students.'
                students.appendChild(listItem);
            }

            HybridWebView.SendRawMessageToDotNet("Data has been loaded");
        }
    });
}
```


## Step 4. Test the app.

Now, you should be able to build the project and test it without any issues. I will test it in a Windows app first.

Here is the initial content of our app. As you can observe, only HTML components (a button and a title) are displayed, since we didn't really add any XAML UI except the Web View that hosts the web elements:


![Initial view of the app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/as7vp8ww1x34kmfduigo.png)


Click on the button. The JavaScript code that interacts with an `IndexedDB` database in the browser is executed, and the following output is presented:


![Data loaded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/csl94u1gf9rrzekgb794.png)


Success! Our app has created a local database with one table and two entries, which are displayed in the app. Moreover, a JavaScript message was passed to the C# part, and this communication is possible thanks to the HybridWebView component. Would it be possible to pass the data instead of a simple message so we can build a UI using XAML? I guess I've just found a new topic to write about, so I will explore this scenario soon =)

Finally, don't forget that since we enabled the Web DevTools, we can bring them to debug or, even better, to see our database:


![IndexedDB content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e846pbj8ij7kjl4m7udj.png)

Great! The IndexedDB storage contains the `schoolDB` database that we created in our app (under `Application` --> `Storage` section). Then, a `Students` table containing two entries is there, so everything is working as expected.

Before we go, two disclaimers:

* It might happen that when you run the app, the window is empty. I'm not sure if this is a bug of the `HybridWebView` (don't forget that it is an experimental component) or if it happens because the `Web DevTools` were enabled. Simply run the app again and it should work (try again if not, it will eventually show up). I noticed that when I comment the line that enables the tools, the app works without any issues, so probably this is an issue. I will do some exploration.

* If you run the app again, the entries will be duplicated. This is kind of obvious, since we insert them after the connection to the database is successful. You can clear the database/table by using the Web DevTools at any time, of course.

Well, that's it! You can find the source code of this project in my [GitHub repo](https://github.com/icebeam7/NetMauiIndexedDb).

I hope this post was helpful for you. Remember to follow the rest of the interesting publications of the [.NET MAUI Advent Calendar 2023](https://devs.school/net-maui-advent-calendar-23/). 

Thank you for reading. Until next time!

Luis