Fixed issue where singleton configuration cache was blocking creation of new components

This commit is contained in:
TheXamlGuy
2024-10-07 23:12:22 +01:00
parent 1c28659eac
commit f47e3deee9
6 changed files with 51 additions and 53 deletions
+3 -3
View File
@@ -68,8 +68,7 @@ public class ComponentBuilder :
where TConfiguration : where TConfiguration :
class, new() class, new()
{ {
hostBuilder.AddConfiguration(section: section, path: ConfigurationFile, hostBuilder.AddConfiguration(section, ConfigurationFile, configuration);
defaultConfiguration: configuration);
return this; return this;
} }
@@ -90,7 +89,8 @@ public class ComponentBuilder :
public IComponentHost Build() public IComponentHost Build()
{ {
hostBuilder.UseContentRoot(ContentRoot, true) hostBuilder
.UseContentRoot(ContentRoot, true)
.ConfigureAppConfiguration(config => .ConfigureAppConfiguration(config =>
{ {
config.AddJsonFile(ConfigurationFile, true, true); config.AddJsonFile(ConfigurationFile, true, true);
+6 -5
View File
@@ -2,16 +2,17 @@
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public static class ConfigurationCache public class ConfigurationCache :
IConfigurationCache
{ {
private static readonly ConcurrentDictionary<string, object?> cache = new(); private readonly ConcurrentDictionary<string, object?> cache = new();
public static void Set<TConfiguration>(string section, public void Set<TConfiguration>(string section,
TConfiguration configuration) => cache[section] = configuration; TConfiguration configuration) => cache[section] = configuration;
public static bool Remove(string section) => cache.TryRemove(section, out _); public bool Remove(string section) => cache.TryRemove(section, out _);
public static bool TryGet<TConfiguration>(string section, public bool TryGet<TConfiguration>(string section,
out TConfiguration? configuration) out TConfiguration? configuration)
{ {
if (cache.TryGetValue(section, out object? cachedValue)) if (cache.TryGetValue(section, out object? cachedValue))
+2 -1
View File
@@ -3,6 +3,7 @@
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public class ConfigurationMonitor<TConfiguration>(string section, public class ConfigurationMonitor<TConfiguration>(string section,
IConfigurationCache cache,
IConfigurationFile<TConfiguration> file, IConfigurationFile<TConfiguration> file,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IPublisher publisher) : IPublisher publisher) :
@@ -20,7 +21,7 @@ public class ConfigurationMonitor<TConfiguration>(string section,
if (serviceProvider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section) is if (serviceProvider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section) is
IConfigurationDescriptor<TConfiguration> configuration) IConfigurationDescriptor<TConfiguration> configuration)
{ {
ConfigurationCache.Remove(section); cache.Remove(section);
publisher.PublishUI(new ChangedEventArgs<TConfiguration>(configuration.Value)); publisher.PublishUI(new ChangedEventArgs<TConfiguration>(configuration.Value));
} }
} }
+21 -38
View File
@@ -9,6 +9,7 @@ namespace Toolkit.Foundation;
public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfiguration> configurationFile, public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfiguration> configurationFile,
string section, string section,
IConfigurationCache cache,
JsonSerializerOptions? serializerOptions = null) : JsonSerializerOptions? serializerOptions = null) :
IConfigurationSource<TConfiguration> IConfigurationSource<TConfiguration>
where TConfiguration : where TConfiguration :
@@ -58,13 +59,13 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
using Stream stream2 = new FileStream(fileInfo.PhysicalPath!, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); using Stream stream2 = new FileStream(fileInfo.PhysicalPath!, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
JsonSerializer.Serialize(stream2, rootNode, serializerOptions ?? defaultSerializerOptions()); JsonSerializer.Serialize(stream2, rootNode, serializerOptions ?? defaultSerializerOptions());
ConfigurationCache.Set(section, value); cache.Set(section, value);
} }
} }
public bool TryGet(out TConfiguration? value) public bool TryGet(out TConfiguration? value)
{ {
if (ConfigurationCache.TryGet(section, out value)) if (cache.TryGet(section, out value))
{ {
return true; return true;
} }
@@ -102,7 +103,7 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
if (currentNode != null && currentNode[segments[lastIndex]] is JsonNode sectionNode) if (currentNode != null && currentNode[segments[lastIndex]] is JsonNode sectionNode)
{ {
value = JsonSerializer.Deserialize<TConfiguration>(sectionNode, serializerOptions ?? defaultSerializerOptions()); value = JsonSerializer.Deserialize<TConfiguration>(sectionNode, serializerOptions ?? defaultSerializerOptions());
ConfigurationCache.Set(section, value); cache.Set(section, value);
return true; return true;
} }
@@ -136,16 +137,9 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
if (currentNode is not null) if (currentNode is not null)
{ {
string lastKey = segments[lastIndex]; string lastKey = segments[lastIndex];
if (currentNode is JsonArray array && int.TryParse(lastKey, out int index)) if (valueNode is JsonArray)
{ {
if (array.Count <= index) currentNode[lastKey] = valueNode;
{
array.Add(valueNode);
}
else
{
array[index] = MergeNodes(array[index], valueNode);
}
} }
else else
{ {
@@ -154,6 +148,20 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
} }
} }
private JsonNode? MergeNodes(JsonNode? existingNode, JsonNode? newNode)
{
if (existingNode is JsonObject existingObject && newNode is JsonObject newObject)
{
foreach (KeyValuePair<string, JsonNode?> property in newObject)
{
existingObject[property.Key] = MergeNodes(existingObject[property.Key], CloneNode(property.Value));
}
return existingObject;
}
return newNode;
}
private JsonNode? CloneNode(JsonNode? node) private JsonNode? CloneNode(JsonNode? node)
{ {
if (node is null) if (node is null)
@@ -165,6 +173,7 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
return JsonNode.Parse(serialized); return JsonNode.Parse(serialized);
} }
private void EnsureFileExists(string? filePath) private void EnsureFileExists(string? filePath)
{ {
if (filePath == null || File.Exists(filePath)) if (filePath == null || File.Exists(filePath))
@@ -180,30 +189,4 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
File.WriteAllText(filePath, "{}"); File.WriteAllText(filePath, "{}");
} }
private JsonNode? MergeNodes(JsonNode? existingNode, JsonNode? newNode)
{
if (existingNode is JsonObject existingObject && newNode is JsonObject newObject)
{
foreach (KeyValuePair<string, JsonNode?> property in newObject)
{
existingObject[property.Key] = MergeNodes(existingObject[property.Key], CloneNode(property.Value));
}
return existingObject;
}
else if (existingNode is JsonArray existingArray && newNode is JsonArray newArray)
{
foreach (JsonNode? item in newArray)
{
existingArray.Add(CloneNode(item));
}
return existingArray;
}
else
{
return newNode;
}
}
} }
@@ -0,0 +1,9 @@
namespace Toolkit.Foundation
{
public interface IConfigurationCache
{
bool Remove(string section);
void Set<TConfiguration>(string section, TConfiguration configuration);
bool TryGet<TConfiguration>(string section, out TConfiguration? configuration);
}
}
+9 -5
View File
@@ -118,7 +118,9 @@ public static class IHostBuilderExtension
context.Properties.Add(section, new List<Type> { typeof(TConfiguration) }); context.Properties.Add(section, new List<Type> { typeof(TConfiguration) });
} }
services.TryAddSingleton<IConfigurationFile<TConfiguration>>(provider => services.AddKeyedScoped<IConfigurationCache, ConfigurationCache>(section);
services.TryAddKeyedTransient<IConfigurationFile<TConfiguration>>(section, (provider, key) =>
{ {
IFileInfo? fileInfo = null; IFileInfo? fileInfo = null;
if (provider.GetService<IHostEnvironment>() is IHostEnvironment hostEnvironment) if (provider.GetService<IHostEnvironment>() is IHostEnvironment hostEnvironment)
@@ -140,8 +142,9 @@ public static class IHostBuilderExtension
serializerDelegate.Invoke(defaultSerializer); serializerDelegate.Invoke(defaultSerializer);
} }
return new ConfigurationSource<TConfiguration>(provider.GetRequiredService<IConfigurationFile<TConfiguration>>(), return new ConfigurationSource<TConfiguration>(provider.GetRequiredKeyedService<IConfigurationFile<TConfiguration>>(section),
section, section,
provider.GetRequiredKeyedService<IConfigurationCache>(section),
defaultSerializer); defaultSerializer);
}); });
@@ -174,15 +177,16 @@ public static class IHostBuilderExtension
services.TryAddKeyedTransient<IConfigurationDescriptor<TConfiguration>>(section, (provider, key) => services.TryAddKeyedTransient<IConfigurationDescriptor<TConfiguration>>(section, (provider, key) =>
new ConfigurationDescriptor<TConfiguration>(section, provider.GetRequiredKeyedService<IConfigurationReader<TConfiguration>>(key))); new ConfigurationDescriptor<TConfiguration>(section, provider.GetRequiredKeyedService<IConfigurationReader<TConfiguration>>(key)));
services.AddTransient(provider => services.TryAddTransient(provider =>
provider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section)); provider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section));
services.AddTransient(provider => services.TryAddTransient(provider =>
provider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section).Value); provider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section).Value);
services.AddHostedService(provider => services.AddHostedService(provider =>
new ConfigurationMonitor<TConfiguration>(section, new ConfigurationMonitor<TConfiguration>(section,
provider.GetRequiredService<IConfigurationFile<TConfiguration>>(), provider.GetRequiredKeyedService<IConfigurationCache>(section),
provider.GetRequiredKeyedService<IConfigurationFile<TConfiguration>>(section),
provider.GetRequiredService<IServiceProvider>(), provider.GetRequiredService<IServiceProvider>(),
provider.GetRequiredService<IPublisher>())); provider.GetRequiredService<IPublisher>()));
} }