Add project files.

This commit is contained in:
Daniel Clark
2022-11-01 15:26:08 +00:00
parent daa7b59f22
commit 7e4f880821
408 changed files with 16863 additions and 0 deletions
@@ -0,0 +1,4 @@
namespace TheXamlGuy.Framework.Core
{
public record ConfigurationChanged<TConfiguration>(TConfiguration Configuration) where TConfiguration : class;
}
@@ -0,0 +1,20 @@
namespace TheXamlGuy.Framework.Core
{
public class ConfigurationInitializer<TConfiguration> : IInitializer where TConfiguration : class, new()
{
private readonly TConfiguration configuration;
private readonly IEventAggregator eventAggregator;
public ConfigurationInitializer(TConfiguration configuration, IEventAggregator eventAggregator)
{
this.configuration = configuration;
this.eventAggregator = eventAggregator;
}
public async Task InitializeAsync()
{
eventAggregator.Publish(configuration);
await Task.CompletedTask;
}
}
}
@@ -0,0 +1,28 @@
using Microsoft.Extensions.Configuration;
namespace TheXamlGuy.Framework.Core
{
public class ConfigurationWriter<TConfiguration> : IConfigurationWriter<TConfiguration> 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);
}
}
}
}
}
}
@@ -0,0 +1,9 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public interface IConfigurationWriter<TConfiguration> where TConfiguration : class
{
void Write(string section, TConfiguration args);
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core
{
public interface IWritableConfigurationProvider
{
void Write<TValue>(string section, TValue value) where TValue : class, new();
}
}
@@ -0,0 +1,11 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public interface IWritableJsonConfigurationDescriptor
{
Type ConfigurationType { get; }
string Key { get; }
}
}
@@ -0,0 +1,15 @@
using System.IO;
namespace TheXamlGuy.Framework.Core
{
public interface IWritableJsonConfigurationBuilder
{
Stream? DefaultFileStream { get; }
IWritableJsonConfigurationBuilder AddDefaultConfiguration<TConfiguration>(string Key) where TConfiguration : class;
IWritableJsonConfigurationBuilder AddDefaultFileStream(Stream stream);
void Build(string path);
}
}
@@ -0,0 +1,109 @@
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 TheXamlGuy.Framework.Core
{
public class WritableJsonConfigurationBuilder : IWritableJsonConfigurationBuilder
{
private readonly List<IWritableJsonConfigurationDescriptor> descriptors = new();
public Stream? DefaultFileStream { get; private set; }
public IReadOnlyCollection<IWritableJsonConfigurationDescriptor> Descriptors => new ReadOnlyCollection<IWritableJsonConfigurationDescriptor>(descriptors);
public IWritableJsonConfigurationBuilder AddDefaultConfiguration<TConfiguration>(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;
}
}
}
@@ -0,0 +1,6 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public record WritableJsonConfigurationDescriptor(Type ConfigurationType, string Key) : IWritableJsonConfigurationDescriptor;
}
@@ -0,0 +1,76 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using System;
namespace TheXamlGuy.Framework.Core
{
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<IWritableJsonConfigurationBuilder>? 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<IWritableJsonConfigurationBuilder>? 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<IWritableJsonConfigurationBuilder>? 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<IWritableJsonConfigurationBuilder>? 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<WritableJsonConfigurationSource> configureSource) => builder.Add(configureSource);
}
}
@@ -0,0 +1,153 @@
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace TheXamlGuy.Framework.Core
{
internal class WritableJsonConfigurationFile
{
private readonly Dictionary<string, (JsonValueKind?, string?)> data = new(StringComparer.OrdinalIgnoreCase);
private readonly Stack<string> paths = new();
private JObject tokenCache = new();
private bool isParsing;
public IDictionary<string, string?> 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<string, string?> 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();
}
}
}
}
@@ -0,0 +1,48 @@
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.FileProviders;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
namespace TheXamlGuy.Framework.Core
{
public class WritableJsonConfigurationProvider : JsonConfigurationProvider, IWritableConfigurationProvider
{
public WritableJsonConfigurationProvider(JsonConfigurationSource source) : base(source)
{
}
public void Write<TValue>(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);
}
}
}
}
@@ -0,0 +1,28 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.FileProviders;
namespace TheXamlGuy.Framework.Core
{
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);
}
}
}
}
+6
View File
@@ -0,0 +1,6 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public record Write<TConfiguration>(string Section, Action<TConfiguration> UpdateDelegate) where TConfiguration : class;
}
@@ -0,0 +1,26 @@
namespace TheXamlGuy.Framework.Core
{
public class WriteHandler<TConfiguration> : IMediatorHandler<Write<TConfiguration>> where TConfiguration : class
{
private readonly IEventAggregator eventAggregator;
private readonly TConfiguration configuration;
private readonly IConfigurationWriter<TConfiguration> writer;
public WriteHandler(TConfiguration configuration,
IConfigurationWriter<TConfiguration> writer,
IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
this.configuration = configuration;
this.writer = writer;
}
public void Handle(Write<TConfiguration> request)
{
request.UpdateDelegate.Invoke(configuration);
writer.Write(request.Section, configuration);
eventAggregator.Publish(new ConfigurationChanged<TConfiguration>(configuration));
}
}
}
+16
View File
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonPatch.Net" Version="2.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0-rc.2.22472.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0-rc.2.22472.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0-rc.2.22472.3" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0-rc.2.22472.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2-beta2" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup>
</Project>
+19
View File
@@ -0,0 +1,19 @@
using System.Collections.ObjectModel;
namespace TheXamlGuy.Framework.Core
{
public class EventBuilder : IEventBuilder
{
private readonly List<IEventBuilderConfiguration> configurations = new();
public IReadOnlyCollection<IEventBuilderConfiguration> Configurations => new ReadOnlyCollection<IEventBuilderConfiguration>(configurations);
public IEventBuilderConfiguration<TEvent> Add<TEvent>() where TEvent : class
{
EventConfiguration<TEvent>? configuration = new();
configurations.Add(configuration);
return configuration;
}
}
}
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace TheXamlGuy.Framework.Core
{
public class EventConfiguration<TEvent> : IEventBuilderConfiguration<TEvent> where TEvent : class
{
private readonly List<IEventDescriptor> descriptors = new();
public EventConfiguration()
{
}
public IReadOnlyCollection<IEventDescriptor> Descriptors => new ReadOnlyCollection<IEventDescriptor>(descriptors);
public Action<IServiceProvider, TEvent>? Factory { get; }
public Action<TEvent>? Next { get; }
public IEventBuilderConfiguration<TEvent> WithHandler<THandlerEvent>() where THandlerEvent : class
{
descriptors.Add(new EventDescriptor<TEvent, THandlerEvent>());
return this;
}
public IEventBuilderConfiguration<TEvent> WithHandler<THandlerEvent>(Func<TEvent, THandlerEvent> factoryDelegate) where THandlerEvent : class
{
descriptors.Add(new EventDescriptor<TEvent, THandlerEvent>(factoryDelegate));
return this;
}
public IEventBuilderConfiguration<TEvent> WithHandler<THandlerEvent>(Func<IServiceProvider, TEvent, THandlerEvent> factoryDelegate) where THandlerEvent : class
{
descriptors.Add(new EventDescriptor<TEvent, THandlerEvent>(factoryDelegate));
return this;
}
}
}
+24
View File
@@ -0,0 +1,24 @@
namespace TheXamlGuy.Framework.Core
{
public record EventDescriptor<TEvent, THandlerEvent> : IEventDescriptor
{
public EventDescriptor()
{
}
public EventDescriptor(Func<IServiceProvider, TEvent, THandlerEvent>? factoryDelegate)
{
FactoryDelegate = factoryDelegate;
}
public EventDescriptor(Func<TEvent, THandlerEvent>? nextDelegate)
{
NextDelegate = nextDelegate;
}
public Func<IServiceProvider, TEvent, THandlerEvent>? FactoryDelegate { get; }
public Func<TEvent, THandlerEvent>? NextDelegate { get; }
}
}
+47
View File
@@ -0,0 +1,47 @@
namespace TheXamlGuy.Framework.Core
{
public class EventHandler<TTEvent> : IEventHandler<TTEvent> where TTEvent : class
{
private readonly IEventBuilderConfiguration<TTEvent> configuration;
private readonly IDisposer disposer;
private readonly IEventAggregator eventAggregator;
private readonly IMediator mediator;
private readonly IServiceFactory serviceFactory;
private readonly IServiceProvider serviceProvider;
public EventHandler(IEventBuilderConfiguration<TTEvent> configuration,
IServiceProvider serviceProvider,
IServiceFactory serviceFactory,
IEventAggregator eventAggregator,
IMediator mediator,
IDisposer disposer)
{
this.configuration = configuration;
this.serviceProvider = serviceProvider;
this.serviceFactory = serviceFactory;
this.eventAggregator = eventAggregator;
this.mediator = mediator;
this.disposer = disposer;
}
public void Dispose()
{
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
public async Task InitializeAsync()
{
disposer.Add(this, eventAggregator.SubscribeUI<TTEvent>(OnEvent));
await Task.CompletedTask;
}
private void OnEvent(TTEvent args)
{
foreach (IEventDescriptor? descriptor in configuration.Descriptors)
{
mediator.Handle((descriptor as dynamic).NextDelegate?.Invoke(args) ?? (descriptor as dynamic).FactoryDelegate?.Invoke(serviceProvider, args));
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace TheXamlGuy.Framework.Core;
public interface IEventBuilder
{
IReadOnlyCollection<IEventBuilderConfiguration> Configurations { get; }
IEventBuilderConfiguration<TEvent> Add<TEvent>() where TEvent : class;
}
@@ -0,0 +1,22 @@
namespace TheXamlGuy.Framework.Core
{
public interface IEventBuilderConfiguration
{
}
public interface IEventBuilderConfiguration<TEvent> : IEventBuilderConfiguration where TEvent : class
{
IReadOnlyCollection<IEventDescriptor> Descriptors { get; }
Action<IServiceProvider, TEvent>? Factory { get; }
Action<TEvent>? Next { get; }
IEventBuilderConfiguration<TEvent> WithHandler<THandlerEvent>() where THandlerEvent : class;
IEventBuilderConfiguration<TEvent> WithHandler<THandlerEvent>(Func<TEvent, THandlerEvent> factoryDelegate) where THandlerEvent : class;
IEventBuilderConfiguration<TEvent> WithHandler<THandlerEvent>(Func<IServiceProvider, TEvent, THandlerEvent> factoryDelegate) where THandlerEvent : class;
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core
{
public interface IEventDescriptor
{
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core
{
public interface IEventHandler<TTEvent> : IInitializer, IDisposable where TTEvent : class
{
}
}
@@ -0,0 +1,14 @@
using System.IO;
using System.Reflection;
namespace TheXamlGuy.Framework.Core
{
public static class AssemblyExtensions
{
public static Stream? ExtractResource(this Assembly assembly, string filename)
{
string? resourceName = $"{assembly.GetName()?.Name?.Replace("-", "_")}.{filename}";
return assembly.GetManifestResourceStream(resourceName);
}
}
}
@@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace TheXamlGuy.Framework.Core
{
public static class ICollectionExtensions
{
public static void AddRange<T>(this ICollection<T> target, IEnumerable<T> source)
{
foreach (T item in source)
{
target.Add(item);
}
}
}
}
@@ -0,0 +1,13 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public static class IDisposableExtensions
{
public static T Dispose<T>(this T target) where T : IDisposable
{
target.Dispose();
return target;
}
}
}
@@ -0,0 +1,10 @@
namespace TheXamlGuy.Framework.Core.Extensions
{
public static class IDisposerExtensions
{
public static void Add(this IDisposer disposer, object subject, IEnumerable<IDisposable> disposers)
{
disposer.Add(subject, disposers.ToArray());
}
}
}
@@ -0,0 +1,26 @@
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reflection;
namespace TheXamlGuy.Framework.Core;
public static class IEventAggregatorExtensions
{
public static IDisposable Subscribe<TEvent>(this IEventAggregator eventAggregator, Action<TEvent> onNext, IScheduler? scheduler = null, Func<TEvent, bool>? where = null)
{
scheduler ??= Scheduler.Default;
where ??= x => true;
return eventAggregator.GetEvent<TEvent>().Where(where).ObserveOn(scheduler).WeakSubscribe(eventAggregator.Invoker, onNext);
}
public static IDisposable SubscribeUI<TEvent>(this IEventAggregator eventAggregator, Action<TEvent> onNext, Func<TEvent, bool>? where = null)
{
return eventAggregator.Subscribe(onNext, eventAggregator.Dispatcher, where);
}
public static void Publish<TEvent>(this IEventAggregator eventAggregator) where TEvent : new()
{
eventAggregator.Publish(new TEvent());
}
}
@@ -0,0 +1,37 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace TheXamlGuy.Framework.Core
{
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);
}
public static IHostBuilder ConfigureEvents(this IHostBuilder hostBuilder, Action<IEventBuilder> builderDelegate)
{
hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
{
EventBuilder? builder = new();
builderDelegate?.Invoke(builder);
foreach (IEventBuilderConfiguration? configuration in builder.Configurations)
{
Type? type = configuration.GetType().GetGenericArguments()[0];
serviceCollection.AddSingleton(typeof(IEventBuilderConfiguration<>).MakeGenericType(type), configuration);
serviceCollection.AddSingleton(typeof(EventHandler<>).MakeGenericType(type));
}
});
return hostBuilder;
}
}
}
@@ -0,0 +1,12 @@
namespace TheXamlGuy.Framework.Core
{
public interface IMediatorAsyncHandler<TRequest>
{
Task Handle(TRequest request, CancellationToken cancellationToken = default);
}
public interface IMediatorAsyncHandler<TResponse, TRequest>
{
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken = default);
}
}
@@ -0,0 +1,27 @@
using System.Threading.Tasks;
namespace TheXamlGuy.Framework.Core
{
public static class IMediatorExtensions
{
public static void Handle<TEvent>(this IMediator mediator) where TEvent : new()
{
mediator.Handle(new TEvent());
}
public static TResponse? Handle<TResponse, TRequest>(this IMediator mediator, params object[] parameters) where TRequest : new()
{
return mediator.Handle<TResponse?>(new TRequest(), parameters);
}
public static Task HandleAsync<TEvent>(this IMediator mediator) where TEvent : new()
{
return mediator.HandleAsync(new TEvent());
}
public static Task<TResponse?> HandleAsync<TResponse, TRequest>(this IMediator mediator, params object[] parameters) where TRequest : new()
{
return mediator.HandleAsync<TResponse?>(new TRequest(), parameters);
}
}
}
@@ -0,0 +1,12 @@
namespace TheXamlGuy.Framework.Core
{
public interface IMediatorHandler<TRequest>
{
void Handle(TRequest request);
}
public interface IMediatorHandler<TResponse, TRequest>
{
TResponse Handle(TRequest request);
}
}
@@ -0,0 +1,29 @@
using System;
using System.Reflection;
namespace TheXamlGuy.Framework.Core
{
public static class IObservableExtensions
{
public static IDisposable WeakSubscribe<TEvent>(this IObservable<TEvent> observable, IEventAggregatorInvoker invoker, Action<TEvent> onNext)
{
MethodInfo methodInfo = onNext.Method;
WeakReference weakReference = new(onNext.Target);
IDisposable? subscription = null;
subscription = observable.Subscribe(item =>
{
if (weakReference.Target is object target)
{
invoker.Invoke(target, item, methodInfo);
}
else
{
subscription?.Dispose();
}
});
return subscription;
}
}
}
@@ -0,0 +1,103 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Reflection;
namespace TheXamlGuy.Framework.Core
{
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection serviceCollection, IConfiguration configuration) where TConfiguration : class, new()
{
serviceCollection.Configure<TConfiguration>(configuration);
serviceCollection.AddTransient(provider => provider.GetService<IOptionsMonitor<TConfiguration>>()!.CurrentValue);
serviceCollection.AddTransient<ConfigurationInitializer<TConfiguration>>();
return serviceCollection;
}
public static IServiceCollection AddCreatable<TService, TImplementation>(this IServiceCollection services)
{
return services.AddSingleton(typeof(IServiceCreator<>).MakeGenericType(typeof(TService)), typeof(ServiceCreator<,>).MakeGenericType(typeof(TService), typeof(TImplementation)));
}
public static IServiceCollection AddCreatable(this IServiceCollection services, Type serviceType, Type implementationType)
{
return services.AddSingleton(typeof(IServiceCreator<>).MakeGenericType(serviceType), typeof(ServiceCreator<,>).MakeGenericType(serviceType, implementationType));
}
public static IServiceCollection AddCreatableSingleton(this IServiceCollection services, Type serviceType, Type implementationType)
{
services.AddSingleton(serviceType, implementationType);
services.AddCreatable(serviceType, implementationType);
return services;
}
public static IServiceCollection AddCreatableTransient(this IServiceCollection services, Type serviceType, Type implementationType)
{
services.AddTransient(serviceType, implementationType);
services.AddCreatable(serviceType, implementationType);
return services;
}
public static IServiceCollection AddReqiredCore(this IServiceCollection services)
{
return services
.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService, (type, parameters) => ActivatorUtilities.CreateInstance(provider, type, parameters!)))
.AddSingleton<IDisposer, Disposer>()
.AddSingleton<IEventAggregator, EventAggregator>()
.AddSingleton<IEventAggregatorInvoker, EventAggregatorInvoker>()
.AddSingleton<IMediator, Mediator>()
.AddSingleton<IScope, Scope>()
.AddTransient<IPropertyBinderCollection, PropertyBinderCollection>()
.AddTransient<IPropertyBuilder, PropertyBuilder>()
.AddSingleton<IInitialization, Initialization>(provider => new Initialization(() =>
{
return services.Where(x => x.ServiceType.GetInterfaces()
.Contains(typeof(IInitializer)) || x.ServiceType == typeof(IInitializer))
.GroupBy(x => x.ServiceType)
.Select(x => x.First())
.SelectMany(x => provider.GetServices(x.ServiceType)
.Select(x => (IInitializer?)x)).ToList();
}))
.RegisterHandlers();
}
public static IServiceCollection AddWritableConfiguration<TConfiguration>(this IServiceCollection serviceCollection, IConfiguration configuration) where TConfiguration : class, new()
{
serviceCollection.Configure<TConfiguration>(configuration);
serviceCollection.AddTransient<IConfigurationWriter<TConfiguration>, ConfigurationWriter<TConfiguration>>();
serviceCollection.AddTransient(provider => provider.GetService<IOptionsMonitor<TConfiguration>>()!.CurrentValue);
serviceCollection.AddTransient<IMediatorHandler<Write<TConfiguration>>, WriteHandler<TConfiguration>>();
serviceCollection.AddTransient<ConfigurationInitializer<TConfiguration>>();
return serviceCollection;
}
public static IServiceCollection RegisterHandlers(this IServiceCollection services, params Assembly[] assemblies)
{
foreach (Tuple<Type, Type> item in GetImplementations(assemblies.Append(Assembly.GetCallingAssembly()), "Handler", typeof(IMediatorHandler<>), typeof(IMediatorHandler<,>), typeof(IMediatorAsyncHandler<>), typeof(IMediatorAsyncHandler<,>)))
{
if (!item.Item2.GetConstructors().Any(x => x.IsPublic))
{
continue;
}
services.AddCreatableTransient(item.Item1, item.Item2);
}
return services;
}
private static IEnumerable<Tuple<Type, Type>> GetImplementations(IEnumerable<Assembly> assemblies, string endsWith, params Type[] interfaces)
{
return assemblies.Distinct().SelectMany(a => a.GetTypes())
.Where(impl => impl.IsClass && impl.IsPublic && !impl.IsAbstract && impl.Name.EndsWith(endsWith))
.SelectMany(impl => impl.GetInterfaces(), (impl, iface) => new { impl, iface })
.Where(i => i.iface.IsGenericType && interfaces.Contains(i.iface.GetGenericTypeDefinition()))
.Select(x => Tuple.Create(x.iface, x.impl));
}
}
}
@@ -0,0 +1,21 @@
namespace TheXamlGuy.Framework.Core
{
public static class IServiceFactoryExtensions
{
public static T? Get<T>(this IServiceFactory serviceFactory)
{
return serviceFactory.Get<T>(typeof(T));
}
public static T Create<T>(this IServiceFactory serviceFactory, params object?[] parameters)
{
return serviceFactory.Create<T>(typeof(T), parameters);
}
public static object? Create(this IServiceFactory serviceFactory, Type type)
{
ServiceFactoryDescriptor? descriptor = new(serviceFactory);
return descriptor.Create(type);
}
}
}
@@ -0,0 +1,21 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public static class StringExtensions
{
public static int CountSubstring(this string text, string textToMatch, StringComparison stringComparison)
{
int count = 0;
int minIndex = text.IndexOf(textToMatch, 0, stringComparison);
while (minIndex != -1)
{
minIndex = text.IndexOf(textToMatch, minIndex + textToMatch.Length, stringComparison);
count++;
}
return count;
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using Microsoft.Extensions.Hosting;
namespace TheXamlGuy.Framework.Core
{
public class AppServices : IHostedService
{
private readonly IMediator mediator;
private readonly IDisposer disposer;
private readonly IInitialization initialization;
public AppServices(IMediator mediator,
IDisposer disposer,
IInitialization initialization)
{
this.mediator = mediator;
this.disposer = disposer;
this.initialization = initialization;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
mediator.Handle<Started>();
await initialization.InitializeAsync();
}
public Task StopAsync(CancellationToken cancellationToken)
{
disposer.Dispose(this);
return Task.CompletedTask;
}
}
}
+77
View File
@@ -0,0 +1,77 @@
using System;
using System.Collections;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
namespace TheXamlGuy.Framework.Core
{
public class Disposer : IDisposer
{
private readonly ConditionalWeakTable<object, CompositeDisposable> subjects = new();
public void Add(object subject, params object[] objects)
{
CompositeDisposable disposables = subjects.GetOrCreateValue(subject);
foreach (IDisposable disposable in objects.OfType<IDisposable>())
{
disposables.Add(disposable);
}
foreach (object notDisposable in objects.Where(x => x is not IDisposable))
{
disposables.Add(Disposable.Create(() => FromNotDisposable(notDisposable)));
}
}
private void FromNotDisposable(object target)
{
if (target is IEnumerable enumerableTarget)
{
foreach (object? item in enumerableTarget)
{
FromNotDisposable(item);
}
}
if (target is IDisposable disposableTarget)
{
disposableTarget.Dispose();
}
if (target is not IDisposable)
{
Dispose(target);
}
}
public TDisposable Replace<TDisposable>(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable
{
CompositeDisposable disposables = subjects.GetOrCreateValue(subject);
if (disposer is not null)
{
disposables.Remove(disposer);
}
disposables.Add(replacement);
return replacement;
}
public void Remove(object subject, IDisposable disposer)
{
CompositeDisposable disposables = subjects.GetOrCreateValue(subject);
if (disposer is not null)
{
disposables.Remove(disposer);
}
}
public void Dispose(object subject)
{
if (subjects.TryGetValue(subject, out CompositeDisposable? disposables))
{
disposables?.Dispose();
}
}
}
}
@@ -0,0 +1,54 @@
using System.Collections.Concurrent;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace TheXamlGuy.Framework.Core
{
public class EventAggregator : IEventAggregator
{
private readonly ConcurrentDictionary<Type, object> subjects;
public EventAggregator(IEventAggregatorInvoker invoker)
{
subjects = new ConcurrentDictionary<Type, object>();
Dispatcher = new SynchronizationContextScheduler(SynchronizationContext.Current!);
Invoker = invoker;
Current = new EventAggregatorCurrent(this);
}
protected EventAggregator(EventAggregator eventAggregator)
{
subjects = eventAggregator.subjects;
Dispatcher = eventAggregator.Dispatcher;
Invoker = eventAggregator.Invoker;
Current = eventAggregator.Current;
}
public IEventAggregator Current { get; }
public IScheduler Dispatcher { get; }
public IEventAggregatorInvoker Invoker { get; }
public virtual IObservable<TEvent> GetEvent<TEvent>()
{
return GetObservable<TEvent>().Skip(1);
}
public virtual ISubject<TEvent> GetSubject<TEvent>()
{
return (ISubject<TEvent>)subjects.GetOrAdd(typeof(TEvent), x => new BehaviorSubject<TEvent>(default!));
}
public virtual void Publish<TEvent>(TEvent domainEvent)
{
GetSubject<TEvent>().OnNext(domainEvent);
}
protected virtual IObservable<TEvent> GetObservable<TEvent>()
{
return GetSubject<TEvent>().AsObservable();
}
}
}
@@ -0,0 +1,17 @@
using System.Reactive.Linq;
namespace TheXamlGuy.Framework.Core
{
public class EventAggregatorCurrent : EventAggregator
{
public EventAggregatorCurrent(EventAggregator eventAggregator) : base(eventAggregator)
{
}
public override IObservable<TEvent> GetEvent<TEvent>()
{
return GetObservable<TEvent>().Skip(0).Where(x => x != null);
}
}
}
@@ -0,0 +1,27 @@
using System.Reflection;
namespace TheXamlGuy.Framework.Core
{
public class EventAggregatorInvoker : IEventAggregatorInvoker
{
private readonly IScope scope;
public EventAggregatorInvoker(IScope scope)
{
this.scope = scope;
}
public void Invoke<TEvent>(object target, TEvent item, MethodInfo methodInfo)
{
using (scope.Enter<TEvent>(target))
{
methodInfo.Invoke(target, new object[] { item! });
}
}
public bool IsInvoking<TEvent>(object target)
{
return scope.IsActive<TEvent>(target);
}
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core
{
public interface ICachable
{
}
}
+15
View File
@@ -0,0 +1,15 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public interface IDisposer
{
void Add(object subject, params object[] objects);
TDisposable Replace<TDisposable>(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable;
void Remove(object subject, IDisposable disposer);
void Dispose(object subject);
}
}
@@ -0,0 +1,20 @@
using System.Reactive.Concurrency;
using System.Reactive.Subjects;
namespace TheXamlGuy.Framework.Core
{
public interface IEventAggregator
{
IEventAggregator Current { get; }
IScheduler Dispatcher { get; }
IEventAggregatorInvoker Invoker { get; }
IObservable<TEvent> GetEvent<TEvent>();
ISubject<TEvent> GetSubject<TEvent>();
void Publish<TEvent>(TEvent args);
}
}
@@ -0,0 +1,11 @@
using System.Reflection;
namespace TheXamlGuy.Framework.Core
{
public interface IEventAggregatorInvoker
{
void Invoke<TEvent>(object target, TEvent item, MethodInfo methodInfo);
bool IsInvoking<TEvent>(object target);
}
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Core;
public interface IHasSensorPlacement
{
SensorPlacement Placement { get; }
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core
{
public interface IInitialization
{
Task InitializeAsync();
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core
{
public interface IInitializer
{
Task InitializeAsync();
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core
{
public interface IKeepAlive
{
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace TheXamlGuy.Framework.Core
{
public interface IMediator
{
void Handle(object request, params object[] parameters);
TResponse? Handle<TResponse>(object request, params object[] parameters);
Task HandleAsync(object request, params object[] parameters);
Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters);
Task<TResponse?> HandleAsync<TResponse>(object request, params object[] parameters);
Task<TResponse?> HandleAsync<TResponse>(object request, CancellationToken cancellationToken, params object[] parameters);
}
}
+11
View File
@@ -0,0 +1,11 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public interface IScope
{
IDisposable Enter<T>(object target);
bool IsActive<T>(object target);
}
}
@@ -0,0 +1,9 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public interface IServiceCreator<I>
{
object Create(Func<Type, object[], object> creator, params object[] parameters);
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.Framework.Core
{
public interface IServiceFactory
{
T? Get<T>(Type type);
T Create<T>(Type type, params object?[] parameters);
}
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Core;
public interface ITwoStateSensor
{
SensorState State { get; }
}
@@ -0,0 +1,23 @@
namespace TheXamlGuy.Framework.Core
{
public class Initialization : IInitialization
{
private readonly Func<IEnumerable<IInitializer?>> factory;
public Initialization(Func<IEnumerable<IInitializer?>> factory)
{
this.factory = factory;
}
public async Task InitializeAsync()
{
foreach (IInitializer? initializer in factory())
{
if (initializer is not null)
{
await initializer.InitializeAsync();
}
}
}
}
}
+64
View File
@@ -0,0 +1,64 @@
namespace TheXamlGuy.Framework.Core;
public class Mediator : IMediator
{
private readonly IServiceFactory serviceFactory;
public Mediator(IServiceFactory serviceFactory)
{
this.serviceFactory = serviceFactory;
}
public void Handle(object request, params object[] parameters)
{
if (GetHandler(typeof(IMediatorHandler<>).MakeGenericType(request.GetType()), parameters) is { } handler)
{
handler.Handle((dynamic)request);
}
}
public TResponse? Handle<TResponse>(object request, params object[] parameters)
{
if (GetHandler(typeof(IMediatorHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters) is { } handler)
{
return handler.Handle((dynamic)request);
}
return default;
}
public Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters)
{
if (GetHandler(typeof(IMediatorAsyncHandler<>).MakeGenericType(request.GetType()), parameters) is { } handler)
{
return handler.Handle((dynamic)request, cancellationToken);
}
return Task.CompletedTask;
}
public Task HandleAsync(object request, params object[] parameters)
{
return HandleAsync(request, CancellationToken.None, parameters);
}
public Task<TResponse?> HandleAsync<TResponse>(object request, CancellationToken cancellationToken, params object[] parameters)
{
if (GetHandler(typeof(IMediatorAsyncHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters) is { } handler)
{
return handler.Handle((dynamic)request, cancellationToken);
}
return Task.FromResult<TResponse?>(default);
}
public Task<TResponse?> HandleAsync<TResponse>(object request, params object[] parameters)
{
return HandleAsync<TResponse?>(request, CancellationToken.None, parameters);
}
private dynamic? GetHandler(Type type, params object[] parameters)
{
return parameters.Length == 0 ? serviceFactory.Get<object>(type) : serviceFactory.Create<object>(type, parameters);
}
}
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Collections.Concurrent;
using System.Reactive.Disposables;
namespace TheXamlGuy.Framework.Core
{
public class Scope : IScope
{
private readonly ConcurrentDictionary<object, bool> scopes = new ConcurrentDictionary<object, bool>();
public IDisposable Enter<T>(object target)
{
scopes.TryAdd(Tuple.Create(target, typeof(T)), true);
return Disposable.Create(() => scopes.TryRemove(Tuple.Create(target, typeof(T)), out bool value));
}
public bool IsActive<T>(object target)
{
return scopes.ContainsKey(Tuple.Create(target, typeof(T)));
}
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.Framework.Core;
public enum SensorPlacement
{
Left = 0,
Top = 1,
Right = 3,
Bottom = 4,
}
+7
View File
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core;
public enum SensorState
{
Off = 0,
On = 1
}
@@ -0,0 +1,10 @@
namespace TheXamlGuy.Framework.Core
{
public class ServiceCreator<I, T> : IServiceCreator<I>
{
public virtual object Create(Func<Type, object[], object> creator, params object[] parameters)
{
return creator(typeof(T), parameters);
}
}
}
@@ -0,0 +1,25 @@
namespace TheXamlGuy.Framework.Core;
public class ServiceFactory : IServiceFactory
{
private readonly Func<Type, object?> factory;
private readonly Func<Type, object?[], object> creator;
public ServiceFactory(Func<Type, object?> factory, Func<Type, object?[], object> creator)
{
this.factory = factory;
this.creator = creator;
}
public T? Get<T>(Type type)
{
T? value = (T?)factory(type);
return value;
}
public T Create<T>(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);
}
}
@@ -0,0 +1,25 @@
using System.Reflection;
namespace TheXamlGuy.Framework.Core
{
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<T>(Type type)
{
return serviceFactory.Create<T>(type);
}
}
}
+4
View File
@@ -0,0 +1,4 @@
namespace TheXamlGuy.Framework.Core
{
public record Started;
}
@@ -0,0 +1,11 @@
using System.ComponentModel;
namespace TheXamlGuy.Framework.Core
{
public interface IObservableViewModel : INotifyDataErrorInfo, IDisposable
{
void Initialize();
bool IsInitialized { get; }
}
}
@@ -0,0 +1,14 @@
using System.ComponentModel;
namespace TheXamlGuy.Framework.Core
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public virtual void OnPropertyChanged(string propertyName, object? before, object? after)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
@@ -0,0 +1,167 @@
using System.Collections;
using System.ComponentModel;
namespace TheXamlGuy.Framework.Core
{
public class ObservableViewModel : ObservableObject, IObservableViewModel
{
public ObservableViewModel(IPropertyBuilder propertyBuilder,
IEventAggregator eventAggregator,
IServiceFactory serviceFactory,
IDisposer disposer)
{
PropertyBuilder = propertyBuilder;
EventAggregator = eventAggregator;
ServiceFactory = serviceFactory;
Disposer = disposer;
ValidationErrors = new PropertyValidationError<string, string>();
}
public event System.EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public IDisposer Disposer { get; }
public IEventAggregator EventAggregator { get; }
public bool HasErrors => ValidationErrors.Count > 0;
public bool IsInitialized { get; private set; }
public IPropertyBuilder PropertyBuilder { get; }
public IServiceFactory ServiceFactory { get; }
public PropertyValidationError<string, string> ValidationErrors { get; }
public void Dispose()
{
OnDisposing();
Disposer.Dispose(this);
GC.SuppressFinalize(this);
}
public IEnumerable GetErrors(string? propertyName)
{
return propertyName is not null && ValidationErrors.Contains(propertyName) ? ValidationErrors[propertyName]! : Array.Empty<string>();
}
public void Initialize()
{
if (IsInitialized)
{
return;
}
IsInitialized = true;
OnInitialize();
}
public override void OnPropertyChanged(string propertyName, object? before, object? after)
{
SetProperty(propertyName, false);
base.OnPropertyChanged(propertyName, before, after);
}
protected virtual void ClearValidationErrors()
{
foreach (PropertyBinder? binder in PropertyBuilder.Binders)
{
if (binder.PropertyName is { })
{
if (ValidationErrors.Contains(binder.PropertyName))
{
ValidationErrors.Remove(binder.PropertyName);
OnErrorsChanged(binder.PropertyName);
}
}
}
OnPropertyChanged(nameof(ValidationErrors), null, null);
}
protected virtual void OnDisposing()
{
}
protected virtual void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
protected virtual void OnInitialize()
{
}
protected void SetProperty(string propertyName)
{
SetProperty(propertyName, true);
}
protected void SetProperty(string? propertyName, bool isExplicit)
{
if (propertyName is { })
{
if (PropertyBuilder.Binders.TryGet(propertyName, out PropertyBinder? binder) && binder is { })
{
if (binder.Mode == PropertyChangedMode.Explicit && !isExplicit)
{
return;
}
ClearValidationError(propertyName);
if (!binder.TryValidate(out string? message) && message is { })
{
AddValidationError(propertyName, message);
}
}
}
}
protected virtual bool Validate(bool clearPreviousErrors = true)
{
if (clearPreviousErrors)
{
ClearValidationErrors();
}
foreach (PropertyBinder? binder in PropertyBuilder.Binders)
{
if (binder.PropertyName is { })
{
if (!binder.TryValidate(out string? message) && message is { })
{
AddValidationError(binder.PropertyName, message);
}
}
}
return !HasErrors;
}
private void AddValidationError(string propertyName, string validationMessage)
{
if (propertyName is { })
{
OnErrorsChanged(propertyName);
ValidationErrors[propertyName] = validationMessage;
OnPropertyChanged(nameof(ValidationErrors), null, null);
}
}
private void ClearValidationError(string propertyName)
{
if (ValidationErrors.Contains(propertyName))
{
ValidationErrors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
OnPropertyChanged(nameof(ValidationErrors), null, null);
}
}
}
@@ -0,0 +1,406 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reactive.Disposables;
namespace TheXamlGuy.Framework.Core
{
public class ObservableViewModelCollection<TItemViewModel> :
ObservableCollection<TItemViewModel>, IObservableViewModel
where TItemViewModel : class
{
private readonly ObservableCollection<TItemViewModel>? source;
public ObservableViewModelCollection(IPropertyBuilder propertyBuilder,
IEventAggregator eventAggregator,
IServiceFactory serviceFactory,
IDisposer disposer,
ObservableCollection<TItemViewModel> source)
{
PropertyBuilder = propertyBuilder;
EventAggregator = eventAggregator;
ServiceFactory = serviceFactory;
Disposer = disposer;
this.source = source;
source.CollectionChanged += OnSourceCollectionChanged;
AddRange(source);
ValidationErrors = new PropertyValidationError<string, string>();
}
public ObservableViewModelCollection(IPropertyBuilder propertyBuilder,
IEventAggregator eventAggregator,
IServiceFactory serviceFactory,
IDisposer disposer)
{
PropertyBuilder = propertyBuilder;
EventAggregator = eventAggregator;
ServiceFactory = serviceFactory;
Disposer = disposer;
ValidationErrors = new PropertyValidationError<string, string>();
}
public event System.EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public IDisposer Disposer { get; }
public IEventAggregator EventAggregator { get; }
public bool HasErrors => ValidationErrors.Count > 0;
public bool IsInitialized { get; private set; }
public IPropertyBuilder PropertyBuilder { get; }
public IServiceFactory ServiceFactory { get; }
public PropertyValidationError<string, string> ValidationErrors { get; }
public TItemViewModel Add()
{
TItemViewModel? item = ServiceFactory.Create<TItemViewModel>();
Disposer.Add(this, item);
base.Add(item);
return item;
}
public TItemViewModel Add<T>() where T : TItemViewModel
{
T? item = ServiceFactory.Create<T>();
Disposer.Add(this, item);
base.Add(item);
return item;
}
public TItemViewModel Add<T>(params object?[] parameters) where T : TItemViewModel
{
T? item = ServiceFactory.Create<T>(parameters);
Disposer.Add(this, item);
Disposer.Add(item, Disposable.Create(() =>
{
if (!isClearing)
{
if (Contains(item))
{
Remove(item);
}
}
}));
base.Add(item);
return item;
}
public new TItemViewModel Add(TItemViewModel item)
{
Disposer.Add(this, item);
Disposer.Add(item, Disposable.Create(() =>
{
if (!isClearing)
{
if (Contains(item))
{
Remove(item);
}
}
}));
base.Add(item);
return item;
}
public TItemViewModel Add(params object?[] parameters)
{
TItemViewModel? item = ServiceFactory.Create<TItemViewModel>(parameters);
Disposer.Add(this, item);
Disposer.Add(item, Disposable.Create(() =>
{
if (!isClearing)
{
if (Contains(item))
{
Remove(item);
}
}
}));
base.Add(item);
return item;
}
public void AddRange(ICollection<TItemViewModel> items)
{
foreach (TItemViewModel? item in items)
{
AddItemToDisposer(item);
base.Add(item);
}
}
private bool isClearing;
public new void Clear()
{
isClearing = true;
foreach (TItemViewModel? item in this)
{
if (item is not IKeepAlive)
{
Disposer.Dispose(item);
}
}
base.Clear();
isClearing = false;
}
public void Dispose()
{
OnDisposing();
if (source is not null)
{
source.CollectionChanged -= OnSourceCollectionChanged;
}
Disposer.Dispose(this);
Clear();
GC.SuppressFinalize(this);
}
public IEnumerable GetErrors(string? propertyName)
{
return propertyName is not null && ValidationErrors.Contains(propertyName) ? ValidationErrors[propertyName]! : Array.Empty<string>();
}
public void Initialize()
{
if (IsInitialized)
{
return;
}
IsInitialized = true;
OnInitialize();
}
public void Insert<TItem>(params object[] parameters) where TItem : TItemViewModel
{
TItem? item = ServiceFactory.Create<TItem>(parameters);
AddItemToDisposer(item);
base.Add(item);
}
public new void Insert(int index, TItemViewModel item)
{
AddItemToDisposer(item);
base.Insert(index, item);
}
public void Insert<TItem>(int index, params object[] parameters) where TItem : TItemViewModel
{
TItem? item = ServiceFactory.Create<TItem>(parameters);
AddItemToDisposer(item);
base.Insert(index, item);
}
public void Insert(TItemViewModel item)
{
base.Insert(0, item);
AddItemToDisposer(item);
}
public new void Remove(TItemViewModel item)
{
if (item is not IKeepAlive)
{
Disposer.Dispose(item);
}
base.Remove(item);
}
protected virtual void ClearValidationErrors()
{
foreach (PropertyBinder? binder in PropertyBuilder.Binders)
{
if (binder.PropertyName is { })
{
if (ValidationErrors.Contains(binder.PropertyName))
{
ValidationErrors.Remove(binder.PropertyName);
OnErrorsChanged(binder.PropertyName);
}
}
}
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ValidationErrors)));
}
protected virtual void OnDisposing()
{
}
protected virtual void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
protected virtual void OnInitialize()
{
}
protected override void OnPropertyChanged(PropertyChangedEventArgs args)
{
SetProperty(args.PropertyName, false);
base.OnPropertyChanged(args);
}
protected void SetProperty(string? propertyName, bool isExplicit)
{
if (propertyName is { })
{
if (PropertyBuilder.Binders.TryGet(propertyName, out PropertyBinder? binder) && binder is { })
{
if (binder.Mode == PropertyChangedMode.Explicit && !isExplicit)
{
return;
}
ClearValidationError(propertyName);
if (!binder.TryValidate(out string? message) && message is { })
{
AddValidationError(propertyName, message);
}
}
}
}
protected virtual bool Validate(bool clearPreviousErrors = true)
{
if (clearPreviousErrors)
{
ClearValidationErrors();
}
foreach (PropertyBinder? binder in PropertyBuilder.Binders)
{
if (binder.PropertyName is { })
{
if (!binder.TryValidate(out string? message) && message is { })
{
AddValidationError(binder.PropertyName, message);
}
}
}
return !HasErrors;
}
private void AddItemToDisposer(TItemViewModel? item)
{
if (item is not IKeepAlive)
{
Disposer.Add(this, item!);
}
}
private void AddValidationError(string propertyName, string validationMessage)
{
if (propertyName is { })
{
OnErrorsChanged(propertyName);
ValidationErrors[propertyName] = validationMessage;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ValidationErrors)));
}
}
private void ClearValidationError(string propertyName)
{
if (ValidationErrors.Contains(propertyName))
{
ValidationErrors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ValidationErrors)));
}
private void OnSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args)
{
SynchronizeCollection(this, args);
}
private void SynchronizeCollection(ObservableCollection<TItemViewModel> target, NotifyCollectionChangedEventArgs args)
{
TItemViewModel[]? newItems = args.NewItems?.Cast<TItemViewModel>().ToArray();
TItemViewModel[]? oldItems = args.OldItems?.Cast<TItemViewModel>().ToArray();
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (newItems is not null)
{
for (int index = 0; index < newItems.Length; index++)
{
target.Insert(args.NewStartingIndex + index, newItems[index]);
}
}
break;
case NotifyCollectionChangedAction.Remove:
if (oldItems is not null)
{
for (int index = 0; index < oldItems.Length; index++)
{
RemoveAt(args.OldStartingIndex);
}
break;
}
break;
case NotifyCollectionChangedAction.Replace:
if (oldItems is not null)
{
for (int index = 0; index < oldItems.Length; index++)
{
target.RemoveAt(index);
}
}
if (newItems is not null)
{
for (int index = 0; index < newItems.Length; index++)
{
target.Insert(index, newItems[index]);
}
}
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Reset:
target.Clear();
if (newItems is not null)
{
for (int index = 0; index < newItems.Length; index++)
{
target.Insert(index, newItems[index]);
}
}
break;
default:
break;
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Framework.Core
{
public interface IRouterContext : IInitializer
{
}
}
+29
View File
@@ -0,0 +1,29 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public record Navigate
{
public Navigate(string name, params object[] parameters)
{
Name = name;
Parameters = parameters;
}
public Navigate(Type type, params object[] parameters)
{
Type = type;
Parameters = parameters;
}
public Type? Type { get; }
public object? Route { get; init; }
public string? Name { get; }
public string? FriendlyName { get; init; }
public object[] Parameters { get; }
}
}
+4
View File
@@ -0,0 +1,4 @@
namespace TheXamlGuy.Framework.Core
{
public record NavigateBack(object Route);
}
+6
View File
@@ -0,0 +1,6 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public record Show(Type ViewModelType, params object[] Parameters);
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Core;
public interface INamedDataTemplateFactory
{
object? Create(string name, params object[] parameters);
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Core;
public interface INamedTemplateFactory
{
object? Create(string name);
}
@@ -0,0 +1,12 @@
using Microsoft.Extensions.DependencyInjection;
namespace TheXamlGuy.Framework.Core;
public interface ITemplateBuilder
{
IReadOnlyCollection<ITemplateDescriptor> Descriptors { get; }
ITemplateBuilder Add<TViewModel, TView>(string name, ServiceLifetime lifetime = ServiceLifetime.Transient);
ITemplateBuilder Add<TViewModel, TView>(ServiceLifetime lifetime = ServiceLifetime.Transient);
}
@@ -0,0 +1,14 @@
using Microsoft.Extensions.DependencyInjection;
namespace TheXamlGuy.Framework.Core;
public interface ITemplateDescriptor
{
Type DataType { get; }
ServiceLifetime Lifetime { get; }
string? Name { get; }
Type TemplateType { get; }
}
@@ -0,0 +1,11 @@
namespace TheXamlGuy.Framework.Core
{
public interface ITemplateDescriptorProvider
{
ITemplateDescriptor? Get(string name);
ITemplateDescriptor? Get(Type type);
ITemplateDescriptor? Get<T>();
}
}
@@ -0,0 +1,8 @@
using System.Diagnostics.CodeAnalysis;
namespace TheXamlGuy.Framework.Core;
public interface ITemplateFactory
{
object? Create([MaybeNull] object? data);
}
@@ -0,0 +1,5 @@
namespace TheXamlGuy.Framework.Core;
public interface ITemplateSelector
{
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Core;
public interface ITypedDataTemplateFactory
{
object? Create(Type type, params object[] parameters);
}
@@ -0,0 +1,36 @@
namespace TheXamlGuy.Framework.Core
{
public class NamedDataTemplateFactory : INamedDataTemplateFactory
{
private readonly Dictionary<string, object> dataTracking = new();
private readonly IReadOnlyCollection<ITemplateDescriptor> descriptors;
private readonly IServiceFactory serviceFactory;
public NamedDataTemplateFactory(IReadOnlyCollection<ITemplateDescriptor> descriptors,
IServiceFactory serviceFactory)
{
this.descriptors = descriptors;
this.serviceFactory = serviceFactory;
}
public virtual object? Create(string name, params object[] parameters)
{
if (dataTracking.TryGetValue(name, out object? data))
{
return data;
}
if (descriptors.FirstOrDefault(x => x.Name == name) is ITemplateDescriptor descriptor)
{
data = parameters is { Length: > 0 } ? serviceFactory.Create<object>(descriptor.DataType, parameters) : serviceFactory.Get<object>(descriptor.DataType);
if (data is ICachable cachable)
{
dataTracking[name] = cachable;
}
}
return data;
}
}
}
@@ -0,0 +1,40 @@
namespace TheXamlGuy.Framework.Core;
public class NamedTemplateFactory : INamedTemplateFactory
{
private readonly Dictionary<string, object> dataTracking = 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 (dataTracking.TryGetValue(name, out object? view))
{
return view;
}
if (provider.Get(name) is ITemplateDescriptor descriptor)
{
view = serviceFactory.Get<object>(descriptor.TemplateType);
if (view is ICachable cachable)
{
dataTracking[name] = cachable;
}
if (descriptor.GetType().GenericTypeArguments is { Length: 2 })
{
(descriptor as dynamic).ViewInvoker?.Invoke(view);
}
}
return view;
}
}
@@ -0,0 +1,23 @@
using Microsoft.Extensions.DependencyInjection;
using System.Collections.ObjectModel;
namespace TheXamlGuy.Framework.Core;
public class TemplateBuilder : ITemplateBuilder
{
private readonly List<ITemplateDescriptor> descriptors = new();
public IReadOnlyCollection<ITemplateDescriptor> Descriptors => new ReadOnlyCollection<ITemplateDescriptor>(descriptors);
public ITemplateBuilder Add<TViewModel, TView>(string name, ServiceLifetime lifetime = ServiceLifetime.Transient)
{
descriptors.Add(new TemplateDescriptor(typeof(TViewModel), typeof(TView), name, lifetime));
return this;
}
public ITemplateBuilder Add<TViewModel, TView>(ServiceLifetime lifetime = ServiceLifetime.Transient)
{
descriptors.Add(new TemplateDescriptor(typeof(TViewModel), typeof(TView), null, lifetime));
return this;
}
}
@@ -0,0 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using System;
namespace TheXamlGuy.Framework.Core;
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; }
}
@@ -0,0 +1,41 @@
namespace TheXamlGuy.Framework.Core;
public class TemplateDescriptorProvider : ITemplateDescriptorProvider
{
private readonly IReadOnlyCollection<ITemplateDescriptor> descriptors;
public TemplateDescriptorProvider(IReadOnlyCollection<ITemplateDescriptor> 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<T>()
{
if (descriptors.FirstOrDefault(x => x.DataType == typeof(T)) is ITemplateDescriptor descriptor)
{
return descriptor;
}
return null;
}
}
@@ -0,0 +1,42 @@
using System.Diagnostics.CodeAnalysis;
namespace TheXamlGuy.Framework.Core;
public class TemplateFactory : ITemplateFactory
{
private readonly Dictionary<object, object> dataTracking = 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 (dataTracking.TryGetValue(data, out object? template))
{
return template;
}
if (provider.Get(data.GetType()) is ITemplateDescriptor descriptor)
{
template = serviceFactory.Get<object>(descriptor.TemplateType);
if (template is ICachable cachable)
{
dataTracking[data] = cachable;
}
}
return template;
}
}
@@ -0,0 +1,35 @@
namespace TheXamlGuy.Framework.Core;
public class TypedDataTemplateFactory : ITypedDataTemplateFactory
{
private readonly Dictionary<Type, object> dataTracking = new();
private readonly IReadOnlyCollection<ITemplateDescriptor> descriptors;
private readonly IServiceFactory serviceFactory;
public TypedDataTemplateFactory(IReadOnlyCollection<ITemplateDescriptor> descriptors,
IServiceFactory serviceFactory)
{
this.descriptors = descriptors;
this.serviceFactory = serviceFactory;
}
public virtual object? Create(Type type, params object[] parameters)
{
if (dataTracking.TryGetValue(type, out object? data))
{
return data;
}
if (descriptors.FirstOrDefault(x => x.DataType == type) is ITemplateDescriptor descriptor)
{
data = parameters is { Length: > 0 } ? serviceFactory.Create<object>(descriptor.DataType, parameters) : serviceFactory.Get<object>(descriptor.DataType);
if (data is ICachable cachable)
{
dataTracking[type] = cachable;
}
}
return data;
}
}
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace TheXamlGuy.Framework.Core
{
public interface IPropertyBinderCollection : IReadOnlyCollection<PropertyBinder>
{
void Add(string key, PropertyBinder binder);
bool TryGet(string key, [MaybeNull] out PropertyBinder? value);
}
}
@@ -0,0 +1,25 @@
using System;
using System.Linq.Expressions;
namespace TheXamlGuy.Framework.Core
{
public interface IPropertyBuilder
{
IPropertyBinderCollection Binders { get; }
void Add<TProperty>(Expression<Func<TProperty>> property, Action propertyChanged);
void Add<TProperty>(Expression<Func<TProperty>> property, Action propertyChanged,
PropertyValidation propertyChangedValidation);
void Add<TProperty>(Expression<Func<TProperty>> property, PropertyValidation propertyChangedValidation);
void Add<TProperty>(Expression<Func<TProperty>> property, PropertyValidation propertyChangedValidation,
PropertyChangedMode mode);
void Add<TProperty>(Expression<Func<TProperty>> property, Action propertyChanged,
PropertyValidation propertyChangedValidation, PropertyChangedMode mode);
void Add<TProperty>(Expression<Func<TProperty>> property, Action propertyChanged, PropertyChangedMode mode);
}
}
@@ -0,0 +1,86 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace TheXamlGuy.Framework.Core
{
public class PropertyBinder
{
private readonly Action? propertyChanged;
private readonly PropertyValidation? propertyValidation;
internal PropertyBinder(string propertyName, Action propertyChanged)
{
PropertyName = propertyName;
this.propertyChanged = propertyChanged;
}
internal PropertyBinder(string propertyName,
Action propertyChanged,
PropertyChangedMode mode)
{
PropertyName = propertyName;
Mode = mode;
this.propertyChanged = propertyChanged;
}
internal PropertyBinder(string propertyName,
Action propertyChanged,
PropertyValidation validation,
PropertyChangedMode mode)
{
PropertyName = propertyName;
Mode = mode;
this.propertyChanged = propertyChanged;
propertyValidation = validation;
}
internal PropertyBinder(string propertyName,
Action propertyChanged,
PropertyValidation validation)
{
PropertyName = propertyName;
this.propertyChanged = propertyChanged;
propertyValidation = validation;
}
internal PropertyBinder(string propertyName, PropertyValidation validation)
{
PropertyName = propertyName;
propertyValidation = validation;
}
internal PropertyBinder(string propertyName, PropertyValidation validation, PropertyChangedMode mode)
{
PropertyName = propertyName;
Mode = mode;
propertyValidation = validation;
}
public PropertyChangedMode Mode { get; }
public string? PropertyName { get; }
public void Set()
{
propertyChanged?.Invoke();
}
public bool TryValidate([MaybeNull] out string message)
{
message = "";
if (propertyValidation is not null && propertyValidation.Validation?.Invoke() == false)
{
message = propertyValidation.Message;
return false;
}
propertyChanged?.Invoke();
return true;
}
}
}
@@ -0,0 +1,34 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace TheXamlGuy.Framework.Core
{
public class PropertyBinderCollection : IPropertyBinderCollection
{
private readonly Dictionary<string, PropertyBinder> binders = new();
public int Count => binders.Count;
public void Add(string key, PropertyBinder binder)
{
binders.Add(key, binder);
}
public IEnumerator<PropertyBinder> GetEnumerator()
{
return binders.Select(x => x.Value).GetEnumerator();
}
public bool TryGet(string key, [MaybeNull] out PropertyBinder? value)
{
return binders.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return binders.Select(x => x.Value).GetEnumerator();
}
}
}
@@ -0,0 +1,64 @@
using System;
using System.Linq.Expressions;
namespace TheXamlGuy.Framework.Core
{
public class PropertyBuilder : IPropertyBuilder
{
public PropertyBuilder(IPropertyBinderCollection binders)
{
Binders = binders;
}
public IPropertyBinderCollection Binders { get; }
public void Add<TProperty>(Expression<Func<TProperty>> property, PropertyValidation propertyChangedValidation)
{
string? name = GetPropertyName(property);
Binders.Add(name, new PropertyBinder(name, propertyChangedValidation));
}
public void Add<TProperty>(Expression<Func<TProperty>> property, Action propertyChanged,
PropertyValidation propertyChangedValidation)
{
string? name = GetPropertyName(property);
Binders.Add(name, new PropertyBinder(name, propertyChanged, propertyChangedValidation));
}
public void Add<TProperty>(Expression<Func<TProperty>> property, Action propertyChanged)
{
string? name = GetPropertyName(property);
Binders.Add(name, new PropertyBinder(name, propertyChanged));
}
public void Add<TProperty>(Expression<Func<TProperty>> property, PropertyValidation propertyChangedValidation, PropertyChangedMode mode)
{
string? name = GetPropertyName(property);
Binders.Add(name, new PropertyBinder(name, propertyChangedValidation, mode));
}
public void Add<TProperty>(Expression<Func<TProperty>> property, Action propertyChanged, PropertyValidation propertyChangedValidation, PropertyChangedMode mode)
{
string? name = GetPropertyName(property);
Binders.Add(name, new PropertyBinder(name, propertyChanged, propertyChangedValidation, mode));
}
public void Add<TProperty>(Expression<Func<TProperty>> property, Action propertyChanged, PropertyChangedMode mode)
{
string? name = GetPropertyName(property);
Binders.Add(name, new PropertyBinder(name, propertyChanged, mode));
}
private string GetPropertyName<T>(Expression<Func<T>> predicate)
{
if (predicate is null)
{
throw new ArgumentNullException(nameof(predicate));
}
Expression? body = predicate.Body;
MemberExpression? memberExpression = body as MemberExpression ?? (MemberExpression)((UnaryExpression)body).Operand;
return memberExpression.Member.Name;
}
}
}
@@ -0,0 +1,8 @@
namespace TheXamlGuy.Framework.Core
{
public enum PropertyChangedMode
{
Default,
Explicit
}
}
@@ -0,0 +1,22 @@
using System;
namespace TheXamlGuy.Framework.Core
{
public class PropertyValidation
{
public PropertyValidation(Func<bool> validation)
{
Validation = validation;
}
public PropertyValidation(Func<bool> validation, string message)
{
Validation = validation;
Message = message;
}
public string? Message { get; }
public Func<bool>? Validation { get; }
}
}
@@ -0,0 +1,38 @@
using System.Diagnostics.CodeAnalysis;
namespace TheXamlGuy.Framework.Core
{
public class PropertyValidationError<TKey, TValue> where TKey : class where TValue : class
{
private readonly Dictionary<TKey, TValue?> dictionary = new();
public int Count => dictionary.Count;
public TValue? this[[MaybeNull]TKey key]
{
get => dictionary.ContainsKey(key) ? dictionary[key] : null;
set
{
if (Contains(key))
{
dictionary.Remove(key);
}
dictionary.Add(key, value);
}
}
public bool Contains(TKey key)
{
return dictionary.ContainsKey(key);
}
public void Remove(TKey key)
{
if (Contains(key))
{
dictionary.Remove(key);
}
}
}
}