Equivalent to UserSettings / ApplicationSettings in WPF .NET 5, .NET 6 or .Net Core

C#Wpf.Net Core.Net 5.Net 6.0

C# Problem Overview


What is the prefered way for persisting user settings for WPF applications with .NET 5, .NET 6 or .Net Core >=3.0?

Created WPF .Net Core 3.0 Project (VS2019 V16.3.1) Now I have seen there is no Properties.Settings section anymore.

SolutionExplorer

After online search, started to dive into Microsoft.Extensions.Configuration.

Beside the bloated code to access the settings, now even worse -> No save?
https://stackoverflow.com/q/51351464/3090544

> Fortunately or unfortunately the Microsoft.Extensions.Configuration > does not support saving by design. Read more in this Github issue Why > there is no save in ConfigurationProvider?


What is the prefered (and easy/fast/simple) way for persisting user settings for WPF applications with .Net Core >=3.0?


Before <= .Net 4.8 it was as easy as:

  • add the variables to the Properties. User Settings

  • Read the variables at startup
    var culture = new CultureInfo(Properties.Settings.Default.LanguageSettings);

  • when a variable changes -> immediately save it
    Properties.Settings.Default.LanguageSettings = selected.TwoLetterISOLanguageName; Properties.Settings.Default.Save();

C# Solutions


Solution 1 - C#

enter image description here

You can add the same old good settings file e.g. via the right click on the Properties -> Add -> New Item and search for the "Settings". The file can be edited in the settings designer and used as in the .net framework projects before (ConfigurationManager, Settings.Default.Upgrade(), Settings.Default.Save, etc. works).

Add also the app.config file to the project root folder (the same way via the Add -> New Item), save the settings once again, compile the project and you will find a .dll.config file in the output folder. You can change now default app values as before.

Tested with Visual Studio 1.16.3.5 and a .net core 3.0 WPF project.

Solution 2 - C#

As pointed out in the posts you referenced, the Microsoft.Extensions.Configuration API is meant as a one time set up for your app, or at the very least to be read-only. If you're main goal is to persist user settings easy/fast/simple, you could roll something up yourself. Storing the settings in the ApplicationData folder, in resemblance to the old API.

public class SettingsManager<T> where T : class
{
    private readonly string _filePath;

    public SettingsManager(string fileName)
    {
        _filePath = GetLocalFilePath(fileName);
    }

    private string GetLocalFilePath(string fileName)
    {
        string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
        return Path.Combine(appData, fileName);
    }

    public T LoadSettings() =>
        File.Exists(_filePath) ?
        JsonConvert.DeserializeObject<T>(File.ReadAllText(_filePath)) :
        null;

    public void SaveSettings(T settings)
    {
        string json = JsonConvert.SerializeObject(settings);
        File.WriteAllText(_filePath, json);
    }
}

A demo using the most basic of UserSettings

public class UserSettings
{
    public string Name { get; set; }
}

I'm not going to provide a full MVVM example, still we'd have an instance in memory, ref _userSettings. Once you load settings, the demo will have its default properties overridden. In production, of course, you wouldn't provide default values on start up. It's just for the purpose of illustration.

public partial class MainWindow : Window
{
    private readonly SettingsManager<UserSettings> _settingsManager;
    private UserSettings _userSettings;

    public MainWindow()
    {
        InitializeComponent();

        _userSettings = new UserSettings() { Name = "Funk" };
        _settingsManager = new SettingsManager<UserSettings>("UserSettings.json");
    }

    private void Button_FromMemory(object sender, RoutedEventArgs e)
    {
        Apply(_userSettings);
    }
    
    private void Button_LoadSettings(object sender, RoutedEventArgs e)
    {
        _userSettings = _settingsManager.LoadSettings();
        Apply(_userSettings);
    }

    private void Button_SaveSettings(object sender, RoutedEventArgs e)
    {
        _userSettings.Name = textBox.Text;
        _settingsManager.SaveSettings(_userSettings);
    }

    private void Apply(UserSettings userSettings)
    {
        textBox.Text = userSettings?.Name ?? "No settings found";
    }
}

XAML

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Margin" Value="10"/>
        </Style> 
    </Window.Resources>
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" x:Name="textBox" Width="150" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Button Grid.Row="1" Click="Button_FromMemory">From Memory</Button>
        <Button Grid.Row="2" Click="Button_LoadSettings">Load Settings</Button>
        <Button Grid.Row="3" Click="Button_SaveSettings">Save Settings</Button>
    </Grid>
</Window>

Solution 3 - C#

You can use a Nuget package System.Configuration.ConfigurationManager. It is compatible with .Net Standard 2.0, so it should be usable in .Net Core application.

There is no designer for this, but otherwise it works the same as .Net version, and you should be able to just copy the code from your Settings.Designer.cs. Also, you can override OnPropertyChanged, so there's no need to call Save.

Here's an example, from the working .Net Standard project:

public class WatchConfig: ApplicationSettingsBase
{
    static WatchConfig _defaultInstance = (WatchConfig)Synchronized(new WatchConfig());

    public static WatchConfig Default { get => _defaultInstance; }

