La lección anterior expuso el dominio de BiblioTech como una API HTTP con ASP.NET Core, pensada
para que cualquier cliente la consuma —incluido un cliente escrito en JavaScript, la opción
tradicional para construir interfaces web—. Blazor propone otra vía: construir esa interfaz
web con C#, reutilizando el mismo lenguaje y buena parte de las ideas de componentes y data
binding ya vistas en WPF, pero ejecutándose en el contexto de un navegador. Esta lección explica
los dos modelos de ejecución de Blazor (Server y WebAssembly), la sintaxis de un componente
.razor, y construye un componente Catalogo.razor que lista el catálogo de BiblioTech e
inyecta Biblioteca directamente como servicio, sin pasar por peticiones HTTP manuales.
Contenido
- Qué es Blazor: C# también en el navegador
- Los dos modelos: Blazor Server frente a Blazor WebAssembly
- Componentes
.razor: estructura básica - Sintaxis de Razor:
@code,@bind,@onclick - Ciclo de vida de un componente:
OnInitializedAsync - Inyección de dependencias en un componente:
@inject - Ejemplo completo:
Catalogo.razorpara BiblioTech - Cuándo elegir Blazor frente a ASP.NET Core "puro" o frente a WPF
- Qué es Blazor: C# también en el navegador
Tradicionalmente, la interfaz de una aplicación web se construye con HTML, CSS y JavaScript,
mientras que el servidor (por ejemplo, con ASP.NET Core, lección anterior) se limita a servir
datos. Blazor rompe esa frontera: permite escribir la interfaz web en C#, organizada en
componentes reutilizables (ficheros .razor), sin necesidad de escribir JavaScript para la
lógica de la interfaz.
La consecuencia práctica más importante: todo lo que ya sabes de C# —clases, LINQ, async/
await, el propio dominio de BiblioTech— se puede usar directamente en el navegador, sin
traducirlo a otro lenguaje ni exponerlo primero como API JSON, como sí haría falta con un
frontend tradicional en JavaScript.
- Los dos modelos: Blazor Server frente a Blazor WebAssembly
Blazor ofrece dos formas distintas de ejecutar ese mismo código C#, con implicaciones muy distintas sobre dónde vive realmente la lógica:
| Blazor Server | Blazor WebAssembly (WASM) | |
|---|---|---|
| Dónde se ejecuta el código C# | En el servidor; el navegador solo recibe actualizaciones de interfaz | Directamente en el navegador, compilado a WebAssembly |
| Comunicación con el servidor | Conexión persistente (SignalR) para cada interacción del usuario | Ninguna necesaria tras la carga inicial (salvo llamadas explícitas a una API) |
| Latencia de interacción | Depende de la red: cada clic viaja al servidor y vuelve | Ninguna latencia de red para la lógica local, se ejecuta en el propio navegador |
| Tamaño de descarga inicial | Pequeño (la lógica no se descarga, se queda en el servidor) | Mayor (hay que descargar el runtime de .NET compilado a WebAssembly) |
| Funciona sin conexión tras cargar | No: cada interacción depende de la conexión con el servidor | Sí, una vez cargado (si no depende de una API externa) |
| Acceso directo a recursos del servidor (base de datos, ficheros) | Directo, sin API intermedia | Requiere una API HTTP, como la del apartado 3 de la lección anterior |
Para BiblioTech, Blazor Server encaja mejor con el ejemplo de esta lección: el componente
Catalogo.razor puede inyectar Biblioteca directamente como servicio del servidor (igual
que un endpoint de ASP.NET Core), sin pasar por HTTP; Blazor WebAssembly, en cambio, obligaría a
consumir la API construida en la lección anterior mediante HttpClient (Módulo 5), porque el
código C# del componente se ejecutaría en el navegador del usuario, sin acceso directo al
servidor ni a su base de datos.
- Componentes
.razor: estructura básica
.razor: estructura básicaUn componente Blazor combina marcado tipo HTML con código C#, en un mismo fichero .razor:
@page "/catalogo"
<h3>Catalogo de BiblioTech</h3>
<ul>
<li>Rayuela</li>
<li>Ficciones</li>
</ul>
@code {
// codigo C# del componente: propiedades, metodos, ciclo de vida (apartado 5)
}| Parte | Papel |
|---|---|
@page "/catalogo" |
Directiva que asigna una ruta URL a este componente (como una página independiente) |
| Marcado HTML | La estructura visual, muy similar a HTML plano |
@code { ... } |
Bloque de código C# del componente: aquí viven propiedades, campos y métodos |
La mezcla de HTML y C# dentro del mismo fichero recuerda a la separación XAML/code-behind de
WPF, pero con una diferencia notable: en Blazor, el marcado y el código C# conviven en un único
fichero .razor, en vez de repartirse entre un .xaml y un .xaml.cs separados.
- Sintaxis de Razor:
@code, @bind, @onclick
@code, @bind, @onclickDentro del marcado, cualquier expresión C# se introduce con @:
Para data binding de un campo de formulario, @bind conecta el valor de un control con una
propiedad C#, de forma parecida al {Binding ...} de WPF pero con sintaxis propia de Razor:
<input @bind="textoBusqueda" />
<p>Buscando: @textoBusqueda</p>
@code {
private string textoBusqueda = string.Empty;
}Para manejar eventos, @onclick (y equivalentes como @onchange, @onsubmit) conecta un evento
del DOM con un método C#, sin necesidad de JavaScript:
<button @onclick="IncrementarContador">Sumar</button>
<p>Contador: @contador</p>
@code {
private int contador = 0;
private void IncrementarContador()
{
contador++; // Blazor vuelve a renderizar el componente automaticamente tras el evento
}
}Tras cualquier evento manejado por Blazor (@onclick, @bind...), el framework vuelve a
renderizar automáticamente la parte del componente que haya cambiado —de forma conceptualmente
parecida a cómo WPF actualiza la vista al disparar PropertyChanged, aunque el mecanismo interno
de Blazor sea distinto (compara el árbol de marcado antes y después del evento, y actualiza solo
las diferencias).
- Ciclo de vida de un componente:
OnInitializedAsync
OnInitializedAsyncUn componente Blazor pasa por una serie de métodos de ciclo de vida, invocados automáticamente
por el framework en momentos concretos. El más habitual para cargar datos al mostrar el
componente es OnInitializedAsync:
@code {
private List<MaterialBibliotecario> catalogo = new();
protected override async Task OnInitializedAsync()
{
// se ejecuta una vez, cuando el componente se muestra por primera vez
catalogo = await ObtenerCatalogoAsync();
}
}OnInitializedAsync es el lugar habitual para cargar datos iniciales —de una base de datos, de
una API, o, como en el apartado 7, directamente de un servicio inyectado—, de forma equivalente a
como el constructor de CatalogoViewModel en WPF preparaba Catalogo antes de mostrar la
ventana. La diferencia es que aquí es un método async, pensado para esperar operaciones que
tarden (como una consulta a base de datos) sin bloquear la carga inicial de la página.
- Inyección de dependencias en un componente:
@inject
@injectUn componente Blazor puede recibir servicios registrados en el contenedor de dependencias (el
mismo mecanismo visto en la lección de ASP.NET Core) con la directiva @inject:
@inject Biblioteca Biblioteca resuelve una instancia de Biblioteca desde el contenedor de
servicios (registrada en Program.cs con builder.Services.AddSingleton<Biblioteca>(), igual
que en la lección anterior) y la expone como una propiedad disponible en todo el componente, sin
necesidad de recibirla por parámetro ni construirla manualmente. Como se indicó en la
introducción del módulo, la inyección de dependencias como mecanismo general se estudia en
profundidad en el Módulo 8; aquí basta con reconocer que @inject es, para un componente Blazor,
el equivalente a recibir un parámetro ya resuelto en un endpoint de ASP.NET Core.
- Ejemplo completo:
Catalogo.razor para BiblioTech
Catalogo.razor para BiblioTechUniendo todas las piezas anteriores, un componente que lista el catálogo y permite prestar cada
libro con un botón, usando Blazor Server e inyectando Biblioteca directamente:
@page "/catalogo"
@inject Biblioteca Biblioteca
<h3>Catalogo de BiblioTech</h3>
@if (mensajeEstado is not null)
{
<p><strong>@mensajeEstado</strong></p>
}
<table>
<thead>
<tr>
<th>Titulo</th>
<th>Autor</th>
<th>Disponible</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (MaterialBibliotecario material in Biblioteca.Catalogo)
{
<tr>
<td>@material.Titulo</td>
<td>@material.Autor</td>
<td>@(material.Disponible ? "Si" : "No")</td>
<td>
<button @onclick="() => PrestarMaterial(material)" disabled="@(!material.Disponible)">
Prestar
</button>
</td>
</tr>
}
</tbody>
</table>
@code {
private string? mensajeEstado;
protected override Task OnInitializedAsync()
{
// El catalogo ya vive en memoria dentro de Biblioteca (inyectada arriba);
// aqui no hace falta cargar nada adicional, a diferencia de un escenario con API externa.
return Task.CompletedTask;
}
private void PrestarMaterial(MaterialBibliotecario material)
{
if (!material.Disponible)
{
mensajeEstado = $"'{material.Titulo}' ya esta prestado.";
return;
}
material.Prestar(); // logica de dominio ya existente, Modulo 2
mensajeEstado = $"Prestamo registrado: {material.Titulo}";
}
}@onclick="() => PrestarMaterial(material)" usa una expresión lambda (Módulo 4) para pasar el
material concreto de cada fila a PrestarMaterial, algo necesario porque @foreach genera una
fila por elemento y cada botón debe actuar sobre su propio material, no sobre uno fijo.
disabled="@(!material.Disponible)" deshabilita el botón cuando el material ya está prestado,
sin necesidad de ningún código adicional: Razor evalúa la expresión C# entre paréntesis y la
asigna al atributo HTML disabled. Igual que en WPF, ningún fragmento de este componente
reimplementa la comprobación de disponibilidad: se apoya enteramente en
material.Prestar(), que ya la resuelve desde el Módulo 2.
- Cuándo elegir Blazor frente a ASP.NET Core "puro" o frente a WPF
| Escenario | Opción más adecuada |
|---|---|
| API pura, sin interfaz visual propia, consumida por distintos clientes | ASP.NET Core (Minimal APIs, lección anterior) |
| Interfaz visual accesible desde cualquier navegador, sin instalación | Blazor |
| Interfaz de escritorio con máximo control visual y sin depender de conexión de red | WPF |
| Interfaz de escritorio simple, rápida de construir, para uso interno en Windows | Windows Forms |
Blazor no sustituye a ASP.NET Core: de hecho, un proyecto Blazor Server se ejecuta sobre ASP.NET Core (usa Kestrel y el mismo contenedor de servicios por debajo); la diferencia es que Blazor añade, encima, un modelo de componentes con interfaz visual, mientras que la Minimal API de la lección anterior se queda solo en la capa de datos, sin interfaz propia.
Errores Comunes y Consejos
- Confundir Blazor Server con Blazor WebAssembly al decidir cómo acceder a los datos: en
Blazor Server, inyectar
Bibliotecadirectamente (como en el apartado 7) es correcto y eficiente; en Blazor WebAssembly, el mismo enfoque no funcionaría, porque el componente se ejecuta en el navegador del cliente, sin acceso directo al servidor —haría faltaHttpClientcontra la API del apartado anterior. - Olvidar
disabled="@(!material.Disponible)")o una comprobación equivalente: sin ella, el usuario podría hacer clic en "Prestar" sobre un material ya prestado; el métodoPrestarMateriallo detecta igualmente (por la comprobación deDisponible), pero deshabilitar el botón mejora la experiencia evitando el intento en primer lugar. - Escribir lógica de negocio directamente en el marcado Razor en vez de en un método del
bloque
@code: dificulta la lectura y la prueba del componente; la regla práctica es la misma que en WPF, mantener el marcado declarativo y la lógica en el código. - Consejo: Blazor Server mantiene una conexión persistente (SignalR) con cada usuario conectado; en una aplicación con muchísimos usuarios simultáneos, esto consume más recursos del servidor que Blazor WebAssembly, donde cada navegador ejecuta su propia copia del código —una consideración de escalabilidad a tener en cuenta al elegir el modelo.
Ejercicios
-
Añade a
Catalogo.razorun<input @bind="textoBusqueda" />y filtra la tabla mostrada para que solo aparezcan los materiales cuyoTitulocontenga el texto introducido (usaContains, ya visto en el Módulo 1, y actualiza el filtro con cada pulsación mediante@bind:event="oninput"). -
Añade un contador
<p>Total disponibles: @Biblioteca.Catalogo.Count(m => m.Disponible)</p>encima de la tabla, y explica en un comentario por qué no hace falta ningún código adicional para que se actualice tras cada préstamo.
Soluciones
<input @bind="textoBusqueda" @bind:event="oninput" placeholder="Buscar por titulo..." />
<table>
<tbody>
@foreach (MaterialBibliotecario material in Biblioteca.Catalogo.Where(
m => m.Titulo.Contains(textoBusqueda, StringComparison.OrdinalIgnoreCase)))
{
<tr>
<td>@material.Titulo</td>
<td>@material.Autor</td>
</tr>
}
</tbody>
</table>
@code {
private string textoBusqueda = string.Empty;
// ... resto del codigo existente ...
}
<p>Total disponibles: @Biblioteca.Catalogo.Count(m => m.Disponible)</p>
@* No hace falta codigo adicional porque Blazor vuelve a renderizar el componente entero
despues de cada evento manejado (como el @onclick de PrestarMaterial); al renderizar de
nuevo, esta expresion se reevalua automaticamente contra el estado actual de
Biblioteca.Catalogo, sin necesidad de actualizar manualmente ninguna variable. *@
Conclusión
En esta lección has visto cómo Blazor lleva C# al navegador, con dos modelos de ejecución
—Server y WebAssembly— que cambian por completo dónde vive la lógica y cómo se accede a los
datos, componentes .razor que combinan marcado y código, @bind/@onclick para interacción, y
@inject para obtener servicios del contenedor de dependencias. El componente Catalogo.razor
reutiliza, una vez más, el mismo Biblioteca.Catalogo y el mismo material.Prestar() vistos
desde el Módulo 2, ahora accesibles desde cualquier navegador.
La última lección de este módulo presenta Xamarin y .NET MAUI, que llevan BiblioTech a dispositivos móviles y de escritorio con un único proyecto multiplataforma, usando un XAML muy similar al de WPF —cerrando así el recorrido por las principales formas de construir interfaces en C#, antes de que el Módulo 8 pula el código de todo lo construido con mejores prácticas y patrones de diseño.
Curso de Programación en C#
Módulo 1: Introducción a C#
- Introducción a C#
- Configuración del Entorno de Desarrollo
- Programa Hola Mundo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Arrays y Cadenas de Texto
Módulo 2: Estructuras de Control
Módulo 3: Programación Orientada a Objetos
- Clases y Objetos
- Métodos
- Constructores y Destructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- Structs y Records: Tipos por Valor y por Referencia
Módulo 4: Conceptos Avanzados de C#
- Interfaces
- Delegados y Eventos
- Pattern Matching y Características Modernas de C#
- Genéricos
- Colecciones
- LINQ (Consulta Integrada en el Lenguaje)
- Programación Asíncrona
Módulo 5: Trabajando con Datos
- Entrada/Salida de Archivos
- Serialización
- Conectividad con Bases de Datos
- Entity Framework
- Trabajo con JSON y Consumo de APIs REST
Módulo 6: Temas Avanzados
- Reflexión
- Atributos
- Programación Dinámica
- Gestión de Memoria y Recolección de Basura
- Multihilo y Programación Paralela
Módulo 7: Construcción de Aplicaciones
- Formularios de Windows
- WPF (Windows Presentation Foundation)
- ASP.NET Core
- Blazor
- Xamarin y .NET MAUI
Módulo 8: Mejores Prácticas y Patrones de Diseño
- Estándares de Codificación y Mejores Prácticas
- Patrones de Diseño
- Inyección de Dependencias e Inversión de Control
- Pruebas Unitarias
- Revisión y Refactorización de Código
