Pattern for hosting Discord.NET bot inside a .NET generic host BackgroundService
When building a Discord bot as part of a larger .NET service architecture (Aspire, microservices), the bot needs to integrate with the generic host lifecycle rather than running standalone.
DiscordSocketConfig and DiscordSocketClient as singletons in DIBackgroundService that receives the client via constructor injectionExecuteAsync: wire events → LoginAsync → StartAsync → Task.Delay(Infinite, token) → StopAsyncLogSeverity to Microsoft.Extensions.Logging.LogLevel via the client.Log eventclient.Ready event (gateway is fully connected at that point)client.SlashCommandExecuted eventIConfiguration["Discord:BotToken"] — works with user secrets and env varsclient.ChannelCreated, client.ChannelDestroyed, client.ChannelUpdated — detect category vs text channel with is ICategoryChannelIConnectionMultiplexer (from Aspire's AddRedisClient) to get ISubscriber for Redis pub/subRedisChannel.Literal(channelName) for publish — avoid pattern-matching overheadChannelUpdated events to only publish when meaningful properties change (name, position) — Discord fires this event for many property changes// Program.cs
builder.Services.AddSingleton(new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.Guilds,
LogLevel = LogSeverity.Info
});
builder.Services.AddSingleton<DiscordSocketClient>();
builder.Services.AddHostedService<DiscordBotWorker>();
// Graceful shutdown in ExecuteAsync
try { await Task.Delay(Timeout.Infinite, stoppingToken); }
catch (OperationCanceledException) { }
await client.StopAsync();
DiscordSocketClient per-request or as transient — it manages a persistent WebSocketReady event — the client isn't connected yetwhile (!token.IsCancellationRequested) { await Task.Delay(...) } — Task.Delay(Infinite) is cleaner and avoids unnecessary wake-ups