Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
FrameworksIntermédiaireSérie : Blazor / Razor

Blazor : injection de dépendances

@inject vs [Inject], enregistrement des services, les lifetimes Singleton/Scoped/Transient et le piège du Scoped par circuit en Blazor Server, plus le pattern state container.

Par Baptiste Vidal
3 min de lecture
Mis à jour il y a 2 semaines
blazordotnetinjectiondiservicesarchitecture

Blazor : injection de dépendances

@inject est passé vite dans la syntaxe, noyé parmi les directives. Mais l'injection de dépendances (DI) est un pilier de .NET, et Blazor cache une subtilité importante sur les durées de vie. On creuse.


# Injecter un service

Deux façons d'obtenir un service dans un composant :

@* 1. La directive @inject, dans le fichier .razor *@
@inject ProduitService Produits
 
@code {
    // 2. L'attribut [Inject], pratique en code-behind
    [Inject] private ILogger<Catalogue> Logger { get; set; } = default!;
 
    protected override void OnInitialized() =>
        Logger.LogInformation("{Count} produits chargés", Produits.Count);
}

Les composants n'ont pas d'injection par constructeur : le framework les instancie lui-même. On injecte donc par propriété ([Inject]) ou via la directive @inject.


# Enregistrer un service

Un service se déclare dans Program.cs, contre son abstraction de préférence :

builder.Services.AddScoped<ProduitService>();
builder.Services.AddSingleton<IHorloge, HorlogeSysteme>();
builder.Services.AddHttpClient();

Le premier type est l'abstraction demandée par les composants (@inject IHorloge), le second l'implémentation concrète fournie.


# Les trois lifetimes

LifetimeUne instance par...Pour quoi
Singletonapplication entièreconfiguration, cache, état sans dimension utilisateur
Scopedscope (voir le piège ci-dessous)services liés à un utilisateur / une session
Transientinjection (une neuve à chaque fois)services légers et sans état

# Le piège : Scoped n'est pas « par requête »

En ASP.NET classique, un service Scoped vit le temps d'une requête HTTP. En Blazor, ce n'est pas du tout ça :

  • Blazor Server : Scoped = par circuit, c'est-à-dire par connexion SignalR de l'utilisateur (voir l'hébergement). Le service vit tant que l'onglet reste connecté, à travers toutes les navigations. Un Scoped s'y comporte plus comme un « singleton par utilisateur » que comme un service jetable.
  • Blazor WebAssembly : il n'y a qu'un utilisateur (le navigateur), donc Scoped se comporte comme un Singleton.

Corollaire sécurité : ne stocke jamais l'état d'un utilisateur dans un Singleton en Blazor Server. Le singleton est partagé par tous les circuits, donc tous les utilisateurs : tu fuiterais les données de l'un vers l'autre. L'état par utilisateur va dans un Scoped.


# Un service d'état partagé (state container)

Le pattern courant pour partager de l'état entre composants éloignés : un service Scoped qui porte l'état et notifie ses abonnés.

public class PanierState
{
    private readonly List<string> _articles = new();
    public IReadOnlyList<string> Articles => _articles;
 
    public event Action? OnChange;
 
    public void Ajouter(string article)
    {
        _articles.Add(article);
        OnChange?.Invoke();   // prévient les composants abonnés
    }
}
@inject PanierState Panier
@implements IDisposable
 
<p>@Panier.Articles.Count article(s) dans le panier</p>
 
@code {
    protected override void OnInitialized() => Panier.OnChange += StateHasChanged;
    public void Dispose() => Panier.OnChange -= StateHasChanged;
}

Le composant s'abonne à OnChange et appelle StateHasChanged pour se re-rendre quand l'état bouge. Se désabonner dans Dispose est obligatoire : sans ça, le composant détruit reste référencé par l'événement, ce qui provoque une fuite mémoire.


# À retenir

  • On injecte via @inject (dans le .razor) ou [Inject] (en code-behind) ; pas d'injection par constructeur pour les composants.
  • Les services s'enregistrent dans Program.cs en Singleton / Scoped / Transient.
  • Piège Blazor : Scoped = par circuit en Server (pas par requête), et se comporte comme un Singleton en WebAssembly.
  • Ne mets jamais d'état utilisateur dans un Singleton en Server : il est partagé par tous.
  • Le state container (service Scoped + event OnChange + StateHasChanged) partage l'état entre composants ; désabonne-toi dans Dispose.

Ce chapitre touche déjà au cycle de vie (OnInitialized, Dispose) : une page dédiée au cycle de vie du composant est le prochain approfondissement naturel.