From 1a5b948d9cf849f509e6e3b821433e8718e938b8 Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Thu, 10 Jul 2025 18:19:02 +0200 Subject: [PATCH 01/12] Add DotNetConsoleLogger --- CHANGELOG.md | 3 + .../Commands/GuidGen/GuidGenCommand.cs | 2 +- ...eolution.DotNet.Console.SampleAsync.csproj | 4 +- .../Program.cs | 22 ++---- .../packages.lock.json | 12 ++-- .../Neolution.DotNet.Console.UnitTests.csproj | 2 +- .../packages.lock.json | 12 ++-- Neolution.DotNet.Console/DotNetConsole.cs | 4 +- .../DotNetConsoleException.cs | 12 ---- .../DotNetConsoleLogger.cs | 72 +++++++++++++++++++ .../NlnHelperLogLevelLayoutRenderer.cs | 2 +- .../Neolution.DotNet.Console.csproj | 2 +- Neolution.DotNet.Console/packages.lock.json | 24 +++---- README.md | 43 +++++++---- 14 files changed, 143 insertions(+), 73 deletions(-) create mode 100644 Neolution.DotNet.Console/DotNetConsoleLogger.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index b570625..753c621 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- The recommended application entry point (`Main` method) now uses `DotNetConsoleLogger` for logger initialization, error logging, and shutdown. The static logger accessor is now `DotNetConsoleLogger.Log`. Update your `Program.cs` to follow the new pattern for improved clarity and consistency. + ## [5.0.0] - 2025-01-27 ### Added diff --git a/Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenCommand.cs b/Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenCommand.cs index a866aa4..43ede30 100644 --- a/Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenCommand.cs +++ b/Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenCommand.cs @@ -41,7 +41,7 @@ public Task RunAsync(GuidGenOptions options, CancellationToken cancellationToken result = result.ToUpperInvariant(); } - System.Console.WriteLine(result); + Console.WriteLine(result); this.logger.LogTrace("Wrote result to console"); return Task.CompletedTask; diff --git a/Neolution.DotNet.Console.SampleAsync/Neolution.DotNet.Console.SampleAsync.csproj b/Neolution.DotNet.Console.SampleAsync/Neolution.DotNet.Console.SampleAsync.csproj index 13a4e78..37757ff 100644 --- a/Neolution.DotNet.Console.SampleAsync/Neolution.DotNet.Console.SampleAsync.csproj +++ b/Neolution.DotNet.Console.SampleAsync/Neolution.DotNet.Console.SampleAsync.csproj @@ -1,4 +1,4 @@ - + Exe @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Neolution.DotNet.Console.SampleAsync/Program.cs b/Neolution.DotNet.Console.SampleAsync/Program.cs index e47d3a7..d20b301 100644 --- a/Neolution.DotNet.Console.SampleAsync/Program.cs +++ b/Neolution.DotNet.Console.SampleAsync/Program.cs @@ -1,10 +1,5 @@ namespace Neolution.DotNet.Console.SampleAsync { - using System; - using System.Globalization; - using NLog; - using NLog.Extensions.Logging; - /// /// The program /// @@ -17,32 +12,25 @@ public static class Program /// The . public static async Task Main(string[] args) { - var builder = DotNetConsole.CreateDefaultBuilder(args); - var logger = LogManager.Setup().LoadConfigurationFromSection(builder.Configuration).GetCurrentClassLogger(); - try { - // Use startup class as composition root + var builder = DotNetConsole.CreateDefaultBuilder(args); + DotNetConsoleLogger.Initialize(builder.Configuration); + var startup = new Startup(builder.Environment, builder.Configuration); startup.ConfigureServices(builder.Services); - - // Check if IHostEnvironment and IConfiguration are available before building the app - logger.Debug(CultureInfo.InvariantCulture, message: $"Environment: {builder.Environment.EnvironmentName}"); - logger.Debug(CultureInfo.InvariantCulture, message: $"Setting Value: {builder.Configuration["NLog:throwConfigExceptions"]}"); - var console = builder.Build(); await console.RunAsync(); } catch (Exception ex) { - // NLog: catch any exception and log it. - logger.Error(ex, "Stopped program because of an unexpected exception"); + DotNetConsoleLogger.Log.Error(ex, "Stopped program because of an unexpected exception"); throw; } finally { // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) - LogManager.Shutdown(); + DotNetConsoleLogger.Shutdown(); } } } diff --git a/Neolution.DotNet.Console.SampleAsync/packages.lock.json b/Neolution.DotNet.Console.SampleAsync/packages.lock.json index 12a7087..be3d03b 100644 --- a/Neolution.DotNet.Console.SampleAsync/packages.lock.json +++ b/Neolution.DotNet.Console.SampleAsync/packages.lock.json @@ -24,11 +24,11 @@ }, "Neolution.CodeAnalysis": { "type": "Direct", - "requested": "[3.2.1, )", - "resolved": "3.2.1", - "contentHash": "AQDkBJ9e6TrnhpwTtf4KrwlAPBLy77I5XkNey586nN2IFfjk1LVwBwvOcjORD1dSNtEhOf69JQjdmiMf54C90w==", + "requested": "[3.3.0-beta.2, )", + "resolved": "3.3.0-beta.2", + "contentHash": "88mG4f4P16K4Ql3uTqeaoeXGJLNMReFh9z3ZKmKZ0bY4RTk4z4syjuF/TefouGM2hR+mcBSdWbAtsKT/+FowlA==", "dependencies": { - "SonarAnalyzer.CSharp": "9.20.0.85982", + "SonarAnalyzer.CSharp": "9.32.0.97167", "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, @@ -341,8 +341,8 @@ }, "SonarAnalyzer.CSharp": { "type": "Transitive", - "resolved": "9.20.0.85982", - "contentHash": "c0IYtFg4mYusTafTy0Bs5wev45vRKNShkuoWyQyPMbC6imEFHL7tY/7xbbUJd6JNLYx5vP8wyi2LgiBsMHqb2Q==" + "resolved": "9.32.0.97167", + "contentHash": "Yxk86RV+8ynJpUhku1Yw2hITFmnmXKkXJ73cIFSy85ol5SnWREQg9RuTyV8nI7V7+pyLKpCfRmD7P0widsgjkg==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", diff --git a/Neolution.DotNet.Console.UnitTests/Neolution.DotNet.Console.UnitTests.csproj b/Neolution.DotNet.Console.UnitTests/Neolution.DotNet.Console.UnitTests.csproj index 49dc81d..1f33088 100644 --- a/Neolution.DotNet.Console.UnitTests/Neolution.DotNet.Console.UnitTests.csproj +++ b/Neolution.DotNet.Console.UnitTests/Neolution.DotNet.Console.UnitTests.csproj @@ -15,7 +15,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Neolution.DotNet.Console.UnitTests/packages.lock.json b/Neolution.DotNet.Console.UnitTests/packages.lock.json index e281163..218a3a5 100644 --- a/Neolution.DotNet.Console.UnitTests/packages.lock.json +++ b/Neolution.DotNet.Console.UnitTests/packages.lock.json @@ -26,11 +26,11 @@ }, "Neolution.CodeAnalysis.TestsRuleset": { "type": "Direct", - "requested": "[3.2.1, )", - "resolved": "3.2.1", - "contentHash": "tjiu8+Zg8rUddWrv5Ltp5PVWEEA3R1hP0zt+ecAgUgZ5Zd3LioArbu+Ut/SzJWY4DH1T9+eUoP3IKz6uDg+F4Q==", + "requested": "[3.3.0-beta.2, )", + "resolved": "3.3.0-beta.2", + "contentHash": "GQXIkpfRlgKFZw+vsQM2wTqRVq10Sq/zcEniQgFGTNZqyBCzogBLzi8FabptRO38yblVPN7YS35IqQ3bJtfp1Q==", "dependencies": { - "SonarAnalyzer.CSharp": "9.20.0.85982", + "SonarAnalyzer.CSharp": "9.32.0.97167", "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, @@ -668,8 +668,8 @@ }, "SonarAnalyzer.CSharp": { "type": "Transitive", - "resolved": "9.20.0.85982", - "contentHash": "c0IYtFg4mYusTafTy0Bs5wev45vRKNShkuoWyQyPMbC6imEFHL7tY/7xbbUJd6JNLYx5vP8wyi2LgiBsMHqb2Q==" + "resolved": "9.32.0.97167", + "contentHash": "Yxk86RV+8ynJpUhku1Yw2hITFmnmXKkXJ73cIFSy85ol5SnWREQg9RuTyV8nI7V7+pyLKpCfRmD7P0widsgjkg==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", diff --git a/Neolution.DotNet.Console/DotNetConsole.cs b/Neolution.DotNet.Console/DotNetConsole.cs index 8758041..b729414 100644 --- a/Neolution.DotNet.Console/DotNetConsole.cs +++ b/Neolution.DotNet.Console/DotNetConsole.cs @@ -111,12 +111,12 @@ public async Task RunAsync(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); await this.commandLineParserResult.WithParsedAsync(async options => await this.RunWithOptionsAsync(options, cancellationToken)); } - catch (OperationCanceledException) + catch (OperationCanceledException ex) { try { var logger = LogManager.Setup().LoadConfigurationFromSection(this.Services.GetRequiredService()).GetCurrentClassLogger(); - logger.Log(LogLevel.Info, CultureInfo.InvariantCulture, message: "Operation was canceled by the user."); + logger.Log(LogLevel.Info, ex, CultureInfo.InvariantCulture, message: "Operation was canceled by the user."); } catch (Exception) { diff --git a/Neolution.DotNet.Console/DotNetConsoleException.cs b/Neolution.DotNet.Console/DotNetConsoleException.cs index 35d3822..cd0d0e8 100644 --- a/Neolution.DotNet.Console/DotNetConsoleException.cs +++ b/Neolution.DotNet.Console/DotNetConsoleException.cs @@ -3,7 +3,6 @@ using System; /// - [Serializable] public class DotNetConsoleException : Exception { /// @@ -31,16 +30,5 @@ public DotNetConsoleException(string message, Exception innerException) public DotNetConsoleException() { } - - /// - /// Initializes a new instance of the class. - /// - /// The serialization information. - /// The streaming context. - [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.", DiagnosticId = "SYSLIB0051")] - protected DotNetConsoleException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) - : base(serializationInfo, streamingContext) - { - } } } diff --git a/Neolution.DotNet.Console/DotNetConsoleLogger.cs b/Neolution.DotNet.Console/DotNetConsoleLogger.cs new file mode 100644 index 0000000..5158533 --- /dev/null +++ b/Neolution.DotNet.Console/DotNetConsoleLogger.cs @@ -0,0 +1,72 @@ +namespace Neolution.DotNet.Console +{ + using System; + using Microsoft.Extensions.Configuration; + using NLog; + using NLog.Extensions.Logging; + using NLog.Targets; + + /// + /// Provides static methods to initialize and manage a logger instance. + /// + public static class DotNetConsoleLogger + { + /// + /// The internal logger instance used for logging. + /// + private static Logger? logger; + + /// + /// Gets the currently initialized logger. + /// + /// Thrown when the logger has not been initialized. + public static Logger Log + { + get + { + if (logger == null) + { + throw new InvalidOperationException("Logger has not been initialized. Call Initialize(configuration) first."); + } + + return logger; + } + } + + /// + /// Initializes the logger based on the provided configuration. + /// + /// The configuration used to initialize the logger. + public static void Initialize(IConfiguration configuration) + { + ConsoleTarget? consoleTarget = null; + try + { + logger = LogManager.Setup().LoadConfigurationFromSection(configuration).GetCurrentClassLogger(); + } + catch (Exception ex) + { + // Create a simple NLog configuration that logs to the console + var config = new NLog.Config.LoggingConfiguration(); + consoleTarget = new ConsoleTarget("console"); + config.AddRule(LogLevel.Info, LogLevel.Fatal, consoleTarget); + + LogManager.Configuration = config; + logger = LogManager.GetCurrentClassLogger(); + logger.Error(ex, "Logger initialization failed"); + } + finally + { + consoleTarget?.Dispose(); + } + } + + /// + /// Ensures the logger flushes messages and shuts down internal timers. + /// + public static void Shutdown() + { + LogManager.Shutdown(); + } + } +} diff --git a/Neolution.DotNet.Console/Logging/NlnHelperLogLevelLayoutRenderer.cs b/Neolution.DotNet.Console/Logging/NlnHelperLogLevelLayoutRenderer.cs index 7b4450b..35780e7 100644 --- a/Neolution.DotNet.Console/Logging/NlnHelperLogLevelLayoutRenderer.cs +++ b/Neolution.DotNet.Console/Logging/NlnHelperLogLevelLayoutRenderer.cs @@ -10,7 +10,7 @@ /// Custom NLog layout renderer to render log level like NLNHelper did. /// /// - [NLog.LayoutRenderers.LayoutRenderer("NLNHelperLogLevel")] + [LayoutRenderer("NLNHelperLogLevel")] public class NlnHelperLogLevelLayoutRenderer : LayoutRenderer { /// diff --git a/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj b/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj index ff4eb43..20bebb1 100644 --- a/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj +++ b/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Neolution.DotNet.Console/packages.lock.json b/Neolution.DotNet.Console/packages.lock.json index dcaa52d..bfeb02e 100644 --- a/Neolution.DotNet.Console/packages.lock.json +++ b/Neolution.DotNet.Console/packages.lock.json @@ -130,11 +130,11 @@ }, "Neolution.CodeAnalysis": { "type": "Direct", - "requested": "[3.2.1, )", - "resolved": "3.2.1", - "contentHash": "AQDkBJ9e6TrnhpwTtf4KrwlAPBLy77I5XkNey586nN2IFfjk1LVwBwvOcjORD1dSNtEhOf69JQjdmiMf54C90w==", + "requested": "[3.3.0-beta.2, )", + "resolved": "3.3.0-beta.2", + "contentHash": "88mG4f4P16K4Ql3uTqeaoeXGJLNMReFh9z3ZKmKZ0bY4RTk4z4syjuF/TefouGM2hR+mcBSdWbAtsKT/+FowlA==", "dependencies": { - "SonarAnalyzer.CSharp": "9.20.0.85982", + "SonarAnalyzer.CSharp": "9.32.0.97167", "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, @@ -353,8 +353,8 @@ }, "SonarAnalyzer.CSharp": { "type": "Transitive", - "resolved": "9.20.0.85982", - "contentHash": "c0IYtFg4mYusTafTy0Bs5wev45vRKNShkuoWyQyPMbC6imEFHL7tY/7xbbUJd6JNLYx5vP8wyi2LgiBsMHqb2Q==" + "resolved": "9.32.0.97167", + "contentHash": "Yxk86RV+8ynJpUhku1Yw2hITFmnmXKkXJ73cIFSy85ol5SnWREQg9RuTyV8nI7V7+pyLKpCfRmD7P0widsgjkg==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", @@ -525,11 +525,11 @@ }, "Neolution.CodeAnalysis": { "type": "Direct", - "requested": "[3.2.1, )", - "resolved": "3.2.1", - "contentHash": "AQDkBJ9e6TrnhpwTtf4KrwlAPBLy77I5XkNey586nN2IFfjk1LVwBwvOcjORD1dSNtEhOf69JQjdmiMf54C90w==", + "requested": "[3.3.0-beta.2, )", + "resolved": "3.3.0-beta.2", + "contentHash": "88mG4f4P16K4Ql3uTqeaoeXGJLNMReFh9z3ZKmKZ0bY4RTk4z4syjuF/TefouGM2hR+mcBSdWbAtsKT/+FowlA==", "dependencies": { - "SonarAnalyzer.CSharp": "9.20.0.85982", + "SonarAnalyzer.CSharp": "9.32.0.97167", "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, @@ -735,8 +735,8 @@ }, "SonarAnalyzer.CSharp": { "type": "Transitive", - "resolved": "9.20.0.85982", - "contentHash": "c0IYtFg4mYusTafTy0Bs5wev45vRKNShkuoWyQyPMbC6imEFHL7tY/7xbbUJd6JNLYx5vP8wyi2LgiBsMHqb2Q==" + "resolved": "9.32.0.97167", + "contentHash": "Yxk86RV+8ynJpUhku1Yw2hITFmnmXKkXJ73cIFSy85ol5SnWREQg9RuTyV8nI7V7+pyLKpCfRmD7P0widsgjkg==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", diff --git a/README.md b/README.md index f34e1a8..1845393 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,37 @@ Neolution.DotNet.Console is a versatile package designed as a launchpad for .NET # Getting Started -To help you kickstart your console application, we've provided a a [sample application](/Neolution.DotNet.Console.SampleAsync/Program.cs) that should demonstrate the basic usage of this package. +To help you kickstart your console application, we've provided a [sample application](/Neolution.DotNet.Console.SampleAsync/Program.cs) that demonstrates the basic usage of this package. +## Example Main Method + +Below is the recommended pattern for your `Program.cs` entry point, using the new `DotNetConsoleLogger`: +public static async Task Main(string[] args) +{ + try + { + var builder = DotNetConsole.CreateDefaultBuilder(args); + DotNetConsoleLogger.Initialize(builder.Configuration); + + // Register your services here... + builder.Services.AddHttpClient(); + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + + var console = builder.Build(); + await console.RunAsync(); + } + catch (Exception ex) + { + DotNetConsoleLogger.Log.Error(ex, "Stopped program because of an unexpected exception"); + throw; + } + finally + { + // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) + DotNetConsoleLogger.Shutdown(); + } +} # Guides ## Migrate from V3 to V5 @@ -22,17 +51,7 @@ In .NET 6 the hosting model for ASP.NET Core applications was changed, we adjust The builder returned from calling `DotNetConsole.CreateDefaultBuilder(args)` now has a `Services` property that can be used to register services. Like in ASP.NET, this can now be done directly in `Program.cs`. - public static async Task Main(string[] args) - { - var builder = DotNetConsole.CreateDefaultBuilder(args); - - // Register your services here... - builder.Services.AddHttpClient(); - builder.Services.AddScoped(); - builder.Services.AddSingleton(); - - await builder.Build().RunAsync(); - } + // See the example above for the new recommended Main method layout. ### Async by default From 78f4d95dde954fea25ced95ee8d50d5b3183f70b Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Thu, 10 Jul 2025 19:19:00 +0200 Subject: [PATCH 02/12] small fixes --- CHANGELOG.md | 5 +- .../Neolution.DotNet.Console.csproj | 10 + Neolution.DotNet.Console/icon.png | Bin 0 -> 2365 bytes README.md | 178 ++++++++++++++---- 4 files changed, 155 insertions(+), 38 deletions(-) create mode 100644 Neolution.DotNet.Console/icon.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 753c621..7991ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Changed -- The recommended application entry point (`Main` method) now uses `DotNetConsoleLogger` for logger initialization, error logging, and shutdown. The static logger accessor is now `DotNetConsoleLogger.Log`. Update your `Program.cs` to follow the new pattern for improved clarity and consistency. +### Added + +- Added `DotNetConsoleLogger.cs` static class to provide initialization, access, and shutdown for the logger instance used in console applications. ## [5.0.0] - 2025-01-27 diff --git a/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj b/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj index 20bebb1..787988a 100644 --- a/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj +++ b/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj @@ -2,10 +2,20 @@ net6.0;net8.0 + Neolution.DotNet.Console + Neolution AG + + https://github.com/neolution-ch/Neolution.DotNet.Console + https://github.com/neolution-ch/Neolution.DotNet.Console + git + dotnet;console;dependency-injection MIT README.md enable true + true + snupkg + See CHANGELOG.md for release notes. diff --git a/Neolution.DotNet.Console/icon.png b/Neolution.DotNet.Console/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..68121cc499157de35fcb6da0f2ae00011926755b GIT binary patch literal 2365 zcmbtW2U8Q;5)M7oNJkI}HF_Zi1(6nkfV9v=iX>D;2<_5EP!uGDcBM%Xkzf=A=_N!# zngpejkWi$H(vhMN5fHfX7rgi8?aY2#zMa{bot<+w$;QfrpGT4h008itnHt$LE%A>6 zIhhC_;Jm;zETOh02td`~sTF3x?rmUc007jc@&5MUV8+}*rcR*%fB^ZAvUCNOdIA7I znwgP--5uBUg0>VFw>EJIE9fS!6OspN}&OoWd@i zQ3I|6_sMAr4?4~6H>(BC_8*?#ol+|V-IL0Q-d>EJiT2*0D8pmqtFXra>IEQwl|Ckj z0}-cuLee>w`k4pI=?GST%Sw1J`rlLO2_Wz2C z$-C&UuUC$Kx0;$gq}IszWXrfnndV6bEF8a+X+;SE`Ex^~h9lY@sHLCxbDa`!yC{jp zO+S;~oqj(;h*8Wm^TpzVFF#{UlQ(f+vMq2w*a#k38=Eo-9ipkD!%3iIAirS|I>6|y z;)^yOZm>EZwe^eOxEK<#5=-6ElDl^1Q^yd@P65bYNMJiHp9wvk$1B8wAVWL>iXo;t z5lu^%$mRL$%!KQ#RHD7I_7h*Br+^^%auH;cY)U5#Fs5=MWw%dAX8s?P zmC;Yl`{9neMd=OUb04vFx@KcjJ)<=lelr%rJoY+3>7L21B_WElHKb#0;!R?A+7m58M+9#mk*62-z)_4tK?Cx!5 z-31@zZQa}+v)^?GVklf8S;|XcaczYMMAzy)KZ}rBb(BV?f@;ptP^r!lNtiKT#OqoX zf*<5NgrtrfrtkYHQglvkL5UV$Pc_Eo+-Sv%@sNh2Li|69>M?ul+em!82ZuICrXLzy zHDOcBIyw1EY=UaD(fXvigkz5;J!8I6GU}ieq7Wp*EDuYcFOsM(fu+CCUAjb}2=UF5 z6<1EX=p2uzBnA?or|oZ>JuNLUoRMA?zsiwYDMQ+x-`|UC?7!XbjQZBmLaiJg%Ey$y zp(bn!9wQ!T_8qBS)7B>NykJhn{Z40)w?! zd>dC$x(`M4?EM01ko) zrVgK~nbs)}gd3+Yh|gpm`_$*;7%aOGuUq%Y;j+i*!0401^nqAvt}L&=6uPO{DH#5U zdY}nM{k}&Yu4u#?FQ2u$EOt)Gud+liN)G9-oUh|o!F2x`rd!5PL!aX{KnO~F)Py=v z+Tbx`XFGpxGKd?|MRkW)UbDQldu9n7rUD(MhtRS@PiV(;uB~`kaVN49gncUUqqaTg zfd%Gq4C2x)bRRDiq!f}#tCu3HX?ZHv_XjD5A#Bg1%1ahb>)bj?u*Tzgb#Ky|fiE7u zk&7^75b?$fS#^lMUXYe^@cgU6B)sQHJI0Z@v*#+p&9w+_(*qBTpEC;0UzZg7sWktc z5^OF=K$7gDeikfvOhqK@wRJ1f+F`DIk?}cVzOixV8bpcrG)Uht>LJswZtNW?rrR%{ zV*D6hc3{H|aZO~bX$Y6h3!{vmcjP;0zJk>BRgd)7*W@PW7+LkmhYiTnRjl1Fp{&uz z;YisODsp(>-HrPlk3K|ALUi$0xn&NSiD~o4=Vd{NP}gmBT~(Tnik3K65F5z znb%I%)gCS$PpQVM2s5?l^6b@>hHpECO2h`O_Da4bb9l?=7IYu0ldLOdkCZmmT;}7W zi0c=0GhY=g$PtAR`$u^Ko!5z@ZK&(M0eNYl4##kQ#F%k!8f*04{uQUZepUq9K+MR~ zw=z5_dwZkN1x!?&DO~33(DH-5d09?@T92XtSW<63*MxUV^@vzfxMTnl)X8D6?2vu{ z-3-)G1^f>`o*;}^Z0V&@+d9rC0&&I2GGA+y6NCSQsSs*6i?_H;qyPvihgf0^xvWTa zp+Z@TmY8vruThoc&{tJ=h5~nG3y*w=JvaPRx(bm<%TUe=HC|g1{Wi&YU?$%5IS+g7 z=KdD=dX^`F@#|}H<&NY}UEE3$FVs$xwk`+t4--hqFj+SDcJ|ZLY*6#hFu~02V1{Uh zGVJ3}AnOlqX!lQhr1U@a9R(LX^djTMj8@?XwZ%qls@0qE)jZGV#Zl?-G~Q?OFBAvN zQypS7dx21Y5^limVVHFl9nN+4EM$i>^16lQk!8diZ@ijCQ8QJlC_FmSJ9A1q#d@E# z@*yTq1rnABJSUt?lJ9E*2$e$A^;!Po{g8aj@|Q^PKg%qiotP&N!0d{ZQ5C}d;lGTq BS(*R< literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 1845393..947e2b3 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,192 @@ -# Introduction +# Introduction + +[![NuGet](https://img.shields.io/nuget/v/Neolution.DotNet.Console.svg)](https://www.nuget.org/packages/Neolution.DotNet.Console) +[![Build Status](https://github.com/neolution-ch/Neolution.DotNet.Console/actions/workflows/cd-production.yml/badge.svg)](https://github.com/neolution-ch/Neolution.DotNet.Console/actions) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) Neolution.DotNet.Console is a versatile package designed as a launchpad for .NET console applications. It comes equipped with a built-in dependency injection setup, mirroring the functionality found in ASP.NET Core applications, thus providing a seamless and intuitive user experience. -# Getting Started +## Features + +- Async command execution +- Built-in dependency injection (like ASP.NET Core) +- NLog integration for flexible logging +- Strict verb matching for safer CLI usage +- Service registration validation at build time (check-deps command) + +## Installation + +Install the package via NuGet: + +### .NET CLI + +```sh +dotnet add package Neolution.DotNet.Console +``` + +### Package Manager + +```powershell +Install-Package Neolution.DotNet.Console +``` + +## Getting Started To help you kickstart your console application, we've provided a [sample application](/Neolution.DotNet.Console.SampleAsync/Program.cs) that demonstrates the basic usage of this package. -## Example Main Method +### Example Main Method with Startup Class (Recommended) + +Below is the recommended pattern for your `Program.cs` entry point, using a `Startup` class for service registration and configuration: -Below is the recommended pattern for your `Program.cs` entry point, using the new `DotNetConsoleLogger`: -public static async Task Main(string[] args) +```csharp +// Program.cs +namespace Neolution.DotNet.Console.SampleAsync { - try + public static class Program { - var builder = DotNetConsole.CreateDefaultBuilder(args); - DotNetConsoleLogger.Initialize(builder.Configuration); - - // Register your services here... - builder.Services.AddHttpClient(); - builder.Services.AddScoped(); - builder.Services.AddSingleton(); - - var console = builder.Build(); - await console.RunAsync(); + public static async Task Main(string[] args) + { + try + { + var builder = DotNetConsole.CreateDefaultBuilder(args); + DotNetConsoleLogger.Initialize(builder.Configuration); + + var startup = new Startup(builder.Environment, builder.Configuration); + startup.ConfigureServices(builder.Services); + + var console = builder.Build(); + await console.RunAsync(); + } + catch (Exception ex) + { + DotNetConsoleLogger.Log.Error(ex, "Stopped program because of an unexpected exception"); + throw; + } + finally + { + // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) + DotNetConsoleLogger.Shutdown(); + } + } } - catch (Exception ex) +} +``` + +```csharp +// Startup.cs +namespace Neolution.DotNet.Console.SampleAsync +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + + /// + /// The startup class, composition root for the application. + /// + internal class Startup { - DotNetConsoleLogger.Log.Error(ex, "Stopped program because of an unexpected exception"); - throw; + public Startup(IHostEnvironment environment, IConfiguration configuration) + { + this.Environment = environment; + this.Configuration = configuration; + } + + public IHostEnvironment Environment { get; } + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + // Register your services here. This is the main place for service registrations. + // Example: services.AddHttpClient(); + + // You have full access to IHostEnvironment and IConfiguration here. + // Use 'this.Environment' to register services based on the environment (e.g., Development, Production). + // Use 'this.Configuration' to register/configure services based on configuration values. + } } - finally +} +``` + +This pattern keeps your `Program.cs` clean and delegates service registration and configuration to a dedicated `Startup` class, similar to ASP.NET Core applications. + +## Usage + +### Defining a Command + +To add a command, implement the `IDotNetConsoleCommand` interface. For example: + +```csharp +using Neolution.DotNet.Console.Abstractions; + +public class EchoCommand : IDotNetConsoleCommand +{ + public async Task RunAsync(CancellationToken cancellationToken) { - // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) - DotNetConsoleLogger.Shutdown(); + Console.WriteLine("Hello from EchoCommand!"); + await Task.CompletedTask; } } -# Guides +``` + +The library will automatically discover and register all commands (classes implementing `IDotNetConsoleCommand`). -## Migrate from V3 to V5 +### Running the Application -*Note: V4 was intentionally skipped, there is no V4 release.* +Run your application and specify the command verb as an argument. The library uses [CommandLineParser](https://github.com/commandlineparser/commandline) verbs and options internally, so you can invoke your command like this: + +```sh +dotnet run -- [verb] [options] +``` + +Replace `[verb]` with the name of your command (e.g., `echo`). Any additional options or parameters defined in your command will be parsed and passed automatically. + +--- + +## Guides + +### Migrate from V3 to V5 + +> ⚠️ *Note: V4 was intentionally skipped, there is no V4 release.* To support cancellation tokens, the `IDotNetConsoleCommand` interface had to be changed: The `RunAsync` method now requires also a `CancellationToken` as a parameter. This change is breaking, so you will need to update your commands to reflect this change. -## Migrate from V2 to V3 +### Migrate from V2 to V3 In .NET 6 the hosting model for ASP.NET Core applications was changed, we adjusted to that to fulfill the primary goal of this package: to provide a seamless and intuitive user experience. This introduces breaking changes that are explained below. -### Removed `ICompositionRoot` interface and `UseCompositionRoot` extension method +#### Removed `ICompositionRoot` interface and `UseCompositionRoot` extension method The builder returned from calling `DotNetConsole.CreateDefaultBuilder(args)` now has a `Services` property that can be used to register services. Like in ASP.NET, this can now be done directly in `Program.cs`. - // See the example above for the new recommended Main method layout. +Refer to the example above for the preferred structure of your Main method. -### Async by default +#### Async by default All commands are now async by default. Use the new `IDotNetConsoleCommand` interface instead of `IConsoleAppCommand` and `IAsyncConsoleAppCommand`. -### Service registration validation +#### Service registration validation The service registrations are now validated when `app.Build()` is called. This means that the application will not start if not all services that are registered (even if not used during runtime) can be created by the DI container or when there are scope/lifetime issues with the services. -### Strict verb matching when default verb is defined +#### Strict verb matching when default verb is defined If one verb is defined as default, but the verb passed as argument is not matching any of the available verbs, the builder will no longer build a console application and instead throw an exception. This is done to avoid running the default verb command if the user accidentally uses a verb that is not available or simply made a typo. E.g. if the app uses `start` as the default verb and the user intention is to start the command assigned to the verb `echo` but mistypes it as `eccho` or similar, the builder will fail with an exception. Previous versions would have run the `start` command, because it's defined as default verb, but it was not the intention. -## Migrate from V1 to V2 +### Migrate from V1 to V2 V2 **introduces breaking changes** from V1, primarily because it upgrades NLog to Version 5. For a detailed review of these changes, please refer to the [official NLog release notes](https://nlog-project.org/2021/08/25/nlog-5-0-preview1-ready.html). -### Removed `Logging` section in appsettings.json +#### Removed `Logging` section in appsettings.json -NLog decided to deprecate Microsoft's `Logging` section in the appsettings.json starting with V5. In accordance of that decision we've also transitioned to only using NLog rules for filtering logging output. Although these rules might be a bit more complex, they offer greater flexibility and control. +NLog decided to deprecate Microsoft's `Logging` section in the appsettings.json starting with V5. In accordance of that decision we've also transitioned to only using NLog rules for filtering logging output. Although these rules might be a bit more complex, they offer greater flexibility and control. To get acquainted with these rules and learn how to migrate your current configuration, check out the [NLog documentation on logging rules](https://github.com/NLog/NLog/wiki/Configuration-file#rules). There's also a guide on the [new finalMinLevel attribute](https://github.com/NLog/NLog/wiki/Logging-Rules-FinalMinLevel) that you might find helpful. -### Removed default logging targets +#### Removed default logging targets - **AWS Logger:** The AWS Logger, previously used for logging to CloudWatch, has been removed due to decreased usage. If you still need it, don't worry, you can easily [download it separately](https://www.nuget.org/packages/AWS.Logger.NLog). The configuration remains the same. - - **DatabaseTarget:** The DatabaseTarget is no longer part of the NLog NuGet package. If you use this target, you'll need to [download it separately](https://www.nuget.org/packages/NLog.Database) to ensure its continued functionality. There are additional targets that have been transitioned into separate packages. Please review [this list](https://nlog-project.org/2021/08/25/nlog-5-0-preview1-ready.html#nlog-targets-extracted-into-their-own-nuget-packages) to determine if any targets you use are affected. From a3b531439f577061fcd84d99bdf5eb53fdf77b7d Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Thu, 10 Jul 2025 19:33:00 +0200 Subject: [PATCH 03/12] fixes --- .github/workflows/ci.yml | 3 + .../packages.lock.json | 24 ++-- .../Neolution.DotNet.Console.UnitTests.csproj | 6 +- .../packages.lock.json | 104 ++++++++++-------- .../DotNetConsoleLogger.cs | 2 +- .../Neolution.DotNet.Console.csproj | 29 +++-- Neolution.DotNet.Console/packages.lock.json | 48 ++++---- README.md | 96 +++++++++++++--- 8 files changed, 199 insertions(+), 113 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6210625..349bd07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,5 +28,8 @@ jobs: - name: Build run: dotnet build --no-restore --configuration '${{ env.BUILD_CONFIGURATION }}' + - name: Validate DI setup in SampleAsync + run: dotnet run --project Neolution.DotNet.Console.SampleAsync --no-build --configuration '${{ env.BUILD_CONFIGURATION }}' -- check-deps + - name: Test run: dotnet test --no-build --verbosity normal --configuration '${{ env.BUILD_CONFIGURATION }}' diff --git a/Neolution.DotNet.Console.SampleAsync/packages.lock.json b/Neolution.DotNet.Console.SampleAsync/packages.lock.json index be3d03b..046052e 100644 --- a/Neolution.DotNet.Console.SampleAsync/packages.lock.json +++ b/Neolution.DotNet.Console.SampleAsync/packages.lock.json @@ -332,8 +332,8 @@ }, "Scrutor": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "5xKT6ND5GqnFzwSaYozHCJe75GFL8sPy4yw/iRFqeBFGlmqPNFpOg1T9Q0Gl2h76Cklt0ZTg6Ypkri5iUBKXsA==", + "resolved": "6.1.0", + "contentHash": "m4+0RdgnX+jeiaqteq9x5SwEtuCjWG0KTw1jBjCzn7V8mCanXKoeF8+59E0fcoRbAjdEq6YqHFCmxZ49Kvqp3g==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", "Microsoft.Extensions.DependencyModel": "8.0.2" @@ -358,18 +358,18 @@ "type": "Project", "dependencies": { "CommandLineParser": "[2.9.1, )", - "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Configuration.CommandLine": "[8.0.0, )", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "[8.0.0, )", - "Microsoft.Extensions.Configuration.Json": "[8.0.1, )", - "Microsoft.Extensions.Configuration.UserSecrets": "[8.0.1, )", - "Microsoft.Extensions.DependencyInjection": "[8.0.1, )", - "Microsoft.Extensions.Hosting": "[8.0.1, )", - "Microsoft.Extensions.Logging": "[8.0.1, )", - "Microsoft.Extensions.Logging.Configuration": "[8.0.1, )", + "Microsoft.Extensions.Configuration.Abstractions": "[8.0.*, )", + "Microsoft.Extensions.Configuration.CommandLine": "[8.0.*, )", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "[8.0.*, )", + "Microsoft.Extensions.Configuration.Json": "[8.0.*, )", + "Microsoft.Extensions.Configuration.UserSecrets": "[8.0.*, )", + "Microsoft.Extensions.DependencyInjection": "[8.0.*, )", + "Microsoft.Extensions.Hosting": "[8.0.*, )", + "Microsoft.Extensions.Logging": "[8.0.*, )", + "Microsoft.Extensions.Logging.Configuration": "[8.0.*, )", "NLog": "[5.3.4, )", "NLog.Extensions.Logging": "[5.3.15, )", - "Scrutor": "[6.0.1, )" + "Scrutor": "[6.1.0, )" } } } diff --git a/Neolution.DotNet.Console.UnitTests/Neolution.DotNet.Console.UnitTests.csproj b/Neolution.DotNet.Console.UnitTests/Neolution.DotNet.Console.UnitTests.csproj index 1f33088..825ac42 100644 --- a/Neolution.DotNet.Console.UnitTests/Neolution.DotNet.Console.UnitTests.csproj +++ b/Neolution.DotNet.Console.UnitTests/Neolution.DotNet.Console.UnitTests.csproj @@ -14,15 +14,15 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Neolution.DotNet.Console.UnitTests/packages.lock.json b/Neolution.DotNet.Console.UnitTests/packages.lock.json index 218a3a5..6e69ab8 100644 --- a/Neolution.DotNet.Console.UnitTests/packages.lock.json +++ b/Neolution.DotNet.Console.UnitTests/packages.lock.json @@ -16,12 +16,12 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[17.12.0, )", - "resolved": "17.12.0", - "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", + "requested": "[17.14.1, )", + "resolved": "17.14.1", + "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==", "dependencies": { - "Microsoft.CodeCoverage": "17.12.0", - "Microsoft.TestPlatform.TestHost": "17.12.0" + "Microsoft.CodeCoverage": "17.14.1", + "Microsoft.TestPlatform.TestHost": "17.14.1" } }, "Neolution.CodeAnalysis.TestsRuleset": { @@ -36,20 +36,20 @@ }, "Objectivity.AutoFixture.XUnit2.AutoNSubstitute": { "type": "Direct", - "requested": "[3.6.2, )", - "resolved": "3.6.2", - "contentHash": "Uo6qw1xkBIT2VNoeME+QfPqmqlOKMgOTWKzaPE4bg9mFcge/ZvrlN95gssYJSXzybx2yWnEqvRxjYXYHeFL49g==", + "requested": "[3.6.3, )", + "resolved": "3.6.3", + "contentHash": "YXjVWh9OumYK0lO9NHLPrNHaYiEvAKeTQ2AI5z8zRJKscbzENjbP66S81xThSN+guODDqc6TdliReGAZ2m4Cfw==", "dependencies": { "AutoFixture": "4.18.1", "AutoFixture.AutoNSubstitute": "4.18.1", "AutoFixture.Xunit2": "4.18.1", - "Castle.Core": "5.1.1", - "JetBrains.Annotations": "2024.2.0", - "NSubstitute": "5.1.0", + "Castle.Core": "5.2.1", + "JetBrains.Annotations": "2024.3.0", + "NSubstitute": "5.3.0", "xunit.abstractions": "2.0.3", - "xunit.core": "2.9.2", - "xunit.extensibility.core": "2.9.2", - "xunit.extensibility.execution": "2.9.2" + "xunit.core": "2.9.3", + "xunit.extensibility.core": "2.9.3", + "xunit.extensibility.execution": "2.9.3" } }, "Shouldly": { @@ -75,9 +75,9 @@ }, "xunit.runner.visualstudio": { "type": "Direct", - "requested": "[3.0.1, )", - "resolved": "3.0.1", - "contentHash": "lbyYtsBxA8Pz8kaf5Xn/Mj1mL9z2nlBWdZhqFaj66nxXBa4JwiTDm4eGcpSMet6du9TOWI6bfha+gQR6+IHawg==" + "requested": "[3.1.1, )", + "resolved": "3.1.1", + "contentHash": "gNu2zhnuwjq5vQlU4S7yK/lfaKZDLmtcu+vTjnhfTlMAUYn+Hmgu8IIX0UCwWepYkk+Szx03DHx1bDnc9Fd+9w==" }, "AutoFixture": { "type": "Transitive", @@ -108,8 +108,8 @@ }, "Castle.Core": { "type": "Transitive", - "resolved": "5.1.1", - "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "resolved": "5.2.1", + "contentHash": "wHARzQA695jwwKreOzNsq54KiGqKP38tv8hi8e2FXDEC/sA6BtrX90tVPDkOfVu13PbEzr00TCV8coikl+D1Iw==", "dependencies": { "System.Diagnostics.EventLog": "6.0.0" } @@ -143,13 +143,13 @@ }, "JetBrains.Annotations": { "type": "Transitive", - "resolved": "2024.2.0", - "contentHash": "GNnqCFW/163p1fOehKx0CnAqjmpPrUSqrgfHM6qca+P+RN39C9rhlfZHQpJhxmQG/dkOYe/b3Z0P8b6Kv5m1qw==" + "resolved": "2024.3.0", + "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "17.12.0", - "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" + "resolved": "17.14.1", + "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -446,19 +446,19 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "17.12.0", - "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", + "resolved": "17.14.1", + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==", "dependencies": { - "System.Reflection.Metadata": "1.6.0" + "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "17.12.0", - "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", + "resolved": "17.14.1", + "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "17.12.0", - "Newtonsoft.Json": "13.0.1" + "Microsoft.TestPlatform.ObjectModel": "17.14.1", + "Newtonsoft.Json": "13.0.3" } }, "Microsoft.Win32.Primitives": { @@ -524,8 +524,8 @@ }, "Newtonsoft.Json": { "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "NLog": { "type": "Transitive", @@ -544,8 +544,8 @@ }, "NSubstitute": { "type": "Transitive", - "resolved": "5.1.0", - "contentHash": "ZCqOP3Kpp2ea7QcLyjMU4wzE+0wmrMN35PQMsdPOHYc2IrvjmusG9hICOiqiOTPKN0gJon6wyCn6ZuGHdNs9hQ==", + "resolved": "5.3.0", + "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { "Castle.Core": "5.1.1" } @@ -659,8 +659,8 @@ }, "Scrutor": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "5xKT6ND5GqnFzwSaYozHCJe75GFL8sPy4yw/iRFqeBFGlmqPNFpOg1T9Q0Gl2h76Cklt0ZTg6Ypkri5iUBKXsA==", + "resolved": "6.1.0", + "contentHash": "m4+0RdgnX+jeiaqteq9x5SwEtuCjWG0KTw1jBjCzn7V8mCanXKoeF8+59E0fcoRbAjdEq6YqHFCmxZ49Kvqp3g==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", "Microsoft.Extensions.DependencyModel": "8.0.2" @@ -728,6 +728,11 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.3.0", @@ -1091,8 +1096,11 @@ }, "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "1.6.0", - "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0" + } }, "System.Reflection.Primitives": { "type": "Transitive", @@ -1489,18 +1497,18 @@ "type": "Project", "dependencies": { "CommandLineParser": "[2.9.1, )", - "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Configuration.CommandLine": "[8.0.0, )", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "[8.0.0, )", - "Microsoft.Extensions.Configuration.Json": "[8.0.1, )", - "Microsoft.Extensions.Configuration.UserSecrets": "[8.0.1, )", - "Microsoft.Extensions.DependencyInjection": "[8.0.1, )", - "Microsoft.Extensions.Hosting": "[8.0.1, )", - "Microsoft.Extensions.Logging": "[8.0.1, )", - "Microsoft.Extensions.Logging.Configuration": "[8.0.1, )", + "Microsoft.Extensions.Configuration.Abstractions": "[8.0.*, )", + "Microsoft.Extensions.Configuration.CommandLine": "[8.0.*, )", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "[8.0.*, )", + "Microsoft.Extensions.Configuration.Json": "[8.0.*, )", + "Microsoft.Extensions.Configuration.UserSecrets": "[8.0.*, )", + "Microsoft.Extensions.DependencyInjection": "[8.0.*, )", + "Microsoft.Extensions.Hosting": "[8.0.*, )", + "Microsoft.Extensions.Logging": "[8.0.*, )", + "Microsoft.Extensions.Logging.Configuration": "[8.0.*, )", "NLog": "[5.3.4, )", "NLog.Extensions.Logging": "[5.3.15, )", - "Scrutor": "[6.0.1, )" + "Scrutor": "[6.1.0, )" } } } diff --git a/Neolution.DotNet.Console/DotNetConsoleLogger.cs b/Neolution.DotNet.Console/DotNetConsoleLogger.cs index 5158533..5692252 100644 --- a/Neolution.DotNet.Console/DotNetConsoleLogger.cs +++ b/Neolution.DotNet.Console/DotNetConsoleLogger.cs @@ -49,7 +49,7 @@ public static void Initialize(IConfiguration configuration) // Create a simple NLog configuration that logs to the console var config = new NLog.Config.LoggingConfiguration(); consoleTarget = new ConsoleTarget("console"); - config.AddRule(LogLevel.Info, LogLevel.Fatal, consoleTarget); + config.AddRule(LogLevel.Trace, LogLevel.Fatal, consoleTarget); LogManager.Configuration = config; logger = LogManager.GetCurrentClassLogger(); diff --git a/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj b/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj index 787988a..190463a 100644 --- a/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj +++ b/Neolution.DotNet.Console/Neolution.DotNet.Console.csproj @@ -4,12 +4,13 @@ net6.0;net8.0 Neolution.DotNet.Console Neolution AG - + Library for building .NET console applications with dependency injection, async command execution, flexible logging via NLog, and configuration support. https://github.com/neolution-ch/Neolution.DotNet.Console https://github.com/neolution-ch/Neolution.DotNet.Console git - dotnet;console;dependency-injection + dotnet;console;cli;dependency-injection;async;commands;logging;nlog;configuration;hosting;commandlineparser;dotnet-cli;hostbuilder;microsoft-extensions-hosting;command-line MIT + icon.png README.md enable true @@ -20,26 +21,30 @@ - - - - - - - - - + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + diff --git a/Neolution.DotNet.Console/packages.lock.json b/Neolution.DotNet.Console/packages.lock.json index bfeb02e..cbe3d0d 100644 --- a/Neolution.DotNet.Console/packages.lock.json +++ b/Neolution.DotNet.Console/packages.lock.json @@ -10,7 +10,7 @@ }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Direct", - "requested": "[8.0.0, )", + "requested": "[8.0.*, )", "resolved": "8.0.0", "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", "dependencies": { @@ -19,7 +19,7 @@ }, "Microsoft.Extensions.Configuration.CommandLine": { "type": "Direct", - "requested": "[8.0.0, )", + "requested": "[8.0.*, )", "resolved": "8.0.0", "contentHash": "NZuZMz3Q8Z780nKX3ifV1fE7lS+6pynDHK71OfU4OZ1ItgvDOhyOC7E6z+JMZrAj63zRpwbdldYFk499t3+1dQ==", "dependencies": { @@ -29,7 +29,7 @@ }, "Microsoft.Extensions.Configuration.EnvironmentVariables": { "type": "Direct", - "requested": "[8.0.0, )", + "requested": "[8.0.*, )", "resolved": "8.0.0", "contentHash": "plvZ0ZIpq+97gdPNNvhwvrEZ92kNml9hd1pe3idMA7svR0PztdzVLkoWLcRFgySYXUJc3kSM3Xw3mNFMo/bxRA==", "dependencies": { @@ -39,7 +39,7 @@ }, "Microsoft.Extensions.Configuration.Json": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "L89DLNuimOghjV3tLx0ArFDwVEJD6+uGB3BMCMX01kaLzXkaXHb2021xOMl2QOxUxbdePKUZsUY7n2UUkycjRg==", "dependencies": { @@ -52,7 +52,7 @@ }, "Microsoft.Extensions.Configuration.UserSecrets": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "7tYqdPPpAK+3jO9d5LTuCK2VxrEdf85Ol4trUr6ds4jclBecadWZ/RyPCbNjfbN5iGTfUnD/h65TOQuqQv2c+A==", "dependencies": { @@ -64,7 +64,7 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", "dependencies": { @@ -73,7 +73,7 @@ }, "Microsoft.Extensions.Hosting": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "bP9EEkHBEfjgYiG8nUaXqMk/ujwJrffOkNPP7onpRMO8R+OUSESSP4xHkCAXgYZ1COP2Q9lXlU5gkMFh20gRuw==", "dependencies": { @@ -103,7 +103,7 @@ }, "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==", "dependencies": { @@ -114,7 +114,7 @@ }, "Microsoft.Extensions.Logging.Configuration": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "QWwTrsgOnJMmn+XUslm8D2H1n3PkP/u/v52FODtyBc/k4W9r3i2vcXXeeX/upnzllJYRRbrzVzT0OclfNJtBJA==", "dependencies": { @@ -157,9 +157,9 @@ }, "Scrutor": { "type": "Direct", - "requested": "[6.0.1, )", - "resolved": "6.0.1", - "contentHash": "5xKT6ND5GqnFzwSaYozHCJe75GFL8sPy4yw/iRFqeBFGlmqPNFpOg1T9Q0Gl2h76Cklt0ZTg6Ypkri5iUBKXsA==", + "requested": "[6.1.0, )", + "resolved": "6.1.0", + "contentHash": "m4+0RdgnX+jeiaqteq9x5SwEtuCjWG0KTw1jBjCzn7V8mCanXKoeF8+59E0fcoRbAjdEq6YqHFCmxZ49Kvqp3g==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", "Microsoft.Extensions.DependencyModel": "8.0.2" @@ -406,7 +406,7 @@ }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Direct", - "requested": "[8.0.0, )", + "requested": "[8.0.*, )", "resolved": "8.0.0", "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", "dependencies": { @@ -415,7 +415,7 @@ }, "Microsoft.Extensions.Configuration.CommandLine": { "type": "Direct", - "requested": "[8.0.0, )", + "requested": "[8.0.*, )", "resolved": "8.0.0", "contentHash": "NZuZMz3Q8Z780nKX3ifV1fE7lS+6pynDHK71OfU4OZ1ItgvDOhyOC7E6z+JMZrAj63zRpwbdldYFk499t3+1dQ==", "dependencies": { @@ -425,7 +425,7 @@ }, "Microsoft.Extensions.Configuration.EnvironmentVariables": { "type": "Direct", - "requested": "[8.0.0, )", + "requested": "[8.0.*, )", "resolved": "8.0.0", "contentHash": "plvZ0ZIpq+97gdPNNvhwvrEZ92kNml9hd1pe3idMA7svR0PztdzVLkoWLcRFgySYXUJc3kSM3Xw3mNFMo/bxRA==", "dependencies": { @@ -435,7 +435,7 @@ }, "Microsoft.Extensions.Configuration.Json": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "L89DLNuimOghjV3tLx0ArFDwVEJD6+uGB3BMCMX01kaLzXkaXHb2021xOMl2QOxUxbdePKUZsUY7n2UUkycjRg==", "dependencies": { @@ -447,7 +447,7 @@ }, "Microsoft.Extensions.Configuration.UserSecrets": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "7tYqdPPpAK+3jO9d5LTuCK2VxrEdf85Ol4trUr6ds4jclBecadWZ/RyPCbNjfbN5iGTfUnD/h65TOQuqQv2c+A==", "dependencies": { @@ -459,7 +459,7 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", "dependencies": { @@ -468,7 +468,7 @@ }, "Microsoft.Extensions.Hosting": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "bP9EEkHBEfjgYiG8nUaXqMk/ujwJrffOkNPP7onpRMO8R+OUSESSP4xHkCAXgYZ1COP2Q9lXlU5gkMFh20gRuw==", "dependencies": { @@ -498,7 +498,7 @@ }, "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==", "dependencies": { @@ -509,7 +509,7 @@ }, "Microsoft.Extensions.Logging.Configuration": { "type": "Direct", - "requested": "[8.0.1, )", + "requested": "[8.0.*, )", "resolved": "8.0.1", "contentHash": "QWwTrsgOnJMmn+XUslm8D2H1n3PkP/u/v52FODtyBc/k4W9r3i2vcXXeeX/upnzllJYRRbrzVzT0OclfNJtBJA==", "dependencies": { @@ -552,9 +552,9 @@ }, "Scrutor": { "type": "Direct", - "requested": "[6.0.1, )", - "resolved": "6.0.1", - "contentHash": "5xKT6ND5GqnFzwSaYozHCJe75GFL8sPy4yw/iRFqeBFGlmqPNFpOg1T9Q0Gl2h76Cklt0ZTg6Ypkri5iUBKXsA==", + "requested": "[6.1.0, )", + "resolved": "6.1.0", + "contentHash": "m4+0RdgnX+jeiaqteq9x5SwEtuCjWG0KTw1jBjCzn7V8mCanXKoeF8+59E0fcoRbAjdEq6YqHFCmxZ49Kvqp3g==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", "Microsoft.Extensions.DependencyModel": "8.0.2" diff --git a/README.md b/README.md index 947e2b3..06a32e6 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,21 @@ [![Build Status](https://github.com/neolution-ch/Neolution.DotNet.Console/actions/workflows/cd-production.yml/badge.svg)](https://github.com/neolution-ch/Neolution.DotNet.Console/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -Neolution.DotNet.Console is a versatile package designed as a launchpad for .NET console applications. It comes equipped with a built-in dependency injection setup, mirroring the functionality found in ASP.NET Core applications, thus providing a seamless and intuitive user experience. +Neolution.DotNet.Console is a library for building .NET console applications. It uses the Microsoft.Extensions.Hosting model to provide a familiar setup for dependency injection, logging, and command-line parsing. + +This project works for simple CLI tools as well as longer-running console services, and supports cancellation tokens, configuration from JSON, environment variables, or command-line arguments. ## Features -- Async command execution -- Built-in dependency injection (like ASP.NET Core) -- NLog integration for flexible logging -- Strict verb matching for safer CLI usage -- Service registration validation at build time (check-deps command) +- Async commands with cancellation support +- Dependency injection setup matching ASP.NET Core patterns +- Command-line parsing with verb and option support +- Logging integration using NLog +- Configuration from appsettings.json, environment variables, user secrets, and command line +- Strict verb matching to prevent unintended command execution +- A built-in `check-deps` command to verify all services are registered correctly +- Support for multiple environments (Development, Production, etc.) +- Automatic scope creation for each command execution to support scoped services ## Installation @@ -113,22 +119,30 @@ This pattern keeps your `Program.cs` clean and delegates service registration an ### Defining a Command -To add a command, implement the `IDotNetConsoleCommand` interface. For example: +To add a command, implement the `IDotNetConsoleCommand` interface. For example: ```csharp +using CommandLine; using Neolution.DotNet.Console.Abstractions; -public class EchoCommand : IDotNetConsoleCommand +[Verb("echo", HelpText = "Write a string into the console.")] +public class EchoOptions { - public async Task RunAsync(CancellationToken cancellationToken) + [Value(0)] + public string? Message { get; set; } +} + +public class EchoCommand : IDotNetConsoleCommand +{ + public async Task RunAsync(EchoOptions options, CancellationToken cancellationToken) { - Console.WriteLine("Hello from EchoCommand!"); + Console.WriteLine($"Echo: {options.Message}"); await Task.CompletedTask; } } ``` -The library will automatically discover and register all commands (classes implementing `IDotNetConsoleCommand`). +The library will automatically discover and register all commands (classes implementing `IDotNetConsoleCommand`). ### Running the Application @@ -140,9 +154,65 @@ dotnet run -- [verb] [options] Replace `[verb]` with the name of your command (e.g., `echo`). Any additional options or parameters defined in your command will be parsed and passed automatically. ---- +## Dependency Validation with check-deps + +The framework includes a built-in `check-deps` command that validates your application's dependency injection setup without running any business logic. This is particularly useful for catching DI configuration issues early. + +### Why use check-deps? + +- **Early Detection**: Catch dependency injection issues during build/CI rather than at runtime +- **Fast Feedback**: Validates your entire DI container setup in seconds without executing business logic +- **Confidence**: Ensures all your services can be properly constructed by the DI container +- **CI Integration**: Perfect for automated validation in your deployment pipeline + +The command validates: + +- All registered services can be instantiated +- No circular dependencies exist +- Service lifetimes are properly configured +- All required dependencies are registered + +If validation fails, the command will exit with a non-zero code and provide detailed error information about what's missing or misconfigured. + +### Running check-deps locally + +```sh +# Validate your DI setup + dotnet run -- check-deps + +# Or if you have a published/built application + myapp.exe check-deps +``` + +### Using check-deps in CI/CD + +The `check-deps` command is especially valuable in CI/CD pipelines to catch dependency injection issues before deployment: + +```yaml +# Example GitHub Actions workflow +name: CI +on: [push, pull_request] + +jobs: + validate-dependencies: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Validate DI setup + run: dotnet run --project YourApp -- check-deps + - name: Run tests + run: dotnet test --no-build +``` -## Guides +## Migration Guides ### Migrate from V3 to V5 From 83d104b521092df1703ebf52523ee1a0b810af7a Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Fri, 11 Jul 2025 09:18:52 +0200 Subject: [PATCH 04/12] release notes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7991ce7..f23b200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `DotNetConsoleLogger.cs` static class to provide initialization, access, and shutdown for the logger instance used in console applications. +### Changed + +- Updated Scrutor to v6.1.0. +- Updated Microsoft.Extensions.* package references to use floating version `8.0.*` to always get the latest patch. + ## [5.0.0] - 2025-01-27 ### Added From c22f6f8f1b9f09f09a67da04805ff3a950915cae Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Fri, 11 Jul 2025 09:20:54 +0200 Subject: [PATCH 05/12] CI sanity check --- Neolution.DotNet.Console.SampleAsync/Startup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Neolution.DotNet.Console.SampleAsync/Startup.cs b/Neolution.DotNet.Console.SampleAsync/Startup.cs index 672b630..7a0e2ae 100644 --- a/Neolution.DotNet.Console.SampleAsync/Startup.cs +++ b/Neolution.DotNet.Console.SampleAsync/Startup.cs @@ -37,8 +37,6 @@ public Startup(IHostEnvironment environment, IConfiguration configuration) /// The services. public void ConfigureServices(IServiceCollection services) { - services.AddHttpClient(); - // EXAMPLE 1: Configure services based on the environment if (this.Environment.IsDevelopment()) { From 5e3a9e3ac65d49f4e943f89e4eb7212d42d8a944 Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Fri, 11 Jul 2025 09:32:36 +0200 Subject: [PATCH 06/12] Revert sanity check, rename Demo Application --- .github/workflows/ci.yml | 4 ++-- .../Commands/Echo/EchoCommand.cs | 2 +- .../Commands/Echo/EchoOptions.cs | 2 +- .../Commands/GuidGen/GuidGenCommand.cs | 2 +- .../Commands/GuidGen/GuidGenOptions.cs | 4 ++-- .../Commands/Start/StartCommand.cs | 2 +- .../Commands/Start/StartOptions.cs | 2 +- .../Dockerfile | 2 +- .../Neolution.DotNet.Console.Demo.csproj | 0 .../Program.cs | 2 +- .../Properties/launchSettings.json | 4 ++-- .../Startup.cs | 4 +++- .../appsettings.Development.json | 0 .../appsettings.Production.json | 0 .../appsettings.json | 0 .../packages.lock.json | 0 Neolution.DotNet.Console.sln | 12 ++++++------ README.md | 6 +++--- 18 files changed, 25 insertions(+), 23 deletions(-) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Commands/Echo/EchoCommand.cs (96%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Commands/Echo/EchoOptions.cs (85%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Commands/GuidGen/GuidGenCommand.cs (95%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Commands/GuidGen/GuidGenOptions.cs (87%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Commands/Start/StartCommand.cs (97%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Commands/Start/StartOptions.cs (85%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Dockerfile (82%) rename Neolution.DotNet.Console.SampleAsync/Neolution.DotNet.Console.SampleAsync.csproj => Neolution.DotNet.Console.Demo/Neolution.DotNet.Console.Demo.csproj (100%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Program.cs (96%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Properties/launchSettings.json (81%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/Startup.cs (95%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/appsettings.Development.json (100%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/appsettings.Production.json (100%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/appsettings.json (100%) rename {Neolution.DotNet.Console.SampleAsync => Neolution.DotNet.Console.Demo}/packages.lock.json (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 349bd07..f87933f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,8 @@ jobs: - name: Build run: dotnet build --no-restore --configuration '${{ env.BUILD_CONFIGURATION }}' - - name: Validate DI setup in SampleAsync - run: dotnet run --project Neolution.DotNet.Console.SampleAsync --no-build --configuration '${{ env.BUILD_CONFIGURATION }}' -- check-deps + - name: Run check-deps in Demo Application + run: dotnet run --project Neolution.DotNet.Console.Demo --no-build --configuration '${{ env.BUILD_CONFIGURATION }}' -- check-deps - name: Test run: dotnet test --no-build --verbosity normal --configuration '${{ env.BUILD_CONFIGURATION }}' diff --git a/Neolution.DotNet.Console.SampleAsync/Commands/Echo/EchoCommand.cs b/Neolution.DotNet.Console.Demo/Commands/Echo/EchoCommand.cs similarity index 96% rename from Neolution.DotNet.Console.SampleAsync/Commands/Echo/EchoCommand.cs rename to Neolution.DotNet.Console.Demo/Commands/Echo/EchoCommand.cs index 6f68bb9..3b52e0c 100644 --- a/Neolution.DotNet.Console.SampleAsync/Commands/Echo/EchoCommand.cs +++ b/Neolution.DotNet.Console.Demo/Commands/Echo/EchoCommand.cs @@ -1,4 +1,4 @@ -namespace Neolution.DotNet.Console.SampleAsync.Commands.Echo +namespace Neolution.DotNet.Console.Demo.Commands.Echo { using System; using Microsoft.Extensions.Configuration; diff --git a/Neolution.DotNet.Console.SampleAsync/Commands/Echo/EchoOptions.cs b/Neolution.DotNet.Console.Demo/Commands/Echo/EchoOptions.cs similarity index 85% rename from Neolution.DotNet.Console.SampleAsync/Commands/Echo/EchoOptions.cs rename to Neolution.DotNet.Console.Demo/Commands/Echo/EchoOptions.cs index 16f41f0..582aeab 100644 --- a/Neolution.DotNet.Console.SampleAsync/Commands/Echo/EchoOptions.cs +++ b/Neolution.DotNet.Console.Demo/Commands/Echo/EchoOptions.cs @@ -1,4 +1,4 @@ -namespace Neolution.DotNet.Console.SampleAsync.Commands.Echo +namespace Neolution.DotNet.Console.Demo.Commands.Echo { using CommandLine; diff --git a/Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenCommand.cs b/Neolution.DotNet.Console.Demo/Commands/GuidGen/GuidGenCommand.cs similarity index 95% rename from Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenCommand.cs rename to Neolution.DotNet.Console.Demo/Commands/GuidGen/GuidGenCommand.cs index 43ede30..f3eca57 100644 --- a/Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenCommand.cs +++ b/Neolution.DotNet.Console.Demo/Commands/GuidGen/GuidGenCommand.cs @@ -1,4 +1,4 @@ -namespace Neolution.DotNet.Console.SampleAsync.Commands.GuidGen +namespace Neolution.DotNet.Console.Demo.Commands.GuidGen { using System; using System.Globalization; diff --git a/Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenOptions.cs b/Neolution.DotNet.Console.Demo/Commands/GuidGen/GuidGenOptions.cs similarity index 87% rename from Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenOptions.cs rename to Neolution.DotNet.Console.Demo/Commands/GuidGen/GuidGenOptions.cs index 20ef17a..494873a 100644 --- a/Neolution.DotNet.Console.SampleAsync/Commands/GuidGen/GuidGenOptions.cs +++ b/Neolution.DotNet.Console.Demo/Commands/GuidGen/GuidGenOptions.cs @@ -1,7 +1,7 @@ -namespace Neolution.DotNet.Console.SampleAsync.Commands.GuidGen +namespace Neolution.DotNet.Console.Demo.Commands.GuidGen { using CommandLine; - using Neolution.DotNet.Console.SampleAsync.Commands.Echo; + using Neolution.DotNet.Console.Demo.Commands.Echo; /// /// The options for the . diff --git a/Neolution.DotNet.Console.SampleAsync/Commands/Start/StartCommand.cs b/Neolution.DotNet.Console.Demo/Commands/Start/StartCommand.cs similarity index 97% rename from Neolution.DotNet.Console.SampleAsync/Commands/Start/StartCommand.cs rename to Neolution.DotNet.Console.Demo/Commands/Start/StartCommand.cs index d12d104..47ca39b 100644 --- a/Neolution.DotNet.Console.SampleAsync/Commands/Start/StartCommand.cs +++ b/Neolution.DotNet.Console.Demo/Commands/Start/StartCommand.cs @@ -1,4 +1,4 @@ -namespace Neolution.DotNet.Console.SampleAsync.Commands.Start +namespace Neolution.DotNet.Console.Demo.Commands.Start { using System; using Microsoft.Extensions.Logging; diff --git a/Neolution.DotNet.Console.SampleAsync/Commands/Start/StartOptions.cs b/Neolution.DotNet.Console.Demo/Commands/Start/StartOptions.cs similarity index 85% rename from Neolution.DotNet.Console.SampleAsync/Commands/Start/StartOptions.cs rename to Neolution.DotNet.Console.Demo/Commands/Start/StartOptions.cs index 4b12266..4861bba 100644 --- a/Neolution.DotNet.Console.SampleAsync/Commands/Start/StartOptions.cs +++ b/Neolution.DotNet.Console.Demo/Commands/Start/StartOptions.cs @@ -1,4 +1,4 @@ -namespace Neolution.DotNet.Console.SampleAsync.Commands.Start +namespace Neolution.DotNet.Console.Demo.Commands.Start { using CommandLine; diff --git a/Neolution.DotNet.Console.SampleAsync/Dockerfile b/Neolution.DotNet.Console.Demo/Dockerfile similarity index 82% rename from Neolution.DotNet.Console.SampleAsync/Dockerfile rename to Neolution.DotNet.Console.Demo/Dockerfile index 51a39b4..fe0faaa 100644 --- a/Neolution.DotNet.Console.SampleAsync/Dockerfile +++ b/Neolution.DotNet.Console.Demo/Dockerfile @@ -12,4 +12,4 @@ RUN dotnet publish -c Release -o out FROM mcr.microsoft.com/dotnet/runtime:6.0 WORKDIR /app COPY --from=build-env /app/out . -ENTRYPOINT ["dotnet", "Neolution.DotNet.Console.SampleAsync.dll"] +ENTRYPOINT ["dotnet", "Neolution.DotNet.Console.Demo.dll"] diff --git a/Neolution.DotNet.Console.SampleAsync/Neolution.DotNet.Console.SampleAsync.csproj b/Neolution.DotNet.Console.Demo/Neolution.DotNet.Console.Demo.csproj similarity index 100% rename from Neolution.DotNet.Console.SampleAsync/Neolution.DotNet.Console.SampleAsync.csproj rename to Neolution.DotNet.Console.Demo/Neolution.DotNet.Console.Demo.csproj diff --git a/Neolution.DotNet.Console.SampleAsync/Program.cs b/Neolution.DotNet.Console.Demo/Program.cs similarity index 96% rename from Neolution.DotNet.Console.SampleAsync/Program.cs rename to Neolution.DotNet.Console.Demo/Program.cs index d20b301..305c94d 100644 --- a/Neolution.DotNet.Console.SampleAsync/Program.cs +++ b/Neolution.DotNet.Console.Demo/Program.cs @@ -1,4 +1,4 @@ -namespace Neolution.DotNet.Console.SampleAsync +namespace Neolution.DotNet.Console.Demo { /// /// The program diff --git a/Neolution.DotNet.Console.SampleAsync/Properties/launchSettings.json b/Neolution.DotNet.Console.Demo/Properties/launchSettings.json similarity index 81% rename from Neolution.DotNet.Console.SampleAsync/Properties/launchSettings.json rename to Neolution.DotNet.Console.Demo/Properties/launchSettings.json index b3628c5..cfae5ad 100644 --- a/Neolution.DotNet.Console.SampleAsync/Properties/launchSettings.json +++ b/Neolution.DotNet.Console.Demo/Properties/launchSettings.json @@ -1,12 +1,12 @@ { "profiles": { - "Sample Console App": { + "Demo Console App": { "commandName": "Project", "environmentVariables": { "DOTNET_ENVIRONMENT": "Development" } }, - "Sample Console App: check-deps": { + "Demo Console App: check-deps": { "commandName": "Project", "commandLineArgs": "check-deps", "environmentVariables": { diff --git a/Neolution.DotNet.Console.SampleAsync/Startup.cs b/Neolution.DotNet.Console.Demo/Startup.cs similarity index 95% rename from Neolution.DotNet.Console.SampleAsync/Startup.cs rename to Neolution.DotNet.Console.Demo/Startup.cs index 7a0e2ae..0756364 100644 --- a/Neolution.DotNet.Console.SampleAsync/Startup.cs +++ b/Neolution.DotNet.Console.Demo/Startup.cs @@ -1,4 +1,4 @@ -namespace Neolution.DotNet.Console.SampleAsync +namespace Neolution.DotNet.Console.Demo { using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -37,6 +37,8 @@ public Startup(IHostEnvironment environment, IConfiguration configuration) /// The services. public void ConfigureServices(IServiceCollection services) { + services.AddHttpClient(); + // EXAMPLE 1: Configure services based on the environment if (this.Environment.IsDevelopment()) { diff --git a/Neolution.DotNet.Console.SampleAsync/appsettings.Development.json b/Neolution.DotNet.Console.Demo/appsettings.Development.json similarity index 100% rename from Neolution.DotNet.Console.SampleAsync/appsettings.Development.json rename to Neolution.DotNet.Console.Demo/appsettings.Development.json diff --git a/Neolution.DotNet.Console.SampleAsync/appsettings.Production.json b/Neolution.DotNet.Console.Demo/appsettings.Production.json similarity index 100% rename from Neolution.DotNet.Console.SampleAsync/appsettings.Production.json rename to Neolution.DotNet.Console.Demo/appsettings.Production.json diff --git a/Neolution.DotNet.Console.SampleAsync/appsettings.json b/Neolution.DotNet.Console.Demo/appsettings.json similarity index 100% rename from Neolution.DotNet.Console.SampleAsync/appsettings.json rename to Neolution.DotNet.Console.Demo/appsettings.json diff --git a/Neolution.DotNet.Console.SampleAsync/packages.lock.json b/Neolution.DotNet.Console.Demo/packages.lock.json similarity index 100% rename from Neolution.DotNet.Console.SampleAsync/packages.lock.json rename to Neolution.DotNet.Console.Demo/packages.lock.json diff --git a/Neolution.DotNet.Console.sln b/Neolution.DotNet.Console.sln index dc1e47b..67bf04b 100644 --- a/Neolution.DotNet.Console.sln +++ b/Neolution.DotNet.Console.sln @@ -18,10 +18,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neolution.DotNet.Console.SampleAsync", "Neolution.DotNet.Console.SampleAsync\Neolution.DotNet.Console.SampleAsync.csproj", "{C31218C2-00A1-4B5C-A4B3-AF83E2CBA3A4}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neolution.DotNet.Console.UnitTests", "Neolution.DotNet.Console.UnitTests\Neolution.DotNet.Console.UnitTests.csproj", "{66E5F184-CCCC-47A2-8B9E-25AE0036CE1B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.DotNet.Console.Demo", "Neolution.DotNet.Console.Demo\Neolution.DotNet.Console.Demo.csproj", "{3F17699A-2864-0EEC-AC50-93648D6E5BDE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,14 +32,14 @@ Global {0206E537-E125-4E03-BAE8-E009211412DF}.Debug|Any CPU.Build.0 = Debug|Any CPU {0206E537-E125-4E03-BAE8-E009211412DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {0206E537-E125-4E03-BAE8-E009211412DF}.Release|Any CPU.Build.0 = Release|Any CPU - {C31218C2-00A1-4B5C-A4B3-AF83E2CBA3A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C31218C2-00A1-4B5C-A4B3-AF83E2CBA3A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C31218C2-00A1-4B5C-A4B3-AF83E2CBA3A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C31218C2-00A1-4B5C-A4B3-AF83E2CBA3A4}.Release|Any CPU.Build.0 = Release|Any CPU {66E5F184-CCCC-47A2-8B9E-25AE0036CE1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {66E5F184-CCCC-47A2-8B9E-25AE0036CE1B}.Debug|Any CPU.Build.0 = Debug|Any CPU {66E5F184-CCCC-47A2-8B9E-25AE0036CE1B}.Release|Any CPU.ActiveCfg = Release|Any CPU {66E5F184-CCCC-47A2-8B9E-25AE0036CE1B}.Release|Any CPU.Build.0 = Release|Any CPU + {3F17699A-2864-0EEC-AC50-93648D6E5BDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F17699A-2864-0EEC-AC50-93648D6E5BDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F17699A-2864-0EEC-AC50-93648D6E5BDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F17699A-2864-0EEC-AC50-93648D6E5BDE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index 06a32e6..3347e29 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Install-Package Neolution.DotNet.Console ## Getting Started -To help you kickstart your console application, we've provided a [sample application](/Neolution.DotNet.Console.SampleAsync/Program.cs) that demonstrates the basic usage of this package. +To help you kickstart your console application, we've provided a [demo application](/Neolution.DotNet.Console.Demo/Program.cs) that demonstrates the basic usage of this package. ### Example Main Method with Startup Class (Recommended) @@ -46,7 +46,7 @@ Below is the recommended pattern for your `Program.cs` entry point, using a `Sta ```csharp // Program.cs -namespace Neolution.DotNet.Console.SampleAsync +namespace Neolution.DotNet.Console.Demo { public static class Program { @@ -80,7 +80,7 @@ namespace Neolution.DotNet.Console.SampleAsync ```csharp // Startup.cs -namespace Neolution.DotNet.Console.SampleAsync +namespace Neolution.DotNet.Console.Demo { using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; From 70fda7bbd89bdec3213025db7964d89d13a0f3d1 Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Fri, 11 Jul 2025 09:42:54 +0200 Subject: [PATCH 07/12] Use new static logger for cancellation message --- Neolution.DotNet.Console/DotNetConsole.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Neolution.DotNet.Console/DotNetConsole.cs b/Neolution.DotNet.Console/DotNetConsole.cs index b729414..9ebe36b 100644 --- a/Neolution.DotNet.Console/DotNetConsole.cs +++ b/Neolution.DotNet.Console/DotNetConsole.cs @@ -6,12 +6,9 @@ using System.Threading; using System.Threading.Tasks; using CommandLine; - using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Neolution.DotNet.Console.Abstractions; - using NLog; - using NLog.Extensions.Logging; /// /// The console application. @@ -115,8 +112,7 @@ public async Task RunAsync(CancellationToken cancellationToken) { try { - var logger = LogManager.Setup().LoadConfigurationFromSection(this.Services.GetRequiredService()).GetCurrentClassLogger(); - logger.Log(LogLevel.Info, ex, CultureInfo.InvariantCulture, message: "Operation was canceled by the user."); + DotNetConsoleLogger.Log.Info(ex, CultureInfo.InvariantCulture, "Operation was canceled by the user."); } catch (Exception) { From dbf7a97bc2220186cfcffd0fad0d5510058c3b25 Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Fri, 11 Jul 2025 10:03:03 +0200 Subject: [PATCH 08/12] release notes --- CHANGELOG.md | 4 ++++ README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f23b200..bea52db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated Scrutor to v6.1.0. - Updated Microsoft.Extensions.* package references to use floating version `8.0.*` to always get the latest patch. +### Removed + +- Removed obsolete serialization constructor and `[Serializable]` attribute from `DotNetConsoleException` as formatter-based serialization is no longer supported or recommended in modern .NET. + ## [5.0.0] - 2025-01-27 ### Added diff --git a/README.md b/README.md index 3347e29..35431fa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Introduction +# Neolution.DotNet.Console [![NuGet](https://img.shields.io/nuget/v/Neolution.DotNet.Console.svg)](https://www.nuget.org/packages/Neolution.DotNet.Console) [![Build Status](https://github.com/neolution-ch/Neolution.DotNet.Console/actions/workflows/cd-production.yml/badge.svg)](https://github.com/neolution-ch/Neolution.DotNet.Console/actions) From 8fc0439442ce25b144dd8202bd8619162706d319 Mon Sep 17 00:00:00 2001 From: GitHub Release Bot Date: Fri, 11 Jul 2025 08:06:19 +0000 Subject: [PATCH 09/12] Release 5.1.0-alpha.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bea52db..ea2dfc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated Scrutor to v6.1.0. -- Updated Microsoft.Extensions.* package references to use floating version `8.0.*` to always get the latest patch. +- Updated Microsoft.Extensions._ package references to use floating version `8.0._` to always get the latest patch. ### Removed From 7ae22c77f10a829b84e437c304c72f5d81a0cb9d Mon Sep 17 00:00:00 2001 From: Sandro Ciervo Date: Fri, 11 Jul 2025 10:22:34 +0200 Subject: [PATCH 10/12] fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea2dfc4..96c89fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated Scrutor to v6.1.0. -- Updated Microsoft.Extensions._ package references to use floating version `8.0._` to always get the latest patch. +- Updated Microsoft.Extensions packages to latest patch versions. ### Removed From 4d08cd69008d1a23cd6d78ed49689ab337c45a1b Mon Sep 17 00:00:00 2001 From: "Sandro C." Date: Fri, 11 Jul 2025 10:33:54 +0200 Subject: [PATCH 11/12] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35431fa..dece8d3 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ If validation fails, the command will exit with a non-zero code and provide deta dotnet run -- check-deps # Or if you have a published/built application - myapp.exe check-deps +myapp.exe check-deps ``` ### Using check-deps in CI/CD From d9d6b38f82402ed71c99d658ad70249bc1470fcf Mon Sep 17 00:00:00 2001 From: "Sandro C." Date: Fri, 11 Jul 2025 10:34:06 +0200 Subject: [PATCH 12/12] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dece8d3..f14b07c 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ If validation fails, the command will exit with a non-zero code and provide deta ```sh # Validate your DI setup - dotnet run -- check-deps +dotnet run -- check-deps # Or if you have a published/built application myapp.exe check-deps