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