Gå til indholdet

En udvidet konsol applikation

Den almindelige C# konsol applikation er ret simpel. Her er en guide til at oprette en mere brugbar applikation med appsettings, log, etc.

Brug AI

I moderne udvikling er det noget nemmere at oprette en god og moderne konsol applikation ved at benytte AI. Her er et eksempel på en prompt til eksempelvis Github CoPilot eller lignende der kører i agent mode:

Set up this .NET console application with a production-ready foundation using the latest .NET LTS version:

- Add necessary NuGet packages using 'dotnet add package' commands (to ensure latest versions):
   - Configuration management with JSON support
   - Dependency injection and hosting
   - Serilog logging with console and file sinks
   - CommandLineParser for argument parsing
   - AsyncFixer and Meziantou.Analyzer for code analysis
   - DotNetEnv for .env file support

   Do NOT manually edit the .csproj file - use dotnet CLI commands instead.

- In the project file (.csproj):
   - Target the latest .NET LTS version (e.g., net8.0)
   - Enable implicit usings and nullable reference types
   - Ensure appsettings.json is copied to output directory
   - Set EnableNETAnalyzers and EnforceCodeStyleInBuild to true
   - Set AnalysisLevel to latest

- Create a .editorconfig file with:
   - dotnet_analyzer_diagnostic.category-Style.severity = none  
        - Add a comment that this is to disable style warnings
   - Example of other analyzer rules to disable - like:
       - dotnet_diagnostic.MA0048.severity = none  # File name must match type name


- Create an AppSettings class to hold application configuration including:
   - ApplicationTitle (string) - as a practical example

- Configure environment variable support:
   - Add .env file to project root
   - Load .env file at application startup
   - Configure configuration provider to check environment variables first, then fall back to appsettings.json

- Add appsettings.json and appsettings.Development.json with:
   - Serilog configuration for both console and file logging
   - ApplicationTitle setting
   - Other application-specific settings that map to the AppSettings class
   - Environment-specific overrides in Development file
   - Document that values can be overridden via environment variables

- Create a CommandArgs class with:
   - Verbose flag (bool)

- Create a DemoService class demonstrating:
   - How to inject dependencies (ILogger, AppSettings)
   - How to use injected services
   - Basic async operations

- Create an App class with a Run method that:
   - Contains the main application logic
   - Uses the DemoService
   - Handles the command-line arguments

- Configure Program.cs with:
   - Set thread culture to da-DK (CultureInfo) for the entire application
   - Load .env file first
   - Dependency injection container
   - Register configuration services with environment-specific configuration (environment variables > appsettings)
   - Bind AppSettings from configuration
   - Configure Serilog from appsettings.json
   - Parse command-line arguments with CommandLineParser
   - IMPORTANT: Handle CommandLineParser async correctly:
     - Separate synchronous command line parsing from asynchronous application logic
     - Use synchronous callbacks in MapResult to avoid deadlock issues
     - Move async application logic to a separate RunApplicationAsync method
     - Explicit handling of help/version requests
     - Explicit handling of parsing errors
     - Explicit handling of successful parsing before calling async logic
   - Register App class and DemoService
   - Proper error handling with try-catch and logging
   - Clean application exit with appropriate exit codes

- Create VS Code/Rider debug launch configuration files:
   - .vscode/launch.json with debug configuration that sets --verbose flag
   - Include both Debug and Release configurations

- Create a .gitignore file:
    - Standard .NET gitignore entries (bin/, obj/, etc.)
    - IDE-specific folders (.vs/, .vscode/, .idea/)
    - Log files
    - .env file (should not be committed)
    - User-specific files

- Create a generic README.md file in Danish (template - no app-specific details):
    - Generic description stating this is a .NET console application template
    - Application setup explanation:
      - Project structure overview
      - Dependency injection pattern used
      - Configuration setup (appsettings + environment variables)
      - Logging setup (Serilog with console and file outputs)
    - NuGet packages section:
      - List all installed packages
      - Explain what each package does
      - Explain why each package was chosen/included
    - Architecture overview (DI, Serilog, configuration pattern)
    - How to build (dotnet build -c Debug/Release)
    - How to run (dotnet run)
    - Command-line arguments pattern explanation
    - Configuration options (appsettings.json and environment variables)
    - Environment variable override pattern
    - Logging behavior and output locations
    - Note that this is a starter template to be customized
    - All text in Danish

