EasyPrefs is a small, easy-to-use preferences library for .NET. It provides a simple, typed API for reading and writing user/application preferences, a cache with pending-change tracking, and a pluggable storage model so you can bring your own persistence.
- Target framework: .NET 9.0
- Core:
EasyPrefs - JSON data provider:
EasyPrefs.JSON
Install with the .NET CLI:
dotnet add package EasyPrefs
dotnet add package EasyPrefs.JSON- Typed reads/writes via
SetPreference<T>(key, value)andGetPreference<T>(key). - Mandatory schemas per preference (type, default value and optional metadata).
- Pluggable storage options via
IPreferenceDataProviderandIPreferencesSchemaProvider. - In-memory caching for fast reads.
- Pending change tracking.
- Batch-saving via
ApplyChanges().- Optional auto-apply on setting preferences.
- Events for preference changes and pending-change updates.
Below is a minimal example showing how to store preferences in a JSON file and read them back.
using EasyPrefs;
using EasyPrefs.Abstractions;
using EasyPrefs.JSON;
using Newtonsoft.Json;
// 1) Create a data provider (in this case using the provided EasyPrefs.JSON provider).
var dataProvider = new JsonPreferenceDataProvider(@"settings\preferences.json");
// Optional: customise JSON serialization.
provider.JsonSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented
};
// 2) Create a schema provider with keys and default values.
var schemaProvider = new BuildablePreferenceSchemaProvider();
schemaProvider.AddSchema(new PreferenceSchema("ui.theme", typeof(string), "light"));
schemaProvider.AddSchema(new PreferenceSchema("ui.zoom", typeof(double), 1));
// 3) Create the main Preferences instance.
var preferences = new Preferences(dataProvider, schemaProvider);
// Optional: listen for change notifications
preferences.PreferencesChanged += (_, args) =>
{
if(args.ChangedPreferenceKeys.Contains("ui.theme"))
{
// ...
}
};
// 4) Write some preferences.
preferences.SetPreference("ui.theme", "dark");
preferences.SetPreference("ui.zoom", 1.25);
// Apply changes to the data provider - in this case save the changes in the JSON file.
preferences.ApplyChanges();
// 5) Read them later.
var theme = preferences.GetPreference<string>("ui.theme"); // "dark"
var zoom = preferences.GetPreference<double>("ui.zoom"); // 1.25You can read preferences by calling preferences.GetPreference<T>(string key) and write preferences by calling preferences.SetPreference<T>(string key, T? value).
Setting a preference via SetPreference will store the new value in a list of pending changes. To apply the pending changes, you need to call preferences.ApplyChanges(). You may set preferences.AutoApplyChanges to true to automatically call ApplyChanges after every SetPreference call.
If you want to use interfaces (e.g. for IoC) Preferences implements:
IPreferenceReaderfor read-only access.IPreferencesfor both read and write access.
Important
If you change a value via SetPreference<T>, GetPreference<T> will return the old value until the changes are applied via ApplyChanges.
- All preferences are stored in a cache within the
Preferencesobject. - Pending changes are stored in a list until
ApplyChanges()is called (unlessAutoApplyChangesis enabled). - Tracking:
HasPendingChanges()tells you if there are unapplied changes.GetKeysWithPendingChanges()returns the keys that have pending changes.ClearPendingChanges()discards unapplied changes (reverts the pending set, keeps the last persisted values).
Each preference must have a schema that describes:
Key: Its unique identifier that is identical to the key used inGetPreference<T>andSetPreference<T>.DataType: The expected data type for the value.DefaultValue: Used when no explicit value is present or when the value was invalid during initialisation.- Optional metadata via a
MetadataCollection.
Important
Each preference must have an associated schema. Trying to access a preference without a schema will result in an error.
You can store optional metadata in each PreferenceSchema via the Metadata property.
Metadata is set and accessed much like preferences, with Add, GetValue<T>, TryGetValue<T> and HasMetadata functions.
You may use this to, for example, add preference names, descriptions and categories if you want to automatically populate your preference controls.
EasyPrefs decouples the cached preferences from the persistence layer via IPreferenceDataProvider. This lets you plug in different backends (JSON files, databases, cloud KV stores, etc.).
EasyPrefs.JSON offers a JSON file-based provider:
- Customize serialization via
JsonPreferenceDataProvider.JsonSettings(uses Newtonsoft.Json). - Writes are designed to be resilient (i.e. temp file and backup) to minimise the risk of corruption.
Example:
using EasyPrefs.JSON;
using Newtonsoft.Json;
var provider = new JsonPreferenceDataProvider(@"<path>\prefs.json", allowMissingFile: true)
{
JsonSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented
}
};Implement IPreferenceDataProvider. Your implementations should:
Initialise(): Prepare your storage and load any existing data, if necessary.GetAllPreferences(): Return all persisting key/value pairs as they currently exist.SetPreferences(changes): Save the provided changes to the persistent data layer.
Tip
Do not cache values in your custom IPreferenceDataProvider, as values are already cached within Preferences.
Caching here would result in a doubled cache.
using System.Collections.Generic;
using EasyPrefs.Abstractions;
public sealed class MyPreferenceDataProvider : IPreferenceDataProvider
{
public void Initialise()
{
// Connect to your store / ensure tables / read initial state, etc..
}
public IEnumerable<PreferenceValue> GetAllPreferences()
{
// Load and return all key/value pairs from your store.
}
public void ApplyChanges(IReadOnlyCollection<PreferenceValue> changes)
{
// Persist all changes.
// Consider atomicity and crash-safety where possible.
}
}- The
Preferencesclass should be thread-safe. Disclaimer: I have never worked on thread-safety before.
The design philosophy behind this library is that there is little to no end-user interaction. You - the developer - should know exactly which preference keys are available and which are not.
Call ApplyChanges() after setting your preferences or enable Preferences.AutoApplyChanges.
See the LICENSE.md file in the repository.