Skip to content

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.

License

Notifications You must be signed in to change notification settings

marcopicker/EasyPrefs

Repository files navigation

EasyPrefs

CI - Build, Test, Validate Pack CI Develop - Build, Test, Validate Pack Release - Publish NuGet Packages

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

Packages

  • Core: EasyPrefs
  • JSON data provider: EasyPrefs.JSON

Install with the .NET CLI:

dotnet add package EasyPrefs
dotnet add package EasyPrefs.JSON

Core features

  • Typed reads/writes via SetPreference<T>(key, value) and GetPreference<T>(key).
  • Mandatory schemas per preference (type, default value and optional metadata).
  • Pluggable storage options via IPreferenceDataProvider and IPreferencesSchemaProvider.
  • 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.

Quick start

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.25

Reading and writing values

You 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:

  • IPreferenceReader for read-only access.
  • IPreferences for 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.

Caching and pending changes

  • All preferences are stored in a cache within the Preferences object.
  • Pending changes are stored in a list until ApplyChanges() is called (unless AutoApplyChanges is 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).

Schemas

Each preference must have a schema that describes:

  • Key: Its unique identifier that is identical to the key used in GetPreference<T> and SetPreference<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.

Metadata

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.

Data providers

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.).

Built-in JSON provider

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
    }
};

Bring your own provider

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.
    }
}

Thread Safety

  • The Preferences class should be thread-safe. Disclaimer: I have never worked on thread-safety before.

FAQ & Troubleshooting

Why are there no methods to see if a preference or schema exists?

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.

I have changed a preference via SetPreference(...) but GetPreference(...) returns the old value.

Call ApplyChanges() after setting your preferences or enable Preferences.AutoApplyChanges.

License

See the LICENSE.md file in the repository.

About

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.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages