diff --git a/src/StevanFreeborn.Extensions.Configuration.Secure/Storage/JsonFileStorageProvider.cs b/src/StevanFreeborn.Extensions.Configuration.Secure/Storage/JsonFileStorageProvider.cs index 8fd8482..ce280ab 100644 --- a/src/StevanFreeborn.Extensions.Configuration.Secure/Storage/JsonFileStorageProvider.cs +++ b/src/StevanFreeborn.Extensions.Configuration.Secure/Storage/JsonFileStorageProvider.cs @@ -2,6 +2,10 @@ using System.Text.Json; namespace StevanFreeborn.Extensions.Configuration.Secure.Storage; +/// +/// Provides a mechanism to store and retrieve secure configuration data in a JSON file. +/// +/// The configuring the storage provider, including file paths. public sealed class JsonFileStorageProvider(JsonStorageOptions options) { private static readonly SemaphoreSlim FileLock = new(1, 1); @@ -12,9 +16,15 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options) private readonly JsonStorageOptions _options = options ?? throw new ArgumentNullException(nameof(options)); + /// + /// Reads the value associated with the specified key from the JSON file asynchronously. + /// + /// The key of the configuration value to read. + /// A cancellation token that can be used to cancel the read operation. + /// A task that represents the asynchronous read operation. The task result contains the value associated with the specified key, or an empty string if the key is not found. public async Task ReadAsync(string key, CancellationToken ct = default) { - var data = await LoadWithLockAsync(ct); + var data = await AcquireLockAndLoadAsync(ct).ConfigureAwait(false); if (data is not null && data.TryGetValue(key, out var v)) { @@ -24,20 +34,32 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options) return string.Empty; } + /// + /// Reads all configuration values from the JSON file asynchronously. + /// + /// A cancellation token that can be used to cancel the read operation. + /// A task that represents the asynchronous read operation. The task result contains a dictionary of all configuration keys and their values. public Task> ReadAllAsync(CancellationToken ct = default) { - return LoadWithLockAsync(ct); + return AcquireLockAndLoadAsync(ct); } + /// + /// Writes the specified key and encrypted data to the JSON file asynchronously. + /// + /// The key of the configuration value to write. + /// The encrypted configuration data to write. + /// A cancellation token that can be used to cancel the write operation. + /// A task that represents the asynchronous write operation. public async Task WriteAsync(string key, string encryptedData, CancellationToken ct = default) { - await FileLock.WaitAsync(ct); + await FileLock.WaitAsync(ct).ConfigureAwait(false); try { - var data = await LoadAsync(ct); + var data = await LoadAsync(ct).ConfigureAwait(false); data[key] = encryptedData; - await SaveAsync(data, ct); + await SaveAsync(data, ct).ConfigureAwait(false); } finally { @@ -45,15 +67,21 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options) } } + /// + /// Deletes the configuration value associated with the specified key from the JSON file asynchronously. + /// + /// The key of the configuration value to delete. + /// A cancellation token that can be used to cancel the delete operation. + /// A task that represents the asynchronous delete operation. The task result contains true if the value was successfully deleted; otherwise, false. public async Task DeleteAsync(string key, CancellationToken ct = default) { - await FileLock.WaitAsync(ct); + await FileLock.WaitAsync(ct).ConfigureAwait(false); try { - var data = await LoadAsync(ct); + var data = await LoadAsync(ct).ConfigureAwait(false); var result = data.Remove(key); - await SaveAsync(data, ct); + await SaveAsync(data, ct).ConfigureAwait(false); return result; } finally @@ -62,13 +90,18 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options) } } - private async Task> LoadWithLockAsync(CancellationToken ct) + /// + /// Acquires an exclusive lock and loads the configuration data from the JSON file. + /// + /// A cancellation token to observe while waiting for the lock or during the load operation. + /// A dictionary containing the loaded configuration data. + private async Task> AcquireLockAndLoadAsync(CancellationToken ct) { - await FileLock.WaitAsync(ct); + await FileLock.WaitAsync(ct).ConfigureAwait(false); try { - return await LoadAsync(ct); + return await LoadAsync(ct).ConfigureAwait(false); } finally { @@ -76,6 +109,11 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options) } } + /// + /// Loads the configuration data from the JSON file. + /// + /// A cancellation token to observe while loading the data. + /// A dictionary containing the loaded configuration data, or an empty dictionary if the file does not exist or is empty. private async Task> LoadAsync(CancellationToken ct) { if (File.Exists(_options.FullPath) is false) @@ -90,11 +128,18 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options) return []; } - var data = await JsonSerializer.DeserializeAsync>(stream, cancellationToken: ct); + var data = await JsonSerializer.DeserializeAsync>(stream, cancellationToken: ct) + .ConfigureAwait(false); return data ?? []; } + /// + /// Saves the configuration data to the JSON file. + /// + /// The configuration data to save. + /// A cancellation token to observe while saving the data. + /// A task that represents the asynchronous save operation. private async Task SaveAsync(Dictionary data, CancellationToken ct) { Directory.CreateDirectory(_options.DirectoryPath); @@ -106,6 +151,7 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options) FileShare.None ); - await JsonSerializer.SerializeAsync(stream, data, JsonOptions, ct); + await JsonSerializer.SerializeAsync(stream, data, JsonOptions, ct) + .ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/StevanFreeborn.Extensions.Configuration.Secure/Storage/JsonStorageOptions.cs b/src/StevanFreeborn.Extensions.Configuration.Secure/Storage/JsonStorageOptions.cs index c40a93a..4abac49 100644 --- a/src/StevanFreeborn.Extensions.Configuration.Secure/Storage/JsonStorageOptions.cs +++ b/src/StevanFreeborn.Extensions.Configuration.Secure/Storage/JsonStorageOptions.cs @@ -1,8 +1,22 @@ namespace StevanFreeborn.Extensions.Configuration.Secure.Storage; +/// +/// Options for configuring the . +/// public sealed class JsonStorageOptions { + /// + /// Gets or sets the name of the JSON file used for storage. + /// public string FileName { get; set; } = string.Empty; + + /// + /// Gets or sets the directory path where the JSON file is located. Defaults to the base directory of the application. + /// public string DirectoryPath { get; set; } = AppContext.BaseDirectory; + + /// + /// Gets the full, combined path to the JSON file, including the directory and file name. + /// public string FullPath => Path.Combine(DirectoryPath, FileName); } \ No newline at end of file