Saving Data using Settings for Windows and Uno Platform Applications

Following on from the posts on Dependency InjectionLogging and Configuration, in this post we’re going to look at how data can be persisted between sessions using Settings within a Windows or Uno Platform application. This series makes use of Uno.Extensions in order to initialize the services that are required by the application.

At the end of the Configuration post we had initialized the app to load configuration from appsettings.json and had registered the AppConfig type to access the corresponding configuration section.

.UseConfiguration(
    configure: configBuilder =>
        configBuilder
            .EmbeddedSource<App>()
            .Section<AppConfig>())

Settings are registered exactly the same way as configuration sections. In fact, with the Uno.Extensions, any class that is registered to access a configuration section, also registers the section so it could be used to persist information.

Let’s register another type, UserSettings, to be used to save the last entered username.

public record UserSettings
{
    public string? UserName { get; init; }
}

The UserSettings is registered with an additional Section method call as part of the UseConfiguration method.

.UseConfiguration(
    configure: configBuilder =>
        configBuilder
            .EmbeddedSource<App>()
            .Section<AppConfig>()
            .Section<UserSettings>())

With UserSettings registered, the current value can be read using IOptions<UserSettings> as we did in the Configuration post. In order to update the value, we need to retrieve an IWriteableOptions<UserSettings>.

var settings = Host.Services.GetRequiredService<IWritableOptions<UserSettings>>();
var cachedUserName = settings.Value?.UserName ?? string.Empty;
await settings.UpdateAsync(oldSettings => oldSettings with { UserName = "Nick" });

The UpdateAsync method includes a callback parameter, which is invoked with the existing UserSettings value. It’s safe to assume that this is not null – if no existing value exists, a default value is created and provided.

After the UserSettings value has been saved, the next time IOptions<UserSettings> is retrieved, it will hold the new UserSettings value. However, in some cases, you may need to detect when the UserSettings value changes, for example to update the current page with the new UserName value. In this case, instead of requesting IOptions you can request IOptionsMonitor as shown in the following code.

var userSettingsMonitor  = Host.Services.GetRequiredService<IOptionsMonitor<UserSettings>>();
userSettingsMonitor.OnChange(settings =>
{
    Debug.WriteLine($"setttings changed - {settings.UserName}");
});

At this point you might be wondering what the difference is between configuration and settings when using Uno.Extensions? The reality is that there isn’t a difference, just in how you use the registered sections – you can register a section to be used just for reading configuration (eg from appsettings.json); you can register a section to be used for reading/writing settings; you can also register a section to be used for both configuration and settings. In the last scenario, the default value for the section will be read from configuration (ie you can role out a default value with your application). When an updated value is written, it will be used in future app sessions.

So far in this series we’ve covered how to setup the DI container using IHost/IHostBuilder/IApplicationBuilder and adding in Logging, Configuration and Settings. All of these features can be directly accessed from the Host instance. In the coming posts we’ll cover the use of navigation which in addition to navigating between views, is also responsible for creating instances of ViewModels/Models and setting them as the DataContext for the navigated view. Where the ViewModels/Models have dependencies, for example on ILogger, they will be loaded from the DI container.

1 thought on “Saving Data using Settings for Windows and Uno Platform Applications”

Leave a comment