A .NET library for simple, persistent application data management with JSON serialization.
ktsu.AppDataStorage is a .NET library designed to simplify the process of persisting application data. It stores configuration or state data as JSON files in the user's application data folder, with built-in safety mechanisms like automatic backups, debounced saves, and thread-safe operations. The library provides a singleton-like access pattern and supports custom subdirectories and file names for organizing data.
- Easy-to-use API: Inherit from
AppData<T>and get automatic JSON persistence withLoadOrCreate(),Save(), andGet(). - Automatic Backup: Creates backup files before overwriting to prevent data loss, with timestamped collision handling.
- Safe Write Pattern: Writes to a temporary file first, then atomically replaces the original to avoid corruption.
- Debounced Saves:
QueueSave()andSaveIfRequired()prevent frequent file writes with a 3-second debounce window. - Thread-Safe Operations: All file operations are synchronized with lock objects (uses
Locktype on .NET 9+,objecton earlier versions). - Singleton Access:
Get()provides lazy-initialized, singleton-like access to your app data instance. - Custom Storage Locations: Support for custom subdirectories and file names via
LoadOrCreate()overloads. - File System Abstraction: Uses
System.IO.Abstractionsfor easy unit testing with mock file systems. - Corrupt File Recovery: Automatically falls back to backup files when the main data file is corrupt or missing.
- Dispose-on-Exit: Registers for process exit to ensure queued saves are flushed before the application terminates.
Install-Package ktsu.AppDataStoragedotnet add package ktsu.AppDataStorage<PackageReferenceInclude="ktsu.AppDataStorage"Version="x.y.z" />Create a class that inherits from AppData<T>, where T is your custom data type.
usingktsu.AppDataStorage;publicclassMySettings:AppData<MySettings>{publicstringTheme{get;set;}="light";publicintFontSize{get;set;}=14;publicboolAutoSave{get;set;}=true;}// Load existing data or create a new instancevarsettings=MySettings.LoadOrCreate();Console.WriteLine(settings.Theme);// "light"Console.WriteLine(settings.FontSize);// 14The Get() method provides a lazy-initialized singleton instance, automatically calling LoadOrCreate() on first access.
usingktsu.AppDataStorage;// Access the singleton from anywhere in your applicationvarsettings=MySettings.Get();settings.Theme="dark";settings.Save();// Same instance returned every timevarsameSettings=MySettings.Get();Console.WriteLine(sameSettings.Theme);// "dark"Modify properties and call Save() to persist changes immediately.
usingktsu.AppDataStorage;varsettings=MySettings.Get();settings.Theme="dark";settings.FontSize=16;settings.Save();Use overloads of LoadOrCreate() to store data in subdirectories or with custom file names.
usingktsu.AppDataStorage;usingktsu.Semantics.Paths;// Store in a subdirectoryvarprofileData=MySettings.LoadOrCreate(RelativeDirectoryPath.Create("profiles"));// Store with a custom file namevarcustomData=MySettings.LoadOrCreate(FileName.Create("user_preferences.json"));// Both subdirectory and custom file namevarspecificData=MySettings.LoadOrCreate(RelativeDirectoryPath.Create("profiles"),FileName.Create("admin_settings.json"));For scenarios with frequent updates (e.g., UI-driven changes), use QueueSave() to schedule a save that is debounced with a 3-second threshold. Call SaveIfRequired() periodically (e.g., in a game loop or timer) to flush queued saves.
usingktsu.AppDataStorage;varsettings=MySettings.Get();settings.Theme="dark";settings.QueueSave();// Schedules a save// Later, in your update loop or timer:settings.SaveIfRequired();// Saves only if 3+ seconds have elapsed since QueueSave// Or use the static convenience methods:MySettings.QueueSave();MySettings.SaveIfRequired();Queued saves are also automatically flushed when the AppData<T> instance is disposed or when the process exits.
The library supports System.IO.Abstractions for testability. Configure a mock file system in your tests:
usingSystem.IO.Abstractions.TestingHelpers;usingktsu.AppDataStorage;// In test setup - each thread gets its own isolated instanceAppData.ConfigureForTesting(()=>newMockFileSystem());// Run your tests...vardata=MySettings.LoadOrCreate();data.Theme="test";data.Save();// In test teardownAppData.ResetFileSystem();Data is stored in a directory unique to the current application domain under the user's %APPDATA% folder. File names are derived from the class name in snake_case.
usingktsu.AppDataStorage;// View the storage pathConsole.WriteLine(AppData.Path);// e.g., C:\Users\{user}\AppData\Roaming\{AppDomainName}// File name is automatically generated from the class name// MySettings -> my_settings.jsonProvides static helper methods and properties for managing application data storage.
| Name | Type | Description |
|---|---|---|
Path | AbsoluteDirectoryPath | The path where persistent data is stored for this application |
| Name | Return Type | Description |
|---|---|---|
WriteText<T>(T appData, string text) | void | Writes text to an app data file using a safe write pattern |
ReadText<T>(T appData) | string | Reads text from an app data file, falling back to backup if missing |
QueueSave<T>(this T appData) | void | Extension method that queues a debounced save operation |
SaveIfRequired<T>(this T appData) | void | Extension method that saves if the debounce threshold has elapsed |
ConfigureForTesting(Func<IFileSystem>) | void | Configures a mock file system factory for unit testing |
ResetFileSystem() | void | Resets the file system to the default implementation after testing |
Base class for app data storage. Inherit from this class to create persistable data types.
T : AppData<T>, IDisposable, new()
| Name | Return Type | Description |
|---|---|---|
Get() | T | Gets the lazy-initialized singleton instance of the app data |
LoadOrCreate() | T | Loads app data from file or creates a new instance if none exists |
LoadOrCreate(RelativeDirectoryPath?) | T | Loads or creates with a custom subdirectory |
LoadOrCreate(FileName?) | T | Loads or creates with a custom file name |
LoadOrCreate(RelativeDirectoryPath?, FileName?) | T | Loads or creates with both custom subdirectory and file name |
Save() | void | Serializes and saves the app data to its JSON file |
QueueSave() | void | Queues a debounced save for the singleton instance |
SaveIfRequired() | void | Saves the singleton instance if the debounce threshold has elapsed |
Dispose() | void | Disposes the instance, flushing any queued saves |
Contributions are welcome! Feel free to open issues or submit pull requests.
This project is licensed under the MIT License. See the LICENSE.md file for details.