NOTE: Apart from
(and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.
Thursday, 22 August 2024
//Less than a minute
C'est un petit article stupide parce que j'étais un peu confus sur la façon de s'assurer que mon IHostedService
a été un cas unique. Je pensais que c'était un peu plus compliqué qu'en fait. Alors j'ai pensé écrire un petit article à ce sujet. Juste au cas où quelqu'un d'autre serait confus à ce sujet.
Dans le article précédent, nous avons couvert la façon de créer un service d'arrière-plan en utilisant le IHostedService
interface pour l'envoi d'emails. Cet article traitera de la façon de s'assurer que votre IHostedService
est une seule instance.
Cela pourrait être évident pour certains, mais ce n'est pas pour d'autres (et ce n'était pas tout de suite pour moi!).............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Eh bien, son un problème que la plupart des articles sur ceux-ci couvrent comment utiliser un IHostedService
mais ils ne couvrent pas comment s'assurer que le service est une seule instance. Ceci est important car vous ne voulez pas plusieurs instances du service en cours d'exécution en même temps.
Qu'est-ce que je veux dire? Bien dans ASP.NET la façon d'enregistrer un IHostedService ou IHostedlifeCycleService (essentiellement la même avec plus de dérogations pour la gestion du cycle de vie) vous utilisez ce
services.AddHostedService(EmailSenderHostedService);
Ce que cela fait est d'appeler dans ce code de moteur:
public static IServiceCollection AddHostedService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>());
return services;
}
Ce qui est bien et dandy mais que faire si vous voulez poster un nouveau message directement à ce service de dire un Controller
L'action?
public class ContactController(EmailSenderHostedService sender,ILogger<BaseController> logger) ...
{
[HttpPost]
[Route("submit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Submit([Bind(Prefix = "")] ContactViewModel comment)
{
ViewBag.Title = "Contact";
//Only allow HTMX requests
if(!Request.IsHtmx())
{
return RedirectToAction("Index", "Contact");
}
if (!ModelState.IsValid)
{
return PartialView("_ContactForm", comment);
}
var commentHtml = commentService.ProcessComment(comment.Comment);
var contactModel = new ContactEmailModel()
{
SenderEmail = string.IsNullOrEmpty(comment.Email) ? "Anonymous" : comment.Email,
SenderName = string.IsNullOrEmpty(comment.Name) ? "Anonymous" : comment.Name,
Comment = commentHtml,
};
await sender.SendEmailAsync(contactModel);
return PartialView("_Response",
new ContactViewModel() { Email = comment.Email, Name = comment.Name, Comment = commentHtml });
return RedirectToAction("Index", "Home");
}
}
Soit vous devez créer une interface qui s'implémente elle-même IHostedService
puis appelez la méthode sur cela ou vous devez vous assurer que le service est une seule instance. Ce dernier est le moyen le plus simple de le faire (dépend de votre scénario cependant, pour tester la méthode Interface pourrait être préféré).
Vous noterez ici qu'il enregistre le service comme un IHostedService
, ceci est lié à la gestion du cycle de vie de ce service puisque le cadre ASP.NET utilisera cette inscription pour déclencher les événements de ce service (StartAsync
et StopAsync
pour IHostedService). Voir ci-dessous. IHostedlifeCycleService
est juste une version plus détaillée de IHostedService.
/// <summary>
/// Defines methods for objects that are managed by the host.
/// </summary>
public interface IHostedService
{
/// <summary>
/// Triggered when the application host is ready to start the service.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous Start operation.</returns>
Task StartAsync(CancellationToken cancellationToken);
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// </summary>
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous Stop operation.</returns>
Task StopAsync(CancellationToken cancellationToken);
}
namespace Microsoft.Extensions.Hosting
{
/// <summary>
/// Defines methods that are run before or after
/// <see cref="M:Microsoft.Extensions.Hosting.IHostedService.StartAsync(System.Threading.CancellationToken)" /> and
/// <see cref="M:Microsoft.Extensions.Hosting.IHostedService.StopAsync(System.Threading.CancellationToken)" />.
/// </summary>
public interface IHostedLifecycleService : IHostedService
{
/// <summary>
/// Triggered before <see cref="M:Microsoft.Extensions.Hosting.IHostedService.StartAsync(System.Threading.CancellationToken)" />.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns>
Task StartingAsync(CancellationToken cancellationToken);
/// <summary>
/// Triggered after <see cref="M:Microsoft.Extensions.Hosting.IHostedService.StartAsync(System.Threading.CancellationToken)" />.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns>
Task StartedAsync(CancellationToken cancellationToken);
/// <summary>
/// Triggered before <see cref="M:Microsoft.Extensions.Hosting.IHostedService.StopAsync(System.Threading.CancellationToken)" />.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns>
Task StoppingAsync(CancellationToken cancellationToken);
/// <summary>
/// Triggered after <see cref="M:Microsoft.Extensions.Hosting.IHostedService.StopAsync(System.Threading.CancellationToken)" />.
/// </summary>
/// <param name="cancellationToken">Indicates that the stop process has been aborted.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns>
Task StoppedAsync(CancellationToken cancellationToken);
}
}
L'approche Interface pourrait être plus simple selon votre scénario. Ici vous ajouteriez une interface qui hérite de IHostedService
puis ajouter une méthode à cette interface que vous pouvez appeler depuis votre contrôleur.
REMARQUE: Vous devez toujours l'ajouter en tant que service hébergé dans ASP.NET pour que le service fonctionne réellement.
public interface IEmailSenderHostedService : IHostedService, IDisposable
{
Task SendEmailAsync(BaseEmailModel message);
}
Tout ce dont nous avons besoin, c'est de l'enregistrer comme un simpleton, puis de l'utiliser dans notre contrôleur.
services.AddSingleton<IEmailSenderHostedService, EmailSenderHostedService>();
services.AddHostedService<IEmailSenderHostedService>(provider => provider.GetRequiredService<IEmailSenderHostedService>());
ASP.NET verra que cela a la bonne interface décorée et utilisera cette inscription pour exécuter le IHostedService
.
Un autre pour s'assurer que votre IHostedService
est une seule instance est d'utiliser le AddSingleton
méthode d'enregistrement de votre service puis passer le IHostedService
l'enregistrement en tant que «méthode d'usine». Cela permettra de s'assurer qu'une seule instance de votre service est créée et utilisée tout au long de la durée de vie de l'application.
services.AddSingleton<EmailSenderHostedService>();
services.AddHostedService(provider => provider.GetRequiredService<EmailSenderHostedService>());
Donc, comme vous le voyez ici, j'inscris d'abord mon IHostedService
(ou IHostedLifeCycleService
) comme un simpleton et puis j'utilise le AddHostedService
méthode d'enregistrement du service comme méthode d'usine. Cela permettra de s'assurer qu'une seule instance du service est créée et utilisée tout au long de la durée de vie de l'application.
Comme d'habitude, il y a deux façons de peler un chat. L'approche de la méthode d'usine est également un bon moyen de s'assurer que votre service est une seule instance. C'est à vous de décider de l'approche que vous prenez. J'espère que cet article vous aidera à comprendre comment vous assurer que votre IHostedService
est une seule instance.