Equivalent to UserSettings / ApplicationSettings in WPF .NET 5, .NET 6 or .Net Core
C#Wpf.Net Core.Net 5.Net 6.0C# 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.
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:
-
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#
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
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.
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
Here you are. Settings are back already.
You might add an Application Configuration File to get the .config file in the output directory
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.