Fixed issue where singleton configuration cache was blocking creation of new components
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>()));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user