En este tutorial vamos a ver cómo crear una aplicación híbrida de escritorio que combina WPF, Webview2, ASP.NET Core y React como frontend.
En el artículo anterior, explicamos cómo montar el backend de la aplicación. Hoy veremos cómo consumir ese backend desde una aplicación en React.
Si aún no has visto el artículo anterior, puedes consultarlo aquí: Cómo crear una aplicación híbrida WPF + ASP.NET Core + Web como UI
Crear el proyecto de React
El primer paso es crear nuestra aplicación web, que servirá como la interfaz de usuario (UI). En este caso, usaremos React para construir una aplicación sencilla.
Para crear el proyecto, abre una terminal en la raíz de la solución y ejecuta los siguientes comandos:
npm create vite@latest ClientReact --template react
cd ClientReact
npm install
Configurar la aplicación React
Ahora vamos a configurar la aplicación React para que se comunique correctamente con nuestro backend.
Para ello, crea un archivo .env
en el directorio ClientReact
y añade la siguiente configuración:
VITE_API_URL=http://localhost:5000
Sin embargo, cuando la app arranque en modo desktop, el puerto va a variar (no siempre va a ser el 5000). Para gestionar esto, creamos un fichero .env.production
.
VITE_API_URL=
Con esto, cuando estemos en producción las llamadas al backend se harán con rutas relativas, por lo que funcionará porque la web será servida desde el backend con el puerto correcto.
A continuación, modifica el archivo vite.config.js
de la siguiente manera:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": "http://localhost:5000", // Redirige las peticiones a la API de C#
},
},
});
Esta configuración redirige las rutas de la API hacia nuestra aplicación WPF con ASP.NET, evitando problemas de CORS durante el desarrollo.
Crear la aplicación en React
A continuación, vamos a crear una aplicación sencilla en React para comprobar que la conexión con el backend de ASP.NET funciona correctamente. Primero, crea el archivo App.jsx
con el siguiente contenido:
import { Link } from 'react-router-dom';
function App() {
return (
<div>
<nav>
<Link to="/">Inicio</Link>
<Link to="/about">Acerca de</Link>
<Link to="/fetch">Fetch</Link>
<Link to="/signalr">SignalR</Link>
</nav>
</div>
);
}
export default App;
Añadir el router
Para manejar las rutas en nuestra aplicación, instala react-router-dom
con el siguiente comando:
npm install react-router-dom
Luego, crea el archivo router.jsx
y añade este código:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './views/Home';
import About from './views/About';
import Fetch from './views/Fetch';
import SignalR from './views/SignalR';
import App from './App';
function AppRouter() {
return (
<Router>
<App />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/fetch" element={<Fetch />} />
<Route path="/signalr" element={<SignalR />} />
</Routes>
</Router>
);
}
export default AppRouter;
Crear las vistas
Ahora crearemos las vistas de nuestra aplicación en la carpeta src/views
. Nuestra Demo va a tener las siguientes vistas
La página principal:
export default function Home() {
return (
<div>
<h1>Inicio</h1>
<p>Esta es la página de inicio.</p>
</div>
);
}
Una página sencilla que muestra información estática sobre la aplicación (para demostrar cómo crear páginas sencillas)
export default function About() {
return (
<div>
<h1>Acerca de</h1>
<p>Esta es la página de información sobre la aplicación.</p>
</div>
);
}
Una página que muestra cómo realizar llamadas a la API mediante fetch
. Se conectará a nuestros tres endpoints (demo
, forecast
y files
):
import { useEffect, useState } from 'react'
function App() {
const [data, setData] = useState(null)
const [forecasts, setForecasts] = useState()
const [files, setFiles] = useState()
useEffect(() => {
fetch(`${import.meta.env.VITE_API_URL}/api/demo`)
.then(res => res.json())
.then(data => setData(data))
}, [])
useEffect(() => {
populateWeatherData()
getFiles()
}, [])
async function populateWeatherData() {
const response = await fetch(`${import.meta.env.VITE_API_URL}/weatherforecast`)
if (response.ok) {
const data = await response.json()
setForecasts(data)
}
}
async function getFiles() {
const response = await fetch(`${import.meta.env.VITE_API_URL}/files`)
if (response.ok) {
const data = await response.json()
setFiles(data)
}
}
const contents = forecasts === undefined || files === undefined
? <p><em>Loading... Please refresh once the ASP.NET backend has started. See <a href="https://aka.ms/jspsintegrationreact">https://aka.ms/jspsintegrationreact</a> for more details.</em></p>
: <>
<ul>
{files.map(file => <li key={file}>{file}</li>)}
</ul>
</>
return (
<>
<h1>{data?.message || "Cargando..."}</h1>
<ul>
{contents}
</ul>
</>
)
}
export default App
Una página que muestra la comunicación en tiempo real mediante SignalR:
import { useEffect, useState } from 'react'
import * as signalR from '@microsoft/signalr'
function App() {
const [data, setData] = useState(null)
const [time, setTime] = useState("") // Nuevo estado para la hora
useEffect(() => {
fetch(`${import.meta.env.VITE_API_URL}/api/demo`)
.then(res => res.json())
.then(data => setData(data))
}, [])
useEffect(() => {
const connection = new signalR.HubConnectionBuilder()
.withUrl(`${import.meta.env.VITE_API_URL}/clockHub`) // Ruta del Hub
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build()
connection.start()
.then(() => console.log("Conectado a SignalR"))
.catch(err => console.error("Error al conectar a SignalR:", err))
// Escuchar el evento "ReceiveTime" del hub
connection.on("ReceiveTime", (time) => {
console.log("Hora recibida:", time)
setTime(time)
})
// Limpieza al desmontar el componente
return () => {
connection.stop()
}
}, [])
return (
<>
<h1>{data?.message || "Cargando..."}</h1>
<h2>Hora: {time || "Sin conexión"}</h2> {/* Muestra la hora en tiempo real */}
</>
)
}
export default App
Estilizar la aplicación
Finalmente, añadimos un poco de CSS a nuestro proyecto. Podéis usar cualquier librería o framework que queráis, o hacerlo con CSS. Aquí tenéis un ejemplo sencillo solo para la demo.
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f9;
color: #333;
}
nav {
background-color: #1f1d21;
padding: 1rem;
display: flex;
gap: 1rem;
}
nav a {
color: white;
text-decoration: none;
font-weight: bold;
}
nav a:hover {
text-decoration: underline;
}
.container {
padding: 2rem;
}
h1 {
color: #1f1d21;
}
Ejecutar la aplicación
Ahora podemos lanzar nuestra aplicación, bien sea como web, o como aplicación de Desktop, simplemente cambiando la configuración (como vimos en la entrada anterior).
Descarga el código
Todo el código de esta entrada está disponible para su descarga en Github.
En el próximo tutorial, veremos cómo integrar Vue.js en nuestra aplicación híbrida. ¡No te lo pierdas! 🚀