Configuration.Writable.Encrypt 0.1.0-alpha.18

This is a prerelease version of Configuration.Writable.Encrypt.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Configuration.Writable.Encrypt --version 0.1.0-alpha.18
                    
NuGet\Install-Package Configuration.Writable.Encrypt -Version 0.1.0-alpha.18
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Configuration.Writable.Encrypt" Version="0.1.0-alpha.18" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Configuration.Writable.Encrypt" Version="0.1.0-alpha.18" />
                    
Directory.Packages.props
<PackageReference Include="Configuration.Writable.Encrypt" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Configuration.Writable.Encrypt --version 0.1.0-alpha.18
                    
#r "nuget: Configuration.Writable.Encrypt, 0.1.0-alpha.18"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Configuration.Writable.Encrypt@0.1.0-alpha.18
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Configuration.Writable.Encrypt&version=0.1.0-alpha.18&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Configuration.Writable.Encrypt&version=0.1.0-alpha.18&prerelease
                    
Install as a Cake Tool

Configuration.Writable

NuGet Version GitHub Actions Workflow Status GitHub last commit (branch)

A lightweight library that extends Microsoft.Extensions.Configuration to easily handle user settings.

Features

  • Read and write user settings with type safety.
  • Extends Microsoft.Extensions.Configuration and integrates seamlessly with IHostApplication.
  • Simple API that can be easily used in applications both with and without DI.
  • Supports various file formats (Json, Xml, Yaml, Encrypted, etc...) via providers.

More...

Usage

Setup

Install Configuration.Writable from NuGet.

dotnet add package Configuration.Writable

Then, prepare a class (UserSetting) that you want to read and write as settings in advance.

public class UserSetting
{
    public string Name { get; set; } = "default name";
    public int Age { get; set; } = 20;
}

Without DI (Console, WinForms, WPF, etc.)

Use WritableConfig as the starting point for reading and writing settings.

using Configuration.Writable;

// initialize at once (application startup)
WritableConfig.Initialize<SampleSetting>();

// -------------
// get the writable config instance with the specified setting class
var options = WritableConfig.GetInstance<SampleSetting>();

// get the config instance
var sampleSetting = options.CurrentValue;
Console.WriteLine($">> Name: {sampleSetting.Name}");

// and save to storage
await options.SaveAsync(setting =>
{
    setting.Name = "new name";
});
// By default, it's saved to ./usersettings.json

With DI (ASP.NET Core, Blazor, Worker Service, etc.)

First, call builder.AddUserConfigurationFile to register the settings.

// Program.cs
builder.AddUserConfigurationFile<UserSetting>();

// If you're not using IHostApplication, do the following:
var configuration = new ConfigurationManager();
services.AddUserConfigurationFile<UserSetting>(configuration);

Then, obtain and use IReadonlyOptions<T> or IWritableOptions<T> from the DI container as follows:

// read config in your class
// you can also use IOptions<T>, IOptionsMonitor<T> or IOptionsSnapshot<T>
public class ConfigReadService(IReadonlyOptions<UserSetting> config)
{
    public void Print()
    {
        // get the config instance
        var sampleSetting = config.CurrentValue;
        Console.WriteLine($">> Name: {sampleSetting.Name}");
    }
}

// read and write config in your class
public class ConfigReadWriteService(IWritableOptions<UserSetting> config)
{
    public async Task UpdateAsync()
    {
        // get the config instance
        var sampleSetting = config.CurrentValue;
        // and save to storage
        await config.SaveAsync(setting =>
        {
            setting.Name = "new name";
        });
    }
}

Customization

Configuration Method

You can change various settings as arguments to Initialize or AddUserConfigurationFile.

// Without DI
var options = WritableConfig.GetInstance<UserSetting>();
options.Initialize(opt => { /* ... */ });

// With DI
builder.AddUserConfigurationFile<UserSetting>(opt => { /* ... */ });

Save Location

Default behavior save to ./usersettings.json.
If you want to change the save location, use opt.FileName or opt.UseStandardSaveLocation("MyAppId").

For example:

// to save to the parent directory
opt.FileName = "../myconfig";

// to save to child directory
opt.FileName = "config/myconfig";

// to save to a common settings directory
opt.UseStandardSaveLocation("MyAppId");

Provider

If you want to change the format when saving files, create a class that implements IWritableConfigProvider and specify it in opt.Provider. Currently, the following providers are available:

Provider Description NuGet Package
WritableConfigJsonProvider save in JSON format. Built-in
WritableConfigXmlProvider save in XML format. NuGet Version
WritableConfigYamlProvider save in YAML format. NuGet Version
WritableConfigEncryptProvider save in AES-256-CBC encrypted JSON format. NuGet Version
// use Json format with indentation
opt.Provider = new WritableConfigJsonProvider() {
    JsonSerializerOptions = { WriteIndented = true },
};

// use Yaml format
// (you need to install Configuration.Writable.Yaml package)
opt.Provider = new WritableConfigYamlProvider();

// use encrypted Json format
// (you need to install Configuration.Writable.Encrypt package)
opt.Provider = new WritableConfigEncryptProvider("any-encrypt-password");

FileWriter

If you want to change the way files are written, create a class that implements IFileWriter and specify it in opt.FileWriter.

For example, if you want to keep backup files when saving, use CommonFileWriter with BackupMaxCount > 0.

// keep 5 backup files when saving
opt.FileWriter = new CommonFileWriter() { BackupMaxCount = 5 };

SectionName

In default, the entire settings are saved in the following structure:

{
  // settings saved to under "UserSettings" section
  "UserSettings": {
    "Name": "custom name",
    "Age": 30
  }
}

As you can see, the settings are written under the “UserSettings” section. This serves as a guardrail to prevent conflicts with settings from other libraries (such as ASP.NET Core). You can freely change this section name.

// change the section name to "MyAppSettings"
opt.SectionName = "MyAppSettings";

// nothing use section (written at the root, not recommended)
opt.SectionName = "";

InstanceName

If you want to manage multiple settings of the same type, you must specify different InstanceName for each setting.

// first setting
builder.AddUserConfigurationFile<UserSetting>(opt => {
    opt.FileName = "firstsettings.json";
    opt.InstanceName = "First";
    // save section will be "UserSettings-First"
});
// second setting
builder.AddUserConfigurationFile<UserSetting>(opt => {
    opt.FileName = "secondsettings.json";
    opt.InstanceName = "Second";
    // save section will be "UserSettings-Second"
});

// and get each setting from DI
public class MyService(IWritableOptions<UserSetting> config)
{
    public void GetAndSave()
    {
        // cannot use .CurrentValue if multiple settings of the same type are registered
        var firstSetting = config.Get("First");
        var secondSetting = config.Get("Second");
        // and you can must specify instance name when saving
        await config.SaveAsync("First", setting => {
            setting.Name = "first name";
        });
        await config.SaveAsync("Second", setting => {
            setting.Name = "second name";
        });
    }
}

It is recommended to avoid managing multiple settings of the same type as much as possible and to create a single class that wraps them.

// recommended
public class AllSettings
{
    public UserSetting First { get; set; } = new UserSetting();
    public UserSetting Second { get; set; } = new UserSetting();
}

Merge multiple settings

TODO.

Testing

You can use InMemoryFileWriter to test reading and writing settings without touching the file system.

// setup
var sampleFileName = Path.GetRandomFileName();
var _fileWriter = new InMemoryFileWriter();

WritableConfig.Initialize<UserSetting>(opt => {
    opt.FileName = sampleFileName;
    opt.UseInMemoryFileWriter(_fileWriter);
});
var option = WritableConfig.GetInstance<UserSetting>();

// and your test execution
await options.SaveAsync(setting => {
    setting.Name = "test name";
    setting.Age = 99;
});

// check the saved content
Assert.True(_fileWriter.FileExists(sampleFileName));
var savedText = _fileWriter.ReadAllText(sampleFileName);
Assert.Contains("test name", savedText);

Why This Library?

There are many ways to handle user settings in C# applications. However, each has some drawbacks, and no de facto standard exists.

Using app.config (Settings.settings)

This is an old-fashioned approach that yields many search results (unfortunately!). When you start using it, you'll likely encounter the following issues:

  • You need to manually write XML-based configuration files (or use Visual Studio's cumbersome GUI)
  • It lacks type safety and is unsuitable for complex settings
  • Files may be included in distributions without careful consideration, risking settings reset during updates

Reading and Writing Configuration Files Yourself

When considering type safety, the first approach that comes to mind is creating and reading/writing your own configuration files.
This method isn't bad, but the drawback is that there are too many things to consider.

  • You need to write configuration management code yourself
  • You need to implement many features like backup creation, settings merging, and update handling
  • Integrating multiple configuration sources requires extra effort
  • You need to implement configuration change reflection yourself

Using (Any Configuration Library)

Since there's so much boilerplate code, there must be some configuration library available.
Indeed, just searching for Config on NuGet yields many libraries.
I examined the major ones among these, but couldn't adopt them for the following reasons:

Using Microsoft.Extensions.Configuration

Considering these current situations, Microsoft.Extensions.Configuration (MS.E.C) can be said to be the most standardized configuration management method in modern times.
It provides many features such as multi-file integration, support for various formats including environment variables, and configuration change reflection, and integrates seamlessly with IHostApplication.
However, since it's primarily designed for application settings, it's insufficient for handling user settings. The major problem is that configuration writing is not supported.
Another issue is that it's based on DI, making it somewhat cumbersome to use in console applications.
Apps that want to use configuration files are more likely to not use DI (examples include WinForms, WPF, console apps, etc.).

Configuration.Writable

The preamble has gotten long, but it's time for promotion!
This library extends Microsoft.Extensions.Configuration to make writing user settings easy.
It's also designed to be easily usable in applications that don't use DI.

Please give it a try. It's simple!

License

This project is licensed under the Apache-2.0 License.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.