- Create a .github/github-instructions.md file (agent-only, concise):
    - Build commands only
    - Run commands only
    - Test verification steps only
    - Commands to check and fix analyzer warnings
    - Code must compile with zero warnings and zero errors
    - No explanatory text

Each type should be in its own file.
Code should be in English with Danish comments explaining key concepts and decisions.
Use modern C# patterns, proper async/await where appropriate, and ensure clean resource disposal.

IMPORTANT: After generating all code, verify that:
- The solution compiles without errors AND without warnings
- All analyzer warnings are addressed and fixed
- The application runs successfully
- Console logging works and displays log messages
- File logging works and creates log files in the expected location
- All configurations are correctly wired up
- Environment variables override appsettings values correctly
- Application uses da-DK culture (dates, numbers, etc.)

Hvis du opretter en helt ny konsol applikation og benytter ovenstående prompt i et AI-værktøj, vil du ende med en ret komplet applikation.

Opret et nyt .NET-projekt

Først skal du oprette et nyt .NET-projekt. For eksempel, for at oprette en ny konsolapplikation, kan du bruge følgende kommandoer:

dotnet new console -n MyApp --use-program-main
cd MyApp
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.Binder
dotnet new class -n AppSettings

Disse kommandoer opretter en ny konsolapplikation i en mappe kaldet “MyApp” med en AppSettings-klasse og nødvendige pakker.

Opret appsettings.json

Opret en appsettings.json-fil i din projektmappe med følgende indhold:

{
  "AppSettings": {
    "Culture": "da-DK",
    "MySetting1": 42,
    "MySetting2": "*"
  }
}

Tilføj appsettings.json til dit projekt

Åbn din .csproj-fil (projektfilen) og tilføj følgende fremhævede linjer for at sikre, at appsettings.json er inkluderet i dit projekt:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <None Update="appsettings.json">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>
</Project>

Tilret AppSettings-klassen

Ret AppSettings-klassen så den mapper appsettings.json:

namespace MyApp;

public class AppSettings
{
    public string? Culture { get; set; }
    public int MySetting1 { get; set; }
    public string? MySetting2 { get; set; }
}

Opdater Program.cs

Opdater din Program.cs så en instans af AppSettings bindes til appsettings.json:

using Microsoft.Extensions.Configuration;
using MyApp;

class Program
{
    internal static AppSettings? appSettings;
    static void Main(string[] args)
    {
        var configuration = new ConfigurationBuilder()
                        .SetBasePath(Directory.GetCurrentDirectory())
                        .AddJsonFile("appsettings.json", true, true)
                        .Build();

        appSettings = new AppSettings();
        configuration.Bind("AppSettings", appSettings);

        Thread.CurrentThread.CurrentCulture =
        new System.Globalization.CultureInfo(appSettings.Culture??"da-DK");  

        Console.WriteLine($"MySetting1: {appSettings.MySetting1}");
        Console.WriteLine($"MySetting2: {appSettings.MySetting2}");
    }
}
using Microsoft.Extensions.Configuration;
using MyApp;

class Program
{
    internal static AppSettings? appSettings;
    static async Task Main(string[] args)
    {
        var configuration = new ConfigurationBuilder()
                        .SetBasePath(Directory.GetCurrentDirectory())
                        .AddJsonFile("appsettings.json", true, true)
                        .Build();

        appSettings = new AppSettings();
        configuration.Bind("AppSettings", appSettings);

        Thread.CurrentThread.CurrentCulture =
        new System.Globalization.CultureInfo(appSettings.Culture??"da-DK");

        await Console.Out.WriteLineAsync($"Culture: {appSettings.Culture}");
        await Console.Out.WriteLineAsync($"MySetting1: {appSettings.MySetting1}");
        await Console.Out.WriteLineAsync($"MySetting2: {appSettings.MySetting2}");            
    }
}

Kør din applikation

Kør din applikation for at se, hvordan den læser værdien fra appsettings.json:

dotnet run
Du skulle se outputtet:
Culture: da-DK
MySetting1: 42
MySetting2: *

Tilføj Serilog

Hvis du ønsker at benytte log i applikationen kan du tilføje Serilog pakkerne:

dotnet add package Serilog
dotnet add package Serilog.Formatting.Compact
dotnet add package Serilog.Settings.Configuration
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.Debug
dotnet add package Serilog.Sinks.File

Tilret appsettings.json

Tilføj følgende til appsettings.json:

