In the middle of investigating the new Rhino Service Bus (more on this on later posts), I was trying to create a console application that’d act like a Console Application Server. Using a console app as a development application service is very common, because running it is a breeze and you can get the log output directly on your screen. But what I didn’t know was that you can actually close the console application by pressing CTRL + C or CTRL + Break without properly closing the application and this sometime will results to very bad things happening like your port being left open and you won’t be able to re-run the application because the port is not properly closed. To my surprise you can not use any managed code to listen to those events!
publicclassProgram { privatestaticreadonly ILog logger = LogManager.GetLogger(typeof(DefaultHost)); publicstaticvoidMain(string[] args) { var host = new DefaultHost(); host.Start(Assembly.GetExecutingAssembly().FullName); logger.Debug("Press CTRL+C to exit..."); bool shouldexit = false; while(!shouldexit) { ConsoleKeyInfo key = Console.ReadKey(true); if (key.Modifiers == ConsoleModifiers.Control && key.Key == ConsoleKey.C) { logger.Debug("Closing the Service Bus"); host.Close(); logger.Debug("Exiting the application"); shouldexit = true; } } } }
…and surprisingly application closed before I could run the code in the if block! Time to do a little unmanaged magic. There is existing functionality in Kernel32.dll to listen to console events but those are not exposed as .NET Console events. So first, let’s import that functionality into our managed world:
publicclassProgram { privatestaticreadonly ILog logger = LogManager.GetLogger(typeof(DefaultHost)); privatestatic DefaultHost host; privatestatic ConsoleController console; publicstaticvoidMain(string[] args) { console = new ConsoleController(); console.ConsoleEvent += ConsoleEventRaised; host = new DefaultHost(); host.Start(Assembly.GetExecutingAssembly().FullName); logger.Debug("Press CTRL+C to exit..."); while (true) { Console.ReadKey(true); } } privatestaticvoidConsoleEventRaised(ConsoleEventTypes consoleEvent) { if(consoleEvent == ConsoleEventTypes.CtrlC || consoleEvent == ConsoleEventTypes.Break) { logger.Debug("Closing the service bus"); host.Close(); logger.Debug("Exiting the application now"); Environment.Exit(-1); } } }
There actual attached code differs somehow. You need to keep the reference of ConsoleEventHandler in case of a garbage collection so the code has a couple of line of code to property handle disposing of the ConsoleController. You can download the source code here. Hope it helps.