Blazor · Laboratorio

Ejercicio Tareas en Blazor

Vas a armar tu primera página en Blazor con Rider: una lista de tareas que agrega, edita y quita a través de un servicio inyectado. Después abrís dos pestañas, cambiás una sola palabra en Program.cs y mirás qué pasa con los datos.

página Blazor @page @inject inyección de dependencias Scoped Singleton CRUD en memoria

Antes de arrancar

dotnet --version   # tiene que empezar con 9
0

Crear el proyecto Blazor

Blazor Web App · net9.0

En Rider elegí New Solution. En el panel de la izquierda, dentro del grupo .NET, hacé click en Web. Completá así:

  • Solution name y Project name: LabBlazor — así tus namespaces coinciden con los del ejercicio
  • Solution directory: la carpeta que prefieras. Dejá sin tildar Put solution and project in the same directory y Create Git repository.
  • Target framework: net9.0 (debería decir from SDK 9.0)
  • Language: C#
  • Template: Blazor Web App, la primera de la lista. No la que dice Blazor WebAssembly Standalone App, que está justo abajo.

Apretá Create.

Plan B por terminal

Si el asistente de Rider te da problemas, abrí la terminal de Rider en una carpeta vacía y pegá esto. Después File → Open y elegí el archivo LabBlazor.csproj.

dotnet new blazor -o LabBlazor -int Server   # crea la carpeta LabBlazor con el proyecto

Dale ▶ Run (o Shift+F10) para confirmar que arranca. Después detenela con ■ Stop antes de seguir.

CheckpointEn la consola de Rider aparece una línea que dice Now listening on: http://localhost:PUERTO. Quiere decir que tu aplicación ya está andando en tu propia computadora: localhost significa «esta máquina» y PUERTO es el número de la «puerta» por la que atiende (Rider elige uno y puede ser distinto en cada proyecto).

Abrí esa dirección en el navegador (si Rider no la abrió solo, copiala y pegala en una pestaña nueva): tenés que ver la Home de la plantilla de Blazor, con su menú lateral. Anotá el PUERTO: lo usás en el paso 1.
1

Tu primera página

@page + contenido + @code

Click derecho sobre la carpeta Components/Pages → Add → Blazor Component. Nombre: Tareas. No elijas Razor Page, que está justo debajo: crea otro tipo de página que no sirve para este ejercicio. Borrá todo lo que traiga el archivo y pegá esto:

@page "/tareas"

<PageTitle>Tareas</PageTitle>

<h1>Mis tareas</h1>
<p>Primera página Blazor de: <strong>@integrantes</strong></p>

<ul>
    <li>Tarea de ejemplo 1</li>
    <li>Tarea de ejemplo 2</li>
</ul>

@code {
    // TODO 1: escribí los nombres de tu equipo entre las comillas
    private string integrantes = "";
}

Ahora ▶ Run y, en la barra de direcciones, escribí a mano http://localhost:PUERTO/tareas.

CheckpointLa página muestra el título Mis tareas con los nombres de tu equipo. Cuando lo veas, ■ Stop.
PistaLa línea @page "/tareas" es la que crea la ruta: el nombre del archivo no importa. Lo que empieza con @ es C# metido dentro del HTML.
2

El servicio y la entidad

una clase que guarda y modifica tareas

Click derecho sobre el proyecto LabBlazor → Add → Class/Interface, nombre TareaServicio. Reemplazá todo el contenido por esto. No hay nada que completar: es el servicio ya armado.

namespace LabBlazor;

public class Tarea
{
    public int Id { get; set; }
    public string Titulo { get; set; } = "";
}

public class TareaServicio
{
    private readonly List<Tarea> _tareas = new();
    private int _proximoId = 1;

    // Un código corto para reconocer cuál instancia del servicio estás usando.
    public string IdInstancia { get; } = Guid.NewGuid().ToString("N")[..6];

    public IReadOnlyList<Tarea> Listar() => _tareas;

    public void Agregar(string titulo)
    {
        _tareas.Add(new Tarea { Id = _proximoId++, Titulo = titulo });
    }

    public void Quitar(int id)
    {
        _tareas.RemoveAll(t => t.Id == id);
    }

    public void Editar(int id, string nuevoTitulo)
    {
        var tarea = _tareas.FirstOrDefault(t => t.Id == id);
        if (tarea is not null) tarea.Titulo = nuevoTitulo;
    }
}

Fijate en tres cosas, porque las vamos a usar durante todo el ejercicio:

  • Tarea es la entidad: tiene un Id y un Titulo.
  • TareaServicio guarda la lista en memoria y sabe Listar, Agregar, Quitar y Editar.
  • IdInstancia es un código al azar que se genera una sola vez, cuando se crea el objeto. Sirve para reconocer qué instancia del servicio estamos usando.

Compilá desde la terminal de Rider:

dotnet build   # tiene que terminar en "Build succeeded."
CheckpointLa compilación termina en Build succeeded. sin errores.
3

Registrar, inyectar y conectar

Program.cs + @inject + CRUD en pantalla

Regla de oroCada vez que toques Program.cs: ■ Stop y después ▶ Run. Con la app corriendo el cambio parece guardado, pero no se aplica: sigue funcionando la versión vieja y no da ningún error. Rider tiene una opción para que los cambios se actualicen sin reiniciar (se llama Hot Reload), pero buscarla queda por cuenta de cada uno.

3A · Registrar el servicio. Program.cs es el archivo donde se configuran los servicios de la aplicación: ahí le decimos al contenedor de inyección de dependencias qué servicios existen y con qué «vida» (Scoped o Singleton). Después, cada página los pide con @inject, que es lo que vas a hacer en 3B.

Abrí Program.cs y completá el TODO 2. El contenedor se arma en Build(), así que la línea tiene que quedar antes:

using LabBlazor;

var builder = WebApplication.CreateBuilder(args);

// ... acá quedan las líneas que ya traía la plantilla ...

// TODO 2: registrar TareaServicio en el contenedor con vida Scoped

var app = builder.Build();   // <- todo lo de arriba tiene que ir ANTES de esta línea
PistaUn servicio se registra con una línea de este estilo: builder.Services.AddScoped<MiClase>();. El nombre del método elige la vida del servicio (AddScoped, AddSingleton o AddTransient) y entre los signos <> va el nombre de la clase que querés registrar.

3B · Inyectar y conectar la página. Reemplazá todo el contenido de Tareas.razor por esto. Rider va a marcar en rojo Servicio hasta que resuelvas el TODO 3:

@page "/tareas"
@rendermode InteractiveServer
@* TODO 3: inyectá acá el servicio (tipo TareaServicio, nombre Servicio) *@

<PageTitle>Tareas</PageTitle>

<h1>Mis tareas</h1>

<p>
    Instancia del servicio:
    <strong>@(RendererInfo.IsInteractive ? Servicio.IdInstancia : "conectando…")</strong>
</p>

<input class="form-control mb-2" @bind="texto" @bind:event="oninput" placeholder="Título de la tarea" />
<button class="btn btn-primary mb-3" @onclick="Guardar">@(editandoId is null ? "Agregar" : "Guardar cambios")</button>

<ul>
    @foreach (var t in Servicio.Listar())
    {
        <li>
            @t.Titulo
            <button class="btn btn-sm btn-outline-secondary" @onclick="() => Editar(t)">Editar</button>
            <button class="btn btn-sm btn-outline-danger" @onclick="() => Quitar(t.Id)">Quitar</button>
        </li>
    }
</ul>

@code {
    private string texto = "";
    private int? editandoId;

    private void Guardar()
    {
        if (string.IsNullOrWhiteSpace(texto)) return;

        if (editandoId is null)
        {
            // TODO 4: agregar la tarea usando el servicio
        }
        else
        {
            // TODO 5: editar la tarea usando el servicio
        }

        texto = "";
        editandoId = null;
    }

    private void Editar(Tarea t)
    {
        editandoId = t.Id;
        texto = t.Titulo;
    }

    private void Quitar(int id)
    {
        // TODO 6: quitar la tarea usando el servicio
    }
}
PistaPara inyectar un servicio en una página se escribe una sola línea con este esquema: @inject TipoDelServicio NombreQueElijas. Primero va el tipo (la clase del servicio) y después el nombre con el que lo vas a usar en el resto de la página: fijate cuál aparece en el código de arriba. Los TODO 4, 5 y 6 se resuelven llamando a los métodos del servicio. Mirá qué métodos tiene TareaServicio (paso 2) y qué datos necesita cada uno. La línea @rendermode InteractiveServer ya viene puesta: es la que hace que los botones respondan.
Si aparece un error al abrir /tareasUna InvalidOperationException que dice Cannot provide a value for property 'Servicio'… There is no registered service of type… significa que falta el TODO 2: el servicio está pedido pero nunca fue registrado.

