Aller au contenu

Fondamentaux • Hôte générique

Pourquoi faire ?

L’hôte générique est un outil puissant que nous propose le framework .NET via l’assembly Microsoft.Extensions.Hosting. Il facilite notre vie de développeur en prenant en charge :

  • le cycle de vie de l’application : démarrage, arrêt
  • la gestion des services en arrière-plan IHostedService
  • l’injection de dépendances Dependencies Injection
  • la configuration
  • la journalisation ILogger<>

Mise en place

Commençons par créer un nouveau projet console. Il nous servira de base de travail.

Terminal window
dotnet new console --name=ConsoleProject

Ajoutons le paquet Nuget Microsoft.Extensions.Hosting.

Terminal window
cd ConsoleProject
dotnet add package Microsoft.Extensions.Hosting

Nous pouvons maintenant ouvrir le projet ConsoleProject dans notre IDE favori.

Hôte générique par défaut

L’hôte générique par défaut sert de base à tous ses dérivés. Ces derniers l’améliorent et l’enrichissent.

Créons le constructeur d’hôte générique par défaut.

Program.cs
using Microsoft.Extensions.Hosting;
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
IHostBuilder builder = Host.CreateDefaultBuilder(args);

Beaucoup de choses sont faites par la méthode CreateDefaultBuilder() :

  • définition de la racine du contenu ;
  • chargement de la configuration ;
  • ajout des fournisseurs de journalisation.

Définition de la racine du contenu

La racine du contenu est définie par le dossier courant GetCurrentDirectory(). Qu’est-ce-que cela signifie ?

Le dossier courant est celui où est situé l’exécutable. Par exemple si nous exécutons notre projet en mode debug alors le dossier courant sera : ./bin/Debug/net8.0.

Si nous souhaitons accéder à des fichiers (comme appsettings.json par exemple) depuis notre application, il faudra alors bien penser à spécifier l’action de compilation Content dans les propriétés du fichier. Définir l&#x27;action de build à Content dans les propriétés du fichier

Chargement de la configuration

La configuration de l’hôte et la configuration de l’application sont chargées. Une vue d’ensemble est présentée dans l’article Configuration .NET.

Sans entrer dans le détail des différences entre configuration de l’hôte et configuration de l’application, ce qui nous intéresse est que plusieurs sources de configuration sont prises en compte et fusionnées :

  • les variables d’environnement ;
  • les arguments de la ligne de commande ;
  • le fichier appsettings.json ;
  • le fichier appsettings.[ENV].json ;
  • le gestionnaire de secrets (quand l’application est lancée dans l’environnement Development) ;
  • tout autre fournisseur de configuration personnalisé.

Ajout des fournisseurs de journalisation (logger)

Les fournisseurs de journalisation sont ajoutés :

  • Console
  • Debugger
  • EventSource
  • EventLog (uniquement pour Windows)

Ces fournisseurs peuvent être paramétrés dans le fichier appsettings.json :

appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information"
},
"Console": {
"Default": "Warning"
},
"Debug": {
"Default": "Trace"
}
}
}

Configuration des services de l’hôte

Une fois que le constructeur d’hôte par défaut est créé, nous avons la possibilité d’ajouter des services au conteneur de l’hôte via la méthode ConfigureServices(). Mais qu’est-ce-que cela signifie exactement ?

Cette méthode nous permet de paramétrer l’injection de dépendances en ajoutant des nouvelles entrées dans le conteneur, c’est-à-dire des nouveaux services dans le vocabulaire utilisé par .NET.

Un certain nombre existent déjà : les services de configuration et de journalisation que nous venons juste de détailler. Mais il est possible d’en ajouter des nouveaux : des services fournis par .NET, ceux amenés par des paquets NuGet et bien entendu nos propres services.

Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHostBuilder builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices(services =>
{
// → Ajout de SingletonService dans le conteneur d'injection de dépendances
services.AddSingleton(new SingletonService("3ddf09bndk3bnf0r5h"));
});
// ...
internal record SingletonService(string Key);

Construction de l’hôte

Une fois que l’hôte a été créé et configuré, il est alors possible de le construire via la méthode Build();

Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHostBuilder builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices(services =>
{
// → Ajout de SingletonService dans le conteneur d'injection de dépendances
services.AddSingleton(new SingletonService("3ddf09bndk3bnf0r5h"));
});
IHost host = builder.Build();
internal record SingletonService(string Key);

