Properties.Settings.Defaultdepends onSystem.Configuration, which is designed around the Windows desktop settings provider model- Modern .NET uses
appsettings.json, and Microsoft directs Framework-to-.NET ports toward that format Uno.Extensions.Configurationexposes bothIOptions<T>andIWritableOptions<T>when you register a section withSection<T>()IWritableOptions<T>.UpdateAsync()takes the current snapshot and returns a new instance; immutable records are the recommended shape- The configuration section does not need to exist in the file ahead of time; Uno creates it the first time
UpdateAsync()runs
The WPF Settings.Default migration path in Uno Platform is to stop calling Properties.Settings.Default.Save() and instead model a settings record, register it with Section<T>() on the host builder, and mutate it through IWritableOptions<T>.UpdateAsync(). appsettings.json holds your defaults, and Uno.Extensions.Configuration handles persistence across iOS, Android, WebAssembly, and desktop.
Why Properties.Settings.Default Doesn't Cross the Platform Boundary
The Properties.Settings.Default API is a thin wrapper over ApplicationSettingsBase. Under that class sits a settings provider. If no provider is specified, LocalFileSettingsProvider is used, which stores application-scoped values in app.exe.config and user-scoped values in a separate user.config file under %LOCALAPPDATA%. The path conventions are Windows-specific.
Microsoft's own guidance for porting .NET Framework apps to modern .NET is explicit: ".NET Framework uses app.config to load settings; modern .NET uses appsettings.json." For a cross-platform Uno Platform app that also targets iOS, Android, and WebAssembly, appsettings.json plus Microsoft.Extensions.Options is the portable replacement.
The Uno Platform Settings Model
Uno.Extensions.Configuration provides a uniform way to read or write configuration data and uses Microsoft.Extensions.Configuration under the hood. You opt into configuration on the host builder via UseConfiguration(), and declare an appsettings.json source with EmbeddedSource<App>():
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var builder = this.CreateBuilder(args)
.Configure(host =>
{
host.UseConfiguration(config =>
config.EmbeddedSource<App>()
.Section<AppConfig>());
});
}EmbeddedSource<App>() is recommended over ContentSource<App>() because configuration data read from embedded resources is available to the application immediately upon startup, particularly on WebAssembly.
Section<T>() is what makes settings writable: it ensures both IOptions<AppConfig> and IWritableOptions<AppConfig> are available from the container. Model the section as an immutable record:
public record AppConfig
{
public double? WindowWidth { get; init; }
public string? Theme { get; init; }
public string? LastFilePath { get; init; }
}{
"AppConfig": {
"WindowWidth": 1200,
"Theme": "Light",
"LastFilePath": null
}
}Writing at Runtime with IWritableOptions<T>.UpdateAsync
A view model resolves IWritableOptions<T> through constructor injection. UpdateAsync() receives the current snapshot and expects you to return a new instance with the modifications you want to persist:
public class SettingsViewModel
{
private readonly IWritableOptions<AppConfig> _settings;
public SettingsViewModel(IWritableOptions<AppConfig> settings)
=> _settings = settings;
public async Task SaveWindowWidthAsync(double width) =>
await _settings.UpdateAsync(current => current with
{
WindowWidth = width
});
}- No pre-seed required. If the selected section does not exist in any backing store, Uno automatically creates it the first time
UpdateAsyncruns. - Snapshot semantics.
IOptionsSnapshot<T>is a scoped service where options are computed once per request when accessed and cached for the lifetime of the request.
Migrating a Typical Settings.Default Surface
Properties.Settings.Default.WindowWidth = 1200;
Properties.Settings.Default.Theme = "Dark";
Properties.Settings.Default.LastFilePath = @"C:\work\notes.txt";
Properties.Settings.Default.Save();await _settings.UpdateAsync(current => current with
{
WindowWidth = 1200,
Theme = "Dark",
LastFilePath = "/Users/me/notes.txt"
});Reads become IOptions<AppConfig> or IOptionsSnapshot<AppConfig> injection, depending on whether you want values frozen at construction or refreshed per scope.
Per-platform storage: Do not hard-code paths the way a WPF project might when "upgrading" user.config between versions. Treat IWritableOptions<T>.UpdateAsync() as the only supported surface for mutation and let the extension decide where bytes land.
Migration Checklist
- Inventory every
Properties.Settings.Default.*access. Only user-scoped values map cleanly toIWritableOptions<T>. Application-scoped static values are a better fit for read-onlyIOptions<T>. - Replace the designer file with a record. The
Settings.settingsdesigner-generated wrapper class disappears; the record you define is the new typed surface. - Register the section. Add
Configurationto<UnoFeatures>, callUseConfiguration(), and register every section withSection<T>(). Without it,IWritableOptions<T>will not be in the DI container. - Replace static access with DI.
Settings.Default.Xis a static property on a singleton wrapper;IWritableOptions<T>is a dependency that must be injected. - Pick your read API.
IOptions<T>is a singleton that does not reread the file after startup;IOptionsSnapshot<T>rebuilds per scope and sees updated values. - Move defaults into
appsettings.json. Microsoft directs .NET Framework-to-modern-.NET ports toappsettings.jsonfor app settings.
IOptions vs. IOptionsSnapshot vs. IOptionsMonitor
| Interface | Lifetime | Sees UpdateAsync changes? |
|---|---|---|
IOptions<T> | Singleton | No (frozen at startup) |
IOptionsSnapshot<T> | Scoped | Yes (recomputed per scope) |
IOptionsMonitor<T> | Singleton | Yes (exposes change notifications) |
Choose based on whether the calling code needs to observe UpdateAsync results mid-session. IOptionsMonitor<T> covers the Settings.Default.Reload() use case.
Binding a Control to a Writable Option
<Slider Minimum="800" Maximum="2400"
Value="{x:Bind ViewModel.WindowWidth, Mode=TwoWay}" />public sealed class MainViewModel : INotifyPropertyChanged
{
private readonly IWritableOptions<AppConfig> _options;
public MainViewModel(IWritableOptions<AppConfig> options)
=> _options = options;
public double WindowWidth
{
get => _options.Value.WindowWidth ?? 1200;
set => _ = _options.UpdateAsync(
c => c with { WindowWidth = value });
}
public event PropertyChangedEventHandler? PropertyChanged;
}x:Bind default mode: {x:Bind} defaults to OneTime, so use Mode=TwoWay (or OneWay) whenever the UI must track UpdateAsync results.
FAQ
Do I need both appsettings.json and IWritableOptions?
appsettings.json holds your defaults and the sections you load with EmbeddedSource<App>(). IWritableOptions<T> is what makes a given section writable at runtime. A section registered with Section<T>() does not have to exist in the file to start with; Uno creates it on first UpdateAsync().
How do I preserve existing user.config values on first run?
The practical pattern is to read the legacy file on first launch using System.Configuration APIs on Windows, then write each value through IWritableOptions<T>.UpdateAsync() before ignoring the legacy file. Because UpdateAsync() creates sections on demand, you do not need to pre-populate appsettings.json.
Does IWritableOptions work on WebAssembly?
Yes. Uno.Extensions.Configuration explicitly supports WebAssembly, and EmbeddedSource<App>() is the recommended approach because embedded data is available immediately on startup.
What happens to Settings.Default.Reload() and Upgrade()?
Reload() semantics are covered by IOptionsMonitor<T> (singleton with change notifications) and IOptionsSnapshot<T> (scoped, recomputed per request). Upgrade() is a cross-version migration primitive tied to LocalFileSettingsProvider; there is no built-in Uno equivalent. Handle version upgrades explicitly in startup code.
Follow the IWritableOptions walkthrough to persist your settings. If you are driving the migration with AI, pair it with the Claude Code integration guide and the Uno MCP servers so the agent can look up IWritableOptions<T> and related APIs while it refactors.
- ApplicationSettingsBase Class (Microsoft Learn) →
- LocalFileSettingsProvider Class →
- Porting from .NET Framework to .NET (Microsoft Learn) →
- IOptionsSnapshot<T> (Microsoft Learn) →
- Manage Application Settings (WPF) →
- Uno Extensions Configuration Overview →
- Uno: How-To Use Writable Options →
- Uno: Writable Options Walkthrough →
- Get Started with Claude + Uno Platform →
- Uno Platform MCP Servers →
- Uno Platform for WPF Developers →
Subscribe to Our Blog
Subscribe via RSS
Back to Top