Skip to content

ConnyConsole is an example console CLI project that uses DI, Serilog for logging, execution cancellation and argument parsing based on Microsoft's System.CommandLine library.

License

Notifications You must be signed in to change notification settings

HaGGi13/ConnyConsole

Repository files navigation

Build and analyze Quality Gate Status

ConnyConsole

ConnyConsole is a console CLI project that uses System.CommandLine from Microsoft for argument parsing to collect some experience with this library.

Table of content

Features

This chapter provides a rough overview what was implemented as examples.
Please note, some of the features listed are based on or inspired by the article series A Beginner's Guide to .NET's HostBuilder: Part 1 and following.

Generic Host & Dependency Injection

  • Console startup implemented with Host.CreateDefaultBuilder for dependency injection, configuration loading and logging integration;
  • Dependency injection configuration via ConfigureServices and extension method to keep Program.cs simple;
    • Own extension method AddConfiguration registers all dependencies incl. the following:
      • Logger is configured via configuration injection;
      • Configuration registered for options pattern usage;
  • Loads configuration from specific subdirectory Config that contains appsettings.json and appsettings.Development.json:
    • Current environment resolved from injected HostBuilderContext to use environment specific setting file;
    • Intentionally appsettings (here loggersettings) files located in subdirectory Config to enforce loading them in code explicitly (as example to bypass appsettings load magic);
    • Allows CLI to explicitly control the loading order and prioritize the layered configuration approach (System/Global/Local) while maintaining hard-coded default values for core logic;
  • Console application icon defined (check *.csproj file tag ApplicationIcon);

Structured Logging with Serilog

  • Structured logging by using **Serilog;
  • Startup-logger and on startup configured based on configuration files (loggersettings) injectable logger;
  • Logging on console and in file with defined format;
  • Serilog can throw strange/not relatable exceptions on app startup when malformed/invalid JSON config is used;
    • Serilog configuration exceptions now printed on console during startup;
  • Logger configuration is stored in loggersettings.json file, because no appsettings.json file is used (default configuration is hard-coded);

Cancellation & Lifecycle Management

  • Graceful and enforceable cancellation of current async executed dummy logic;
    • First [Ctrl] + [C] or [Ctrl] + [Break] initiates graceful cancellation;
      • Waits until logic finishes or a configurable timeout reaches and closes the application;
    • Second [Ctrl] + [C] or [Ctrl] + [Break] initiates immediate enforced cancellation;
      • Application exists immediately;
  • All that magic happens in ConsoleCancellationTokenSource class;
  • Console cancellation event is registered in App class;

Multi-Level Layered Configuration

  • Layered configuration files approach supported;
  • Possible to configure on each level the Cancellation.Timeout setting (right now no other settings are supported);
  • In regard to the following listed configuration order, the next lower level overrides the one above (more global level):
    1. System-level
    2. Global-level (aka User-level)
      • Configuration file is applied to the user, who created it only;
      • Location: SpecialFolder.UserProfile\.connyconfig
        • Windows: C:\Users\<username>\.connyconfig
        • Linux: ~/home/<username>/.connyconfig
    3. Local-level
      • Configuration file is applied to the current working directory only;
      • Location, when current working directory would be C:\Temp
        • Windows: C:\Temp\.connyconsole\config
        • Linux: /c/Temp/.connyconsole/config

Architecture Goals

  • Flexibility
    • Can have a "safe" default (like a 30-second timeout) but easily change it to 5 seconds for just one specific project without editing the global files;
  • Separation of Concerns
    • Distinguishes between settings that belong to machine, developer, and workspace;
  • Predictable Order
    • Creates clear hierarchy (see diagram Configuration override order) where most "local" or "specific" setting always wins;

Hierarchy & File Path Mapping

Configuration scope Windows Linux Description
System C:\ProgramData\ConnyConsole\config ~/usr/share/ConnyConsole/config - Configuration file is applied to all users of the system;
Global (User) C:\Users\<username>\.connyconfig ~/home/<username>/.connyconfig - Configuration file is applied to the user, who created it only;
Local C:\Temp\.connyconsole\config /c/Temp/.connyconsole/config - Configuration file is applied to the current working directory only;
- Example working directory is C:\Temp;