3C · Probar. ▶ Run, abrí /tareas y agregá tres tareas, editá una y quitá otra.

CheckpointLas tres operaciones andan (al tocar Editar el botón pasa a decir Guardar cambios) y en Instancia del servicio aparece un código de 6 caracteres.
Para pensarEn ningún lado escribiste new TareaServicio() y aun así Servicio tiene una instancia lista para usar. ¿Quién la creó? ¿Cuándo?
4

Prueba 1: el servicio Scoped

dos pestañas + F5

Sin tocar nada del código, vas a mirar lo mismo desde dos pestañas. Escribí tu predicción antes de mirar.

  1. En la pestaña A (/tareas) dejá las tareas que ya cargaste y anotá el código de instancia.
  2. Abrí la pestaña B: una pestaña nueva del navegador con la misma dirección http://localhost:PUERTO/tareas. Anotá qué lista ve y su código.
  3. En B agregá una tarea y volvé a A.
  4. En A apretá F5.
Código de instancia
Qué hicimos Predicción (antes de mirar) Qué pasó
Abrimos B con la misma dirección
Agregamos una tarea en B y volvemos a A
F5 en A
CheckpointCompletaron predicción y resultado en las tres filas y anotaron los dos códigos.
Para pensarMisma dirección, mismo código, dos pestañas. ¿Qué es lo que nace y muere con cada pestaña? ¿Cuánto dura, exactamente, un «scope» en esta app?
5

Cambiá una palabra

de Scoped a Singleton

Ahora el cambio de una sola palabra. El resto del código no se toca.

  1. En Rider: ■ Stop. Cerrá las dos pestañas viejas del navegador: quedaron desconectadas.
  2. En Program.cs, resolvé el TODO 7:
// TODO 7: cambiá SOLO la palabra Scoped por Singleton (misma línea, no agregues otra)
builder.Services.AddScoped<TareaServicio>();
  1. ▶ Run. Rider abre una pestaña nueva en la Home: escribí a mano /tareas (será la pestaña A).
  2. Repitan el experimento del paso 4: tareas en A, predicción, abrir B, agregar en B y volver a A, F5 en A.
  3. Última fila: ■ Stop y ▶ Run de nuevo. ¿Qué pasa con la lista?
Código de instancia
Qué hicimos Predicción (antes de mirar) Qué pasó
Abrimos B con la misma dirección
Agregamos una tarea en B y volvemos a A sin tocar nada
F5 en A
■ Stop y ▶ Run (se reinicia la app)
CheckpointSi en B ves las tareas que cargaste en A y el mismo código de instancia, la app está en Singleton. Si B sigue vacía y con otro código, la app sigue en Scoped: revisá la trampa de arriba.
Para pensarMirá la fila 2 de la bitácora: ¿A se enteró sola de lo que agregó B? Si no, ¿por qué no? Y con Singleton todos ven lo mismo… hasta que reiniciás la app: ¿dónde viven realmente esos datos?
6

Enlazar la página desde el menú

NavMenu.razor + NavLink

Hasta ahora llegabas a tu página escribiendo /tareas a mano. En este paso la conectás al menú lateral, para entrar desde la Home con un click y volver, sin tocar la barra de direcciones.

Abrí Components/Layout/NavMenu.razor. Ahí está el menú lateral: cada opción (Home, Counter, Weather) es un bloque con un NavLink.

TODO 8: agregá una opción nueva, debajo de las que ya están, que lleve a tu página de tareas.

PistaUn NavLink lleva a otra página con su atributo href (la ruta que definiste con @page) y muestra el texto que pongas adentro. Lo más simple es copiar una de las opciones que ya existen, pegarla debajo y cambiar la ruta y el texto.

Detené la app con ■ Stop y volvé a correrla con ▶ Run. Se abre en la Home, en la dirección de siempre, sin /tareas. Probá el recorrido completo: entrá a tu página desde el menú, volvé a la Home desde el menú y entrá de nuevo.

CheckpointDesde la Home llegás a la página de tareas con un click en tu opción del menú, y con un click en Home volvés. Sin escribir la dirección a mano.

🏁 Vuelta extra (opcional)