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 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:
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 component that was presented a few months ago and highlighted during the .NET Conf 2023. 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.
Add the EJL.MauiHybridWebView
NuGet package in your application:
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
:
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 theContent
section of theContentPage
. Set the following properties and values:- Set
Name
tohwv
. - Set
HybridAssetRoot
to thehybrid_root
folder created in Step 1. - Set
MainFile
to theindex.html
file created in Step 1. - Set
RawMessageReceived
to anOnJsRawMessageReceived
method that will be created in the C# code.
- Set
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 afterInitializeComponent
method. - Create an async method called
OnJsRawMessageReceived
which displays a message sent by JavaScript code. It uses aDispatcher
for a safe interaction with the UI. The message is included in theHybridWebViewRawMessageReceivedEventArgs
argument from theHybridWebView
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) anddbscript.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 indbscript.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 invokesinit_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 aschoolDB
IndexedDB database. If it doesn't exist yet, it is created. Theonupgradeneeded
event is invoked once the database is created for the first time, and here we create thestudents
table (object store). Theonsuccess
event is fired once we have successfully connected to the database, and here theinsert_student
method is called twice. -
insert_student
: As you can expect, this method adds an entry in thestudents
table. -
show_students
: This method firstly removes all the content of the ordered list. Then, it gets all entries from thestudents
table. ACursor
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:
Click on the button. The JavaScript code that interacts with an IndexedDB
database in the browser is executed, and the following output is presented:
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:
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 theWeb 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.
I hope this post was helpful for you. Remember to follow the rest of the interesting publications of the .NET MAUI Advent Calendar 2023.
Thank you for reading. Until next time!
Luis