I turned Slack into a live console for my production dotnet app

mykeels1 pts0 comments

I turned Slack into a live console for my production dotnet app - mykeels.comVideo Demo (Sound on)<br>The problem<br>I've been using Mykeels.CSharpRepl for a few years to debug my .NET applications. It gives me a C# REPL with full access to execute my application's code interactively, much like Visual Studio's Immediate Window, except it isn't tied to a debugger breakpoint or an IDE.For my applications, this effectively creates another entry point. I can simply run:./myapplication.exe repl<br>and begin executing application code interactively.However, when an application is already running on a server, I can SSH into the machine and start a REPL, but that launches another process . It has access to the same assemblies and configuration, but it isn't the running application.The live process has state that a new process doesn't. It has in-memory caches, singleton services, active background workers, open connections, and the exact execution context that produced the bug I'm trying to investigate.What I wanted was a REPL inside the running application itself .The idea<br>A few weeks ago, while building an Incident Simulator, I decided to use Slack as its primary interface. Instead of exposing HTTP endpoints or building a custom dashboard, the application communicated entirely through Slack using Socket Mode.That meant the running process already had a persistent, two-way communication channel.Then it hit me.<br>If my application can receive messages over Slack......why couldn't those messages be C#?More importantly, why couldn't they be executed inside the application's own process ?At that point, Slack stopped being the interesting part.It became nothing more than the transport layer for Mykeels.CSharpRepl.The goal was to give a live .NET application an interactive console that happened to be accessible from Slack.Architecture<br>If Slack was going to become the transport layer for a live REPL, there were three problems to solve:The REPL couldn't block the application's main thread.Every Slack session needed its own independent execution context.The REPL needed access to the application's services and configuration.The implementation that made this possible is available in this pull requestRunning the REPL in the background<br>The first problem was fairly straightforward.A REPL is effectively an infinite loop waiting for user input, which obviously isn't something you want running on your application's main thread.Instead, I host the Slack REPL inside a BackgroundService. If the Slack connection drops for any reason, the service simply reconnects after a short delay without affecting the rest of the application.using Microsoft.Extensions.Configuration;<br>using Microsoft.Extensions.Hosting;<br>using Microsoft.Extensions.Logging;

namespace MyApplication;

// Runs ReplHost.RunSlack for the lifetime of the host. Restarts on failure instead of<br>// letting an unhandled exception here bring down the whole ASP.NET app.<br>public sealed class SlackReplBackgroundService(<br>IConfiguration configuration,<br>ILogger logger<br>) : BackgroundService<br>private static readonly TimeSpan RestartDelay = TimeSpan.FromSeconds(5);

protected override async Task ExecuteAsync(CancellationToken stoppingToken)<br>while (!stoppingToken.IsCancellationRequested)<br>try<br>logger.Info("Starting Slack REPL host");<br>await ReplHost.RunSlack(configuration, stoppingToken);<br>catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)<br>break;<br>catch (Exception ex)<br>logger.LogError(ex, "Slack REPL host crashed, restarting in {RestartDelay}", RestartDelay);<br>try<br>await Task.Delay(RestartDelay, stoppingToken);<br>catch (OperationCanceledException)<br>break;<br>In ReplHost.cs, we have:using Microsoft.Extensions.Configuration;<br>using Mykeels.CSharpRepl;

namespace Under4Games;

public static class ReplHost<br>private static string[] Namespaces = [<br>"System",<br>"System.Collections.Generic",<br>"System.Linq",<br>"MyApplication"<br>];

public static async Task RunSlack(IConfiguration configuration, CancellationToken cancellationToken = default)<br>const string ApplicationName = "MyApplication.Slack";<br>string RequireConfiguration(string key) => configuration.GetValue(key) ?? throw new Exception($"Configuration key {key} is required");<br>await SlackReplHost.Run(<br>new SlackReplOptions<br>BotToken = RequireConfiguration("Slack:BotToken"),<br>AppToken = RequireConfiguration("Slack:AppToken"),<br>// Fails closed if left unset — see SlackReplOptions.AllowedUserIds.<br>AllowedChannelIds = RequireConfiguration("Slack:AllowedChannelIds")<br>.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)<br>.ToHashSet(),<br>SlashCommand = "/myapplication-repl",<br>},<br>commands: [<br>"using static MyApplication.ScriptGlobals;"<br>],<br>config: new CSharpRepl.Services.Configuration(<br>usings: Namespaces,<br>applicationName: ApplicationName<br>),<br>onLoad: (rosylnServices) => {<br>rosylnServices<br>.EvaluateAsync(<br>"using static MyApplication.ScriptGlobals;",<br>cancellationToken: CancellationToken.None<br>.ConfigureAwait(false);<br>string cwd =...

slack repl application configuration using myapplication

Related Articles