Une fois construit, l’hôte a accès à une instance de IServiceProvider via la propriété Services. Cela nous donne accès à tous les services paramétrés dans le conteneur d’injection de dépendances.

Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHostBuilder builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices(services =>
{
// → Ajout de SingletonService dans le conteneur d'injection de dépendances
services.AddSingleton(new SingletonService("3ddf09bndk3bnf0r5h"));
});
IHost host = builder.Build();
var singletonService = host.Services.GetRequiredService<SingletonService>();
internal record SingletonService(string Key);

Démarrage et arrêt de l’hôte

Une fois l’hôte construit, il est possible de le démarrer et de l’arrêter.

Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
IHostBuilder builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices(services =>
{
// ...
});
IHost host = builder.Build();
// → Démarrage de l'hôte
await host.StartAsync();
// → Lecture de la touche tapée dans la console
Console.ReadKey();
// → Arrêt de l'hôte
await host.StopAsync();
// ...

Ajout de IHostedService

Un IHostedService est un service qui est démarré et arrêté en même temps que l’hôte.

La classe qui l’implémente doit hériter de l’interface IHostedService. Nous pouvons ajouter cette classe conteneur d’injection de dépendances via la méthode AddHostedService().

Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
IHostBuilder builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices(services =>
{
// ...
// → Ajout de MyHostedService dans le conteneur d'injection de dépendances en tant qu'HostedService
services.AddHostedService<MyHostedService>();
});
// ...
internal class MyHostedService(ILogger<MyHostedService> logger) : IHostedService
{
private readonly ILogger<MyHostedService> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("MyHostedService started");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("MyHostedService stopped");
return Task.CompletedTask;
}
}

Au démarrage de l’hôte, la méthode IHostedService.StartAsync() est appelée pour tous les services enregistrés via AddHostedService(). Et à l’arrêt de l’hôte, c’est la méthode IHostedService.StopAsync() qui est appelée pour tous les HostedServices.

Chaque HostedService peut avoir sa logique propre et injecter toutes les dépendances dont il a besoin. Dans notre cas, nous avons injecté le service de journalisation.

BackgroundService

La classe abstraite BackgroundService implémente l’interface IHostedService et propose la méthode ExecuteAsync(). Cette méthode contient la logique à exécuter et elle est lancée au démarrage de l’hôte.

Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
IHostBuilder builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices(services =>
{
// ...
// → Ajout de MyBackgroundService dans le conteneur d'injection de dépendances en tant qu'HostedService
services.AddHostedService<MyBackgroundService>();
});
// ...
internal class MyBackgroundService(ILogger<MyBackgroundService> logger) : BackgroundService
{
private readonly ILogger<MyBackgroundService> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
private PeriodicTimer? _timer;
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("MyBackgroundService started");
_timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
while (!cancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync(cancellationToken))
{
_logger.LogInformation("MyBackgroundService pings at {now}", DateTimeOffset.Now);
}
}
public override void Dispose()
{
_timer?.Dispose();
_logger.LogInformation("MyBackgroundService stopped");
}
}

Dans notre exemple :

  1. Au démarrage de l’hôte la méthode MyBackgroundService.ExecuteAsync() est appelée ;
  2. Le message d’information “MyBackgroundService started” est émis par le service de journalisation ;
  3. Au bout de 5 secondes et toutes les 5 secondes, le message d’information “MyBackgroundService pings at {now}” est émis, où {now} est égal à l’heure courante ;
  4. À l’arrêt de l’hôte, la boucle while est stoppée via le CancellationToken ;
  5. La méthode Dispose() est appelée : le PeriodicTimer est libéré et le message “MyBackgroundService stopped” est émis.

Pour aller plus loin

Nous avons vu ensemble comment créer, paramétrer, construire, démarrer et arrêter un hôte par défaut. Nous connaissons le fonctionnement d’une brique essentielle au fonctionnement de nombreuses applications .NET.

Il existe également d’autres constructeurs d’hôte fournis par .NET dérivant de celui de l’hôte par défaut. Voici les deux qui seront souvent utilisés :

  • HostApplicationBuilder souvent utilisé à la place de IHostBuilder ;
  • WebApplicationBuilder (assembly Microsoft.AspNetCore) utilisé dans les application web ASP.NET.

Microsoft propose une documentation complète sur ce sujet (et bien d’autres). N’hésitez pas à la consulter pour approfondir ou éclaircir certains points.