Hôte générique .NET
Dans cet article, vous découvrez les différents modèles de configuration et de génération d’un hôte générique .NET disponible dans le package NuGet Microsoft.Extensions.Hosting. L’hôte générique .NET est responsable de la gestion du démarrage et de la durée de vie de l’application. Les modèles de service Worker créent un hôte générique .NET, HostApplicationBuilder. L’hôte générique peut être utilisé avec d’autres types d’applications .NET, comme les applications de console.
Un hôte est un objet qui encapsule les ressources et les fonctionnalités de durée de vie d’une application, par exemple :
- Injection de dépendances (DI)
- Journalisation
- Configuration
- Arrêt de l’application
- Implémentations de
IHostedService
Lorsqu’un hôte démarre, il appelle IHostedService.StartAsync sur chaque implémentation de IHostedService inscrite dans la collection de services hébergés du conteneur de services. Dans une application de service Worker, toutes les implémentations IHostedService
qui contiennent des instances BackgroundService ont leurs méthodes BackgroundService.ExecuteAsync appelées.
La principale raison d’inclure toutes les ressources interdépendantes de l’application dans un objet est la gestion de la durée de vie : contrôler le démarrage de l’application et l’arrêt approprié.
Configurer un hôte
L’hôte est généralement configuré, généré et exécuté par du code dans la classe Program
. La méthode Main
:
- Appelle une méthode CreateApplicationBuilder pour créer et configurer un objet builder.
- Appelle Build() pour créer une instance IHost.
- Appelle la méthode Run ou RunAsync sur l’objet hôte.
Les modèles de service Worker .NET génèrent le code suivant pour créer un hôte générique :
using Example.WorkerService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
IHost host = builder.Build();
host.Run();
Pour plus d’informations sur les services Worker, consultez Services Worker dans .NET.
Paramètres du générateur d’hôte
La méthode CreateApplicationBuilder :
- Définit le chemin retourné par GetCurrentDirectory() comme racine de contenu.
- Charge la configuration de l’hôte à partir de :
- Variables d’environnement comportant le préfixe
DOTNET_
. - Arguments de ligne de commande
- Variables d’environnement comportant le préfixe
- Charge la configuration de l’application à partir de :
- appsettings.json
- appsettings.{Environment}.json
- Secret Manager quand l’application s’exécute dans l’environnement
Development
. - Variables d'environnement.
- Arguments de ligne de commande
- Ajoute les fournisseurs de journalisation suivants :
- Console
- Déboguer
- EventSource
- EventLog (uniquement en cas d’exécution sur Windows)
- Active la validation de l’étendue et la validation de dépendances lorsque l’environnement est
Development
.
HostApplicationBuilder.Services constitue une instance de Microsoft.Extensions.DependencyInjection.IServiceCollection. Ces services permettent de créer un IServiceProvider utilisé avec l’injection de dépendances pour résoudre les services inscrits.
Services fournis par le framework
Lorsque vous appelez IHostBuilder.Build() ou HostApplicationBuilder.Build(), les services suivants sont automatiquement inscrits :
Générateurs d’hôte basés sur des scénarios supplémentaires
Si vous générez pour le web ou écrivez une application distribuée, vous devrez peut-être utiliser un autre générateur d’hôte. Tenez compte de la liste suivante de générateurs d’hôte supplémentaires :
- DistributedApplicationBuilder : Générateur pour la création d’applications distribuées. Pour plus d’informations, consultez .NET Aspire.
- WebApplicationBuilder : Générateur pour les services et applications web. Pour plus d’informations, consultez ASP.NET Core .
- WebHostBuilder : Générateur pour
IWebHost
. Pour plus d’informations, consultez Hôte web ASP.NET Core.
IHostApplicationLifetime
Injectez le service IHostApplicationLifetime dans n’importe quelle classe pour gérer les tâches post-démarrage et d’arrêt approprié. Trois propriétés de l’interface sont des jetons d’annulation utilisés pour inscrire les méthodes du gestionnaire d’événements de démarrage et d’arrêt d’application. L’interface inclut également une méthode StopApplication().
L’exemple suivant est un IHostedService et une implémentation IHostedLifecycleService qui inscrit les événements IHostApplicationLifetime
:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace AppLifetime.Example;
public sealed class ExampleHostedService : IHostedService, IHostedLifecycleService
{
private readonly ILogger _logger;
public ExampleHostedService(
ILogger<ExampleHostedService> logger,
IHostApplicationLifetime appLifetime)
{
_logger = logger;
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
}
Task IHostedLifecycleService.StartingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("1. StartingAsync has been called.");
return Task.CompletedTask;
}
Task IHostedService.StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("2. StartAsync has been called.");
return Task.CompletedTask;
}
Task IHostedLifecycleService.StartedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("3. StartedAsync has been called.");
return Task.CompletedTask;
}
private void OnStarted()
{
_logger.LogInformation("4. OnStarted has been called.");
}
private void OnStopping()
{
_logger.LogInformation("5. OnStopping has been called.");
}
Task IHostedLifecycleService.StoppingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("6. StoppingAsync has been called.");
return Task.CompletedTask;
}
Task IHostedService.StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("7. StopAsync has been called.");
return Task.CompletedTask;
}
Task IHostedLifecycleService.StoppedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("8. StoppedAsync has been called.");
return Task.CompletedTask;
}
private void OnStopped()
{
_logger.LogInformation("9. OnStopped has been called.");
}
}
Le modèle de service Worker peut être modifié pour ajouter l’implémentation ExampleHostedService
:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using AppLifetime.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<ExampleHostedService>();
using IHost host = builder.Build();
await host.RunAsync();
L’application écrit l’exemple de sortie suivant :
// Sample output:
// info: AppLifetime.Example.ExampleHostedService[0]
// 1.StartingAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 2.StartAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 3.StartedAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 4.OnStarted has been called.
// info: Microsoft.Hosting.Lifetime[0]
// Application started. Press Ctrl+C to shut down.
// info: Microsoft.Hosting.Lifetime[0]
// Hosting environment: Production
// info: Microsoft.Hosting.Lifetime[0]
// Content root path: ..\app-lifetime\bin\Debug\net8.0
// info: AppLifetime.Example.ExampleHostedService[0]
// 5.OnStopping has been called.
// info: Microsoft.Hosting.Lifetime[0]
// Application is shutting down...
// info: AppLifetime.Example.ExampleHostedService[0]
// 6.StoppingAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 7.StopAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 8.StoppedAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 9.OnStopped has been called.
La sortie montre l’ordre de tous les divers événements de cycle de vie :
IHostedLifecycleService.StartingAsync
IHostedService.StartAsync
IHostedLifecycleService.StartedAsync
IHostApplicationLifetime.ApplicationStarted
Une fois l’application arrêtée, par exemple avec Ctrl+C, les événements suivants sont déclenchés :
IHostApplicationLifetime.ApplicationStopping
IHostedLifecycleService.StoppingAsync
IHostedService.StopAsync
IHostedLifecycleService.StoppedAsync
IHostApplicationLifetime.ApplicationStopped
IHostLifetime
L’implémentation de IHostLifetime contrôle quand l’hôte démarre et quand il s’arrête. La dernière implémentation inscrite est utilisée. Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
est l’implémentation de IHostLifetime
par défaut. Pour plus d’informations sur les mécanismes de durée de vie de l’arrêt, consultez Arrêt de l’hôte.
L’interface IHostLifetime
expose une méthode IHostLifetime.WaitForStartAsync appelée au début de IHost.StartAsync
qui attend sa fin avant de continuer. Cela permet de retarder le démarrage jusqu'à ce que celui-ci soit signalé par un événement externe.
En outre, l’interface IHostLifetime
expose une méthode IHostLifetime.StopAsync appelée à partir de IHost.StopAsync
pour indiquer que l’hôte s’arrête et qu’il est temps de s’arrêter.
IHostEnvironment
Injectez le service IHostEnvironment dans une classe pour obtenir des informations sur les paramètres suivants :
- IHostEnvironment.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
En outre, le service IHostEnvironment
ouvre la possibilité d’évaluer l’environnement à l’aide des méthodes d’extension suivantes :
- HostingEnvironmentExtensions.IsDevelopment
- HostingEnvironmentExtensions.IsEnvironment
- HostingEnvironmentExtensions.IsProduction
- HostingEnvironmentExtensions.IsStaging
Configuration de l’hôte
La configuration de l’hôte est utilisée pour configurer les propriétés de l’implémentation IHostEnvironment.
La configuration de l’hôte est disponible dans la propriété HostApplicationBuilderSettings.Configuration et l’implémentation de l’environnement est disponible dans la propriété IHostApplicationBuilder.Environment. Pour configurer l’hôte, accédez à la propriété Configuration
et appelez l’une des méthodes d’extension disponibles.
Examinez l’exemple suivant pour ajouter la configuration d’hôte :
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
HostApplicationBuilderSettings settings = new()
{
Args = args,
Configuration = new ConfigurationManager(),
ContentRootPath = Directory.GetCurrentDirectory(),
};
settings.Configuration.AddJsonFile("hostsettings.json", optional: true);
settings.Configuration.AddEnvironmentVariables(prefix: "PREFIX_");
settings.Configuration.AddCommandLine(args);
HostApplicationBuilder builder = Host.CreateApplicationBuilder(settings);
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
Le code précédent :
- Définit le chemin retourné par GetCurrentDirectory() comme racine de contenu.
- Charge la configuration de l’hôte à partir de :
- hostsettings.json.
- Variables d’environnement comportant le préfixe
PREFIX_
. - Arguments de ligne de commande
la configuration d’une application ;
La configuration d’application est créée en appelant ConfigureAppConfiguration sur un IHostApplicationBuilder. La propriété publique IHostApplicationBuilder.Configuration permet aux consommateurs de lire la configuration existante ou de la modifier à l’aide de méthodes d’extension disponibles.
Pour plus d’informations, consultez Configuration dans .NET.
Arrêt de l’hôte
Il existe plusieurs façons d’arrêter un processus hôte. Le plus souvent, un processus hôte peut être arrêté de la manière suivante :
- Si quelqu’un n’appelle pas Run ou HostingAbstractionsHostExtensions.WaitForShutdown et si l’application se ferme normalement à l’achèvement
Main
. - Si l’application se bloque.
- Si l’application est arrêtée de force à l’aide de SIGKILL (ou CTRL+Z).
Le code d’hébergement n’est pas responsable de la gestion de ces scénarios. Le propriétaire du processus doit les traiter de la même façon que n’importe quelle autre application. Il existe plusieurs autres façons d’arrêter un processus de service hébergé :
- Si
ConsoleLifetime
est utilisé (UseConsoleLifetime), il écoute les signaux suivants et tente d’arrêter l’hôte correctement. - Lorsque l’application appelle Environment.Exit.
La logique d’hébergement intégrée gère ces scénarios, en particulier la classe ConsoleLifetime
. ConsoleLifetime
tente de manipuler les signaux d’arrêt SIGINT, SIGQUIT et SIGTERM pour permettre une sortie appropriée de l’application.
Avant .NET 6, il n’existait pas de moyen pour le code .NET de manipuler correctement SIGTERM. Pour contourner cette limitation, ConsoleLifetime
devait s’abonner à System.AppDomain.ProcessExit. Quand ProcessExit
était déclenché, ConsoleLifetime
indiquait à l’hôte d’arrêter et de bloquer le thread ProcessExit
, en attendant que l’hôte s’arrête.
Le gestionnaire de sortie du processus permet d’exécuter le code de nettoyage dans l’application, IHost.StopAsync et le code après HostingAbstractionsHostExtensions.Run dans la méthode Main
.
Toutefois, cette approche posait d’autres problèmes, car SIGTERM n’était pas le seul moyen de déclencher ProcessExit
. SIGTERM est également déclenché lorsque le code de l’application appelle Environment.Exit
. Environment.Exit
n’est pas un moyen approprié d’arrêter un processus dans le modèle d’application Microsoft.Extensions.Hosting
. Il déclenche l’événement ProcessExit
, puis quitte le processus. La fin de la méthode Main
n’est pas exécutée. Les threads d’arrière-plan et de premier plan sont terminés et les blocs finally
ne sont pas exécutés.
Étant donné que ConsoleLifetime
bloquait ProcessExit
en attendant que l’hôte s’arrête, ce comportement conduisait à des interblocages à partir du moment où Environment.Exit
se bloquait également en attendant l’appel à ProcessExit
. En outre, étant donné que la gestion SIGTERM tentait d’arrêter correctement le processus, ConsoleLifetime
définissait le ExitCode sur 0
, ce qui écrasait le code de sortie de l’utilisateur passé à Environment.Exit
.
Dans .NET 6, les signaux POSIX sont pris en charge et manipulés. Le ConsoleLifetime
gère correctement SIGTERM, et ne s’implique plus lorsque Environment.Exit
est invoqué.
Conseil
Pour .NET 6+, ConsoleLifetime
n’a plus de logique pour gérer le scénario Environment.Exit
. Les applications qui appellent Environment.Exit
et doivent effectuer une logique de nettoyage peuvent s’abonner à ProcessExit
elles-mêmes. L’hébergement ne tentera plus d’arrêter correctement l’hôte dans ces scénarios.
Si votre application utilise l’hébergement et que vous souhaitez arrêter correctement l’hôte, vous pouvez appeler IHostApplicationLifetime.StopApplication au lieu de Environment.Exit
.
Processus d’arrêt d’hébergement
Le diagramme de séquence suivant montre comment les signaux sont manipulés en interne dans le code d’hébergement. La plupart des utilisateurs n’ont pas besoin de comprendre ce processus. Mais pour les développeurs qui ont besoin d’une compréhension approfondie, un bon visuel peut vous aider à démarrer.
Une fois l’hôte démarré, lorsqu’un utilisateur appelle Run
ou WaitForShutdown
, un gestionnaire est inscrit pour IApplicationLifetime.ApplicationStopping. L’exécution est suspendue dans WaitForShutdown
, en attendant que l’événement ApplicationStopping
soit déclenché. La méthode Main
ne revient pas immédiatement et l’application continue à s’exécuter jusqu’à ce que Run
ou WaitForShutdown
revienne.
Lorsqu’un signal est envoyé au processus, il lance la séquence suivante :
- Le contrôle passe de
ConsoleLifetime
àApplicationLifetime
pour déclencher l’événementApplicationStopping
. Cela signale àWaitForShutdownAsync
de débloquer le code d’exécutionMain
. En attendant, le gestionnaire de signal POSIX revient avecCancel = true
puisque ce signal POSIX a été manipulé. - Le code d’exécution
Main
recommence à s’exécuter et indique à l’hôte deStopAsync()
, qui à son tour arrête tous les services hébergés et déclenche tous les autres événements arrêtés. - Enfin,
WaitForShutdown
se ferme, ce qui permet à n’importe quel code de nettoyage d’application de s’exécuter et à la méthodeMain
de se fermer correctement.
Arrêt de l’hôte dans les scénarios de serveur web
Il existe d’autres scénarios courants dans lesquels l’arrêt normal fonctionne dans Kestrel pour les protocoles HTTP/1.1 et HTTP/2, et comment vous pouvez le configurer dans différents environnements avec un équilibreur de charge pour drainer le trafic en douceur. Bien que la configuration du serveur web dépasse le cadre de cet article, vous trouverez plus d’informations sur Options de configuration de la documentation du serveur web ASP.NET Core Kestrel.
Lorsque l’hôte reçoit un signal d’arrêt (par exemple, CTL+C ou StopAsync
), il avertit l’application en signalant ApplicationStopping. Vous devriez vous abonner à cet événement si vous avez des opérations de longue durée qui doivent se terminer correctement.
Ensuite, l’hôte appelle IServer.StopAsync avec un délai d’arrêt que vous pouvez configurer (30s par défaut). Kestrel (et Http.Sys) ferment leurs liaisons de port et cessent d’accepter de nouvelles connexions. Ils indiquent également aux connexions actuelles d’arrêter de traiter les nouvelles requêtes. Pour HTTP/2 et HTTP/3, un message préliminaire GOAWAY
est envoyé au client. Pour HTTP/1.1, ils arrêtent la boucle de connexion, car les requêtes sont traitées dans l’ordre. IIS se comporte différemment en rejetant les nouvelles requêtes avec un code d’état 503.
Les requêtes actives ont jusqu’à l’expiration du délai d’arrêt pour se terminer. Si elles sont toutes terminées avant l’expiration du délai, le serveur renvoie le contrôle à l’hôte plus tôt. Si le délai expire, les connexions et requêtes en attente sont interrompues de force, ce qui peut entraîner des erreurs dans les journaux et pour les clients.
Considérations relatives à l’équilibreur de charge
Pour garantir une transition fluide des clients vers une nouvelle destination lors de l’utilisation d’un équilibreur de charge, vous pouvez suivre les étapes suivantes :
- Affichez la nouvelle instance et commencez à équilibrer le trafic vers celle-ci (il se peut que vous ayez déjà plusieurs instances à des fins de mise à l’échelle).
- Désactivez ou supprimez l’ancienne instance dans la configuration de l’équilibreur de charge afin qu’elle ne reçoive plus de nouveau trafic.
- Signalez à l’ancienne instance qu’elle doit s’arrêter.
- Attendez qu’elle se vide ou qu’elle expire.
Voir aussi
- Injection de dépendances dans .NET
- Journalisation dans .NET
- Configuration dans .NET
- Services Worker dans .NET
- Hôte web ASP.NET Core
- Configuration du serveur web ASP.NET Core Kestrel
- Les bogues de l’hôte générique doivent être créés dans le référentiel github.com/dotnet/runtime