Home > front end >  How to force Dotnet6 Desktop Winforms App to use appsettings.json instead of Settings.settings?
How to force Dotnet6 Desktop Winforms App to use appsettings.json instead of Settings.settings?

Time:12-25

How can I force a Dotnet6 Desktop Winforms App to use appsettings.json instead of Settings.settings[App.config]?

If I create a new desktop application in Visual Studio 2022 to use Dotnet6.0, it, by default will create a Settings.settings file on the fly, then create an App.config file as well (which is odd as this was supposedly deprecated in Dotnet5.0 and up!).

Unfortunately, all settings are embedded in the application after installation/publishing and cannot be changed anywhere in the application folder, once installed.

For older DotNet4.72 desktop applications, the app.config was always in the release output and, if changed, the app would read any changes just fine upon execution.

I'd like to use the appsettings.json just like for web apps (dotnet6.0) and there's a lot of info on how to do this with console apps (dotnet6.0), but not for desktop windows.

I have quite a few DotNet4.72 Desktop Apps that I wish to convert that still require the ability to simply change a configuration setting by editing a file on the fly (if conditions change). While I can roll my own solution, I'd like to do this similarly like in my web applications (appsettings.json). Better yet, I'd love to see a VS2022 desktop template that does this every time a new desktop app is created for DotNet6 and 7.

Any ideas?

CodePudding user response:

Basically you can use the Microsoft.Extensions.Configuration but you can also make use of Microsoft.Extensions.Hosting nuget package as well.

Here is a tested working example that handles configuration change as well.

Steps:

  • Add an appsettings.json file to the project.
    • Set Build Action to None.
    • Set Copy to Output Directory to Copy always.
  • Create a class for your options. (In this example: SampleOptions)
  • Configure your options in appsettings.json.
  • Add Microsoft.Extensions.Hosting nuget package to your project.
  • Modify Program.cs:
    • Configure and build a host. (e.g. register your forms, options into DI)
    • Start host.
    • Resolve IHostApplicationLifetime.
    • Create an IServiceScope using host's IServiceProvider.
    • Resolve your MainForm.
    • Call Application.Run() method with your resolved MainForm instance.
  • Modify MainForm.cs:
    • Inject IOptionsMonitor<SampleOptions> into the .ctor
    • Register a listener to be called whenever SampleOptions changes
      using IOptionsMonitor<SampleOptions>.OnChange method.

Here are the files.

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  },
  "SampleOptions": {
    "SampleStringKey": "Sample value",
    "SampleIntegerKey": 42
  }
}

SampleOptions.cs

namespace WinFormsAppNet6;

public class SampleOptions
{
    public string SampleStringKey { get; set; } = "N/A";

    public int SampleIntegerKey { get; set; }
}

Program.cs

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace WinFormsAppNet6;

internal static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static async Task Main() // <-- 'async Task' instead of 'void'
    {
        // To customize application configuration such as
        // set high DPI settings or default font,
        // see https://aka.ms/applicationconfiguration.
        ApplicationConfiguration.Initialize();

        using IHost host = CreateHost();
        await host.StartAsync();

        IHostApplicationLifetime lifetime = 
            host.Services.GetRequiredService<IHostApplicationLifetime>();

        using (IServiceScope scope = host.Services.CreateScope())
        {
            var mainForm = scope.ServiceProvider.GetRequiredService<MainForm>();

            Application.Run(mainForm);
        }

        lifetime.StopApplication();
        await host.WaitForShutdownAsync();
    }

    private static IHost CreateHost()
    {
        string[] args = Environment.GetCommandLineArgs().Skip(1).ToArray();

        HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

        builder.Services.AddSingleton<MainForm>();

        builder.Services.Configure<SampleOptions>(
            builder.Configuration.GetSection(nameof(SampleOptions))
        );

        return builder.Build();
    }
}

MainForm.cs

Controls:

  • Button: UpdateConfigurationButton
  • Button: ExitButton
  • RichTextBox: AppLog
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Windows.Forms;

using Microsoft.Extensions.Options;

namespace WinFormsAppNet6;

public partial class MainForm : Form
{
    private readonly IOptionsMonitor<SampleOptions> optionsMonitor;

    private readonly JsonSerializerOptions jsonSerializerOptions =
        new(JsonSerializerDefaults.Web)
        {
            WriteIndented = true
        };

    public MainForm(IOptionsMonitor<SampleOptions> optionsMonitor)
    {
        InitializeComponent();

        this.optionsMonitor = optionsMonitor;
        optionsMonitor.OnChange(OnOptionsChange);

        LogOptions(optionsMonitor.CurrentValue);
    }

    private void OnOptionsChange(SampleOptions options)
    {
        LogOptions(options);
    }

    private void LogOptions(
        SampleOptions options,
        [CallerMemberName] string? callerMemberName = null
    )
    {
        AppendLog(
            Environment.NewLine   JsonSerializer.Serialize(options),
            callerMemberName
        );
    }

    private void AppendLog(
        string message,
        [CallerMemberName] string? callerMemberName = null
    )
    {
        if (AppLog.InvokeRequired)
        {
            AppLog.Invoke(() => AppendLog(message, callerMemberName));
            return;
        }

        AppLog.AppendText(
            $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.ffffff} [{nameof(MainForm)}]::[{callerMemberName ?? "Unknown"}] {message}{Environment.NewLine}"
        );
    }

    private void UpdateConfigurationButton_Click(object sender, EventArgs e)
    {
        const string AppSettingsJsonFileName = "appsettings.json";

        if (!File.Exists(AppSettingsJsonFileName))
        {
            AppendLog(
                $"{nameof(FileNotFoundException)}: {AppSettingsJsonFileName}"
            );

            return;
        }

        string jsonContent = File.ReadAllText(AppSettingsJsonFileName);
        JsonNode? rootNode = JsonNode.Parse(jsonContent);

        if (rootNode is null)
        {
            AppendLog($"{nameof(JsonException)}: File parse failed.");
            return;
        }

        AppendLog(
            $"Finding key: {nameof(SampleOptions)}:{nameof(SampleOptions.SampleStringKey)}"
        );

        JsonObject rootObject = rootNode.AsObject();
        JsonObject? optionsObject = rootObject[nameof(SampleOptions)]?.AsObject();

        if (optionsObject is null)
        {
            AppendLog(
                $"{nameof(KeyNotFoundException)}: {nameof(SampleOptions)}:{nameof(SampleOptions.SampleStringKey)}"
            );

            return;
        }

        AppendLog(
            $"Updating key: {nameof(SampleOptions)}:{nameof(SampleOptions.SampleStringKey)}"
        );

        optionsObject[nameof(SampleOptions.SampleStringKey)] =
            JsonValue.Create(
                $"Value modified at {DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.ffffff}"
            );

        AppendLog($"Saving file: {AppSettingsJsonFileName}");

        using var stream =
            new FileStream(
                AppSettingsJsonFileName,
                FileMode.OpenOrCreate,
                FileAccess.Write,
                FileShare.None
            );

        jsonContent = rootObject.ToJsonString(jsonSerializerOptions);

        stream.Write(Encoding.UTF8.GetBytes(jsonContent));
        stream.Flush();
    }

    private void ExitButton_Click(object? sender, EventArgs e)
    {
        Application.Exit();
    }
}

Screenshot Running the sample application

  • Related