Add Config locking and caching
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Toolkit.Foundation;
|
||||||
|
|
||||||
|
public static class ConfigurationCache
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<string, object?> cache = new();
|
||||||
|
|
||||||
|
public static void Set<TConfiguration>(string section,
|
||||||
|
TConfiguration configuration) => cache[section] = configuration;
|
||||||
|
|
||||||
|
public static bool Remove(string section) => cache.TryRemove(section, out _);
|
||||||
|
|
||||||
|
public static bool TryGet<TConfiguration>(string section,
|
||||||
|
out TConfiguration? configuration)
|
||||||
|
{
|
||||||
|
if (cache.TryGetValue(section, out object? cachedValue))
|
||||||
|
{
|
||||||
|
configuration = (TConfiguration?)cachedValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
namespace Toolkit.Foundation;
|
||||||
|
|
||||||
|
public static class ConfigurationLock
|
||||||
|
{
|
||||||
|
private static readonly ReaderWriterLockSlim readerWriterLock = new();
|
||||||
|
|
||||||
|
public static IDisposable EnterRead()
|
||||||
|
{
|
||||||
|
readerWriterLock.EnterReadLock();
|
||||||
|
return new ConfigurationReaderLockDisposer(readerWriterLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IDisposable EnterWrite()
|
||||||
|
{
|
||||||
|
readerWriterLock.EnterWriteLock();
|
||||||
|
return new ConfigurationWriterLockDisposer(readerWriterLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConfigurationWriterLockDisposer(ReaderWriterLockSlim lockSlim) :
|
||||||
|
IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lockSlim.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConfigurationReaderLockDisposer(ReaderWriterLockSlim lockSlim) :
|
||||||
|
IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lockSlim.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
namespace Toolkit.Foundation;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
public class ConfigurationMonitor<TConfiguration>(IConfigurationFile<TConfiguration> file,
|
namespace Toolkit.Foundation;
|
||||||
IConfigurationReader<TConfiguration> reader,
|
|
||||||
|
public class ConfigurationMonitor<TConfiguration>(string section,
|
||||||
|
IConfigurationFile<TConfiguration> file,
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
IPublisher publisher) :
|
IPublisher publisher) :
|
||||||
IConfigurationMonitor<TConfiguration>
|
IConfigurationMonitor<TConfiguration>
|
||||||
where TConfiguration :
|
where TConfiguration :
|
||||||
@@ -14,9 +17,11 @@ public class ConfigurationMonitor<TConfiguration>(IConfigurationFile<TConfigurat
|
|||||||
void ChangedHandler(object sender,
|
void ChangedHandler(object sender,
|
||||||
FileSystemEventArgs args)
|
FileSystemEventArgs args)
|
||||||
{
|
{
|
||||||
if (reader.Read() is { } configuration)
|
if (serviceProvider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section) is
|
||||||
|
IConfigurationDescriptor<TConfiguration> configuration)
|
||||||
{
|
{
|
||||||
publisher.PublishUI(new ChangedEventArgs<TConfiguration>(configuration));
|
ConfigurationCache.Remove(section);
|
||||||
|
publisher.PublishUI(new ChangedEventArgs<TConfiguration>(configuration.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
|||||||
where TConfiguration :
|
where TConfiguration :
|
||||||
class
|
class
|
||||||
{
|
{
|
||||||
private static readonly Func<JsonSerializerOptions> defaultSerializerOptions = new(() =>
|
private static readonly Func<JsonSerializerOptions> defaultSerializerOptions = () =>
|
||||||
{
|
{
|
||||||
return new JsonSerializerOptions
|
return new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
@@ -26,133 +26,82 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
|||||||
new DictionaryStringObjectJsonConverter()
|
new DictionaryStringObjectJsonConverter()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
};
|
||||||
|
|
||||||
private readonly object lockingObject = new();
|
|
||||||
|
|
||||||
public void Set(TConfiguration value) => Set((object)value);
|
public void Set(TConfiguration value) => Set((object)value);
|
||||||
|
|
||||||
public void Set(object value)
|
public void Set(object value)
|
||||||
{
|
{
|
||||||
lock (lockingObject)
|
using (ConfigurationLock.EnterWrite())
|
||||||
{
|
{
|
||||||
IFileInfo fileInfo = configurationFile.FileInfo;
|
IFileInfo fileInfo = configurationFile.FileInfo;
|
||||||
if (!File.Exists(fileInfo.PhysicalPath))
|
EnsureFileExists(fileInfo.PhysicalPath);
|
||||||
{
|
|
||||||
string? fileDirectoryPath = Path.GetDirectoryName(fileInfo.PhysicalPath);
|
|
||||||
if (!string.IsNullOrEmpty(fileDirectoryPath))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(fileDirectoryPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllText(fileInfo.PhysicalPath!, "{}");
|
string? content;
|
||||||
|
JsonNode? rootNode;
|
||||||
|
|
||||||
|
using (Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||||||
|
using (StreamReader reader = new(stream))
|
||||||
|
{
|
||||||
|
content = reader.ReadToEnd();
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
rootNode = JsonNode.Parse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
using Stream stream = fileInfo.PhysicalPath is not null
|
JsonNode? valueNode = JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(value, serializerOptions ?? defaultSerializerOptions()));
|
||||||
? new FileStream(fileInfo.PhysicalPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
|
|
||||||
: fileInfo.CreateReadStream();
|
|
||||||
|
|
||||||
using StreamReader? reader = new(stream);
|
ApplyConfigurationUpdates(ref rootNode, valueNode, section);
|
||||||
|
|
||||||
string? content = reader.ReadToEnd();
|
using Stream stream2 = new FileStream(fileInfo.PhysicalPath!, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
JsonSerializer.Serialize(stream2, rootNode, serializerOptions ?? defaultSerializerOptions());
|
||||||
|
|
||||||
JsonNode? rootNode = JsonNode.Parse(content);
|
ConfigurationCache.Set(section, value);
|
||||||
JsonNode? valueNode = JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(value,
|
|
||||||
serializerOptions ?? defaultSerializerOptions()));
|
|
||||||
|
|
||||||
string[] segments = section.Split(':');
|
|
||||||
JsonNode? currentNode = rootNode;
|
|
||||||
int lastIndex = segments.Length - 1;
|
|
||||||
|
|
||||||
for (int i = 0; i < lastIndex; i++)
|
|
||||||
{
|
|
||||||
if (currentNode is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string currentKey = segments[i];
|
|
||||||
if (currentNode[currentKey] is null)
|
|
||||||
{
|
|
||||||
currentNode[currentKey] = new JsonObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentNode = currentNode[currentKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentNode is not null)
|
|
||||||
{
|
|
||||||
string lastKey = segments[lastIndex];
|
|
||||||
if (currentNode is JsonArray array && int.TryParse(lastKey, out int index))
|
|
||||||
{
|
|
||||||
if (array.Count <= index)
|
|
||||||
{
|
|
||||||
array.Add(value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
array[index] = MergeNodes(array[index], valueNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentNode[lastKey] = MergeNodes(currentNode[lastKey], valueNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using Stream stream2 = fileInfo.PhysicalPath is not null
|
|
||||||
? new FileStream(fileInfo.PhysicalPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)
|
|
||||||
: fileInfo.CreateReadStream();
|
|
||||||
|
|
||||||
JsonSerializer.Serialize(stream, rootNode, serializerOptions ?? defaultSerializerOptions());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGet(out TConfiguration? value)
|
public bool TryGet(out TConfiguration? value)
|
||||||
{
|
{
|
||||||
lock (lockingObject)
|
if (ConfigurationCache.TryGet(section, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (ConfigurationLock.EnterRead())
|
||||||
{
|
{
|
||||||
IFileInfo fileInfo = configurationFile.FileInfo;
|
IFileInfo fileInfo = configurationFile.FileInfo;
|
||||||
if (File.Exists(fileInfo.PhysicalPath))
|
|
||||||
|
if (!File.Exists(fileInfo.PhysicalPath))
|
||||||
{
|
{
|
||||||
static Stream OpenRead(IFileInfo fileInfo)
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||||
|
using StreamReader reader = new(stream);
|
||||||
|
string content = reader.ReadToEnd();
|
||||||
|
JsonNode? rootNode = JsonNode.Parse(content);
|
||||||
|
|
||||||
|
string[] segments = section.Split(':');
|
||||||
|
JsonNode? currentNode = rootNode;
|
||||||
|
|
||||||
|
int lastIndex = segments.Length - 1;
|
||||||
|
for (int i = 0; i < lastIndex; i++)
|
||||||
|
{
|
||||||
|
if (currentNode is null)
|
||||||
{
|
{
|
||||||
return fileInfo.PhysicalPath is not null
|
value = default;
|
||||||
? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)
|
return false;
|
||||||
: fileInfo.CreateReadStream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using Stream stream = OpenRead(fileInfo);
|
currentNode = currentNode[segments[i]];
|
||||||
using StreamReader? reader = new(stream);
|
}
|
||||||
|
|
||||||
string? content = reader.ReadToEnd();
|
if (currentNode != null && currentNode[segments[lastIndex]] is JsonNode sectionNode)
|
||||||
JsonNode? rootNode = JsonNode.Parse(content);
|
{
|
||||||
|
value = JsonSerializer.Deserialize<TConfiguration>(sectionNode, serializerOptions ?? defaultSerializerOptions());
|
||||||
string[] segments = section.Split(':');
|
ConfigurationCache.Set(section, value);
|
||||||
JsonNode? currentNode = rootNode;
|
return true;
|
||||||
int lastIndex = segments.Length - 1;
|
|
||||||
|
|
||||||
for (int i = 0; i < lastIndex; i++)
|
|
||||||
{
|
|
||||||
if (currentNode is null)
|
|
||||||
{
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentNode = currentNode[segments[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentNode is not null)
|
|
||||||
{
|
|
||||||
if (currentNode[segments[lastIndex]] is JsonNode sectionNode)
|
|
||||||
{
|
|
||||||
value = JsonSerializer.Deserialize<TConfiguration>(sectionNode,
|
|
||||||
serializerOptions ?? defaultSerializerOptions());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value = default;
|
value = default;
|
||||||
@@ -160,6 +109,49 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyConfigurationUpdates(ref JsonNode? rootNode, JsonNode? valueNode, string section)
|
||||||
|
{
|
||||||
|
string[] segments = section.Split(':');
|
||||||
|
JsonNode? currentNode = rootNode;
|
||||||
|
int lastIndex = segments.Length - 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < lastIndex; i++)
|
||||||
|
{
|
||||||
|
if (currentNode is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string currentKey = segments[i];
|
||||||
|
if (currentNode[currentKey] is null)
|
||||||
|
{
|
||||||
|
currentNode[currentKey] = new JsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentNode = currentNode[currentKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentNode is not null)
|
||||||
|
{
|
||||||
|
string lastKey = segments[lastIndex];
|
||||||
|
if (currentNode is JsonArray array && int.TryParse(lastKey, out int index))
|
||||||
|
{
|
||||||
|
if (array.Count <= index)
|
||||||
|
{
|
||||||
|
array.Add(valueNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
array[index] = MergeNodes(array[index], valueNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentNode[lastKey] = MergeNodes(currentNode[lastKey], valueNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private JsonNode? CloneNode(JsonNode? node)
|
private JsonNode? CloneNode(JsonNode? node)
|
||||||
{
|
{
|
||||||
if (node is null)
|
if (node is null)
|
||||||
@@ -171,15 +163,29 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
|||||||
return JsonNode.Parse(serialized);
|
return JsonNode.Parse(serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EnsureFileExists(string? filePath)
|
||||||
|
{
|
||||||
|
if (filePath == null || File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? directoryPath = Path.GetDirectoryName(filePath);
|
||||||
|
if (!string.IsNullOrEmpty(directoryPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(filePath, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
private JsonNode? MergeNodes(JsonNode? existingNode, JsonNode? newNode)
|
private JsonNode? MergeNodes(JsonNode? existingNode, JsonNode? newNode)
|
||||||
{
|
{
|
||||||
newNode = CloneNode(newNode);
|
|
||||||
|
|
||||||
if (existingNode is JsonObject existingObject && newNode is JsonObject newObject)
|
if (existingNode is JsonObject existingObject && newNode is JsonObject newObject)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<string, JsonNode?> property in newObject)
|
foreach (KeyValuePair<string, JsonNode?> property in newObject)
|
||||||
{
|
{
|
||||||
existingObject[property.Key] = MergeNodes(existingObject[property.Key], property.Value);
|
existingObject[property.Key] = MergeNodes(existingObject[property.Key], CloneNode(property.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return existingObject;
|
return existingObject;
|
||||||
@@ -188,7 +194,7 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
|||||||
{
|
{
|
||||||
foreach (JsonNode? item in newArray)
|
foreach (JsonNode? item in newArray)
|
||||||
{
|
{
|
||||||
existingArray.Add(item);
|
existingArray.Add(CloneNode(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
return existingArray;
|
return existingArray;
|
||||||
@@ -198,4 +204,4 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
|||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,22 +15,29 @@ public partial class ConfigurationValueViewModel<TConfiguration, TValue>(IServic
|
|||||||
INotificationHandler<ChangedEventArgs<TConfiguration>>
|
INotificationHandler<ChangedEventArgs<TConfiguration>>
|
||||||
where TConfiguration : class
|
where TConfiguration : class
|
||||||
{
|
{
|
||||||
public Task Handle(ChangedEventArgs<TConfiguration> args)
|
public async Task Handle(ChangedEventArgs<TConfiguration> args)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (args.Sender is TConfiguration configuration)
|
||||||
|
{
|
||||||
|
// await Task.Run(() => Value = read(configuration));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnChanged(TValue? value)
|
public override async Task OnActivated()
|
||||||
{
|
{
|
||||||
writer.Write(args => write(value, args));
|
await Task.Run(() => Value = read(configuration));
|
||||||
|
await base.OnActivated();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnChanged(TValue? value)
|
||||||
|
{
|
||||||
|
if (IsActivated)
|
||||||
|
{
|
||||||
|
await Task.Run(() => writer.Write(args => write(value, args)));
|
||||||
|
}
|
||||||
|
|
||||||
base.OnChanged(value);
|
base.OnChanged(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task OnActivated()
|
|
||||||
{
|
|
||||||
Value = read(configuration);
|
|
||||||
return base.OnActivated();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ConfigurationValueViewModel<TConfiguration, TValue, TItem> :
|
public partial class ConfigurationValueViewModel<TConfiguration, TValue, TItem> :
|
||||||
@@ -41,10 +48,9 @@ public partial class ConfigurationValueViewModel<TConfiguration, TValue, TItem>
|
|||||||
IDisposable
|
IDisposable
|
||||||
{
|
{
|
||||||
private readonly TConfiguration configuration;
|
private readonly TConfiguration configuration;
|
||||||
private readonly IWritableConfiguration<TConfiguration> writer;
|
|
||||||
private readonly Func<TConfiguration, TValue?> read;
|
private readonly Func<TConfiguration, TValue?> read;
|
||||||
private readonly Action<TValue?, TConfiguration> write;
|
private readonly Action<TValue?, TConfiguration> write;
|
||||||
|
private readonly IWritableConfiguration<TConfiguration> writer;
|
||||||
public ConfigurationValueViewModel(IServiceProvider provider,
|
public ConfigurationValueViewModel(IServiceProvider provider,
|
||||||
IServiceFactory factory,
|
IServiceFactory factory,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
@@ -86,20 +92,27 @@ public partial class ConfigurationValueViewModel<TConfiguration, TValue, TItem>
|
|||||||
Value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Handle(ChangedEventArgs<TConfiguration> args)
|
public async Task Handle(ChangedEventArgs<TConfiguration> args)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (args.Sender is TConfiguration configuration)
|
||||||
|
{
|
||||||
|
// await Task.Run(() => Value = read(configuration));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnChanged(TValue? value)
|
public override async Task OnActivated()
|
||||||
{
|
{
|
||||||
writer.Write(args => write(value, args));
|
await Task.Run(() => Value = read(configuration));
|
||||||
|
await base.OnActivated();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnChanged(TValue? value)
|
||||||
|
{
|
||||||
|
if (IsActivated)
|
||||||
|
{
|
||||||
|
await Task.Run(() => writer.Write(args => write(value, args)));
|
||||||
|
}
|
||||||
|
|
||||||
base.OnChanged(value);
|
base.OnChanged(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task OnActivated()
|
|
||||||
{
|
|
||||||
Value = read(configuration);
|
|
||||||
return base.OnActivated();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,4 @@ public interface IConfigurationSource<TConfiguration>
|
|||||||
void Set(TConfiguration value);
|
void Set(TConfiguration value);
|
||||||
|
|
||||||
void Set(object value);
|
void Set(object value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRemovable : IDisposable;
|
|
||||||
@@ -141,10 +141,10 @@ public static class IHostBuilderExtension
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new ConfigurationSource<TConfiguration>(provider.GetRequiredService<IConfigurationFile<TConfiguration>>(),
|
return new ConfigurationSource<TConfiguration>(provider.GetRequiredService<IConfigurationFile<TConfiguration>>(),
|
||||||
section, defaultSerializer);
|
section,
|
||||||
|
defaultSerializer);
|
||||||
});
|
});
|
||||||
|
|
||||||
//services.AddHostedService<ConfigurationMonitor<TConfiguration>>();
|
|
||||||
services.TryAddKeyedTransient<IConfigurationReader<TConfiguration>>(section, (provider, key) =>
|
services.TryAddKeyedTransient<IConfigurationReader<TConfiguration>>(section, (provider, key) =>
|
||||||
new ConfigurationReader<TConfiguration>(provider.GetRequiredKeyedService<IConfigurationSource<TConfiguration>>(key),
|
new ConfigurationReader<TConfiguration>(provider.GetRequiredKeyedService<IConfigurationSource<TConfiguration>>(key),
|
||||||
provider.GetRequiredKeyedService<IConfigurationFactory<TConfiguration>>(key)));
|
provider.GetRequiredKeyedService<IConfigurationFactory<TConfiguration>>(key)));
|
||||||
@@ -179,6 +179,12 @@ public static class IHostBuilderExtension
|
|||||||
|
|
||||||
services.AddTransient(provider =>
|
services.AddTransient(provider =>
|
||||||
provider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section).Value);
|
provider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section).Value);
|
||||||
|
|
||||||
|
services.AddHostedService(provider =>
|
||||||
|
new ConfigurationMonitor<TConfiguration>(section,
|
||||||
|
provider.GetRequiredService<IConfigurationFile<TConfiguration>>(),
|
||||||
|
provider.GetRequiredService<IServiceProvider>(),
|
||||||
|
provider.GetRequiredService<IPublisher>()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -186,7 +192,7 @@ public static class IHostBuilderExtension
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder,
|
public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder,
|
||||||
string contentRoot,
|
string contentRoot,
|
||||||
bool createDirectory)
|
bool createDirectory)
|
||||||
{
|
{
|
||||||
if (createDirectory)
|
if (createDirectory)
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
namespace Toolkit.Foundation;
|
||||||
|
|
||||||
|
public interface IRemovable :
|
||||||
|
IDisposable;
|
||||||
Reference in New Issue
Block a user