diff --git a/Hyperbar.Desktop.Contextual/ContextualCommandBuilder.cs b/Hyperbar.Desktop.Contextual/ContextualCommandBuilder.cs index 4bb13e7..0d1c942 100644 --- a/Hyperbar.Desktop.Contextual/ContextualCommandBuilder.cs +++ b/Hyperbar.Desktop.Contextual/ContextualCommandBuilder.cs @@ -1,4 +1,3 @@ -using Hyperbar.Extensions; using Hyperbar.Lifecycles; using Microsoft.Extensions.DependencyInjection; diff --git a/Hyperbar.Desktop.Contextual/Hyperbar.Desktop.Contextual.csproj b/Hyperbar.Desktop.Contextual/Hyperbar.Desktop.Contextual.csproj index f238d6e..15d233c 100644 --- a/Hyperbar.Desktop.Contextual/Hyperbar.Desktop.Contextual.csproj +++ b/Hyperbar.Desktop.Contextual/Hyperbar.Desktop.Contextual.csproj @@ -7,9 +7,6 @@ true true - - - @@ -19,9 +16,4 @@ - - - MSBuild:Compile - - diff --git a/Hyperbar.Desktop.Primary/Hyperbar.Desktop.Primary.csproj b/Hyperbar.Desktop.Primary/Hyperbar.Desktop.Primary.csproj index d00105c..ebdcde3 100644 --- a/Hyperbar.Desktop.Primary/Hyperbar.Desktop.Primary.csproj +++ b/Hyperbar.Desktop.Primary/Hyperbar.Desktop.Primary.csproj @@ -7,9 +7,14 @@ true true - - - + + True + True + + + True + True + @@ -18,9 +23,4 @@ - - - MSBuild:Compile - - diff --git a/Hyperbar.Desktop.Primary/PrimaryCommandBuilder.cs b/Hyperbar.Desktop.Primary/PrimaryCommandBuilder.cs index 60fd2c5..af612f9 100644 --- a/Hyperbar.Desktop.Primary/PrimaryCommandBuilder.cs +++ b/Hyperbar.Desktop.Primary/PrimaryCommandBuilder.cs @@ -1,4 +1,3 @@ -using Hyperbar.Extensions; using Hyperbar.Lifecycles; using Microsoft.Extensions.DependencyInjection; diff --git a/Hyperbar.Desktop/App.xaml.cs b/Hyperbar.Desktop/App.xaml.cs index 38edcc0..3c9e866 100644 --- a/Hyperbar.Desktop/App.xaml.cs +++ b/Hyperbar.Desktop/App.xaml.cs @@ -3,6 +3,7 @@ using Hyperbar.Desktop.Controls; using Hyperbar.Desktop.Primary; using Hyperbar.Lifecycles; using Hyperbar.Templates; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.UI.Xaml; @@ -11,6 +12,10 @@ using System.Collections.Generic; namespace Hyperbar.Desktop; +public class AppConfiguration +{ + +} public partial class App : Application { @@ -22,7 +27,14 @@ public partial class App : IHost? host = Host.CreateDefaultBuilder() .UseContentRoot(AppContext.BaseDirectory) - .ConfigureServices(services => + .ConfigureAppConfiguration(config => + { + config.SetBasePath(AppContext.BaseDirectory); + config.AddJsonFile("Settings.json", true); + + config.Build(); + }) + .ConfigureServices((context, services) => { services.AddHostedService(); @@ -34,8 +46,8 @@ public partial class App : services.AddDataTemplate(); - services.AddCommandBuilder("Contexual.Commands"); - services.AddCommandBuilder("Primary.Command"); + services.AddCommand(""); + services.AddCommand(""); services.AddTransient(provider => { @@ -53,9 +65,11 @@ public partial class App : return Resolve(provider); }); + + services.ConfigureWritableOptions(); + }) .Build(); - await host.RunAsync(); } } diff --git a/Hyperbar.Desktop/Hyperbar.Desktop.csproj b/Hyperbar.Desktop/Hyperbar.Desktop.csproj index 44c7805..65df773 100644 --- a/Hyperbar.Desktop/Hyperbar.Desktop.csproj +++ b/Hyperbar.Desktop/Hyperbar.Desktop.csproj @@ -14,8 +14,8 @@ enable - - + + @@ -36,27 +36,12 @@ - - MSBuild:Compile - - - - - - - - - - - MSBuild:Compile - - - - - MSBuild:Compile - + + + + true - + \ No newline at end of file diff --git a/Hyperbar.Desktop/Lifecycles/AppInitializer.cs b/Hyperbar.Desktop/Lifecycles/AppInitializer.cs index a289ac8..aff4d1f 100644 --- a/Hyperbar.Desktop/Lifecycles/AppInitializer.cs +++ b/Hyperbar.Desktop/Lifecycles/AppInitializer.cs @@ -5,8 +5,8 @@ using System.Threading.Tasks; namespace Hyperbar.Desktop; -public class AppInitializer([FromKeyedServices(nameof(CommandView))] CommandView view, - [FromKeyedServices(nameof(CommandView))] CommandViewModel viewModel, +public class AppInitializer([FromKeyedServices(nameof(CommandViewModel))] CommandView view, + [FromKeyedServices(nameof(CommandViewModel))] CommandViewModel viewModel, DesktopFlyout desktopFlyout) : IInitializer { diff --git a/Hyperbar.Desktop/Lifecycles/IServiceCollectionExtensions.cs b/Hyperbar.Desktop/Lifecycles/IServiceCollectionExtensions.cs index b588995..cb3c62d 100644 --- a/Hyperbar.Desktop/Lifecycles/IServiceCollectionExtensions.cs +++ b/Hyperbar.Desktop/Lifecycles/IServiceCollectionExtensions.cs @@ -7,19 +7,19 @@ namespace Hyperbar.Desktop { public static class IServiceCollectionExtensions { - public static IServiceCollection AddCommandBuilder(this IServiceCollection services, + public static IServiceCollection AddCommand(this IServiceCollection services, string key) where TCommandBuilder : ICommandBuilder, new() { TCommandBuilder builder = new(); IHost? host = new HostBuilder() - .ConfigureServices(services => + .ConfigureServices(isolatedServices => { - services.AddTransient(); - services.AddTransient(); + isolatedServices.AddTransient(); + isolatedServices.AddTransient(); - builder.Create(services); + builder.Create(isolatedServices); }).Build(); services.AddTransient(provider => new CommandContext(host.Services)); diff --git a/Hyperbar.Desktop/Views/CommandView.xaml b/Hyperbar.Desktop/Views/CommandView.xaml index 51581ad..64d9024 100644 --- a/Hyperbar.Desktop/Views/CommandView.xaml +++ b/Hyperbar.Desktop/Views/CommandView.xaml @@ -9,6 +9,5 @@ - diff --git a/Hyperbar.Desktop/Views/CommandViewModel.cs b/Hyperbar.Desktop/Views/CommandViewModel.cs index b0125e6..66b3220 100644 --- a/Hyperbar.Desktop/Views/CommandViewModel.cs +++ b/Hyperbar.Desktop/Views/CommandViewModel.cs @@ -1,6 +1,5 @@ using Hyperbar.Lifecycles; using Hyperbar.Templates; -using System.Collections; using System.Collections.Generic; namespace Hyperbar.Desktop; @@ -10,14 +9,13 @@ public partial class CommandViewModel : ITemplatedViewModel { public CommandViewModel(ITemplateFactory templateFactory, - IEnumerable commands) + IEnumerable commands, + IWritableConfiguration options) { TemplateFactory = templateFactory; + AddRange(commands); - foreach (var command in commands) - { - this.Add(command); - } + options.Update(args => { }); } public ITemplateFactory TemplateFactory { get; } diff --git a/Hyperbar.sln b/Hyperbar.sln index 5240bbc..d3664bc 100644 --- a/Hyperbar.sln +++ b/Hyperbar.sln @@ -11,9 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Desktop.Win32", "H EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar", "Hyperbar\Hyperbar.csproj", "{E5795878-C7E3-4386-86FA-33681BCF8D5B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hyperbar.Desktop.Contextual", "Hyperbar.Desktop.Contextual\Hyperbar.Desktop.Contextual.csproj", "{C32D4073-2A9B-4257-8895-09951FAD8E7A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Desktop.Contextual", "Hyperbar.Desktop.Contextual\Hyperbar.Desktop.Contextual.csproj", "{C32D4073-2A9B-4257-8895-09951FAD8E7A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hyperbar.Desktop.Primary", "Hyperbar.Desktop.Primary\Hyperbar.Desktop.Primary.csproj", "{AFB8A3EB-8831-4041-AE05-3E0EF672887C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Desktop.Primary", "Hyperbar.Desktop.Primary\Hyperbar.Desktop.Primary.csproj", "{AFB8A3EB-8831-4041-AE05-3E0EF672887C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Hyperbar/Configuration/ConfigurationWriter.cs b/Hyperbar/Configuration/ConfigurationWriter.cs new file mode 100644 index 0000000..76a150a --- /dev/null +++ b/Hyperbar/Configuration/ConfigurationWriter.cs @@ -0,0 +1,101 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Hyperbar.Options; + +public class ConfigurationWriter(string path, + string section, + JsonSerializerOptions? serializerOptions = null) : + IConfigurationWriter + where TConfiguration : + class, new() +{ + internal static Func DefaultSerializerOptions = new(() => + { + return new JsonSerializerOptions + { + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = { new JsonStringEnumConverter() } + }; + }); + + private readonly JsonSerializerOptions? serializerOptions = serializerOptions ??= DefaultSerializerOptions(); + + public void Write(Action? updateDelegate = null) + { + TConfiguration? updatedValue = TryGet(out TConfiguration? value) ? value : new TConfiguration(); + + updateDelegate?.Invoke(updatedValue); + Write(updatedValue); + } + + public void Write(TConfiguration? value) + { + if (!File.Exists(path)) + { + string? fileDirectoryPath = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(fileDirectoryPath)) + { + Directory.CreateDirectory(fileDirectoryPath); + } + + File.WriteAllText(path, "{}"); + } + + byte[] jsonContent = File.ReadAllBytes(path); + + using JsonDocument jsonDocument = JsonDocument.Parse(jsonContent); + using FileStream stream = File.OpenWrite(path); + Utf8JsonWriter writer = new(stream, new JsonWriterOptions() + { + Indented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + + writer.WriteStartObject(); + bool isWritten = false; + JsonDocument optionsElement = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(value, serializerOptions)); + + foreach (JsonProperty element in jsonDocument.RootElement.EnumerateObject()) + { + if (element.Name != section) + { + element.WriteTo(writer); + continue; + } + writer.WritePropertyName(element.Name); + optionsElement.WriteTo(writer); + isWritten = true; + } + + if (!isWritten) + { + writer.WritePropertyName(section); + optionsElement.WriteTo(writer); + } + + writer.WriteEndObject(); + writer.Flush(); + stream.SetLength(stream.Position); + } + + private bool TryGet(out T? value) + { + if (File.Exists(path)) + { + byte[] jsonContent = File.ReadAllBytes(path); + + using JsonDocument jsonDocument = JsonDocument.Parse(jsonContent); + if (jsonDocument.RootElement.TryGetProperty(section, out JsonElement sectionValue)) + { + value = JsonSerializer.Deserialize(sectionValue.ToString(), serializerOptions); + return true; + } + } + + value = default; + return false; + } +} \ No newline at end of file diff --git a/Hyperbar/Configuration/IConfigurationWriter.cs b/Hyperbar/Configuration/IConfigurationWriter.cs new file mode 100644 index 0000000..7426659 --- /dev/null +++ b/Hyperbar/Configuration/IConfigurationWriter.cs @@ -0,0 +1,12 @@ + +namespace Hyperbar.Options +{ + public interface IConfigurationWriter + where TConfiguration : + class, new() + { + void Write(Action? updateDelegate = null); + + void Write(TConfiguration? value); + } +} \ No newline at end of file diff --git a/Hyperbar/Configuration/IWritableConfiguration.cs b/Hyperbar/Configuration/IWritableConfiguration.cs new file mode 100644 index 0000000..f0e74f1 --- /dev/null +++ b/Hyperbar/Configuration/IWritableConfiguration.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Options; + +namespace Hyperbar; + +public interface IWritableConfiguration : + IOptionsSnapshot + where TConfiguration : + class, new() +{ + void Update(Action updateAction, + bool reload = true); +} diff --git a/Hyperbar/Configuration/WritableConfiguration.cs b/Hyperbar/Configuration/WritableConfiguration.cs new file mode 100644 index 0000000..8f84103 --- /dev/null +++ b/Hyperbar/Configuration/WritableConfiguration.cs @@ -0,0 +1,27 @@ +using Hyperbar.Options; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; + +namespace Hyperbar; + +public class WritableConfiguration(IConfigurationWriter writer, + IOptionsMonitor options, + IConfiguration configuration) : + IWritableConfiguration + where TConfiguration : + class, new() +{ + public TConfiguration Value => options.CurrentValue; + + public TConfiguration Get(string? name) => options.Get(name); + + public void Update(Action updateDelegate, + bool reload = true) + { + writer.Write(updateDelegate); + if (reload && configuration is IConfigurationRoot configurationRoot) + { + configurationRoot.Reload(); + } + } +} diff --git a/Hyperbar/Configurations/ConfigurationWriter.cs b/Hyperbar/Configurations/ConfigurationWriter.cs deleted file mode 100644 index 8d42110..0000000 --- a/Hyperbar/Configurations/ConfigurationWriter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.Extensions.Configuration; - -namespace Hyperbar.Configurations; - -public class ConfigurationWriter(IConfiguration rootConfiguration) : - IConfigurationWriter where TConfiguration : class, new() -{ - 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); - } - } - } - } -} \ No newline at end of file diff --git a/Hyperbar/Configurations/IConfigurationWriter.cs b/Hyperbar/Configurations/IConfigurationWriter.cs deleted file mode 100644 index b2f414b..0000000 --- a/Hyperbar/Configurations/IConfigurationWriter.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hyperbar.Configurations; - -public interface IConfigurationWriter where TConfiguration : class -{ - void Write(string section, TConfiguration args); -} diff --git a/Hyperbar/Configurations/IWritableConfigurationProvider.cs b/Hyperbar/Configurations/IWritableConfigurationProvider.cs deleted file mode 100644 index f7a7cdb..0000000 --- a/Hyperbar/Configurations/IWritableConfigurationProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hyperbar.Configurations; - -public interface IWritableConfigurationProvider -{ - void Write(string section, TValue value) where TValue : class, new(); -} diff --git a/Hyperbar/Configurations/IWritableJsonConfigurationBuilder.cs b/Hyperbar/Configurations/IWritableJsonConfigurationBuilder.cs deleted file mode 100644 index 2dd777c..0000000 --- a/Hyperbar/Configurations/IWritableJsonConfigurationBuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Hyperbar.Configurations; - -public interface IWritableJsonConfigurationBuilder -{ - Stream? DefaultFileStream { get; } - - IWritableJsonConfigurationBuilder AddDefaultConfiguration(string Key) where TConfiguration : class; - - IWritableJsonConfigurationBuilder AddDefaultFileStream(Stream stream); - - void Build(string path); -} diff --git a/Hyperbar/Configurations/IWritableJsonConfigurationDescriptor.cs b/Hyperbar/Configurations/IWritableJsonConfigurationDescriptor.cs deleted file mode 100644 index 8e66414..0000000 --- a/Hyperbar/Configurations/IWritableJsonConfigurationDescriptor.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Hyperbar.Configurations; - -public interface IWritableJsonConfigurationDescriptor -{ - Type ConfigurationType { get; } - - string Key { get; } -} diff --git a/Hyperbar/Configurations/WritableJsonConfigurationBuilder.cs b/Hyperbar/Configurations/WritableJsonConfigurationBuilder.cs deleted file mode 100644 index 8c84ce6..0000000 --- a/Hyperbar/Configurations/WritableJsonConfigurationBuilder.cs +++ /dev/null @@ -1,110 +0,0 @@ -using Json.Patch; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using System.Text.Json.Serialization; -using JsonSerializer = System.Text.Json.JsonSerializer; - -namespace Hyperbar.Configurations; - -public class WritableJsonConfigurationBuilder : - IWritableJsonConfigurationBuilder -{ - private readonly List descriptors = []; - - 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) - { - JsonSerializerOptions options = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; - - JObject? sourceDocument = []; - if (TryLoadSource(out string? defaultContent)) - { - sourceDocument = JObject.Parse(defaultContent!); - } - - JObject? targetDocument = []; - 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); - if (patch.Apply(source) is object sourcePatched) - { - targetSection.Replace(JToken.Parse(JsonSerializer.Serialize(sourcePatched, options))); - } - } - else - { - object? source = JsonSerializer.Deserialize(JsonConvert.SerializeObject(sourceSection), descriptor.ConfigurationType); - targetDocument.Add(descriptor.Key, JToken.Parse(JsonSerializer.Serialize(source, options))); - } - } - else - { - object? configuration = Activator.CreateInstance(descriptor.ConfigurationType); - targetDocument.Add(descriptor.Key, JToken.Parse(JsonSerializer.Serialize(configuration, options))); - } - } - - 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 static 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/Hyperbar/Configurations/WritableJsonConfigurationDescriptor.cs b/Hyperbar/Configurations/WritableJsonConfigurationDescriptor.cs deleted file mode 100644 index 8b274c2..0000000 --- a/Hyperbar/Configurations/WritableJsonConfigurationDescriptor.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Hyperbar.Configurations; - -public record WritableJsonConfigurationDescriptor(Type ConfigurationType, string Key) : IWritableJsonConfigurationDescriptor; diff --git a/Hyperbar/Configurations/WritableJsonConfigurationFile.cs b/Hyperbar/Configurations/WritableJsonConfigurationFile.cs deleted file mode 100644 index 1c14ced..0000000 --- a/Hyperbar/Configurations/WritableJsonConfigurationFile.cs +++ /dev/null @@ -1,147 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Text.Json; - -namespace Hyperbar.Configurations; -public class WritableJsonConfigurationFile -{ - private readonly Dictionary data = new(StringComparer.OrdinalIgnoreCase); - private readonly Stack paths = new(); - private JObject tokenCache = []; - 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(); - } - } -} \ No newline at end of file diff --git a/Hyperbar/Configurations/WritableJsonConfigurationProvider.cs b/Hyperbar/Configurations/WritableJsonConfigurationProvider.cs deleted file mode 100644 index 6edfb0f..0000000 --- a/Hyperbar/Configurations/WritableJsonConfigurationProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.Extensions.Configuration.Json; -using Microsoft.Extensions.FileProviders; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Hyperbar.Configurations; - -public class WritableJsonConfigurationProvider(JsonConfigurationSource source) : - JsonConfigurationProvider(source), - IWritableConfigurationProvider -{ - 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/Hyperbar/Configurations/WritableJsonConfigurationSource.cs b/Hyperbar/Configurations/WritableJsonConfigurationSource.cs deleted file mode 100644 index a12f105..0000000 --- a/Hyperbar/Configurations/WritableJsonConfigurationSource.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.Json; -using Microsoft.Extensions.FileProviders; - -namespace Hyperbar.Configurations; - -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/Hyperbar/Extensions/IServiceCollectionExtensions.cs b/Hyperbar/Extensions/IServiceCollectionExtensions.cs index 0b888c1..66bf49b 100644 --- a/Hyperbar/Extensions/IServiceCollectionExtensions.cs +++ b/Hyperbar/Extensions/IServiceCollectionExtensions.cs @@ -1,10 +1,62 @@ using Hyperbar.Lifecycles; +using Hyperbar.Options; using Hyperbar.Templates; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using System.Text.Json; namespace Hyperbar; public static class IServiceCollectionExtensions { + public static IServiceCollection ConfigureWritableOptions(this IServiceCollection services, + string path = "Settings.json", + Func? defaultSerializerOptions = null) + where TConfiguration : + class, new() + { + return services.ConfigureWritableOptions(typeof(TConfiguration).Name, path); + } + + public static IServiceCollection ConfigureWritableOptions(this IServiceCollection services, + string section, + string path = "Settings.json", + Action? serializerDelegate = null) + where TConfiguration : + class, new() + { + services.AddOptions(); + services.AddSingleton>(new ConfigureNamedOptions("", args => { })); + + services.AddTransient>(provider => + { + string? jsonFilePath = null; + if (provider.GetService() is IHostEnvironment hostEnvironment) + { + IFileProvider fileProvider = hostEnvironment.ContentRootFileProvider; + IFileInfo fileInfo = fileProvider.GetFileInfo(path); + + jsonFilePath = fileInfo.PhysicalPath; + } + + jsonFilePath ??= Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path); + + JsonSerializerOptions? defaultSerializerOptions = null; + if (serializerDelegate is not null) + { + defaultSerializerOptions = new JsonSerializerOptions(); + serializerDelegate.Invoke(defaultSerializerOptions); + } + + return new ConfigurationWriter(jsonFilePath, section, defaultSerializerOptions); + }); + + services.AddTransient, WritableConfiguration>(); + return services; + } + public static IServiceCollection AddCommandTemplate(this IServiceCollection services) where TCommand : ICommandViewModel @@ -13,14 +65,13 @@ public static class IServiceCollectionExtensions Type templateType = typeof(TCommandTemplate); string key = dataType.Name; - - _ = services.AddTransient(typeof(ICommandViewModel), dataType); - _ = services.AddTransient(templateType); - - _ = services.AddKeyedTransient(typeof(ICommandViewModel), key, dataType); - _ = services.AddKeyedTransient(templateType, key); - - _ = services.AddTransient(provider => new DataTemplateDescriptor + + services.AddTransient(typeof(ICommandViewModel), dataType); + services.AddTransient(templateType); + services.AddKeyedTransient(typeof(ICommandViewModel), key, dataType); + services.AddKeyedTransient(templateType, key); + + services.AddTransient(provider => new DataTemplateDescriptor { DataType = dataType, TemplateType = templateType, @@ -38,10 +89,10 @@ public static class IServiceCollectionExtensions key ??= dataType.Name; - _ = services.AddKeyedTransient(dataType, key); - _ = services.AddKeyedTransient(templateType, key); - - _ = services.AddTransient(provider => new DataTemplateDescriptor + services.AddKeyedTransient(dataType, key); + services.AddKeyedTransient(templateType, key); + + services.AddTransient(provider => new DataTemplateDescriptor { DataType = dataType, TemplateType = templateType, diff --git a/Hyperbar/Extensions/WritableJsonConfigurationExtensions.cs b/Hyperbar/Extensions/WritableJsonConfigurationExtensions.cs deleted file mode 100644 index edff984..0000000 --- a/Hyperbar/Extensions/WritableJsonConfigurationExtensions.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Hyperbar.Configurations; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.FileProviders; - -namespace Hyperbar.Extensions; - -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) - { - return builder.Add(configureSource); - } -} diff --git a/Hyperbar/Hyperbar.csproj b/Hyperbar/Hyperbar.csproj index d638b86..68ec528 100644 --- a/Hyperbar/Hyperbar.csproj +++ b/Hyperbar/Hyperbar.csproj @@ -3,6 +3,7 @@ net8.0 enable enable + true diff --git a/Hyperbar/Lifecycles/AppService.cs b/Hyperbar/Lifecycles/AppService.cs index a18247e..614583f 100644 --- a/Hyperbar/Lifecycles/AppService.cs +++ b/Hyperbar/Lifecycles/AppService.cs @@ -1,14 +1,7 @@ using Microsoft.Extensions.Hosting; -using System.Collections.ObjectModel; namespace Hyperbar.Lifecycles; -public class ObservableCollectionViewModel : - ObservableCollection -{ - -} - public class AppService(IEnumerable initializers) : IHostedService { diff --git a/Hyperbar/Lifecycles/ObservableCollectionViewModel.cs b/Hyperbar/Lifecycles/ObservableCollectionViewModel.cs new file mode 100644 index 0000000..3808bea --- /dev/null +++ b/Hyperbar/Lifecycles/ObservableCollectionViewModel.cs @@ -0,0 +1,15 @@ +using System.Collections.ObjectModel; + +namespace Hyperbar.Lifecycles; + +public class ObservableCollectionViewModel : + ObservableCollection +{ + public void AddRange(IEnumerable collection) + { + foreach (var item in collection) + { + Add(item); + } + } +}