Amend configuration to support writing to nested sections

This commit is contained in:
TheXamlGuy
2024-04-21 19:38:38 +01:00
parent 5b055bb2d7
commit e0f49832f8
7 changed files with 135 additions and 132 deletions
+1 -8
View File
@@ -1,13 +1,6 @@
using System.Text.Json.Serialization; namespace Toolkit.Foundation;
namespace Toolkit.Foundation;
public record ComponentConfiguration public record ComponentConfiguration
{ {
public string? Description { get; set; }
public string? Name { get; set; }
[JsonInclude]
internal Guid Id { get; set; } = Guid.NewGuid();
} }
@@ -1,9 +1,10 @@
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public class ConfigurationInitializer<TConfiguration>(IPublisher publisher, public class ConfigurationInitializer<TConfiguration>(string section,
IConfigurationReader<TConfiguration> reader, IConfigurationReader<TConfiguration> reader,
IConfigurationWriter<TConfiguration> writer, IConfigurationWriter<TConfiguration> writer,
IConfigurationFactory<TConfiguration> factory) : IConfigurationFactory<TConfiguration> factory,
IPublisher publisher) :
IConfigurationInitializer<TConfiguration>, IConfigurationInitializer<TConfiguration>,
IInitializer IInitializer
where TConfiguration : where TConfiguration :
@@ -11,6 +12,7 @@ public class ConfigurationInitializer<TConfiguration>(IPublisher publisher,
{ {
public async Task Initialize() public async Task Initialize()
{ {
var d = section;
if (!reader.TryRead(out TConfiguration? configuration)) if (!reader.TryRead(out TConfiguration? configuration))
{ {
if (factory.Create() is object defaultConfiguration) if (factory.Create() is object defaultConfiguration)
+52 -33
View File
@@ -1,6 +1,8 @@
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using System;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
@@ -35,7 +37,6 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
lock (lockingObject) lock (lockingObject)
{ {
IFileInfo fileInfo = configurationFile.FileInfo; IFileInfo fileInfo = configurationFile.FileInfo;
if (!File.Exists(fileInfo.PhysicalPath)) if (!File.Exists(fileInfo.PhysicalPath))
{ {
string? fileDirectoryPath = Path.GetDirectoryName(fileInfo.PhysicalPath); string? fileDirectoryPath = Path.GetDirectoryName(fileInfo.PhysicalPath);
@@ -47,53 +48,71 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
File.WriteAllText(fileInfo.PhysicalPath!, "{}"); File.WriteAllText(fileInfo.PhysicalPath!, "{}");
} }
static Stream OpenReadWrite(IFileInfo fileInfo) using Stream stream = fileInfo.PhysicalPath is not null
{ ? new FileStream(fileInfo.PhysicalPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
return fileInfo.PhysicalPath is not null
? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)
: fileInfo.CreateReadStream(); : fileInfo.CreateReadStream();
}
using Stream stream = OpenReadWrite(fileInfo);
using StreamReader? reader = new(stream); using StreamReader? reader = new(stream);
string? content = reader.ReadToEnd(); string? content = reader.ReadToEnd();
using JsonDocument jsonDocument = JsonDocument.Parse(content); stream.Seek(0, SeekOrigin.Begin);
using Stream stream2 = OpenReadWrite(fileInfo); JsonNode? rootNode = JsonNode.Parse(content);
Utf8JsonWriter writer = new(stream2, new JsonWriterOptions() JsonNode? valueNode = JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(value,
{
Indented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
writer.WriteStartObject();
bool isWritten = false;
JsonDocument optionsElement = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(value,
serializerOptions ?? defaultSerializerOptions())); serializerOptions ?? defaultSerializerOptions()));
foreach (JsonProperty element in jsonDocument.RootElement.EnumerateObject()) string[] segments = section.Split(':');
JsonNode? currentNode = rootNode;
int lastIndex = segments.Length - 1;
for (int i = 0; i < lastIndex; i++)
{ {
if (element.Name != section) if (currentNode is null)
{ {
element.WriteTo(writer); return;
continue;
}
writer.WritePropertyName(element.Name);
optionsElement.WriteTo(writer);
isWritten = true;
} }
if (!isWritten) string currentKey = segments[i];
if (currentNode[currentKey] is null)
{ {
writer.WritePropertyName(section); if (int.TryParse(segments[i + 1], out int _))
optionsElement.WriteTo(writer); {
currentNode[currentKey] = new JsonArray();
}
else
{
currentNode[currentKey] = new JsonObject();
}
} }
writer.WriteEndObject(); currentNode = currentNode[currentKey];
writer.Flush(); }
stream2.SetLength(stream2.Position);
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] = valueNode;
}
}
else
{
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());
} }
} }
-1
View File
@@ -1,7 +1,6 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Toolkit.Foundation;
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
@@ -1,7 +0,0 @@
namespace Toolkit.Foundation;
public interface INavigationViewModel :
IObservableViewModel
{
string Text { get; set; }
}
@@ -49,6 +49,11 @@ public static class IServiceCollectionExtensions
return services; return services;
} }
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services,
string section)
where TConfiguration : class =>
services.AddConfiguration<TConfiguration>(section, "Settings.json", null);
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services) public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services)
where TConfiguration : class => where TConfiguration : class =>
services.AddConfiguration<TConfiguration>(typeof(TConfiguration).Name, "Settings.json", null); services.AddConfiguration<TConfiguration>(typeof(TConfiguration).Name, "Settings.json", null);
@@ -63,6 +68,17 @@ public static class IServiceCollectionExtensions
return services.AddConfiguration(typeof(TConfiguration).Name, "Settings.json", configuration); return services.AddConfiguration(typeof(TConfiguration).Name, "Settings.json", configuration);
} }
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services,
Action<TConfiguration> configurationDelegate,
string section)
where TConfiguration : class, new()
{
TConfiguration configuration = new();
configurationDelegate.Invoke(configuration);
return services.AddConfiguration(section, "Settings.json", configuration);
}
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services, public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services,
TConfiguration configuration) TConfiguration configuration)
where TConfiguration : class => where TConfiguration : class =>
@@ -81,20 +97,7 @@ public static class IServiceCollectionExtensions
Action<JsonSerializerOptions>? serializerDelegate = null) Action<JsonSerializerOptions>? serializerDelegate = null)
where TConfiguration : class where TConfiguration : class
{ {
services.AddSingleton<IConfigurationSource<TConfiguration>>(provider => services.TryAddSingleton<IConfigurationFile<TConfiguration>>(provider =>
{
JsonSerializerOptions? defaultSerializer = null;
if (serializerDelegate is not null)
{
defaultSerializer = new JsonSerializerOptions();
serializerDelegate.Invoke(defaultSerializer);
}
return new ConfigurationSource<TConfiguration>(provider.GetRequiredService<IConfigurationFile<TConfiguration>>(),
section, defaultSerializer);
});
services.AddSingleton<IConfigurationFile<TConfiguration>>(provider =>
{ {
IFileInfo? fileInfo = null; IFileInfo? fileInfo = null;
if (provider.GetService<IHostEnvironment>() is IHostEnvironment hostEnvironment) if (provider.GetService<IHostEnvironment>() is IHostEnvironment hostEnvironment)
@@ -107,15 +110,39 @@ public static class IServiceCollectionExtensions
return new ConfigurationFile<TConfiguration>(fileInfo); return new ConfigurationFile<TConfiguration>(fileInfo);
}); });
services.AddHostedService<ConfigurationMonitor<TConfiguration>>(); services.TryAddKeyedSingleton<IConfigurationSource<TConfiguration>>(section, (provider, KeyAccelerator) =>
services.AddSingleton<IConfigurationReader<TConfiguration>, ConfigurationReader<TConfiguration>>(); {
services.AddSingleton<IConfigurationWriter<TConfiguration>, ConfigurationWriter<TConfiguration>>(); JsonSerializerOptions? defaultSerializer = null;
if (serializerDelegate is not null)
{
defaultSerializer = new JsonSerializerOptions();
serializerDelegate.Invoke(defaultSerializer);
}
services.AddTransient<IConfigurationFactory<TConfiguration>>(provider => new ConfigurationFactory<TConfiguration>(() => return new ConfigurationSource<TConfiguration>(provider.GetRequiredService<IConfigurationFile<TConfiguration>>(),
configuration ?? provider.GetRequiredService<TConfiguration>())); section, defaultSerializer);
});
services.AddTransient<IInitializer, ConfigurationInitializer<TConfiguration>>(); //services.AddHostedService<ConfigurationMonitor<TConfiguration>>();
services.AddTransient<IConfigurationInitializer<TConfiguration>, ConfigurationInitializer<TConfiguration>>(); services.TryAddKeyedTransient<IConfigurationReader<TConfiguration>>(section, (provider, key) =>
new ConfigurationReader<TConfiguration>(provider.GetRequiredKeyedService<IConfigurationSource<TConfiguration>>(key),
provider.GetRequiredKeyedService<IConfigurationFactory<TConfiguration>>(key)));
services.TryAddKeyedTransient<IConfigurationWriter<TConfiguration>>(section, (provider, key) =>
new ConfigurationWriter<TConfiguration>(provider.GetRequiredKeyedService<IConfigurationSource<TConfiguration>>(key)));
services.TryAddKeyedTransient<IConfigurationFactory<TConfiguration>>(section, (provider, key) =>
new ConfigurationFactory<TConfiguration>(() => configuration ?? provider.GetRequiredService<TConfiguration>()));
services.AddTransient<IInitializer, ConfigurationInitializer<TConfiguration>>(provider =>
new ConfigurationInitializer<TConfiguration>(section,
provider.GetRequiredKeyedService<IConfigurationReader<TConfiguration>>(section),
provider.GetRequiredKeyedService<IConfigurationWriter<TConfiguration>>(section),
provider.GetRequiredKeyedService<IConfigurationFactory<TConfiguration>>(section),
provider.GetRequiredService<IPublisher>()));
services.AddTransient<IConfigurationInitializer<TConfiguration>, ConfigurationInitializer<TConfiguration>>(provider =>
provider.GetRequiredService<IServiceFactory>().Create<ConfigurationInitializer<TConfiguration>>(section));
services.AddTransient<IWritableConfiguration<TConfiguration>, WritableConfiguration<TConfiguration>>(); services.AddTransient<IWritableConfiguration<TConfiguration>, WritableConfiguration<TConfiguration>>();
@@ -125,31 +152,6 @@ public static class IServiceCollectionExtensions
return services; return services;
} }
public static IServiceCollection AddTemplate<TViewModel, TView>(this IServiceCollection services,
object? key = null,
params object[]? parameters)
{
Type viewModelType = typeof(TViewModel);
Type viewType = typeof(TView);
key ??= viewModelType.Name.Replace("ViewModel", "");
services.AddTransient(viewModelType, provider =>
provider.GetRequiredService<IServiceFactory>().Create<TViewModel>(parameters)!);
services.AddTransient(viewType);
services.AddKeyedTransient(viewModelType, key, (provider, key) =>
provider.GetRequiredService<IServiceFactory>().Create<TViewModel>(parameters)!);
services.AddKeyedTransient(viewType, key);
services.AddTransient<IContentTemplateDescriptor>(provider =>
new ContentTemplateDescriptor(key, viewModelType, viewType, parameters));
return services;
}
public static IServiceCollection AddHandler<THandler>(this IServiceCollection services, public static IServiceCollection AddHandler<THandler>(this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Transient) ServiceLifetime lifetime = ServiceLifetime.Transient)
where THandler : IHandler where THandler : IHandler
@@ -231,6 +233,7 @@ public static class IServiceCollectionExtensions
services.AddHandler<THandler>(); services.AddHandler<THandler>();
return services; return services;
} }
public static IServiceCollection AddRange(this IServiceCollection services, public static IServiceCollection AddRange(this IServiceCollection services,
IServiceCollection fromServices) IServiceCollection fromServices)
{ {
@@ -241,4 +244,29 @@ public static class IServiceCollectionExtensions
return services; return services;
} }
public static IServiceCollection AddTemplate<TViewModel, TView>(this IServiceCollection services,
object? key = null,
params object[]? parameters)
{
Type viewModelType = typeof(TViewModel);
Type viewType = typeof(TView);
key ??= viewModelType.Name.Replace("ViewModel", "");
services.AddTransient(viewModelType, provider =>
provider.GetRequiredService<IServiceFactory>().Create<TViewModel>(parameters)!);
services.AddTransient(viewType);
services.AddKeyedTransient(viewModelType, key, (provider, key) =>
provider.GetRequiredService<IServiceFactory>().Create<TViewModel>(parameters)!);
services.AddKeyedTransient(viewType, key);
services.AddTransient<IContentTemplateDescriptor>(provider =>
new ContentTemplateDescriptor(key, viewModelType, viewType, parameters));
return services;
}
} }
-31
View File
@@ -1,31 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Toolkit.Foundation;
public partial class NavigationViewModel(IServiceProvider serviceProvider,
IServiceFactory serviceFactory,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
string text) :
ObservableViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer),
INavigationViewModel
{
[ObservableProperty]
private string? text = text;
}
public partial class NavigationViewModel<TNavigationViewModel>(IServiceProvider serviceProvider,
IServiceFactory serviceFactory,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
string text) :
ObservableViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer),
INavigationViewModel
where TNavigationViewModel :
INavigationViewModel
{
[ObservableProperty]
private string? text = text;
}