96

What is the prefered way for persisting user settings for WPF applications with , , or >=3.0?
Where are the .NET user settings gone?

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

[Update 2022: With .NET 6 it is still the same]
[Update 2023: With .NET 7 it is still the same]

SolutionExplorer

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

Beside the bloated code to access the settings, now even worse -> No save?
User Configuration Settings in .NET Core

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 / .NET5 / .NET6 / .NET7?
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();

MarkusEgle
  • 2,795
  • 4
  • 41
  • 61
  • I'm curious, what has lead you to want to mutate your configuration files at runtime? What prompts these changes? Are you trying to use your config as some kind of user profile? – Nathan Cooper Jul 02 '19 at 12:24
  • 5
    I'm talking about the settings to improve user convenience. In my example above it seems like application settings, but I like to allow users to select their language. And why should I force my users to edit a config file? Why shouldn't provide an admin area to let it configure? Why shouldn't do it like [How To: Write User Settings at Run Time with C#](https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-write-user-settings-at-run-time-with-csharp) I'm using/saving successfully these user settings since about 15 years. Should we go back to the registry? Back to the roots? – MarkusEgle Jul 02 '19 at 13:31
  • 1
    I have no "answer" right now. But I would expect there to be some kind of configuration abstraction that would write either to LocalApplicationData, ApplicationData or CommonApplicationData, depending on the scope of the user settings. I would expect this abstraction to forbid you to change the application level config of your app (for many reasons, including that your permissions levels are likely to be inadequate). This is what I'd expect from Microsoft's configuration code *for client apps*. The documentation you've linked is asp.net documentation, which is completely different. – Nathan Cooper Jul 02 '19 at 16:04
  • 1
    I assumed that Microsoft.Extensions.Configuration is the way to go even for WPF/WinForms <-> .Net Core not only ASP.Net. I haven't found yet any other information. – MarkusEgle Jul 02 '19 at 17:45
  • One _reference_ where I read that Microsoft.Extensions.Configuration should be used: https://stackoverflow.com/a/48866609/3090544 – MarkusEgle Jul 03 '19 at 09:22
  • Ahhh, I may have been wrong when I said that was asp.net only then. I hope someone who knows answers. In the meantime the way I'd tackle it might be to just serial a file in LocalApplicationData and just ignore the microsoft config stuff. – Nathan Cooper Jul 03 '19 at 09:41
  • 1
    I had similar frustrations. I just went with JSON.net in the end. Super simple – GazTheDestroyer Jul 09 '19 at 09:42
  • You can always use a simple plain text file (JSON, XML, plain _key=value;key=value_, or ini-file-formatting if you need sections) or lightweight database like SQLite. You can also create plain data objects and serialize them. Setting this up (maybe except SQLite and serialization) is done very quickly. XML and JSON (via API) can modify and search the data without explicit parsing. XML allows simple and easy traversal (e.g. LINQ to XML). – BionicCode Jul 12 '19 at 19:50
  • 2
    So we are back to "just roll your own (crappy) solution" for the most basic of things. I thought we had left that behind some 20 years ago. – user3700562 Mar 23 '21 at 18:38
  • If I roll my own JSON file, where am I supposed to save it? – Arrow_Raider Jul 26 '21 at 14:23

8 Answers8

97

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.

Alexander Zwitbaum
  • 4,776
  • 4
  • 48
  • 55
  • 4
    Could be so easy! This should be the answer with bounty... Tested with VS2019 V16.3.6 – MarkusEgle Oct 25 '19 at 08:18
  • 1
    And where is the .config to be editied when deploying? Or are they renamed/relocated? Before .Net Core settings we had our config to be editied in the same directory as the exe file. – MarkusEgle Oct 25 '19 at 08:59
  • This is replaced now by file. Can I get the bounty, please? – Alexander Zwitbaum Nov 07 '19 at 22:22
  • 2
    To my knowledge a change of the bounty assignment isn't possible, but I changed the accepted answer to yours. Include at least 15 points... :-) – MarkusEgle Nov 09 '19 at 16:31
  • 15
    In my WPF Core 3.1 project, the solution explorer looks like in the question, not like in the answer. There are no Properties, meaning I also cannot right click them. – Peter Huber Jan 07 '20 at 08:20
  • 3
    You also have to install the NuGet Package `System.Configuration.ConfigurationManager` – A_Binary_Story Jan 18 '20 at 09:50
  • 9
    @PeterHuber if you can't find the Properties folder, just create them, and later add a .settings file to it – VT Chiew Feb 03 '20 at 02:44
  • Works perfect. But I get a lot of IntelliSense Messages in the Error List. "Could not find schema information for the element 'userSetting' ", "Could not find schema information for the element 'setting' ", and so on. – Quergo Feb 26 '20 at 15:06
  • Subsequent additions to the Settings file in the UI followed by Save gives error dialog: An Error occurred while save saving values to the app.config file. The file might be corrupted or contain invalid XML. I'm using .net core 3.0 in console app – PBMe_HikeIt Apr 10 '20 at 17:47
  • After adding app.config, how do I save..? Also, app.settings does not support a usersettings tag anymore. How does the data from Settings get into the app.config file. – Rye bread Dec 01 '20 at 09:51
  • Thanks. Automatically creates .dll.config from App.Config which can be modified externally. Unfortunately it fails when creating a single exe. Any suggestions? – TomB Dec 22 '20 at 14:41
  • 2
    @PeterHuber my project, initially, didnt include the Properties folder. i had to create a Publish profile which created the properties folder with the publish profile under it. then Settings were then add as the answer suggested. – Aaron. S Jan 23 '21 at 16:23
  • Where is the user.config saved on the user's machine now? – Josh Noe Oct 04 '22 at 20:51