    protected override void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        Save();
        base.OnPropertyChanged(sender, e);
    }

    [UserScopedSetting]
    [global::System.Configuration.DefaultSettingValueAttribute(
    @"<?xml    version=""1.0"" encoding=""utf-16""?>
    <ArrayOfString>
      <string>C:\temp</string>
     <string>..\otherdir</string>
     </ArrayOfString>")]
    public StringCollection Directories
    {
        get { return (StringCollection)this[nameof(Directories)]; }
        set { this[nameof(Directories)] = value; }
    }
}

Solution 4 - C#

For Wpf Net.Core

Project click Right Mouse Button -> Add New Item -> Settings File (General)

Use

Settings1.Default.Height = this.Height;
Settings1.Default.Width = this.Width;

this.Height = Settings1.Default.Height;
this.Width = Settings1.Default.Width;

Settings1.Default.Save();

Where 'Settings1' created file name

EXAMPLE

Double click 'Settings1.settings' file and Edit

private void MainWindowRoot_SourceInitialized(object sender, EventArgs e)
{
    this.Top = Settings1.Default.Top;
    this.Left = Settings1.Default.Left;
    this.Height = Settings1.Default.Height;
    this.Width = Settings1.Default.Width;
    // Very quick and dirty - but it does the job
    if (Settings1.Default.Maximized)
    {
        WindowState = WindowState.Maximized;
    }
}

private void MainWindowRoot_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    if (WindowState == WindowState.Maximized)
    {
        // Use the RestoreBounds as the current values will be 0, 0 and the size of the screen
        Settings1.Default.Top = RestoreBounds.Top;
        Settings1.Default.Left = RestoreBounds.Left;
        Settings1.Default.Height = RestoreBounds.Height;
        Settings1.Default.Width = RestoreBounds.Width;
        Settings1.Default.Maximized = true;
    }
    else
    {
        Settings1.Default.Top = this.Top;
        Settings1.Default.Left = this.Left;
        Settings1.Default.Height = this.Height;
        Settings1.Default.Width = this.Width;
        Settings1.Default.Maximized = false;
    }

    Settings1.Default.Save();
}

Solution 5 - C#

Based on Funk's answer here's an abstract generic singleton-style variation that removes some of the administration around SettingsManager and makes creating additional settings classes and using them as simple as possible:

Typed Settings class:

//Use System.Text.Json attributes to control serialization and defaults
public class MySettings : SettingsManager<MySettings>
{
    public bool SomeBoolean { get; set; }
    public string MyText { get; set; }
}

Usage:

//Loading and reading values
MySettings.Load();
var theText = MySettings.Instance.MyText;
var theBool = MySettings.Instance.SomeBoolean;

//Updating values
MySettings.Instance.MyText = "SomeNewText"
MySettings.Save();

As you can see the number of lines to create and use your settings are just as minimal, and a bit more rigid as there are no parameters.

The base class defines where settings are stored and allows only for one settings file per MySettings subclass - assembly and class names determine its location. For the purpose of replacing a properties file that is enough.

using System;
using System.IO;
using System.Linq;
using System.Reflection;

public abstract class SettingsManager<T> where T : SettingsManager<T>, new()
{
    private static readonly string filePath = GetLocalFilePath($"{typeof(T).Name}.json");

    public static T Instance { get; private set; }

    private static string GetLocalFilePath(string fileName)
    {
        string appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 
        var companyName = Assembly.GetEntryAssembly().GetCustomAttributes<AssemblyCompanyAttribute>().FirstOrDefault();
        return Path.Combine(appData, companyName?.Company ?? Assembly.GetEntryAssembly().GetName().Name, fileName);
    }

    public static void Load()
    {
        if (File.Exists(filePath))
            Instance = System.Text.Json.JsonSerializer.Deserialize<T>(File.ReadAllText(filePath));
        else
            Instance = new T(); 
    }

    public static void Save()
    {
        string json = System.Text.Json.JsonSerializer.Serialize(Instance);
        Directory.CreateDirectory(Path.GetDirectoryName(filePath));
        File.WriteAllText(filePath, json);
    }
}

Some improvements might be made in disabling the constructor of the settings subclass and creation of SettingsManager<T>.Instance without Load()ing it; that depends on your own use cases.

Solution 6 - C#

My improvements to the accepted answer were rejected, so here as separate answer.

There is no need for any nuget package and no need to roll your own JSON etc.
By default when creating new .NET Core or .NET5/6 projects the settings section is missing and you have to manually add it.

Just manually create the Properties folder in the solution. As you name the new folder Properties you will see that the folder icon will change slightly.

Properties folder

Right click on this new Properties folder and add New Item

Add a Settings File and to be same as in old projects rename the proposed name from Settings1.settings to Settings.settings

Add Settings File

Here you are. Settings are back already.

Settings dialog

You might add an Application Configuration File to get the .config file in the output directory

Add Application Configuration File

Solution 7 - C#

Just double click the Settings.settings file in your project. It will still open up in the designer just like before. You just do not have it listed in Properties windows anymore.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMarkusEgleView Question on Stackoverflow
Solution 1 - C#Alexander ZwitbaumView Answer on Stackoverflow
Solution 2 - C#FunkView Answer on Stackoverflow
Solution 3 - C#user3188639View Answer on Stackoverflow
Solution 4 - C#mdimai666View Answer on Stackoverflow
Solution 5 - C#H BView Answer on Stackoverflow
Solution 6 - C#MarkusEgleView Answer on Stackoverflow
Solution 7 - C#Danny McNaughtView Answer on Stackoverflow