diff --git a/doc/Learn/Tutorials/Configuration/HowTo-WritableConfiguration.md b/doc/Learn/Tutorials/Configuration/HowTo-WritableConfiguration.md index 7ec2a91415..42c334e803 100644 --- a/doc/Learn/Tutorials/Configuration/HowTo-WritableConfiguration.md +++ b/doc/Learn/Tutorials/Configuration/HowTo-WritableConfiguration.md @@ -1,12 +1,137 @@ --- uid: Learn.Tutorials.Configuration.HowToWritableConfiguration --- + # How-To: Writable Configuration -This how-to is currently under construction. It will be available soon. +**Writable Configuration** extends the standard, [read-only](xref:Learn.Tutorials.Configuration.HowToConfiguration) pattern by allowing for programmatic writing of configuration values at runtime. This is useful for scenarios where you want to persist user preferences or other trivial information that may be changed over time. `Uno.Extensions.Configuration` extends the `IOptionsSnapshot` interface from [Microsoft.Extensions.Options](https://docs.microsoft.com/dotnet/api/microsoft.extensions.options) to support this. + +A special interface called `IWritableOptions` is registered as a service when you use the `UseConfiguration()` extension method. In this tutorial, we will walk through how to use the `UpdateAsync()` method on this interface to modify values of a specific configuration section. For a refresher on configuration sections, see [Sections](xref:Overview.Configuration#sections). + +> [!NOTE] +> It is common to see this referred to as the _settings_ pattern in certain documentation. This is because the `IOptions` interface is often used to represent settings that can be changed by the user. + +## Step-by-steps + +### 1. Prepare for writing configuration values + +* Ensure your project has `Uno.Extensions.Configuration` installed as a NuGet [package](https://www.nuget.org/packages/Uno.Extensions.Configuration/). + +* To enable configuration, you first need to call `UseConfiguration()` on the `IHostBuilder` instance: + + ```csharp + private IHost Host { get; } + + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseConfiguration() + }); + + Host = appBuilder.Build(); + ... + ``` + +* Use the `EmbeddedSource()` extension method to load configuration information from a JSON file called `appsettings.json` embedded inside the `App` assembly: + + ```csharp + private IHost Host { get; } + + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseConfiguration(configure: configBuilder => + configBuilder + .EmbeddedSource() + ); + }); + + Host = appBuilder.Build(); + ... + ``` + +### 2. Define a configuration section + +* To model the configuration section you want to write to, author a new class or record with related properties: + + ```csharp + public partial record ToDoApp + { + public bool? IsDark { get; init; } + public string? LastTaskList { get; init; } + } + ``` + +* For instance, the `IsDark` property could be used to toggle between light and dark themes, while `LastTaskList` could be used to persist the last task list the user was viewing. + +* Register the newly-defined configuration section by calling `Section()` on `IConfigBuilder`: + + ```csharp + private IHost Host { get; } + + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseConfiguration(configure: configBuilder => + configBuilder + .EmbeddedSource() + .Section() + ); + }); + + Host = appBuilder.Build(); + ... + ``` + +### 3. Write configuration values + +* From any view model registered with the dependency injection (DI) container, you can now inject an instance of `IWritableOptions` to write configuration values: + + ```csharp + public class SettingsViewModel + { + private readonly IWritableOptions _appSettings; + + public SettingsViewModel(IWritableOptions appSettings) + { + _appSettings = appSettings; + } + ... + ``` + +* To update the `IsDark` property, create a method that calls `UpdateAsync()` on the injected instance: + + ```csharp + public class SettingsViewModel + { + private readonly IWritableOptions _appSettings; + + public SettingsViewModel(IWritableOptions appSettings) + { + _appSettings = appSettings; + } + + public async Task ToggleTheme() + { + await _appSettings.UpdateAsync(settings => settings with { + IsDark = !settings.IsDark + }); + } + ... + ``` + +* Note that the `with` expression is used to create a new instance of the `ToDoApp` class with the updated value. This is because the `UpdateAsync()` method expects a function that returns a new instance of the class. -## Work in progress 🚧 +* The configuration section that was registered is not required to exist in any source beforehand. Sections like these will be created automatically when you call `UpdateAsync()`. -### Have questions or feedback? +## See also -* Help us shape the documentation for this topic by providing feedback on the Uno.Extensions [repo](https://github.com/unoplatform/uno.extensions/discussions/categories/general) \ No newline at end of file +* [Configuration](xref:Overview.Configuration) +* [Microsoft.Extensions.Configuration](https://docs.microsoft.com/dotnet/api/microsoft.extensions.configuration) +* [`IOptionsSnapshot`](https://docs.microsoft.com/dotnet/api/microsoft.extensions.options.ioptionssnapshot-1) \ No newline at end of file