{
  "AppSettings": {
    "Culture": "da-DK",
    "MySetting1": 42,
    "MySetting2": "*"
  },
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Debug" ],
    "MinimumLevel": "Verbose",
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "{Timestamp:HH:mm:ss} {Level:u3} {Message} {NewLine}{Exception}"
        }
      },
      { "Name": "Debug" },
      {
        "Name": "File",
        "Args": { "path": "logs/mylog.log" }
      }
    ]
  }
}

Tilret Program.cs

Tilret Program.cs for at konfigurere Serilog.

using Microsoft.Extensions.Configuration;
using MyApp;
using Serilog;

class Program
{
    internal static AppSettings? appSettings;
    internal static ILogger? logger;
    static void Main(string[] args)
    {
        var configuration = new ConfigurationBuilder()
                        .SetBasePath(Directory.GetCurrentDirectory())
                        .AddJsonFile("appsettings.json", true, true)
                        .Build();

        appSettings = new AppSettings();
        configuration.Bind("AppSettings", appSettings);

        Thread.CurrentThread.CurrentCulture =
          new System.Globalization.CultureInfo(appSettings.Culture??"da-DK");

        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(configuration)
            .CreateLogger();
        logger = Log.Logger;

        logger.Information("Information");
        logger.Debug("Debug");
        logger.Warning("Warning");
        logger.Error("Error");

        logger.Information($"Culture: {appSettings.Culture}");
        logger.Information($"MySetting1: {appSettings.MySetting1}");
        logger.Information($"MySetting2: {appSettings.MySetting2}");

    }
}

Kør din applikation

Kør din applikation for at se, hvordan den læser værdien fra appsettings.json:

dotnet run
Du skulle se outputtet:
16:16:00 INF Information
16:16:00 DBG Debug
16:16:00 WRN Warning
16:16:00 ERR Error
16:16:00 INF Culture: da-DK
16:16:00 INF MySetting1: 42
16:16:00 INF MySetting2: *

Hvor tidspunktet naturligvis er din systemtid.

Du burde også kunne finde en log-fil under \MyApp\bin\Debug\net8.0\logs (.NET version kan være anderledes!) kaldet mylog.log med samme informationer.

Avanceret brug af VSC til mange projekter

Hvis man arbejde med mange projekter og en solution fil i VSC kan det være lidt teknisk at sætte op - men brug evt dette som skabelon:

  • opret en tom folder (feks c:\temp\vsc-cs-template)
  • Kør følgende fra terminal i folderen
dotnet new sln -n MyApp
dotnet new console -n MyProject1
dotnet new console -n MyProject2
dotnet sln add .\MyProject1\MyProject1.csproj
dotnet sln add .\MyProject2\MyProject2.csproj
  • Det vil oprette en solution fil (sln), 2 projekter samt binde de to projekter til løsningen
  • Tilføj en folder .vscode
  • Tilføj filen launch.json
{
"version": "0.2.0",
"configurations": [
    {
        "name": ".NET Core Launch (Active Project)",
        "type": "coreclr",
        "request": "launch",
        "preLaunchTask": "build",
        // Brug dynamisk stien til den aktive projekt's DLL-fil
        "program": "${workspaceFolder}/${relativeFileDirname}/bin/Debug/net8.0/${relativeFileDirname}.dll",
        "args": [],
        // Sørg for, at cwd peger på den aktive mappe, hvor den aktuelle fil er placeret
        "cwd": "${workspaceFolder}/${relativeFileDirname}",
        "console": "internalConsole",
        "stopAtEntry": false
    }
]}
  • Tilføj filen tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet",
            "type": "process",
            "args": [
                "build",
                "${workspaceFolder}/MyApp.sln",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary;ForceNoAlign"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "publish",
            "command": "dotnet",
            "type": "process",
            "args": [
                "publish",
                "${workspaceFolder}/MyApp.sln",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary;ForceNoAlign"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "watch",
            "command": "dotnet",
            "type": "process",
            "args": [
                "watch",
                "run",
                "--project",
                "${workspaceFolder}/MyApp.sln"
            ],
            "problemMatcher": "$msCompile"
        }
    ]
}    
  • Nu burde du kunne redigere en cs-fil i et projekt og benytte debugger (F5).

Spectre

Check iøvrigt Spectre.Console som er en populær pakke til skrive/hente fra konsol samt gøre det nemt at skabe en CLI (Command Line Interface).