Configuration load and override flow

---
title: Configuration override order
---
graph TD

    A[CLI]

    subgraph "Highest Priority (Final)"
        B[Local-level]
    end

    subgraph "High Priority"
        C[Global / User-level]
    end

    subgraph "Middle Priority"
        D[System-level]
    end

    subgraph "Lowest Priority (Base)"
        E[Default Configuration
        _no config files_]
    end

    A -->|0. Present| E
    A -->|1. Load| D
    A -->|2. Load| C
    A -->|3. Load| B
    B -->|Overwrites| C
    C -->|Overwrites| D
    D -->|Overwrites| E
Loading

Configuration Schema & Validation

  • Configuration is stored in JSON format, according to predefined and cross-checked setting keys;
  • During configuration via CLI a setting key describes its nested level using a dot '.' as a separator;
    • For instance: ConnyConfig config set Cancellation.Timeout 1s
    • Example above Configures Timeout setting that is nested in Cancellation setting;
  • Scope Selection: By default, config set targets Local scope, other levels can be target using flags:
    • -l, --local: Targets current working directory (default);
    • -g, --global: Targets global User-level configuration;
    • -s, --system: Targets System-level configuration;
    • Note: Scope flags are mutually exclusive; only one can be used at a time
    • ConnyConsole config set <key> <value> [[-l|--local]|[-g|--global]|[-s|--system]]
  • Case-Sensitivity: Note that setting keys for the config set command are case-sensitive to match the internal schema and JSON property naming;
  • Validation via Schema: Supported setting keys are hard-coded in a Dictionary<string, object> within the AppSettings model;
    • Dictionary uses boolean values (true) to mark leaf nodes (actual settings) and nested dictionaries to represent the hierarchy;
    • Before value is written, editor verifies that provided key path exists within schema;
  • Configuration Editor: The JsonConfigurationEditor manages the physical file I/O;
    • Parses existing JSON (if any), traverses the tree according to the dot-notation key, and either updates existing value or creates necessary objects to reach the new setting;
// Example: How supported configuration keys are defined in the schema
private static readonly Dictionary<string, object> SupportedSettingKeys = new()
{
    ["LoopOutputInterval"] = true, // Top-level setting
    ["Cancellation"] = new Dictionary<string, object>
    {
        ["Timeout"] = true,        // Nested setting: Cancellation.Timeout
        ["RetryPolicy"] = new Dictionary<string, object>
        {
            ["Enabled"] = true     // Deeply nested: Cancellation.RetryPolicy.Enabled
        }
    }
};
/* Example: Resulting configuration file structure */
{
  "LoopOutputInterval": "00:00:01",
  "Cancellation": {
    "Timeout": "00:00:30",
    "RetryPolicy": {
      "Enabled": false
    }
  }
}

Configuration duration parsing

  • Duration configuration more user-friendly, custom DurationTimeParser implemented for time-based settings (like Cancellation.Timeout);
  • Allows users provide intuitive values instead of raw TimeSpan strings;
    • Supports various input formats such as:
      • Standard format: 00:00:30 or 1.12:00:00 (1 day, 12 hours)
      • Short suffixes: 500ms, 10s, 5m, 1h, 2d
      • Full words: 30 seconds, 5 minutes, 1 day
      • Combinations: 1h 30m, 2 days 4 hours 15m 30s

Testability

Development

Test implementation

As an application is only as good as it was tested, this chapter gives some insights into how the console application tests were implemented.

Pipeline

This chapter provides an overview of what the current build pipeline provides.

  • GitVersion integrated for auto SemVer (Semantic Versioning) based on Git history;
  • SonarQube (cloud, free plan) integrated;
  • Build console application with Release configuration;
  • Run tests;
    • Collect test run results as *.trx file;
    • Collect code coverage in OpenCover format, later on used to publish on SonarQube;
    • Both files, the coverage file and test result file, are published as build artifacts;
    • Passed, failed and skipped tests listed as part of run summary, realized with GitHubActionsTestLogger package;

References/documentation

The following are some used articles listed.

About

ConnyConsole is an example console CLI project that uses DI, Serilog for logging, execution cancellation and argument parsing based on Microsoft's System.CommandLine library.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages