blazor-inyeccion-dependencias-di-lifetimes

Inyección de dependencias en Blazor: ciclos de vida

  • 5 min

La inyección de dependencias es el mecanismo por el que una clase recibe los servicios que necesita sin construirlos directamente.

En Blazor, la DI es el mecanismo que nos permite desacoplar la Vista (Componentes .razor) de la Lógica (Servicios). En lugar de crear instancias de clases con new ServicioUsuario(), le pedimos al framework que nos las dé.

En Blazor, el modelo de renderizado cambia el alcance real de Scoped. Conviene entenderlo antes de guardar estado en un servicio.

Ciclo de VidaComportamientoUso recomendado
TransientNueva instancia siempre.Lógica ligera, Helpers, Procesadores independientes.
ScopedPor petición en SSR, por circuito en Interactive Server y por app cliente en WebAssembly.Estado del circuito o cliente y servicios cuyo alcance encaje con esos límites.
SingletonUna instancia global para toda la aplicación.Caché, Configuración, Servicios de solo lectura.

El contenedor de servicios

Todo empieza en el Program.cs. Allí tenemos una colección llamada Services. Antes de construir la aplicación (app.Build()), registramos nuestras clases y le decimos a Blazor cómo debe instanciarlas.

var builder = WebApplication.CreateBuilder(args);

// Aquí registramos nuestros servicios
builder.Services.AddSingleton<ServicioGlobal>();
builder.Services.AddScoped<ICarrito, CarritoService>();
builder.Services.AddTransient<CalculadoraImpuestos>();

var app = builder.Build();
Copied!

Cuando un componente pide una dependencia, Blazor mira en este contenedor. Si ya existe una instancia válida según su ciclo de vida, la entrega. Si no, la crea.

Los tres ciclos de vida

La clave de la DI es decidir cuánto tiempo vive el objeto que hemos inyectado.

Transient (AddTransient)

“Nuevo cada vez”. El contenedor crea una nueva instancia cada vez que alguien la pide.

  • Si tienes 3 componentes en una página y los 3 inyectan este servicio, se crearán 3 instancias distintas.
  • Es ideal para servicios ligeros y sin estado (Stateless), como conversores de unidades o validadores.

Singleton (AddSingleton)

“Uno para todos”. Se crea una única instancia la primera vez que se solicita y esa misma instancia se reutiliza en toda la aplicación y para todos los usuarios.

  • En el servidor puede servir para caché, configuración global o estado realmente compartido.
  • Cuidado: En Interactive Server, una instancia Singleton se comparte entre todos los usuarios conectados. En WebAssembly, el contenedor vive en el navegador y su Singleton solo pertenece a esa aplicación cliente.

Scoped (AddScoped)

“Uno por alcance”. La complejidad aparece al comparar los distintos modelos de ejecución de Blazor.

  • Se crea una instancia por “ámbito” y se reutiliza dentro de ese ámbito.
  • En aplicaciones web clásicas (API, MVC), el ámbito es la Petición HTTP.
  • En Blazor, el ámbito cambia según el modelo de hosting.

Cómo cambia Scoped según el renderizado

Este es el punto más crítico del artículo. El concepto de Scoped funciona de manera radicalmente distinta en Blazor Server y Blazor WebAssembly.

Como la aplicación corre en el navegador del cliente, no hay “peticiones HTTP” constantes. Aquí, el Scope es la vida de la aplicación.

  • Un servicio Scoped se comporta casi igual que un Singleton.
  • Solo muere cuando el usuario recarga la página (F5) o cierra la pestaña.

Aquí la aplicación vive en el servidor, y el navegador se conecta mediante un WebSocket (SignalR). El Scope es el Circuito (La conexión SignalR).

  1. El usuario entra en la web -> Se crea un Circuito -> Se crea una instancia del servicio Scoped.
  2. El usuario navega por la web (Home -> Perfil) -> El Circuito se mantiene -> La instancia del servicio es la misma.
  3. El usuario pulsa F5 -> Se rompe el circuito y se crea uno nuevo -> Se crea una nueva instancia.
  4. Entra otro usuario -> Tiene su propio circuito -> Tiene su propia instancia.

Durante el renderizado estático del servidor, el alcance coincide con la petición HTTP. Por tanto, un servicio Scoped creado para una petición no es la misma instancia que utilizará después un componente interactivo.

Cómo inyectar servicios

Tenemos dos formas de pedir estas dependencias, dependiendo de dónde estemos escribiendo código.

En archivos .razor (Directiva @inject)

Usamos la directiva al principio del archivo. Detrás de escena, esto crea una propiedad en la clase generada.

@page "/clima"
@inject IWeatherService WeatherService
@inject NavigationManager NavManager

<h1>El tiempo</h1>
<p>Temperatura: @temperatura</p>

@code {
    private int temperatura;

    protected override async Task OnInitializedAsync()
    {
        // Usamos la instancia inyectada
        temperatura = await WeatherService.GetTemperatureAsync();
    }
}
Copied!

En clases C# (.cs) (Inyección por Constructor)

Si estamos en una clase code-behind, en un Servicio, o en un ViewModel, usamos la inyección por constructor estándar de .NET.

public class CarritoService
{
    private readonly IProductoRepository _repo;

    // El contenedor nos inyecta el repositorio automáticamente
    public CarritoService(IProductoRepository repo)
    {
        _repo = repo;
    }
}
Copied!

Si usas Code-Behind (.razor.cs) para tus componentes, no puedes usar inyección por constructor. Debes usar el atributo [Inject] en propiedades públicas.

public partial class MiComponente
{
    [Inject]
    public IWeatherService WeatherService { get; set; } = default!;
}
Copied!

Un DbContext de Entity Framework representa una unidad de trabajo corta y no es seguro para uso concurrente. En Interactive Server, no conviene conservar uno durante todo el circuito; usa IDbContextFactory<TContext> para crear y liberar un contexto por operación.