30

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>
Welcor
  • 2,431
  • 21
  • 32
Funk
  • 10,976
  • 1
  • 17
  • 33
  • 3
    Suggest switch to System.Text.Json rather than newtonsoft – Sam Mackrill Feb 07 '20 at 10:34
  • This is the better solution! Using the "old" Settings-Approach in combination with MSIX results in your Application-Settings getting overwritten on every auto-update. – Mr. Muh Feb 08 '20 at 21:32
  • 1
    The Property.Settings api had a save method. it was not readonly. It was awesome and super easy to use to store and load users settings in the local appdata, where settings like that belog. It was TYPE SAFE without relying on crappy string stuff like settings["LeftHotkey"]. – Welcor May 23 '20 at 18:12
  • @Blechdose Have you even read OP, we were talking about Microsoft.Extensions.Configuration, which doesn't have a save method. My proposed solution is both type safe and saves settings in local appdata, or anywhere you'd like. – Funk May 28 '20 at 06:09
  • 1
    @Funk OP post is about Property.Settings which he is missing in .NET Core and you said "The Configuration API" which then refers to Property.Settings. Though you are right, i missed the "posts you reference" part (you mean links?). But it is still confusing. Could you please edit it to "Configuration API" to "Microsoft.Extensions.Configuration"? – Welcor May 28 '20 at 06:42
  • 1
    How in the hell is this progress? Going from a well-working and established system for storing user-settings, that was easy to use and integrated into the IDE to "oh well just cook your own, store it in a txt file somewhere or something". This whole .net core project is such a massive step back from the .NET Framework, it is truly mind-boggling. – user3700562 Mar 23 '21 at 18:32
14

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; }
    }
}
  • 1
    For me it seems to be the current simplest replacement for the missing user settings. It will keep the saving scheme in ApplicationData like it was before you don't need to create a app.config. Successfully tested with VS 2019 Preview 16.2.0 Preview 3.0 and .Net Core SDK 3.0.0-preview6-27804 and Nuget package System.Configuration.ConfigurationManager 4.6.0-preview6.199303.8 – MarkusEgle Jul 16 '19 at 07:06
  • It is just to add this 'custom-settings-class' to your project where you have to add the settings variables as properties with some annotations where you could watch how it should look like in other old `Settings.Designer.cs`. – MarkusEgle Jul 16 '19 at 07:17
12

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/7 projects the settings section is missing and you have to manually add it.

<Update 2023>

As described by Viacheslav there is a solution with almost just one button click. Open project properties, go to Setting and just click on the link "Create or open application settings".
Tested with VS 2022 and

Settings in project properties

The described old manual method is kept under this update.

</Update 2023>


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

MarkusEgle
  • 2,795
  • 4
  • 41
  • 61
7

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();
}
mdimai666
  • 699
  • 8
  • 14
  • interesting, it works in WPF, (Net Core 3.1) but not in a console app (Net Core 3.1). The console app version simply does not have the .Save() method – Welcor May 29 '20 at 12:16
  • @Welcor My .NET console app had no save method until I installed "System.Configuration.ConfigurationManager" NuGet package. Then it worked. – Manuel Hoffmann Feb 02 '23 at 10:29
5

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.

H B
  • 701
  • 6
  • 9
2

Just a button click

Project => Properties -> Resources

Project Resources Settings

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Viacheslav
  • 21
  • 2
  • Your screenshot only depicts to create/open assembly resources. You should have included the "Settings" section at the bottom for the screenshot. – MarkusEgle Jan 31 '23 at 08:51
-1

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.

kreld
  • 732
  • 1
  • 5
  • 16
  • And where could I find this settings file? There is no such file in a solution for a WPF .Net Core App, even not on folder level. Just tested again with a newly created project with VS2019 V16.3.3 – MarkusEgle Oct 11 '19 at 10:11
  • 2
    "Settings File" is in the list of file types when you right-click the project and select _Add New Item_. However, it's a read-only settings file because there's no _Save()_ method. (You also can't call _Upgrade()_ or _Reload()_, so you can't move settings from an old version to a new one.) – skst Oct 19 '19 at 00:16