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

  1. Qué es Blazor: C# también en el navegador
  2. Los dos modelos: Blazor Server frente a Blazor WebAssembly
  3. Componentes .razor: estructura básica
  4. Sintaxis de Razor: @code, @bind, @onclick
  5. Ciclo de vida de un componente: OnInitializedAsync
  6. Inyección de dependencias en un componente: @inject
  7. Ejemplo completo: Catalogo.razor para BiblioTech
  8. Cuándo elegir Blazor frente a ASP.NET Core "puro" o frente a WPF

  1. 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.

  1. 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.

  1. Componentes .razor: estructura básica

Un 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.

  1. Sintaxis de Razor: @code, @bind, @onclick

Dentro del marcado, cualquier expresión C# se introduce con @:

<p>Total de libros: @totalLibros</p>

@code {
    private int totalLibros = 2;
}

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).

  1. Ciclo de vida de un componente: OnInitializedAsync

Un 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.

  1. Inyección de dependencias en un componente: @inject

Un 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

<p>Total de materiales: @Biblioteca.Catalogo.Count</p>

@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.

  1. Ejemplo completo: Catalogo.razor para BiblioTech

Uniendo 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.

  1. 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 Biblioteca directamente (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 falta HttpClient contra 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étodo PrestarMaterial lo detecta igualmente (por la comprobación de Disponible), 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

  1. Añade a Catalogo.razor un <input @bind="textoBusqueda" /> y filtra la tabla mostrada para que solo aparezcan los materiales cuyo Titulo contenga el texto introducido (usa Contains, ya visto en el Módulo 1, y actualiza el filtro con cada pulsación mediante @bind:event="oninput").

  2. 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#

Módulo 2: Estructuras de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Conceptos Avanzados de C#

Módulo 5: Trabajando con Datos

Módulo 6: Temas Avanzados

Módulo 7: Construcción de Aplicaciones

Módulo 8: Mejores Prácticas y Patrones de Diseño

Módulo 9: Proyecto Final

© Copyright 2026. Todos los derechos reservados