From 3b09265a3084d5a10c9e587f6b25bf252d6273b5 Mon Sep 17 00:00:00 2001 From: Daniel Clark Date: Mon, 5 Dec 2022 17:47:16 +0000 Subject: [PATCH] Start to port main gut of the foundation --- .../Configurations/ConfigurationChanged.cs | 4 + .../ConfigurationInitializer.cs | 23 +++ .../Configurations/ConfigurationWriter.cs | 31 ++++ .../Configurations/IConfigurationWriter.cs | 8 + .../IWritableConfigurationProvider.cs | 8 + .../IWritableJsonConfigurationBuilder.cs | 14 ++ .../IWritableJsonConfigurationDescriptor.cs | 10 ++ .../WritableJsonConfigurationBuilder.cs | 110 +++++++++++++ .../WritableJsonConfigurationDescriptor.cs | 5 + .../WritableJsonConfigurationExtensions.cs | 76 +++++++++ .../WritableJsonConfigurationFile.cs | 150 ++++++++++++++++++ .../WritableJsonConfigurationProvider.cs | 48 ++++++ .../WritableJsonConfigurationSource.cs | 28 ++++ Toolkit.Foundation/Configurations/Write.cs | 4 + .../Configurations/WriteHandler.cs | 28 ++++ .../Extensions/IHostBuilderExtensions.cs | 18 +++ .../Extensions/IServiceFactoryExtensions.cs | 21 +++ Toolkit.Foundation/Lifecycles/AppService.cs | 29 ++++ Toolkit.Foundation/Lifecycles/ICache.cs | 7 + .../Lifecycles/IInitialization.cs | 7 + Toolkit.Foundation/Lifecycles/IInitializer.cs | 7 + .../Lifecycles/Initialization.cs | 23 +++ Toolkit.Foundation/Lifecycles/Initialize.cs | 4 + .../Services/IServiceCreator.cs | 7 + .../Services/IServiceFactory.cs | 9 ++ Toolkit.Foundation/Services/ServiceCreator.cs | 10 ++ Toolkit.Foundation/Services/ServiceFactory.cs | 26 +++ .../Services/ServiceFactoryDescriptor.cs | 25 +++ .../Templates/INamedDataTemplateFactory.cs | 7 + .../Templates/INamedTemplateFactory.cs | 7 + .../Templates/ITemplateBuilder.cs | 13 ++ .../Templates/ITemplateDescriptor.cs | 15 ++ .../Templates/ITemplateDescriptorProvider.cs | 12 ++ .../Templates/ITemplateFactory.cs | 9 ++ .../Templates/ITemplateSelector.cs | 6 + .../Templates/ITypedDataTemplateFactory.cs | 7 + .../Templates/NamedDataTemplateFactory.cs | 36 +++++ .../Templates/NamedTemplateFactory.cs | 41 +++++ .../Templates/TemplateBuilder.cs | 24 +++ .../Templates/TemplateDescriptor.cs | 26 +++ .../Templates/TemplateDescriptorProvider.cs | 42 +++++ .../Templates/TemplateFactory.cs | 43 +++++ .../Templates/TypedDataTemplateFactory.cs | 36 +++++ Toolkit.Foundation/Toolkit.Foundation.csproj | 12 +- 44 files changed, 1075 insertions(+), 1 deletion(-) create mode 100644 Toolkit.Foundation/Configurations/ConfigurationChanged.cs create mode 100644 Toolkit.Foundation/Configurations/ConfigurationInitializer.cs create mode 100644 Toolkit.Foundation/Configurations/ConfigurationWriter.cs create mode 100644 Toolkit.Foundation/Configurations/IConfigurationWriter.cs create mode 100644 Toolkit.Foundation/Configurations/IWritableConfigurationProvider.cs create mode 100644 Toolkit.Foundation/Configurations/IWritableJsonConfigurationBuilder.cs create mode 100644 Toolkit.Foundation/Configurations/IWritableJsonConfigurationDescriptor.cs create mode 100644 Toolkit.Foundation/Configurations/WritableJsonConfigurationBuilder.cs create mode 100644 Toolkit.Foundation/Configurations/WritableJsonConfigurationDescriptor.cs create mode 100644 Toolkit.Foundation/Configurations/WritableJsonConfigurationExtensions.cs create mode 100644 Toolkit.Foundation/Configurations/WritableJsonConfigurationFile.cs create mode 100644 Toolkit.Foundation/Configurations/WritableJsonConfigurationProvider.cs create mode 100644 Toolkit.Foundation/Configurations/WritableJsonConfigurationSource.cs create mode 100644 Toolkit.Foundation/Configurations/Write.cs create mode 100644 Toolkit.Foundation/Configurations/WriteHandler.cs create mode 100644 Toolkit.Foundation/Extensions/IHostBuilderExtensions.cs create mode 100644 Toolkit.Foundation/Extensions/IServiceFactoryExtensions.cs create mode 100644 Toolkit.Foundation/Lifecycles/AppService.cs create mode 100644 Toolkit.Foundation/Lifecycles/ICache.cs create mode 100644 Toolkit.Foundation/Lifecycles/IInitialization.cs create mode 100644 Toolkit.Foundation/Lifecycles/IInitializer.cs create mode 100644 Toolkit.Foundation/Lifecycles/Initialization.cs create mode 100644 Toolkit.Foundation/Lifecycles/Initialize.cs create mode 100644 Toolkit.Foundation/Services/IServiceCreator.cs create mode 100644 Toolkit.Foundation/Services/IServiceFactory.cs create mode 100644 Toolkit.Foundation/Services/ServiceCreator.cs create mode 100644 Toolkit.Foundation/Services/ServiceFactory.cs create mode 100644 Toolkit.Foundation/Services/ServiceFactoryDescriptor.cs create mode 100644 Toolkit.Foundation/Templates/INamedDataTemplateFactory.cs create mode 100644 Toolkit.Foundation/Templates/INamedTemplateFactory.cs create mode 100644 Toolkit.Foundation/Templates/ITemplateBuilder.cs create mode 100644 Toolkit.Foundation/Templates/ITemplateDescriptor.cs create mode 100644 Toolkit.Foundation/Templates/ITemplateDescriptorProvider.cs create mode 100644 Toolkit.Foundation/Templates/ITemplateFactory.cs create mode 100644 Toolkit.Foundation/Templates/ITemplateSelector.cs create mode 100644 Toolkit.Foundation/Templates/ITypedDataTemplateFactory.cs create mode 100644 Toolkit.Foundation/Templates/NamedDataTemplateFactory.cs create mode 100644 Toolkit.Foundation/Templates/NamedTemplateFactory.cs create mode 100644 Toolkit.Foundation/Templates/TemplateBuilder.cs create mode 100644 Toolkit.Foundation/Templates/TemplateDescriptor.cs create mode 100644 Toolkit.Foundation/Templates/TemplateDescriptorProvider.cs create mode 100644 Toolkit.Foundation/Templates/TemplateFactory.cs create mode 100644 Toolkit.Foundation/Templates/TypedDataTemplateFactory.cs diff --git a/Toolkit.Foundation/Configurations/ConfigurationChanged.cs b/Toolkit.Foundation/Configurations/ConfigurationChanged.cs new file mode 100644 index 0000000..d14c42c --- /dev/null +++ b/Toolkit.Foundation/Configurations/ConfigurationChanged.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation +{ + public record ConfigurationChanged(TConfiguration Configuration) where TConfiguration : class; +} diff --git a/Toolkit.Foundation/Configurations/ConfigurationInitializer.cs b/Toolkit.Foundation/Configurations/ConfigurationInitializer.cs new file mode 100644 index 0000000..c90cb9e --- /dev/null +++ b/Toolkit.Foundation/Configurations/ConfigurationInitializer.cs @@ -0,0 +1,23 @@ +using CommunityToolkit.Mvvm.Messaging; + +namespace Toolkit.Foundation +{ + public class ConfigurationInitializer : IInitializer where TConfiguration : class, new() + { + private readonly TConfiguration configuration; + private readonly IMessenger messenger; + + public ConfigurationInitializer(TConfiguration configuration, + IMessenger messenger) + { + this.configuration = configuration; + this.messenger = messenger; + } + + public async Task InitializeAsync() + { + messenger.Send(configuration); + await Task.CompletedTask; + } + } +} diff --git a/Toolkit.Foundation/Configurations/ConfigurationWriter.cs b/Toolkit.Foundation/Configurations/ConfigurationWriter.cs new file mode 100644 index 0000000..81b14d5 --- /dev/null +++ b/Toolkit.Foundation/Configurations/ConfigurationWriter.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Toolkit.Foundation +{ + public class ConfigurationWriter : IConfigurationWriter where TConfiguration : class, new() + { + private readonly IConfiguration rootConfiguration; + + public ConfigurationWriter(IConfiguration rootConfiguration) + { + this.rootConfiguration = rootConfiguration; + } + + public void Write(string section, TConfiguration configuration) + { + if (rootConfiguration is IConfigurationRoot root) + { + foreach (IConfigurationProvider? provider in root.Providers) + { + if (provider is IWritableConfigurationProvider writableConfigurationProvider) + { + writableConfigurationProvider.Write(section, configuration); + } + } + } + } + } + +} diff --git a/Toolkit.Foundation/Configurations/IConfigurationWriter.cs b/Toolkit.Foundation/Configurations/IConfigurationWriter.cs new file mode 100644 index 0000000..ca158cf --- /dev/null +++ b/Toolkit.Foundation/Configurations/IConfigurationWriter.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation +{ + public interface IConfigurationWriter where TConfiguration : class + { + void Write(string section, TConfiguration args); + } + +} diff --git a/Toolkit.Foundation/Configurations/IWritableConfigurationProvider.cs b/Toolkit.Foundation/Configurations/IWritableConfigurationProvider.cs new file mode 100644 index 0000000..13eaaf0 --- /dev/null +++ b/Toolkit.Foundation/Configurations/IWritableConfigurationProvider.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation +{ + public interface IWritableConfigurationProvider + { + void Write(string section, TValue value) where TValue : class, new(); + } + +} diff --git a/Toolkit.Foundation/Configurations/IWritableJsonConfigurationBuilder.cs b/Toolkit.Foundation/Configurations/IWritableJsonConfigurationBuilder.cs new file mode 100644 index 0000000..97c1f2a --- /dev/null +++ b/Toolkit.Foundation/Configurations/IWritableJsonConfigurationBuilder.cs @@ -0,0 +1,14 @@ +namespace Toolkit.Foundation +{ + public interface IWritableJsonConfigurationBuilder + { + Stream? DefaultFileStream { get; } + + IWritableJsonConfigurationBuilder AddDefaultConfiguration(string Key) where TConfiguration : class; + + IWritableJsonConfigurationBuilder AddDefaultFileStream(Stream stream); + + void Build(string path); + } + +} diff --git a/Toolkit.Foundation/Configurations/IWritableJsonConfigurationDescriptor.cs b/Toolkit.Foundation/Configurations/IWritableJsonConfigurationDescriptor.cs new file mode 100644 index 0000000..8f14804 --- /dev/null +++ b/Toolkit.Foundation/Configurations/IWritableJsonConfigurationDescriptor.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation +{ + public interface IWritableJsonConfigurationDescriptor + { + Type ConfigurationType { get; } + + string Key { get; } + } + +} diff --git a/Toolkit.Foundation/Configurations/WritableJsonConfigurationBuilder.cs b/Toolkit.Foundation/Configurations/WritableJsonConfigurationBuilder.cs new file mode 100644 index 0000000..6e8975d --- /dev/null +++ b/Toolkit.Foundation/Configurations/WritableJsonConfigurationBuilder.cs @@ -0,0 +1,110 @@ +using Json.Patch; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Toolkit.Foundation +{ + public class WritableJsonConfigurationBuilder : IWritableJsonConfigurationBuilder + { + private readonly List descriptors = new(); + + public Stream? DefaultFileStream { get; private set; } + + public IReadOnlyCollection Descriptors => new ReadOnlyCollection(descriptors); + + public IWritableJsonConfigurationBuilder AddDefaultConfiguration(string Key) where TConfiguration : class + { + descriptors.Add(new WritableJsonConfigurationDescriptor(typeof(TConfiguration), Key)); + return this; + } + + public IWritableJsonConfigurationBuilder AddDefaultFileStream(Stream stream) + { + DefaultFileStream = stream; + return this; + } + + public void Build(string path) + { + JObject? sourceDocument = new(); + if (TryLoadSource(out string? defaultContent)) + { + sourceDocument = JObject.Parse(defaultContent!); + } + + JObject? targetDocument = new(); + if (TryLoadTarget(path, out string? targetContent)) + { + targetDocument = JObject.Parse(targetContent!); + } + + foreach (IWritableJsonConfigurationDescriptor? descriptor in descriptors) + { + if (sourceDocument.SelectToken($"$.{descriptor.Key}") is JToken sourceSection) + { + if (targetDocument.SelectToken($"$.{descriptor.Key}") is JToken targetSection) + { + object? source = JsonSerializer.Deserialize(JsonConvert.SerializeObject(sourceSection), descriptor.ConfigurationType); + object? target = JsonSerializer.Deserialize(JsonConvert.SerializeObject(targetSection), descriptor.ConfigurationType); + + JsonPatch? patch = source.CreatePatch(target); + + object? sourcePatched = patch.Apply(source); + targetSection.Replace(JToken.Parse(JsonSerializer.Serialize(sourcePatched, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }))); + } + else + { + object? source = JsonSerializer.Deserialize(JsonConvert.SerializeObject(sourceSection), descriptor.ConfigurationType); + targetDocument.Add(descriptor.Key, JToken.Parse(JsonSerializer.Serialize(source, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }))); + } + } + else + { + object? configuration = Activator.CreateInstance(descriptor.ConfigurationType); + targetDocument.Add(descriptor.Key, JToken.Parse(JsonSerializer.Serialize(configuration, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }))); + } + } + + using FileStream? fileStream = new(path, FileMode.Create, FileAccess.Write); + using StreamWriter streamWriter = new(fileStream); + using JsonTextWriter writer = new(streamWriter) { Formatting = Formatting.Indented }; + targetDocument.WriteTo(writer); + } + + private bool TryLoadTarget(string path, [MaybeNull] out string? content) + { + if (File.Exists(path)) + { + using FileStream? fileStream = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using StreamReader? streamReader = new(fileStream); + content = streamReader.ReadToEnd(); + return true; + } + + content = null; + return false; + } + + private bool TryLoadSource([MaybeNull] out string? content) + { + if (DefaultFileStream is Stream fileStream) + { + using StreamReader? streamReader = new(fileStream); + content = streamReader.ReadToEnd(); + return true; + } + + content = null; + return false; + } + } + +} diff --git a/Toolkit.Foundation/Configurations/WritableJsonConfigurationDescriptor.cs b/Toolkit.Foundation/Configurations/WritableJsonConfigurationDescriptor.cs new file mode 100644 index 0000000..fa44c4f --- /dev/null +++ b/Toolkit.Foundation/Configurations/WritableJsonConfigurationDescriptor.cs @@ -0,0 +1,5 @@ +namespace Toolkit.Foundation +{ + public record WritableJsonConfigurationDescriptor(Type ConfigurationType, string Key) : IWritableJsonConfigurationDescriptor; + +} diff --git a/Toolkit.Foundation/Configurations/WritableJsonConfigurationExtensions.cs b/Toolkit.Foundation/Configurations/WritableJsonConfigurationExtensions.cs new file mode 100644 index 0000000..804231a --- /dev/null +++ b/Toolkit.Foundation/Configurations/WritableJsonConfigurationExtensions.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.FileProviders; + +namespace Toolkit.Foundation +{ + public static class WritableJsonConfigurationExtensions + { + public static IConfigurationBuilder AddWritableJsonFile(this IConfigurationBuilder builder, + string path) + { + return builder.AddWritableJsonFile(null, path, false, false, null); + } + + public static IConfigurationBuilder AddWritableJsonFile(this IConfigurationBuilder builder, + string path, + Action? factoryDelegate) + { + return builder.AddWritableJsonFile(null, path, false, false, factoryDelegate); + } + + public static IConfigurationBuilder AddWritableJsonFile(this IConfigurationBuilder builder, + string path, + bool optional) + { + return builder.AddWritableJsonFile(null, path, optional, false, null); + } + + public static IConfigurationBuilder AddWritableJsonFile(this IConfigurationBuilder builder, + string path, + bool optional, + Action? factoryDelegate) + { + return builder.AddWritableJsonFile(null, path, optional, false, factoryDelegate); + } + + public static IConfigurationBuilder AddWritableJsonFile(this IConfigurationBuilder builder, + string path, + bool optional, + bool reloadOnChange) + { + return builder.AddWritableJsonFile(null, path, optional, reloadOnChange, null); + } + + public static IConfigurationBuilder AddWritableJsonFile(this IConfigurationBuilder builder, + string path, + bool optional, + bool reloadOnChange, + Action? factoryDelegate) + { + return builder.AddWritableJsonFile(null, path, optional, reloadOnChange, factoryDelegate); + } + + public static IConfigurationBuilder AddWritableJsonFile(this IConfigurationBuilder builder, + IFileProvider? provider, + string path, + bool optional, + bool reloadOnChange, Action? writableJsonConfigurationDelegate) + { + IWritableJsonConfigurationBuilder writableJsonConfigurationBuilder = new WritableJsonConfigurationBuilder(); + writableJsonConfigurationDelegate?.Invoke(writableJsonConfigurationBuilder); + + return builder.AddWritableJsonFile(configuration => + { + configuration.FileProvider = provider; + configuration.Path = path; + configuration.Optional = optional; + configuration.ReloadOnChange = reloadOnChange; + configuration.Factory = writableJsonConfigurationBuilder; + configuration.ResolveFileProvider(); + }); + } + + public static IConfigurationBuilder AddWritableJsonFile(this IConfigurationBuilder builder, Action configureSource) => builder.Add(configureSource); + } + +} diff --git a/Toolkit.Foundation/Configurations/WritableJsonConfigurationFile.cs b/Toolkit.Foundation/Configurations/WritableJsonConfigurationFile.cs new file mode 100644 index 0000000..f6421c8 --- /dev/null +++ b/Toolkit.Foundation/Configurations/WritableJsonConfigurationFile.cs @@ -0,0 +1,150 @@ +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Text.Json; + +namespace Toolkit.Foundation +{ + internal class WritableJsonConfigurationFile + { + private readonly Dictionary data = new(StringComparer.OrdinalIgnoreCase); + private readonly Stack paths = new(); + private JObject tokenCache = new(); + private bool isParsing; + + public IDictionary Parse(Stream input) + { + return ParseStream(input); + } + + public void Write(string key, string value, Stream output) + { + if (isParsing) + { + return; + } + + if (key[^1] == ':') + { + key = key[0..^1]; + } + + string? tokenPath = $"$.{key.Replace(":", ".")}"; + key = key.Replace("[", "").Replace("]", ""); + + if (tokenCache.SelectToken(tokenPath) is JToken token && data.ContainsKey(key)) + { + (JsonValueKind? kind, string _) = data[key]; + object? newValue = ConvertValue(kind, value); + + data[key] = new(kind, value); + token.Replace(JToken.FromObject(newValue)); + } + + using StreamWriter streamWriter = new(output); + using JsonTextWriter writer = new(streamWriter) { Formatting = Formatting.Indented }; + tokenCache.WriteTo(writer); + + output.SetLength(output.Position); + } + + private static object ConvertValue(JsonValueKind? kind, string value) + { + return kind is JsonValueKind.True or JsonValueKind.False ? bool.Parse(value) : value; + } + + private void EnterContext(string context) + { + paths.Push(paths.Count > 0 ? paths.Peek() + ConfigurationPath.KeyDelimiter + context : context); + } + + private void ExitContext() + { + paths.Pop(); + } + + private IDictionary ParseStream(Stream input) + { + data.Clear(); + + JsonDocumentOptions jsonDocumentOptions = new() + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + }; + + isParsing = true; + using (StreamReader? reader = new(input)) + { + string? content = reader.ReadToEnd(); + tokenCache = JObject.Parse(content); + + using JsonDocument? doc = JsonDocument.Parse(content, jsonDocumentOptions); + VisitElement(doc.RootElement); + } + isParsing = false; + + return data.ToDictionary(k => k.Key, v => v.Value.Item2?.ToString() ?? null); + } + + private void VisitElement(JsonElement element) + { + bool isEmpty = true; + + foreach (JsonProperty property in element.EnumerateObject()) + { + isEmpty = false; + + EnterContext(property.Name); + VisitValue(property.Value); + ExitContext(); + } + + if (isEmpty && paths.Count > 0) + { + data[paths.Peek()] = (JsonValueKind.Null, null); + } + } + + private void VisitValue(JsonElement value) + { + switch (value.ValueKind) + { + case JsonValueKind.Object: + VisitElement(value); + break; + + case JsonValueKind.Array: + int index = 0; + foreach (JsonElement arrayElement in value.EnumerateArray()) + { + EnterContext(index.ToString()); + VisitValue(arrayElement); + ExitContext(); + index++; + } + break; + + case JsonValueKind.Number: + case JsonValueKind.String: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + string key = paths.Peek(); + if (data.ContainsKey(key)) + { + throw new FormatException(); + } + data[key] = new(value.ValueKind, value.ToString()); + break; + + case JsonValueKind.Undefined: + break; + + default: + throw new FormatException(); + } + } + } + +} diff --git a/Toolkit.Foundation/Configurations/WritableJsonConfigurationProvider.cs b/Toolkit.Foundation/Configurations/WritableJsonConfigurationProvider.cs new file mode 100644 index 0000000..1b6124f --- /dev/null +++ b/Toolkit.Foundation/Configurations/WritableJsonConfigurationProvider.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.FileProviders; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Toolkit.Foundation +{ + public class WritableJsonConfigurationProvider : JsonConfigurationProvider, IWritableConfigurationProvider + { + public WritableJsonConfigurationProvider(JsonConfigurationSource source) : base(source) + { + + } + + public void Write(string section, TValue value) where TValue : class, new() + { + IFileInfo? file = Source.FileProvider?.GetFileInfo(Source.Path ?? string.Empty); + static Stream OpenRead(IFileInfo fileInfo) + { + return fileInfo.PhysicalPath is not null + ? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite) + : fileInfo.CreateReadStream(); + } + + if (file is null) + { + throw new FileNotFoundException(); + } + + using Stream stream = OpenRead(file); + using StreamReader? reader = new(stream); + + string? content = reader.ReadToEnd(); + JObject? document = JObject.Parse(content); + + if (document.SelectToken($"$.{section}") is JToken sectionToken) + { + sectionToken.Replace(JToken.Parse(JsonConvert.SerializeObject(value))); + stream.SetLength(0); + + using StreamWriter streamWriter = new(stream); + using JsonTextWriter writer = new(streamWriter) { Formatting = Formatting.Indented }; + document.WriteTo(writer); + } + } + } + +} diff --git a/Toolkit.Foundation/Configurations/WritableJsonConfigurationSource.cs b/Toolkit.Foundation/Configurations/WritableJsonConfigurationSource.cs new file mode 100644 index 0000000..0e6c618 --- /dev/null +++ b/Toolkit.Foundation/Configurations/WritableJsonConfigurationSource.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.FileProviders; + +namespace Toolkit.Foundation +{ + public class WritableJsonConfigurationSource : JsonConfigurationSource + { + public IWritableJsonConfigurationBuilder? Factory { get; set; } + + public override IConfigurationProvider Build(IConfigurationBuilder builder) + { + EnsureDefaultsWithSteam(builder); + return new WritableJsonConfigurationProvider(this); + } + + private void EnsureDefaultsWithSteam(IConfigurationBuilder builder) + { + EnsureDefaults(builder); + + if (FileProvider is PhysicalFileProvider physicalFileProvider) + { + string? outputFile = System.IO.Path.Combine(physicalFileProvider.Root, Path); + Factory?.Build(outputFile); + } + } + } +} diff --git a/Toolkit.Foundation/Configurations/Write.cs b/Toolkit.Foundation/Configurations/Write.cs new file mode 100644 index 0000000..df659bc --- /dev/null +++ b/Toolkit.Foundation/Configurations/Write.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation +{ + public record Write(string Section, Action UpdateDelegate) where TConfiguration : class; +} diff --git a/Toolkit.Foundation/Configurations/WriteHandler.cs b/Toolkit.Foundation/Configurations/WriteHandler.cs new file mode 100644 index 0000000..9ce22ab --- /dev/null +++ b/Toolkit.Foundation/Configurations/WriteHandler.cs @@ -0,0 +1,28 @@ +using CommunityToolkit.Mvvm.Messaging; + +namespace Toolkit.Foundation +{ + public class WriteHandler : IRecipient> where TConfiguration : class + { + private readonly IMessenger messenger; + private readonly TConfiguration configuration; + private readonly IConfigurationWriter writer; + + public WriteHandler(TConfiguration configuration, + IConfigurationWriter writer, + IMessenger messenger) + { + this.messenger = messenger; + this.configuration = configuration; + this.writer = writer; + } + + public void Receive(Write request) + { + request.UpdateDelegate.Invoke(configuration); + writer.Write(request.Section, configuration); + + messenger.Send(new ConfigurationChanged(configuration)); + } + } +} diff --git a/Toolkit.Foundation/Extensions/IHostBuilderExtensions.cs b/Toolkit.Foundation/Extensions/IHostBuilderExtensions.cs new file mode 100644 index 0000000..5f65431 --- /dev/null +++ b/Toolkit.Foundation/Extensions/IHostBuilderExtensions.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Toolkit.Foundation +{ + public static class IHostBuilderExtensions + { + public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, string contentRoot, bool createDirectory) + { + if (!Directory.Exists(contentRoot) && createDirectory) + { + Directory.CreateDirectory(contentRoot); + } + + return hostBuilder.UseContentRoot(contentRoot); + } + } +} diff --git a/Toolkit.Foundation/Extensions/IServiceFactoryExtensions.cs b/Toolkit.Foundation/Extensions/IServiceFactoryExtensions.cs new file mode 100644 index 0000000..ba6fa57 --- /dev/null +++ b/Toolkit.Foundation/Extensions/IServiceFactoryExtensions.cs @@ -0,0 +1,21 @@ +namespace Toolkit.Foundation +{ + public static class IServiceFactoryExtensions + { + public static T? Get(this IServiceFactory serviceFactory) + { + return serviceFactory.Get(typeof(T)); + } + + public static T Create(this IServiceFactory serviceFactory, params object?[] parameters) + { + return serviceFactory.Create(typeof(T), parameters); + } + + public static object? Create(this IServiceFactory serviceFactory, Type type) + { + ServiceFactoryDescriptor? descriptor = new(serviceFactory); + return descriptor.Create(type); + } + } +} diff --git a/Toolkit.Foundation/Lifecycles/AppService.cs b/Toolkit.Foundation/Lifecycles/AppService.cs new file mode 100644 index 0000000..35fd142 --- /dev/null +++ b/Toolkit.Foundation/Lifecycles/AppService.cs @@ -0,0 +1,29 @@ +using CommunityToolkit.Mvvm.Messaging; +using Microsoft.Extensions.Hosting; + +namespace Toolkit.Foundation +{ + public class AppService : IHostedService + { + private readonly IMessenger messenger; + private readonly IInitialization initialization; + + public AppService(IMessenger messenger, + IInitialization initialization) + { + this.messenger = messenger; + this.initialization = initialization; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + messenger.Send(new Initialize()); + await initialization.InitializeAsync(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/Toolkit.Foundation/Lifecycles/ICache.cs b/Toolkit.Foundation/Lifecycles/ICache.cs new file mode 100644 index 0000000..6173d14 --- /dev/null +++ b/Toolkit.Foundation/Lifecycles/ICache.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation +{ + public interface ICache + { + + } +} diff --git a/Toolkit.Foundation/Lifecycles/IInitialization.cs b/Toolkit.Foundation/Lifecycles/IInitialization.cs new file mode 100644 index 0000000..b70f951 --- /dev/null +++ b/Toolkit.Foundation/Lifecycles/IInitialization.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation +{ + public interface IInitialization + { + Task InitializeAsync(); + } +} diff --git a/Toolkit.Foundation/Lifecycles/IInitializer.cs b/Toolkit.Foundation/Lifecycles/IInitializer.cs new file mode 100644 index 0000000..234ecc4 --- /dev/null +++ b/Toolkit.Foundation/Lifecycles/IInitializer.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation +{ + public interface IInitializer + { + Task InitializeAsync(); + } +} diff --git a/Toolkit.Foundation/Lifecycles/Initialization.cs b/Toolkit.Foundation/Lifecycles/Initialization.cs new file mode 100644 index 0000000..620eac0 --- /dev/null +++ b/Toolkit.Foundation/Lifecycles/Initialization.cs @@ -0,0 +1,23 @@ +namespace Toolkit.Foundation +{ + public class Initialization : IInitialization + { + private readonly Func> factory; + + public Initialization(Func> factory) + { + this.factory = factory; + } + + public async Task InitializeAsync() + { + foreach (IInitializer? initializer in factory()) + { + if (initializer is not null) + { + await initializer.InitializeAsync(); + } + } + } + } +} diff --git a/Toolkit.Foundation/Lifecycles/Initialize.cs b/Toolkit.Foundation/Lifecycles/Initialize.cs new file mode 100644 index 0000000..d1e73c9 --- /dev/null +++ b/Toolkit.Foundation/Lifecycles/Initialize.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation +{ + public record class Initialize; +} diff --git a/Toolkit.Foundation/Services/IServiceCreator.cs b/Toolkit.Foundation/Services/IServiceCreator.cs new file mode 100644 index 0000000..f987388 --- /dev/null +++ b/Toolkit.Foundation/Services/IServiceCreator.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation +{ + public interface IServiceCreator + { + object Create(Func creator, params object[] parameters); + } +} diff --git a/Toolkit.Foundation/Services/IServiceFactory.cs b/Toolkit.Foundation/Services/IServiceFactory.cs new file mode 100644 index 0000000..81b833c --- /dev/null +++ b/Toolkit.Foundation/Services/IServiceFactory.cs @@ -0,0 +1,9 @@ +namespace Toolkit.Foundation +{ + public interface IServiceFactory + { + T? Get(Type type); + + T Create(Type type, params object?[] parameters); + } +} diff --git a/Toolkit.Foundation/Services/ServiceCreator.cs b/Toolkit.Foundation/Services/ServiceCreator.cs new file mode 100644 index 0000000..74446ba --- /dev/null +++ b/Toolkit.Foundation/Services/ServiceCreator.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation +{ + public class ServiceCreator : IServiceCreator + { + public virtual object Create(Func creator, params object[] parameters) + { + return creator(typeof(T), parameters); + } + } +} diff --git a/Toolkit.Foundation/Services/ServiceFactory.cs b/Toolkit.Foundation/Services/ServiceFactory.cs new file mode 100644 index 0000000..5325191 --- /dev/null +++ b/Toolkit.Foundation/Services/ServiceFactory.cs @@ -0,0 +1,26 @@ +namespace Toolkit.Foundation +{ + public class ServiceFactory : IServiceFactory + { + private readonly Func factory; + private readonly Func creator; + + public ServiceFactory(Func factory, Func creator) + { + this.factory = factory; + this.creator = creator; + } + + public T? Get(Type type) + { + T? value = (T?)factory(type); + return value; + } + + public T Create(Type type, params object?[] parameters) + { + dynamic? lookup = factory(typeof(IServiceCreator<>).MakeGenericType(type)); + return lookup is not null ? (T)lookup.Create(creator, parameters) : (T)creator(type, parameters); + } + } +} diff --git a/Toolkit.Foundation/Services/ServiceFactoryDescriptor.cs b/Toolkit.Foundation/Services/ServiceFactoryDescriptor.cs new file mode 100644 index 0000000..ab2ea72 --- /dev/null +++ b/Toolkit.Foundation/Services/ServiceFactoryDescriptor.cs @@ -0,0 +1,25 @@ +using System.Reflection; + +namespace Toolkit.Foundation +{ + internal class ServiceFactoryDescriptor + { + private readonly IServiceFactory serviceFactory; + + public ServiceFactoryDescriptor(IServiceFactory serviceFactory) + { + this.serviceFactory = serviceFactory; + } + + public object? Create(Type type) + { + MethodInfo? methodInfo = typeof(ServiceFactoryDescriptor).GetMethod(nameof(Create), BindingFlags.NonPublic | BindingFlags.Instance); + return methodInfo?.MakeGenericMethod(type).Invoke(this, new object[] { type }); + } + + private T Create(Type type) + { + return serviceFactory.Create(type); + } + } +} diff --git a/Toolkit.Foundation/Templates/INamedDataTemplateFactory.cs b/Toolkit.Foundation/Templates/INamedDataTemplateFactory.cs new file mode 100644 index 0000000..a3a13b6 --- /dev/null +++ b/Toolkit.Foundation/Templates/INamedDataTemplateFactory.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation +{ + public interface INamedDataTemplateFactory + { + object? Create(string name, params object[] parameters); + } +} diff --git a/Toolkit.Foundation/Templates/INamedTemplateFactory.cs b/Toolkit.Foundation/Templates/INamedTemplateFactory.cs new file mode 100644 index 0000000..19d5308 --- /dev/null +++ b/Toolkit.Foundation/Templates/INamedTemplateFactory.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation +{ + public interface INamedTemplateFactory + { + object? Create(string name); + } +} diff --git a/Toolkit.Foundation/Templates/ITemplateBuilder.cs b/Toolkit.Foundation/Templates/ITemplateBuilder.cs new file mode 100644 index 0000000..3b44737 --- /dev/null +++ b/Toolkit.Foundation/Templates/ITemplateBuilder.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Toolkit.Foundation +{ + public interface ITemplateBuilder + { + IReadOnlyCollection Descriptors { get; } + + ITemplateBuilder Add(string name, ServiceLifetime lifetime = ServiceLifetime.Transient); + + ITemplateBuilder Add(ServiceLifetime lifetime = ServiceLifetime.Transient); + } +} diff --git a/Toolkit.Foundation/Templates/ITemplateDescriptor.cs b/Toolkit.Foundation/Templates/ITemplateDescriptor.cs new file mode 100644 index 0000000..18b7b1a --- /dev/null +++ b/Toolkit.Foundation/Templates/ITemplateDescriptor.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Toolkit.Foundation +{ + public interface ITemplateDescriptor + { + Type DataType { get; } + + ServiceLifetime Lifetime { get; } + + string? Name { get; } + + Type TemplateType { get; } + } +} diff --git a/Toolkit.Foundation/Templates/ITemplateDescriptorProvider.cs b/Toolkit.Foundation/Templates/ITemplateDescriptorProvider.cs new file mode 100644 index 0000000..b369e06 --- /dev/null +++ b/Toolkit.Foundation/Templates/ITemplateDescriptorProvider.cs @@ -0,0 +1,12 @@ +namespace Toolkit.Foundation +{ + + public interface ITemplateDescriptorProvider + { + ITemplateDescriptor? Get(string name); + + ITemplateDescriptor? Get(Type type); + + ITemplateDescriptor? Get(); + } +} diff --git a/Toolkit.Foundation/Templates/ITemplateFactory.cs b/Toolkit.Foundation/Templates/ITemplateFactory.cs new file mode 100644 index 0000000..acace84 --- /dev/null +++ b/Toolkit.Foundation/Templates/ITemplateFactory.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Toolkit.Foundation +{ + public interface ITemplateFactory + { + object? Create([MaybeNull] object? data); + } +} diff --git a/Toolkit.Foundation/Templates/ITemplateSelector.cs b/Toolkit.Foundation/Templates/ITemplateSelector.cs new file mode 100644 index 0000000..5fa83e4 --- /dev/null +++ b/Toolkit.Foundation/Templates/ITemplateSelector.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation +{ + public interface ITemplateSelector + { + } +} diff --git a/Toolkit.Foundation/Templates/ITypedDataTemplateFactory.cs b/Toolkit.Foundation/Templates/ITypedDataTemplateFactory.cs new file mode 100644 index 0000000..11a87f6 --- /dev/null +++ b/Toolkit.Foundation/Templates/ITypedDataTemplateFactory.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation +{ + public interface ITypedDataTemplateFactory + { + object? Create(Type type, params object[] parameters); + } +} diff --git a/Toolkit.Foundation/Templates/NamedDataTemplateFactory.cs b/Toolkit.Foundation/Templates/NamedDataTemplateFactory.cs new file mode 100644 index 0000000..b993b40 --- /dev/null +++ b/Toolkit.Foundation/Templates/NamedDataTemplateFactory.cs @@ -0,0 +1,36 @@ +namespace Toolkit.Foundation +{ + public class NamedDataTemplateFactory : INamedDataTemplateFactory + { + private readonly Dictionary cache = new(); + + private readonly IReadOnlyCollection descriptors; + private readonly IServiceFactory serviceFactory; + + public NamedDataTemplateFactory(IReadOnlyCollection descriptors, + IServiceFactory serviceFactory) + { + this.descriptors = descriptors; + this.serviceFactory = serviceFactory; + } + + public virtual object? Create(string name, params object[] parameters) + { + if (cache.TryGetValue(name, out object? data)) + { + return data; + } + + if (descriptors.FirstOrDefault(x => x.Name == name) is ITemplateDescriptor descriptor) + { + data = parameters is { Length: > 0 } ? serviceFactory.Create(descriptor.DataType, parameters) : serviceFactory.Get(descriptor.DataType); + if (data is ICache cache) + { + this.cache[name] = cache; + } + } + + return data; + } + } +} diff --git a/Toolkit.Foundation/Templates/NamedTemplateFactory.cs b/Toolkit.Foundation/Templates/NamedTemplateFactory.cs new file mode 100644 index 0000000..570f838 --- /dev/null +++ b/Toolkit.Foundation/Templates/NamedTemplateFactory.cs @@ -0,0 +1,41 @@ +namespace Toolkit.Foundation +{ + public class NamedTemplateFactory : INamedTemplateFactory + { + private readonly Dictionary cache = new(); + + private readonly ITemplateDescriptorProvider provider; + private readonly IServiceFactory serviceFactory; + + public NamedTemplateFactory(ITemplateDescriptorProvider provider, + IServiceFactory serviceFactory) + { + this.provider = provider; + this.serviceFactory = serviceFactory; + } + + public virtual object? Create(string name) + { + if (cache.TryGetValue(name, out object? view)) + { + return view; + } + + if (provider.Get(name) is ITemplateDescriptor descriptor) + { + view = serviceFactory.Get(descriptor.TemplateType); + if (view is ICache cache) + { + this.cache[name] = cache; + } + + if (descriptor.GetType().GenericTypeArguments is { Length: 2 }) + { + (descriptor as dynamic).ViewInvoker?.Invoke(view); + } + } + + return view; + } + } +} diff --git a/Toolkit.Foundation/Templates/TemplateBuilder.cs b/Toolkit.Foundation/Templates/TemplateBuilder.cs new file mode 100644 index 0000000..d6a4c4a --- /dev/null +++ b/Toolkit.Foundation/Templates/TemplateBuilder.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Collections.ObjectModel; + +namespace Toolkit.Foundation +{ + public class TemplateBuilder : ITemplateBuilder + { + private readonly List descriptors = new(); + + public IReadOnlyCollection Descriptors => new ReadOnlyCollection(descriptors); + + public ITemplateBuilder Add(string name, ServiceLifetime lifetime = ServiceLifetime.Transient) + { + descriptors.Add(new TemplateDescriptor(typeof(TViewModel), typeof(TView), name, lifetime)); + return this; + } + + public ITemplateBuilder Add(ServiceLifetime lifetime = ServiceLifetime.Transient) + { + descriptors.Add(new TemplateDescriptor(typeof(TViewModel), typeof(TView), null, lifetime)); + return this; + } + } +} diff --git a/Toolkit.Foundation/Templates/TemplateDescriptor.cs b/Toolkit.Foundation/Templates/TemplateDescriptor.cs new file mode 100644 index 0000000..2e738ff --- /dev/null +++ b/Toolkit.Foundation/Templates/TemplateDescriptor.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Toolkit.Foundation +{ + public class TemplateDescriptor : ITemplateDescriptor + { + public TemplateDescriptor(Type dataType, + Type templateType, + string? name = null, + ServiceLifetime lifetime = ServiceLifetime.Transient) + { + TemplateType = templateType; + DataType = dataType; + Name = name; + Lifetime = lifetime; + } + + public ServiceLifetime Lifetime { get; } + + public Type TemplateType { get; } + + public Type DataType { get; } + + public string? Name { get; } + } +} diff --git a/Toolkit.Foundation/Templates/TemplateDescriptorProvider.cs b/Toolkit.Foundation/Templates/TemplateDescriptorProvider.cs new file mode 100644 index 0000000..56b50fe --- /dev/null +++ b/Toolkit.Foundation/Templates/TemplateDescriptorProvider.cs @@ -0,0 +1,42 @@ +namespace Toolkit.Foundation +{ + public class TemplateDescriptorProvider : ITemplateDescriptorProvider + { + private readonly IReadOnlyCollection descriptors; + + public TemplateDescriptorProvider(IReadOnlyCollection descriptors) + { + this.descriptors = descriptors; + } + + public ITemplateDescriptor? Get(string name) + { + if (descriptors.FirstOrDefault(x => x.Name == name) is ITemplateDescriptor descriptor) + { + return descriptor; + } + + return null; + } + + public ITemplateDescriptor? Get(Type type) + { + if (descriptors.FirstOrDefault(x => x.DataType == type) is ITemplateDescriptor descriptor) + { + return descriptor; + } + + return null; + } + + public ITemplateDescriptor? Get() + { + if (descriptors.FirstOrDefault(x => x.DataType == typeof(T)) is ITemplateDescriptor descriptor) + { + return descriptor; + } + + return null; + } + } +} diff --git a/Toolkit.Foundation/Templates/TemplateFactory.cs b/Toolkit.Foundation/Templates/TemplateFactory.cs new file mode 100644 index 0000000..b7f1dfb --- /dev/null +++ b/Toolkit.Foundation/Templates/TemplateFactory.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Toolkit.Foundation +{ + public class TemplateFactory : ITemplateFactory + { + private readonly Dictionary cache = new(); + + private readonly ITemplateDescriptorProvider provider; + private readonly IServiceFactory serviceFactory; + + public TemplateFactory(ITemplateDescriptorProvider provider, + IServiceFactory serviceFactory) + { + this.provider = provider; + this.serviceFactory = serviceFactory; + } + + public virtual object? Create([MaybeNull] object? data) + { + if (data is null) + { + return null; + } + + if (cache.TryGetValue(data, out object? template)) + { + return template; + } + + if (provider.Get(data.GetType()) is ITemplateDescriptor descriptor) + { + template = serviceFactory.Get(descriptor.TemplateType); + if (template is ICache cache) + { + this.cache[data] = cache; + } + } + + return template; + } + } +} diff --git a/Toolkit.Foundation/Templates/TypedDataTemplateFactory.cs b/Toolkit.Foundation/Templates/TypedDataTemplateFactory.cs new file mode 100644 index 0000000..d4e13f7 --- /dev/null +++ b/Toolkit.Foundation/Templates/TypedDataTemplateFactory.cs @@ -0,0 +1,36 @@ +namespace Toolkit.Foundation +{ + public class TypedDataTemplateFactory : ITypedDataTemplateFactory + { + private readonly Dictionary cache = new(); + + private readonly IReadOnlyCollection descriptors; + private readonly IServiceFactory serviceFactory; + + public TypedDataTemplateFactory(IReadOnlyCollection descriptors, + IServiceFactory serviceFactory) + { + this.descriptors = descriptors; + this.serviceFactory = serviceFactory; + } + + public virtual object? Create(Type type, params object[] parameters) + { + if (cache.TryGetValue(type, out object? data)) + { + return data; + } + + if (descriptors.FirstOrDefault(x => x.DataType == type) is ITemplateDescriptor descriptor) + { + data = parameters is { Length: > 0 } ? serviceFactory.Create(descriptor.DataType, parameters) : serviceFactory.Get(descriptor.DataType); + if (data is ICache cache) + { + this.cache[type] = cache; + } + } + + return data; + } + } +} diff --git a/Toolkit.Foundation/Toolkit.Foundation.csproj b/Toolkit.Foundation/Toolkit.Foundation.csproj index cfadb03..786c8bb 100644 --- a/Toolkit.Foundation/Toolkit.Foundation.csproj +++ b/Toolkit.Foundation/Toolkit.Foundation.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -6,4 +6,14 @@ enable + + + + + + + + + +