Esta publicación forma parte del Calendario de Adviento .NET MAUI 2023, una iniciativa liderada por Héctor Pérez, Alex Rostan, Pablo Piovano y Luis Beltrán. Consulta este enlace para obtener más artículos interesantes sobre .NET MAUI creados por la comunidad.
La opción más utilizada para almacenar datos estructurados localmente en una aplicación .NET MAUI es la base de datos SQLite. Sin embargo, debido a que es posible crear una aplicación .NET MAUI Blazor Hybrid, se puede considerar una nueva opción: IndexedDB, una base de datos integrada en un navegador.
Definición
Tomada de aquí:
IndexedDB es una forma de almacenar datos de forma persistente dentro del navegador. Debido a que le permite crear aplicaciones web con capacidades de consulta enriquecidas independientemente de la disponibilidad de la red, sus aplicaciones pueden funcionar tanto en línea como fuera de línea.
Antes de escribir esta publicación, no sabía si era posible usar IndexedDB en una aplicación Blazor Hybrid. En teoría lo es, así que pensé que sería un caso interesante de explorar. No estoy seguro de si existen ventajas o desventajas sobre SQLite, pero sí sé que existe el beneficio típico de Blazor Hybrid: Si ya tienes una aplicación web que almacena datos locales utilizando IndexedDB, puedes traer tu código a un aplicación móvil que utiliza Blazor Hybrid.
Demostremos esto. Además, voy a utilizar el componente experimental MauiHybridWebView que fue presentado hace unos meses y destacado durante .NET Conf 2023. Este componente te permite usar JavaScript en su aplicación .NET MAUI; además, hace posible la comunicación entre el código en WebView (JavaScript) y el código que aloja WebView (C#/.NET) para que puedas tener por ejemplo una aplicación React JS alojada en una aplicación nativa .NET MAUI. ¡Suena increíble!
Paso 1. Crear y configurar el proyecto
En primer lugar, crea una aplicación Blazor Hybrid .NET MAUI. Debes utilizar .NET 7 como mínimo para que funcione el componente MauiHybridWebView.
Agrega el paquete NuGet EJL.MauiHybridWebView
en tu aplicación:
Usando el Explorador de soluciones
, abre la carpeta Resources
de tu proyecto. Dentro de la carpeta "raw", crea una nueva carpeta llamada "hybrid_root". Luego, crea dos archivos nuevos allí: index.html
y dbscript.js
:
Paso 2. Agrega el código .NET MAUI C#/XAML (parte móvil):
Abre MauiProgram.cs
. Agrega el soporte a HybridWebView
en el método CreateMauiApp
, justo antes de devolver la instancia construida MauiApp
:
...
public static MauiApp CreateMauiApp()
{
...
builder.Services.AddHybridWebView();
return builder.Build();
}
Ahora, abre MainPage.xaml
y elimina los controles (solo conserva las definiciones de ContentPage
ubicadas en la parte superior). Luego, modifícalo de acuerdo con las siguientes instrucciones:
- Agrega una referencia al ensamblado
HybridWebView
. - Agrega un componente
HybridWebView
en la secciónContent
deContentPage
. Establece las siguientes propiedades y valores:- Coloca
Name
enhwv
. - Configura la propiedad
HybridAssetRoot
a la carpetahybrid_root
creada en el Paso 1. - Establece la propiedad
MainFile
en el archivoindex.html
creado en el Paso 1. - Establece
RawMessageReceived
en un métodoOnJsRawMessageReceived
que se creará en el código C#.
- Coloca
El siguiente código muestra lo anterior:
<?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>
Ahora, abre MainPage.xaml.cs
. Aquí realiza lo siguiente:
- Agrega el espacio de nombres
HybridWebView
. - Habilita las herramientas de desarrollo web desde el componente
HybridWebView
en el constructor después del métodoInitializeComponent
. - Crea un método asíncrono llamado
OnJsRawMessageReceived
que muestra un mensaje enviado mediante código JavaScript. Se utiliza unDispatcher
para una interacción segura con la interfaz de usuario. El mensaje se incluye en el argumentoHybridWebViewRawMessageReceivedEventArgs
del componenteHybridWebView
.
Este es el código:
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");
});
}
}
}
Paso 3. Agrega el código HTML/JS (parte web):
Para la página index.html
, puedes:
- Definir una página HTML5 básica que haga referencia a dos scripts:
HybridWebView.js
(del paquete NuGet) ydbscript.js
(que incluye el código para manejar la base de datos IndexedDB). - Agregar un botón: Cuando se presiona, ejecutará un método
load_data
definido endbscript.js
. - Agregar una lista ordenada: Mostrará dinámicamente los datos almacenados en una tabla
student
de la base de datos IndexedDB.
Considera que la referencia dbscript.js
se agrega antes de que termine el body
, porque primero necesitamos crear los elementos HTML.
Este es el código:
<!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>
Finalmente, para dbscript.js
, se crean dos variables locales: students_table
(para el nombre de la tabla que existirá en nuestra base de datos) y students
(la lista ordenada que mostrará el contenido de la tabla).
Además, se definen algunos métodos:
-
load_data
: Simplemente invoca el métodoinit_database
. -
init_database
: En primer lugar, comprueba si indexedDB es soportado por el navegador. Si es así, intenta configurar una conexión a una base de datos IndexedDBschoolDB
. Si aún no existe, se crea. El eventoonupgradeneeded
se invoca una vez que se crea la base de datos por primera vez, y aquí creamos la tablastudents
(almacén de objetos). El eventoonsuccess
se activa una vez que nos hemos conectado exitosamente a la base de datos, y aquí el métodoinsert_student
se llama dos veces. -
insert_student
: Como es de esperar, este método agrega una entrada en la tablastudents
. -
show_students
: Este método primero elimina todo el contenido de la lista ordenada. Luego, obtiene todas las entradas de la tabla "estudiantes". Un objetoCursor
itera sobre cada registro de la tabla para crear contenido HTML dinámico para mostrar cada entrada.
Por cierto, el método SendRawMessageToDotNet
(del componente HybridWebView
) se utiliza para enviar un mensaje desde JavaScript a código .NET.
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. Prueba la aplicación
Ahora debería poder construir el proyecto y probarlo sin ningún problema. Primero lo probaré en una aplicación de Windows.
Aquí está el contenido inicial de nuestra aplicación. Como puedes observar, sólo se muestran los componentes HTML (un botón y un título), ya que realmente no agregamos ninguna interfaz de usuario XAML excepto la vista web que aloja los elementos web:
Haz clic en el botón. Se ejecuta el código JavaScript que interactúa con una base de datos IndexedDB
en el navegador y se presenta el siguiente resultado:
¡Éxito! Nuestra aplicación ha creado una base de datos local con una tabla y dos entradas, que se muestran en la aplicación. Además, se pasó un mensaje JavaScript a la parte C# y esta comunicación es posible gracias al componente HybridWebView
. ¿Sería posible pasar los datos en lugar de un simple mensaje para que podamos crear una interfaz de usuario usando XAML? Supongo que acabo de encontrar un nuevo tema sobre el cual escribir, así que exploraré este escenario pronto =)
Finalmente, no olvides que como habilitamos Web DevTools, podemos traerlas para depurar o, mejor aún, para ver nuestra base de datos:
¡Excelente! El almacenamiento IndexedDB contiene la base de datos schoolDB
que creamos en nuestra aplicación (en la sección Application
--> Storage
). Luego, hay una tabla "Estudiantes" que contiene dos entradas, por lo que todo funciona como se esperaba.
Antes de continuar, dos situaciones a comentar:
Puede suceder que cuando ejecutas la aplicación, se muestre vacía, sin contenido. No estoy seguro si esto es un error de
HybridWebView
(no olvides que es un componente experimental) o si sucede porque lasWeb DevTools
estaban habilitadas. Simplemente ejecuta la aplicación nuevamente y debería funcionar (inténtalo nuevamente, si no, eventualmente aparecerá). Noté que cuando comento la línea que habilita las herramientas, la aplicación funciona sin problemas, así que probablemente esto sea una situación a considerar. Haré un poco de exploración al respecto.Si vuelves a ejecutar la aplicación, las entradas se duplicarán. Esto es algo obvio, ya que los insertamos después de que la conexión a la base de datos sea exitosa. Puede borrar la base de datos/tabla utilizando Web DevTools en cualquier momento, por supuesto.
¡Bueno, eso es todo! Puedes encontrar el código fuente de este proyecto en mi
- Si vuelves a ejecutar la aplicación, las entradas se duplicarán. Esto es algo obvio, ya que los insertamos después de que la conexión a la base de datos es exitosa. Puedes borrar la base de datos/tabla utilizando Web DevTools en cualquier momento, por supuesto.
¡Bueno, eso es todo! Puedes encontrar el código fuente de este proyecto en mi repositorio de GitHub.
Espero que esta publicación te haya sido útil. Recuerda seguir el resto de publicaciones interesantes del Calendario de Adviento .NET MAUI 2023.
Gracias por leer. ¡Hasta la próxima!
Luis