This commit is contained in:
Dan Clark
2024-11-02 19:57:33 +00:00
471 changed files with 13991 additions and 5471 deletions
+8
View File
@@ -0,0 +1,8 @@
namespace Toolkit.Foundation;
public record Activated
{
public static ActivatedEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
public static ActivatedEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record ActivatedEventArgs<TSender>(TSender? Sender = default);
+9
View File
@@ -0,0 +1,9 @@
namespace Toolkit.Foundation;
public record Activation
{
public static ActivationEventArgs<TSender, TValue> As<TSender, TValue>(TValue value) => new(value);
public static ActivationEventArgs<TSender> As<TSender>() =>
new();
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record ActivationBuilder(IActivation Value, object? Key = null);
@@ -0,0 +1,7 @@
namespace Toolkit.Foundation;
public record ActivationEventArgs<TSynchronize, TValue>(TValue? Value = default) :
IActivation;
public record ActivationEventArgs<TSynchronize>() :
IActivation;
+50
View File
@@ -0,0 +1,50 @@
using System.Security.Cryptography;
namespace Toolkit.Foundation;
public class AesDecryptor :
IDecryptor
{
private const int IvSize = 16;
public bool TryDecrypt(byte[] cipher,
byte[] key,
out byte[]? decryptedData)
{
decryptedData = null;
if (cipher is null || key is null || cipher.Length < IvSize)
{
return false;
}
try
{
Span<byte> iv = cipher.AsSpan(0, IvSize);
ReadOnlySpan<byte> encryptedContent = cipher.AsSpan(IvSize);
using Aes aes = Aes.Create();
aes.Key = key;
aes.IV = iv.ToArray();
using MemoryStream memoryStream = new(encryptedContent.ToArray());
using ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using CryptoStream cryptoStream = new(memoryStream, decryptor, CryptoStreamMode.Read);
using MemoryStream resultStream = new();
cryptoStream.CopyTo(resultStream);
decryptedData = resultStream.ToArray();
return true;
}
catch (CryptographicException)
{
return false;
}
catch (Exception)
{
return false;
}
}
}
+49
View File
@@ -0,0 +1,49 @@
using System.Security.Cryptography;
namespace Toolkit.Foundation;
public class AesEncryptor :
IEncryptor
{
private const int IvSize = 16;
public bool TryEncrypt(byte[] data,
byte[] key,
out byte[]? encryptedData)
{
encryptedData = null;
if (data is null || key is null || key.Length != 32)
{
return false;
}
try
{
using Aes aes = Aes.Create();
aes.Key = key;
aes.GenerateIV();
using MemoryStream memoryStream = new();
memoryStream.Write(aes.IV, 0, IvSize);
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (CryptoStream cryptoStream = new(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
}
encryptedData = memoryStream.ToArray();
return true;
}
catch (CryptographicException)
{
return false;
}
catch (Exception)
{
return false;
}
}
}
+10 -4
View File
@@ -2,18 +2,24 @@
namespace Toolkit.Foundation;
public class AppService(IEnumerable<IInitializer> initializers,
public class AppService(IEnumerable<IInitialization> initializations,
IEnumerable<IAsyncInitialization> asyncInitializations,
IPublisher publisher) :
IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
foreach (IInitializer initializer in initializers)
foreach (IInitialization initialization in initializations)
{
await initializer.Initialize();
initialization.Initialize();
}
await publisher.Publish<Started>(cancellationToken);
foreach (IAsyncInitialization initialization in asyncInitializations)
{
await initialization.Initialize();
}
publisher.Publish<StartedEventArgs>();
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+20 -5
View File
@@ -2,22 +2,37 @@
namespace Toolkit.Foundation;
public class AsyncLock(int initial = 1,
int maximum = 1) :
public class ActivityLock(IActivityIndicator activityIndicator) : AsyncLock
{
public override TaskAwaiter<AsyncLock> GetAwaiter()
{
activityIndicator.IsActive = true;
return base.GetAwaiter();
}
public override void Dispose()
{
activityIndicator.IsActive = false;
base.Dispose();
}
}
public class AsyncLock(int initial = 1,
int maximum = 1) :
IDisposable
{
private readonly SemaphoreSlim semaphore = new(initial, maximum);
public void Dispose()
public virtual void Dispose()
{
semaphore.Release();
}
public TaskAwaiter<AsyncLock> GetAwaiter() => LockAsync().GetAwaiter();
public virtual TaskAwaiter<AsyncLock> GetAwaiter() => LockAsync().GetAwaiter();
private async Task<AsyncLock> LockAsync()
{
await semaphore.WaitAsync();
return this;
}
}
}
+166 -29
View File
@@ -1,68 +1,205 @@
using HyperX;
using System.Collections;
using System.Collections.Concurrent;
using System.Reactive.Disposables;
using System.Collections;
namespace Toolkit.Foundation;
public class Cache<TValue>(IDisposer disposer) :
public class Cache<TValue>(IComparer<TValue> comparer) :
ICache<TValue>
{
private readonly List<TValue> cache = [];
private readonly List<TValue> items = [];
public void Add(TValue value)
public IEnumerable<TValue> Items =>
items;
public TValue this[int index] =>
items[index];
public void Add(TValue item)
{
if (value is null)
{
return;
}
disposer.Add(value, Disposable.Create(() => Remove(value)));
cache.Add(value);
int index = FindInsertIndex(item);
items.Insert(index, item);
}
public void Clear() => cache.Clear();
public void Clear() =>
items.Clear();
public IEnumerator<TValue> GetEnumerator() => cache.GetEnumerator();
public bool Contains(TValue item)
{
int index = items.IndexOf(item);
if (index >= 0)
{
return true;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
return false;
}
public bool Remove(TValue value) => cache.Remove(value);
public IEnumerator<TValue> GetEnumerator() =>
items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator();
public int IndexOf(TValue item) =>
items.IndexOf(item);
public bool Remove(TValue item)
{
int index = items.IndexOf(item);
if (index >= 0)
{
items.RemoveAt(index);
return true;
}
return false;
}
public bool TryGetValue(TValue key, out TValue? item)
{
int index = items.IndexOf(key);
if (index >= 0)
{
item = items[index];
return true;
}
item = default;
return false;
}
private int FindInsertIndex(TValue item)
{
int low = 0, high = items.Count - 1;
while (low <= high)
{
int mid = (low + high) / 2;
int cmp = comparer.Compare(items[mid], item);
if (cmp < 0)
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
while (low < items.Count && comparer.Compare(items[low], item) == 0)
{
low++;
}
return low;
}
}
public class Cache<TKey, TValue> :
public class Cache<TKey, TValue>(IComparer<TKey> comparer) :
ICache<TKey, TValue>
where TKey :
notnull
where TValue :
notnull
{
private readonly ConcurrentDictionary<TKey, TValue> cache = new();
private readonly List<KeyValuePair<TKey, TValue?>> items = [];
public void Add(TKey key,
TValue value)
public TValue? this[TKey key]
{
cache.TryAdd(key, value);
get
{
int index = items.BinarySearch(new KeyValuePair<TKey, TValue?>(key, default),
new KeyValuePairComparer<TKey, TValue?>(comparer));
if (index >= 0)
{
return items[index].Value;
}
return default;
}
set
{
int index = items.BinarySearch(new KeyValuePair<TKey, TValue?>(key, default),
new KeyValuePairComparer<TKey, TValue?>(comparer));
if (index >= 0)
{
items[index] = new KeyValuePair<TKey, TValue?>(key, value);
}
else
{
items.Insert(~index, new KeyValuePair<TKey, TValue?>(key, value));
}
}
}
public void Clear() => cache.Clear();
public void Add(TKey key, TValue value)
{
int index = items.BinarySearch(new KeyValuePair<TKey, TValue?>(key, default),
new KeyValuePairComparer<TKey, TValue?>(comparer));
public bool ContainsKey(TKey key) => cache.ContainsKey(key);
if (index < 0)
{
index = ~index;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => cache.GetEnumerator();
items.Insert(index, new KeyValuePair<TKey, TValue?>(key, value));
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Clear() => items.Clear();
public bool Remove(TKey key) => cache.Remove(key, out _);
public bool Contains(TKey key)
{
int index = items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0);
if (index >= 0)
{
items.RemoveAt(index);
return true;
}
return false;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() =>
items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
public int IndexOf(TKey key) =>
items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0);
public bool Remove(TKey key)
{
int index = items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0);
if (index >= 0)
{
items.RemoveAt(index);
return true;
}
return false;
}
public bool TryGetValue(TKey key, out TValue? value)
{
if (cache.TryGetValue(key, out value))
int index = items.BinarySearch(new KeyValuePair<TKey, TValue?>(key, default(TValue)),
new KeyValuePairComparer<TKey, TValue?>(comparer));
if (index >= 0)
{
value = items[index].Value;
return true;
}
value = default;
return false;
}
private class KeyValuePairComparer<TK, TV>(IComparer<TK> comparer) :
IComparer<KeyValuePair<TK, TV>>
{
private readonly IComparer<TK> comparer = comparer ?? Comparer<TK>.Default;
public int Compare(KeyValuePair<TK, TV> x, KeyValuePair<TK, TV> y) =>
comparer.Compare(x.Key, y.Key);
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Toolkit.Foundation;
public record Cancel
{
public static CancelEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
public static CancelEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record CancelEventArgs<TSender>(TSender Sender);
+6 -1
View File
@@ -1,3 +1,8 @@
namespace Toolkit.Foundation;
public record Changed<TValue>(TValue? Value = default) : INotification;
public record Changed
{
public static ChangedEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
public static ChangedEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record ChangedEventArgs<TSender>(TSender? Sender = default);
+5
View File
@@ -0,0 +1,5 @@
namespace Toolkit.Foundation;
public record Clipboard;
public record Clipboard<TValue>(TValue Value);
+8
View File
@@ -0,0 +1,8 @@
namespace Toolkit.Foundation;
public record Close
{
public static CloseEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
public static CloseEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record CloseEventArgs<TSender>(TSender? Sender = default);
+9
View File
@@ -0,0 +1,9 @@
namespace Toolkit.Foundation;
public record Closed
{
public static ClosedEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
public static ClosedEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record ClosedEventArgs<TSender>(TSender? Sender = default);
+5 -3
View File
@@ -2,12 +2,14 @@
namespace Toolkit.Foundation;
public partial class CommandValueViewModel<TValue>(IServiceProvider serviceProvider,
IServiceFactory serviceFactory,
public partial class CommandValueViewModel<TValue>(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer) :
ValueViewModel<TValue>(serviceProvider, serviceFactory, publisher, subscriber, disposer)
Observable<TValue>(provider, factory, mediator, publisher, subscriber, disposer)
where TValue : notnull
{
public IRelayCommand InvokeCommand =>
new AsyncRelayCommand(InvokeAsync);
+6 -5
View File
@@ -2,16 +2,17 @@
namespace Toolkit.Foundation;
public partial class CommandViewModel(IServiceProvider serviceProvider,
IServiceFactory serviceFactory,
public partial class CommandViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer) :
ObservableViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer)
Observable(provider, factory, mediator, publisher, subscriber, disposer)
{
public IRelayCommand InvokeCommand =>
public IRelayCommand InvokeCommand =>
new AsyncRelayCommand(InvokeAsync);
protected virtual Task InvokeAsync() =>
protected virtual Task InvokeAsync() =>
Task.CompletedTask;
}
+60
View File
@@ -0,0 +1,60 @@
using Microsoft.Extensions.DependencyInjection;
namespace Toolkit.Foundation;
public class Component :
IComponent
{
private readonly IComponentBuilder builder;
protected Component(IComponentBuilder builder) =>
this.builder = builder;
public static TComponent Create<TComponent>(IServiceProvider provider,
Action<IComponentBuilder> builderDelegate)
where TComponent : class, IComponent
{
IServiceFactory factory = provider.GetRequiredService<IServiceFactory>();
IComponentBuilder builder = ComponentBuilder.Create();
builderDelegate(builder);
return factory.Create<TComponent>(builder);
}
public IComponentBuilder Configure(Action<IComponentBuilder>? componentDelegate = null)
{
if (componentDelegate is not null)
{
componentDelegate(builder);
}
return Configuring(builder);
}
public IComponentBuilder Configure<TConfiguration>(Action<IConfigurationBuilder<TConfiguration>>? configurationDelegate = null,
Action<IComponentBuilder>? componentDelegate = null)
where TConfiguration : class, new()
{
if (componentDelegate is not null)
{
componentDelegate(builder);
}
if (configurationDelegate is not null)
{
ConfigurationBuilder<TConfiguration> configurationBuilder = new();
configurationDelegate(configurationBuilder);
if (configurationBuilder.Section is { Length: > 0 } section)
{
builder.AddConfiguration(section, configurationBuilder.Configuration ?? new TConfiguration());
}
}
return Configuring(builder);
}
public virtual IComponentBuilder Configuring(IComponentBuilder builder) => builder;
}
+47 -36
View File
@@ -4,83 +4,81 @@ using Microsoft.Extensions.Hosting;
namespace Toolkit.Foundation;
public class ComponentBuilder :
public class ComponentBuilder :
IComponentBuilder
{
private readonly IHostBuilder hostBuilder;
private bool configurationRegistered;
public string ContentRoot { get; set; } = "Local";
public string ConfigurationFile { get; set; } = "Settings.json";
private ComponentBuilder()
{
hostBuilder = new HostBuilder()
.UseContentRoot("Local", true)
.ConfigureAppConfiguration(config =>
{
config.AddJsonFile("Settings.json", true, true);
})
.ConfigureServices((context, services) =>
{
services.AddScoped<IComponentHost, ComponentHost>();
services.AddScoped<IServiceFactory>(provider =>
new ServiceFactory((type, parameters) =>
ActivatorUtilities.CreateInstance(provider, type, parameters!)));
new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type,
parameters?.Where(x => x is not null).ToArray()!)));
services.AddSingleton<IDisposer, Disposer>();
services.AddScoped<SubscriptionCollection>();
services.AddScoped<ISubscriptionManager, SubscriptionManager>();
services.AddTransient<ISubscriber, Subscriber>();
services.AddTransient<IHandlerProvider, HandlerProvider>();
services.AddScoped<ISubscriber, Subscriber>();
services.AddTransient<IPublisher, Publisher>();
services.AddTransient<IMediator, Mediator>();
services.AddScoped<IDisposer, Disposer>();
services.AddTransient<INavigationScope, NavigationScope>();
services.AddTransient<IValidation, Validation>();
services.AddTransient<IValidatorCollection, ValidatorCollection>();
services.AddTransient<INavigationProvider, NavigationProvider>();
services.AddScoped<INavigationContextCollection, NavigationContextCollection>();
services.AddTransient<INavigationContextProvider, NavigationContextProvider>();
services.AddTransient<IContentFactory, ContentFactory>();
services.AddTransient<INavigation, Navigation>();
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>();
services.AddTransient<INavigationRegionProvider, NavigationRegionProvider>();
services.AddHandler<NavigateHandler>();
services.AddHandler<NavigateBackHandler>();
});
}
public static IComponentBuilder Create() =>
new ComponentBuilder();
public static IComponentBuilder Create() => new ComponentBuilder();
public IComponentBuilder AddConfiguration<TConfiguration>(Action<TConfiguration> configurationDelegate)
where TConfiguration : ComponentConfiguration, new()
where TConfiguration :
class, new()
{
if (configurationRegistered)
{
return this;
}
configurationRegistered = true;
TConfiguration configuration = new();
if (configurationDelegate is not null)
{
configurationDelegate(configuration);
}
hostBuilder.ConfigureServices(services =>
{
services.AddConfiguration<ComponentConfiguration>(section: configuration.GetType().Name,
configuration: configuration);
AddConfiguration(typeof(TConfiguration).Name, configuration);
return this;
}
services.AddConfiguration(configuration);
});
public IComponentBuilder AddConfiguration<TConfiguration>(string section,
TConfiguration? configuration = null)
where TConfiguration :
class, new()
{
hostBuilder.AddConfiguration(section, ConfigurationFile, configuration);
return this;
}
public IComponentHost Build()
public IComponentBuilder AddConfiguration<TConfiguration>(string section)
where TConfiguration :
class, new()
{
IHost host = hostBuilder.Build();
return host.Services.GetRequiredService<IComponentHost>();
AddConfiguration<TConfiguration>(section, null);
return this;
}
public IComponentBuilder AddServices(Action<IServiceCollection> configureDelegate)
@@ -88,4 +86,17 @@ public class ComponentBuilder :
hostBuilder.ConfigureServices(configureDelegate);
return this;
}
public IComponentHost Build()
{
hostBuilder
.UseContentRoot(ContentRoot, true)
.ConfigureAppConfiguration(config =>
{
config.AddJsonFile(ConfigurationFile, true, true);
});
IHost host = hostBuilder.Build();
return host.Services.GetRequiredService<IComponentHost>();
}
}
@@ -1,13 +0,0 @@
using System.Text.Json.Serialization;
namespace Toolkit.Foundation;
public record ComponentConfiguration
{
public string? Description { get; set; }
public string? Name { get; set; }
[JsonInclude]
internal Guid Id { get; set; } = Guid.NewGuid();
}
@@ -1,111 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Toolkit.Foundation;
public partial class ComponentConfigurationViewModel<TConfiguration, TValue, THeader, TDescription, TAction> :
ValueViewModel<TValue>,
IComponentConfigurationViewModel,
INotificationHandler<Changed<TConfiguration>>
where TConfiguration : class
{
public ComponentConfigurationViewModel(IServiceProvider serviceProvider,
IServiceFactory serviceFactory,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
THeader header,
TDescription description,
TAction action) : base(serviceProvider, serviceFactory, publisher, subscriber, disposer)
{
}
public Task Handle(Changed<TConfiguration> args,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
public partial class ComponentConfigurationViewModel<TConfiguration, TValue, TAction>(IServiceProvider serviceProvider,
IServiceFactory serviceFactory,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
TAction action,
TConfiguration configuration,
Func<TConfiguration, TValue> valueDelegate,
object header,
object description) :
ValueViewModel<TValue>(serviceProvider, serviceFactory, publisher, subscriber, disposer),
IComponentConfigurationViewModel,
INotificationHandler<Changed<TConfiguration>>
where TConfiguration : class
{
[ObservableProperty]
private TAction action = action;
[ObservableProperty]
private object header = header;
[ObservableProperty]
private object description = description;
public override Task Activated()
{
Value = valueDelegate.Invoke(configuration);
return base.Activated();
}
public Task Handle(Changed<TConfiguration> args,
CancellationToken cancellationToken = default)
{
if (args.Value is TConfiguration configuration)
{
Value = valueDelegate.Invoke(configuration);
}
return Task.CompletedTask;
}
}
public partial class ComponentConfigurationViewModel<TConfiguration, TValue, TDescription, TAction>(IServiceProvider serviceProvider,
IServiceFactory serviceFactory,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
TAction action,
TDescription description,
TConfiguration configuration,
Func<TConfiguration, TValue> valueDelegate,
object header) :
ValueViewModel<TValue>(serviceProvider, serviceFactory, publisher, subscriber, disposer),
IComponentConfigurationViewModel,
INotificationHandler<Changed<TConfiguration>>
where TConfiguration : class
{
[ObservableProperty]
private TAction action = action;
[ObservableProperty]
private object header = header;
[ObservableProperty]
private TDescription description = description;
public override Task Activated()
{
Value = valueDelegate.Invoke(configuration);
return base.Activated();
}
public Task Handle(Changed<TConfiguration> args,
CancellationToken cancellationToken = default)
{
if (args.Value is TConfiguration configuration)
{
Value = valueDelegate.Invoke(configuration);
}
return Task.CompletedTask;
}
}
+67
View File
@@ -0,0 +1,67 @@
using Microsoft.Extensions.DependencyInjection;
namespace Toolkit.Foundation;
public class ComponentFactory(IServiceProvider provider,
IProxyServiceCollection<IComponentBuilder> proxy,
IComponentScopeCollection scopes) :
IComponentFactory
{
public IComponentHost? Create<TComponent, TConfiguration>(string key,
Action<IConfigurationBuilder<TConfiguration>>? configurationDelegate = null,
Action<IComponentBuilder>? componentDelegate = null,
Action<IServiceCollection>? servicesDelegate = null)
where TComponent : IComponent
where TConfiguration : class, new()
{
if (provider.GetRequiredService<TComponent>() is TComponent component)
{
IComponentBuilder builder = component.Configure(configurationDelegate,
componentDelegate);
builder.AddServices(services =>
{
services.AddTransient(_ =>
provider.GetRequiredService<IProxyServiceCollection<IComponentBuilder>>());
services.AddTransient(_ =>
provider.GetRequiredService<IComponentFactory>());
services.AddTransient(_ =>
provider.GetRequiredService<IProxyService<IPublisher>>());
services.AddTransient(_ =>
provider.GetRequiredService<IProxyService<IComponentHostCollection>>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationRegionCollection>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationRegionProvider>());
services.AddScoped(_ =>
provider.GetRequiredService<IComponentScopeCollection>());
services.AddTransient(_ =>
provider.GetRequiredService<IComponentScopeProvider>());
services.AddRange(proxy.Services);
services.AddSingleton(new NamedComponent(key));
if (servicesDelegate is not null)
{
servicesDelegate(services);
}
});
IComponentHost host = builder.Build();
scopes.Add(new ComponentScopeDescriptor(key,
host.Services.GetRequiredService<IServiceProvider>()));
return host;
}
return default;
}
}
+16 -8
View File
@@ -3,26 +3,34 @@ using Microsoft.Extensions.Hosting;
namespace Toolkit.Foundation;
public sealed class ComponentHost(IServiceProvider services,
IEnumerable<IInitializer> initializers,
public class ComponentHost(IServiceProvider services,
IEnumerable<IInitialization> initializations,
IEnumerable<IAsyncInitialization> asyncInitializations,
IEnumerable<IHostedService> hostedServices) :
IComponentHost
{
public IServiceProvider Services => services;
public ComponentConfiguration? Configuration =>
Services.GetService<ComponentConfiguration>();
public void Dispose()
{
}
public TConfiguration? GetConfiguration<TConfiguration>()
where TConfiguration : class
{
return Services.GetService<TConfiguration>();
}
public async Task StartAsync(CancellationToken cancellationToken = default)
{
foreach (IInitializer initializer in initializers)
foreach (IInitialization initialization in initializations)
{
await initializer.Initialize();
initialization.Initialize();
}
foreach (IAsyncInitialization initialization in asyncInitializations)
{
await initialization.Initialize();
}
foreach (IHostedService service in hostedServices)
@@ -38,4 +46,4 @@ public sealed class ComponentHost(IServiceProvider services,
await service.StopAsync(cancellationToken);
}
}
}
}
@@ -18,4 +18,4 @@ public class ComponentHostCollection :
IEnumerator IEnumerable.GetEnumerator() =>
hosts.GetEnumerator();
}
}
+18 -15
View File
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Toolkit.Foundation;
@@ -7,43 +8,45 @@ public class ComponentInitializer(IEnumerable<IComponent> components,
IComponentHostCollection hosts,
IComponentScopeCollection scopes,
IServiceProvider provider) :
IInitializer
IInitialization
{
public async Task Initialize()
public void Initialize()
{
foreach (IComponent component in components)
{
IComponentBuilder builder = component.Create();
IComponentBuilder builder = component.Configure();
builder.AddServices(services =>
{
services.AddTransient(_ =>
services.AddTransient(_ =>
provider.GetRequiredService<IProxyService<IPublisher>>());
services.AddTransient(_ =>
provider.GetRequiredService<IProxyService<IComponentHostCollection>>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationContextCollection>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationContextProvider>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationRegionCollection>());
services.AddScoped(_ =>
services.AddScoped(_ =>
provider.GetRequiredService<INavigationRegionProvider>());
services.AddScoped(_ =>
provider.GetRequiredService<IComponentScopeCollection>());
services.AddTransient(_ =>
services.AddTransient(_ =>
provider.GetRequiredService<IComponentScopeProvider>());
services.AddRange(typedServices.Services);
services.AddSingleton(new NamedComponent(component.GetType().Name));
});
IComponentHost host = builder.Build();
scopes.Add(component.GetType().Name,
host.Services.GetRequiredService<IServiceProvider>());
scopes.Add(new ComponentScopeDescriptor(component.GetType().Name,
provider.GetRequiredService<IServiceProvider>()));
hosts.Add(host);
await host.StartAsync();
host.Start();
}
}
}
}
@@ -1,4 +1,4 @@
namespace Toolkit.Foundation;
public class ComponentScopeCollection : Dictionary<string, IServiceProvider>,
IComponentScopeCollection;
public class ComponentScopeCollection : List<ComponentScopeDescriptor>,
IComponentScopeCollection;
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record ComponentScopeDescriptor(string Key, IServiceProvider Services);
+4 -5
View File
@@ -3,10 +3,9 @@
public class ComponentScopeProvider(IComponentScopeCollection scopes) :
IComponentScopeProvider
{
public IServiceProvider? Get(string name)
public ComponentScopeDescriptor? Get(string key)
{
return scopes.TryGetValue(name,
out IServiceProvider? scope) ? scope : default;
return scopes.FirstOrDefault(x => x.Key == key) is ComponentScopeDescriptor
descriptor ? descriptor : default;
}
}
}
-9
View File
@@ -1,9 +0,0 @@
namespace Toolkit.Foundation;
public class Configuration<TConfiguration>(IConfigurationReader<TConfiguration> reader) :
IConfiguration<TConfiguration>
where TConfiguration :
class
{
public TConfiguration Value => reader.Read();
}
@@ -0,0 +1,10 @@
namespace Toolkit.Foundation;
public class ConfigurationBuilder<TConfiguration> :
IConfigurationBuilder<TConfiguration>
where TConfiguration : class
{
public string? Section { get; set; }
public TConfiguration? Configuration { get; set; }
}
+27
View File
@@ -0,0 +1,27 @@
using System.Collections.Concurrent;
namespace Toolkit.Foundation;
public class ConfigurationCache :
IConfigurationCache
{
private readonly ConcurrentDictionary<string, object?> cache = new();
public void Set<TConfiguration>(string section,
TConfiguration configuration) => cache[section] = configuration;
public bool Remove(string section) => cache.TryRemove(section, out _);
public bool TryGet<TConfiguration>(string section,
out TConfiguration? configuration)
{
if (cache.TryGetValue(section, out object? cachedValue))
{
configuration = (TConfiguration?)cachedValue;
return true;
}
configuration = default;
return false;
}
}
@@ -1,18 +1,16 @@
namespace Toolkit.Foundation;
public class ConfigurationChangedHandler<TConfiguration, TValue>(ConfigurationValue<TConfiguration, TValue> configurationValue) :
INotificationHandler<Changed<TConfiguration>>
INotificationHandler<ChangedEventArgs<TConfiguration>>
where TValue :
class, new()
{
public Task Handle(Changed<TConfiguration> args,
CancellationToken cancellationToken = default)
public Task Handle(ChangedEventArgs<TConfiguration> args)
{
if (args.Value is TConfiguration configuration)
if (args.Sender is TConfiguration configuration)
{
if (configurationValue.TryUpdate(configuration, out TValue value))
{
}
}
@@ -0,0 +1,14 @@
namespace Toolkit.Foundation;
public class ConfigurationDescriptor<TConfiguration>(string section,
IConfigurationReader<TConfiguration> reader) :
IConfigurationDescriptor<TConfiguration>
where TConfiguration :
class
{
public TConfiguration Value => reader.Read();
public string Section => section;
public string Name => section.Split(':').LastOrDefault() ?? section;
}
+2 -2
View File
@@ -1,9 +1,9 @@
namespace Toolkit.Foundation;
public class ConfigurationFactory<TConfiguration>(Func<TConfiguration> factory) :
IConfigurationFactory<TConfiguration>
IConfigurationFactory<TConfiguration>
where TConfiguration :
class
{
public object Create() => factory.Invoke();
}
}
+2 -2
View File
@@ -2,10 +2,10 @@
namespace Toolkit.Foundation;
public class ConfigurationFile<TConfiguration>(IFileInfo fileInfo) :
public class ConfigurationFile<TConfiguration>(IFileInfo fileInfo) :
IConfigurationFile<TConfiguration>
where TConfiguration :
class
{
public IFileInfo FileInfo => fileInfo;
}
}
@@ -1,15 +1,15 @@
namespace Toolkit.Foundation;
public class ConfigurationInitializer<TConfiguration>(IPublisher publisher,
IConfigurationReader<TConfiguration> reader,
public class ConfigurationInitializer<TConfiguration>(IConfigurationReader<TConfiguration> reader,
IConfigurationWriter<TConfiguration> writer,
IConfigurationFactory<TConfiguration> factory) :
IConfigurationFactory<TConfiguration> factory,
IPublisher publisher) :
IConfigurationInitializer<TConfiguration>,
IInitializer
IInitialization
where TConfiguration :
class
{
public async Task Initialize()
public void Initialize()
{
if (!reader.TryRead(out TConfiguration? configuration))
{
@@ -20,6 +20,6 @@ public class ConfigurationInitializer<TConfiguration>(IPublisher publisher,
}
}
await publisher.PublishUI(new Changed<TConfiguration>(configuration));
publisher.PublishUI(new ActivatedEventArgs<TConfiguration>(configuration));
}
}
}
+36
View File
@@ -0,0 +1,36 @@
namespace Toolkit.Foundation;
public static class ConfigurationLock
{
private static readonly ReaderWriterLockSlim readerWriterLock = new();
public static IDisposable EnterRead()
{
readerWriterLock.EnterReadLock();
return new ConfigurationReaderLockDisposer(readerWriterLock);
}
public static IDisposable EnterWrite()
{
readerWriterLock.EnterWriteLock();
return new ConfigurationWriterLockDisposer(readerWriterLock);
}
private class ConfigurationWriterLockDisposer(ReaderWriterLockSlim lockSlim) :
IDisposable
{
public void Dispose()
{
lockSlim.ExitWriteLock();
}
}
private class ConfigurationReaderLockDisposer(ReaderWriterLockSlim lockSlim) :
IDisposable
{
public void Dispose()
{
lockSlim.ExitReadLock();
}
}
}
+13 -7
View File
@@ -1,7 +1,11 @@
namespace Toolkit.Foundation;
using Microsoft.Extensions.DependencyInjection;
public class ConfigurationMonitor<TConfiguration>(IConfigurationFile<TConfiguration> file,
IConfigurationReader<TConfiguration> reader,
namespace Toolkit.Foundation;
public class ConfigurationMonitor<TConfiguration>(string section,
IConfigurationCache cache,
IConfigurationFile<TConfiguration> file,
IServiceProvider serviceProvider,
IPublisher publisher) :
IConfigurationMonitor<TConfiguration>
where TConfiguration :
@@ -11,12 +15,14 @@ public class ConfigurationMonitor<TConfiguration>(IConfigurationFile<TConfigurat
public Task StartAsync(CancellationToken cancellationToken)
{
async void ChangedHandler(object sender,
void ChangedHandler(object sender,
FileSystemEventArgs args)
{
if (reader.Read() is { } configuration)
if (serviceProvider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section) is
IConfigurationDescriptor<TConfiguration> configuration)
{
await publisher.PublishUI(new Changed<TConfiguration>(configuration));
cache.Remove(section);
publisher.PublishUI(new ChangedEventArgs<TConfiguration>(configuration.Value));
}
}
@@ -43,4 +49,4 @@ public class ConfigurationMonitor<TConfiguration>(IConfigurationFile<TConfigurat
watcher?.Dispose();
return Task.CompletedTask;
}
}
}
+1 -1
View File
@@ -30,4 +30,4 @@ public class ConfigurationReader<TConfiguration>(IConfigurationSource<TConfigura
configuration = default;
return false;
}
}
}
+136 -76
View File
@@ -1,132 +1,192 @@
using Microsoft.Extensions.FileProviders;
using Json.More;
using Microsoft.Extensions.FileProviders;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace Toolkit.Foundation;
public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfiguration> configurationFile,
string section,
IConfigurationCache cache,
JsonSerializerOptions? serializerOptions = null) :
IConfigurationSource<TConfiguration>
where TConfiguration :
class
{
private readonly object lockingObject = new();
private static readonly Func<JsonSerializerOptions> defaultSerializerOptions = new(() =>
private static readonly Func<JsonSerializerOptions> defaultSerializerOptions = () =>
{
return new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters =
{
new JsonStringEnumConverter(),
new DictionaryStringObjectJsonConverter()
new DictionaryStringObjectJsonConverter(),
new JsonArrayTupleConverter()
}
};
});
};
public void Set(TConfiguration value) => Set((object)value);
public void Set(object value)
{
lock (lockingObject)
using (ConfigurationLock.EnterWrite())
{
IFileInfo fileInfo = configurationFile.FileInfo;
EnsureFileExists(fileInfo.PhysicalPath);
if (!File.Exists(fileInfo.PhysicalPath))
string? content;
JsonNode? rootNode;
using (Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
using (StreamReader reader = new(stream))
{
string? fileDirectoryPath = Path.GetDirectoryName(fileInfo.PhysicalPath);
if (!string.IsNullOrEmpty(fileDirectoryPath))
{
Directory.CreateDirectory(fileDirectoryPath);
}
content = reader.ReadToEnd();
stream.Seek(0, SeekOrigin.Begin);
File.WriteAllText(fileInfo.PhysicalPath!, "{}");
rootNode = JsonNode.Parse(content);
}
static Stream OpenReadWrite(IFileInfo fileInfo)
{
return fileInfo.PhysicalPath is not null
? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)
: fileInfo.CreateReadStream();
}
JsonNode? valueNode = JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(value, serializerOptions ?? defaultSerializerOptions()));
using Stream stream = OpenReadWrite(fileInfo);
using StreamReader? reader = new(stream);
ApplyConfigurationUpdates(ref rootNode, valueNode, section);
string? content = reader.ReadToEnd();
using JsonDocument jsonDocument = JsonDocument.Parse(content);
using Stream stream2 = OpenReadWrite(fileInfo);
Utf8JsonWriter writer = new(stream2, new JsonWriterOptions()
{
Indented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
writer.WriteStartObject();
bool isWritten = false;
JsonDocument optionsElement = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(value,
serializerOptions ?? defaultSerializerOptions()));
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();
stream2.SetLength(stream2.Position);
using Stream stream2 = new FileStream(fileInfo.PhysicalPath!, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
JsonSerializer.Serialize(stream2, rootNode, serializerOptions ?? defaultSerializerOptions());
cache.Set(section, value);
}
}
public bool TryGet(out TConfiguration? value)
{
lock (lockingObject)
if (cache.TryGet(section, out value))
{
return true;
}
using (ConfigurationLock.EnterRead())
{
IFileInfo fileInfo = configurationFile.FileInfo;
if (File.Exists(fileInfo.PhysicalPath))
if (!File.Exists(fileInfo.PhysicalPath))
{
static Stream OpenRead(IFileInfo fileInfo)
value = default;
return false;
}
using Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
using StreamReader reader = new(stream);
string content = reader.ReadToEnd();
JsonNode? rootNode = JsonNode.Parse(content);
string[] segments = section.Split(':');
JsonNode? currentNode = rootNode;
int lastIndex = segments.Length - 1;
for (int i = 0; i < lastIndex; i++)
{
if (currentNode is null)
{
return fileInfo.PhysicalPath is not null
? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)
: fileInfo.CreateReadStream();
value = default;
return false;
}
using Stream stream = OpenRead(fileInfo);
using StreamReader? reader = new(stream);
currentNode = currentNode[segments[i]];
}
string? content = reader.ReadToEnd();
using JsonDocument jsonDocument = JsonDocument.Parse(content);
if (jsonDocument.RootElement.TryGetProperty(section, out JsonElement sectionValue))
{
value = JsonSerializer.Deserialize<TConfiguration>(sectionValue.ToString(), serializerOptions ?? defaultSerializerOptions());
return true;
}
if (currentNode != null && currentNode[segments[lastIndex]] is JsonNode sectionNode)
{
value = JsonSerializer.Deserialize<TConfiguration>(sectionNode, serializerOptions ?? defaultSerializerOptions());
cache.Set(section, value);
return true;
}
value = default;
return false;
}
}
}
private void ApplyConfigurationUpdates(ref JsonNode? rootNode, JsonNode? valueNode, string section)
{
string[] segments = section.Split(':');
JsonNode? currentNode = rootNode;
int lastIndex = segments.Length - 1;
for (int i = 0; i < lastIndex; i++)
{
if (currentNode is null)
{
return;
}
string currentKey = segments[i];
if (currentNode[currentKey] is null)
{
currentNode[currentKey] = new JsonObject();
}
currentNode = currentNode[currentKey];
}
if (currentNode is not null)
{
string lastKey = segments[lastIndex];
if (valueNode is JsonArray)
{
currentNode[lastKey] = valueNode;
}
else
{
currentNode[lastKey] = MergeNodes(currentNode[lastKey], valueNode);
}
}
}
private JsonNode? MergeNodes(JsonNode? existingNode, JsonNode? newNode)
{
if (existingNode is JsonObject existingObject && newNode is JsonObject newObject)
{
foreach (KeyValuePair<string, JsonNode?> property in newObject)
{
existingObject[property.Key] = MergeNodes(existingObject[property.Key], CloneNode(property.Value));
}
return existingObject;
}
return newNode;
}
private JsonNode? CloneNode(JsonNode? node)
{
if (node is null)
{
return null;
}
string serialized = JsonSerializer.Serialize(node, serializerOptions ?? defaultSerializerOptions());
return JsonNode.Parse(serialized);
}
private void EnsureFileExists(string? filePath)
{
if (filePath == null || File.Exists(filePath))
{
return;
}
string? directoryPath = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
File.WriteAllText(filePath, "{}");
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
namespace Toolkit.Foundation;
public class ConfigurationValue<TConfiguration, TValue>(Func<TConfiguration, Action<TValue>> changed)
where TValue :
where TValue :
class, new()
{
private TValue? currentValue;
public bool TryUpdate(TConfiguration configuration,
public bool TryUpdate(TConfiguration configuration,
out TValue value)
{
TValue newValue = new();
@@ -0,0 +1,118 @@
namespace Toolkit.Foundation;
public partial class ConfigurationValueViewModel<TConfiguration, TValue>(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write) :
Observable<TValue>(provider, factory, mediator, publisher, subscriber, disposer),
INotificationHandler<ChangedEventArgs<TConfiguration>>
where TConfiguration : class
{
public async Task Handle(ChangedEventArgs<TConfiguration> args)
{
if (args.Sender is TConfiguration configuration)
{
// await Task.Run(() => Value = read(configuration));
}
}
public override async Task OnActivated()
{
await Task.Run(() => Value = read(configuration));
await base.OnActivated();
}
protected override async void OnChanged(TValue? value)
{
if (IsActivated)
{
await Task.Run(() => writer.Write(args => write(value, args)));
}
base.OnChanged(value);
}
}
public partial class ConfigurationValueViewModel<TConfiguration, TValue, TItem> :
ObservableCollection<TValue, TItem>,
INotificationHandler<ChangedEventArgs<TConfiguration>>
where TConfiguration : class
where TItem : notnull,
IDisposable
{
private readonly TConfiguration configuration;
private readonly Func<TConfiguration, TValue?> read;
private readonly Action<TValue?, TConfiguration> write;
private readonly IWritableConfiguration<TConfiguration> writer;
public ConfigurationValueViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write,
TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer, value)
{
this.configuration = configuration;
this.writer = writer;
this.read = read;
this.write = write;
Value = value;
}
public ConfigurationValueViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
IEnumerable<TItem> items,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write,
TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer, items, value)
{
this.configuration = configuration;
this.writer = writer;
this.read = read;
this.write = write;
Value = value;
}
public async Task Handle(ChangedEventArgs<TConfiguration> args)
{
if (args.Sender is TConfiguration configuration)
{
// await Task.Run(() => Value = read(configuration));
}
}
public override async Task OnActivated()
{
await Task.Run(() => Value = read(configuration));
await base.OnActivated();
}
protected override async void OnChanged(TValue? value)
{
if (IsActivated)
{
await Task.Run(() => writer.Write(args => write(value, args)));
}
base.OnChanged(value);
}
}
+10 -7
View File
@@ -1,19 +1,22 @@
namespace Toolkit.Foundation;
public class ConfigurationWriter<TConfiguration>(IConfigurationSource<TConfiguration> source) :
public class ConfigurationWriter<TConfiguration>(IConfigurationSource<TConfiguration> source,
IConfigurationFactory<TConfiguration> factory) :
IConfigurationWriter<TConfiguration>
where TConfiguration :
class
{
public void Write(Action<TConfiguration> updateDelegate)
{
if (source.TryGet(out TConfiguration? value))
if (!source.TryGet(out TConfiguration? value))
{
if (value is not null)
{
updateDelegate?.Invoke(value);
Write(value);
}
value = (TConfiguration)factory.Create();
}
if (value is not null)
{
updateDelegate?.Invoke(value);
Write(value);
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Toolkit.Foundation;
public record Confirm
{
public static ConfirmEventArgs<TValue> As<TValue>(TValue value) =>
new(value);
public static ConfirmEventArgs<TValue> As<TValue>() where TValue : new() =>
new(new TValue());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record ConfirmEventArgs<TValue>(TValue Value);
+28
View File
@@ -0,0 +1,28 @@
namespace Toolkit.Foundation;
public class ContentFactory(IServiceProvider provider,
IServiceFactory factory) :
IContentFactory
{
public object? Create(IContentTemplateDescriptor descriptor,
object[] parameters)
{
object? content = parameters is { Length: > 0 }
? factory.Create(descriptor.ContentType, args =>
{
if (args is IInitialization initialization)
{
initialization.Initialize();
}
}, parameters)
: provider.GetRequiredKeyedService(descriptor.ContentType, args =>
{
if (args is IInitialization initialization)
{
initialization.Initialize();
}
}, descriptor.Key);
return content;
}
}
@@ -1,7 +1,7 @@
namespace Toolkit.Foundation;
public class ContentTemplateDescriptor(object key,
Type viewModelType,
Type viewModelType,
Type viewType,
params object[]? parameters) :
IContentTemplateDescriptor
@@ -1,18 +0,0 @@
using System.Xml.Linq;
namespace Toolkit.Foundation;
public class ContentTemplateDescriptorProvider(IEnumerable<IContentTemplateDescriptor> contentTemplates) :
IContentTemplateDescriptorProvider
{
public IContentTemplateDescriptor? Get(object key)
{
if (contentTemplates.FirstOrDefault(x => x.Key.Equals(key) || x.Key.Equals($"{key}".Replace("ViewModel", "")))
is IContentTemplateDescriptor viewModelTemplate)
{
return viewModelTemplate;
}
return default;
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Toolkit.Foundation;
public record Count
{
public static CountEventArgs<TSender> As<TSender>(TSender sender) =>
new(sender);
public static CountEventArgs<TSender> As<TSender>() where TSender : new() =>
new(new TSender());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record CountEventArgs<TSender>(TSender Sender);
+9 -4
View File
@@ -1,5 +1,10 @@
namespace Toolkit.Foundation;
namespace Toolkit.Foundation;
public record Create<TValue>(TValue Value) :
INotification;
public record Create
{
public static CreateEventArgs<TSender> As<TSender>(TSender sender, params object[] parameters) =>
new(sender, parameters);
public static CreateEventArgs<TSender> As<TSender>(params object[] parameters) where TSender : new() =>
new(new TSender(), parameters);
}
+4
View File
@@ -0,0 +1,4 @@
namespace Toolkit.Foundation;
public record CreateEventArgs<TSender>(TSender? Sender = default,
params object[] Parameters);
+10
View File
@@ -0,0 +1,10 @@
namespace Toolkit.Foundation;
public record Created
{
public static CreatedEventArgs<TSender> As<TSender>(TSender sender) =>
new(sender);
public static CreatedEventArgs<TSender> As<TSender>() where TSender : new() =>
new(new TSender());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record CreatedEventArgs<TSender>(TSender? Sender = default);
+8
View File
@@ -0,0 +1,8 @@
namespace Toolkit.Foundation;
public record Deactivated
{
public static DeactivatedEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
public static DeactivatedEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
}
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record DeactivatedEventArgs<TSender>(TSender? Sender = default);
+9
View File
@@ -0,0 +1,9 @@
namespace Toolkit.Foundation;
public class DecoratorService<T> :
IDecoratorService<T>
{
public T? Value { get; private set; }
public void Set(T? value) => Value = value;
}
-68
View File
@@ -1,68 +0,0 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Toolkit.Foundation;
namespace Toolkit.Foundation;
public class DefaultBuilder :
HostBuilder
{
public static IHostBuilder Create()
{
return new HostBuilder()
.UseContentRoot("Local", true)
.ConfigureAppConfiguration(config =>
{
config.AddJsonFile("Settings.json", true, true);
})
.ConfigureServices((context, services) =>
{
services.AddScoped<IServiceFactory>(provider =>
new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, parameters!)));
services.AddSingleton<IComponentHostCollection,
ComponentHostCollection>();
services.AddScoped<SubscriptionCollection>();
services.AddScoped<ISubscriptionManager, SubscriptionManager>();
services.AddTransient<ISubscriber, Subscriber>();
services.AddTransient<IPublisher, Publisher>();
services.AddScoped<IMediator, Mediator>();
services.AddScoped<IProxyService<IPublisher>>(provider =>
new ProxyService<IPublisher>(provider.GetRequiredService<IPublisher>()));
services.AddScoped<IProxyService<INavigationContextProvider>>(provider =>
new ProxyService<INavigationContextProvider>(provider.GetRequiredService<INavigationContextProvider>()));
services.AddScoped<IProxyService<IComponentHostCollection>>(provider =>
new ProxyService<IComponentHostCollection>(provider.GetRequiredService<IComponentHostCollection>()));
services.AddScoped<IDisposer, Disposer>();
services.AddTransient<IContentTemplateDescriptorProvider, ContentTemplateDescriptorProvider>();
services.AddTransient<INavigationProvider, NavigationProvider>();
services.AddScoped<INavigationContextCollection, NavigationContextCollection>();
services.AddTransient<INavigationContextProvider, NavigationContextProvider>();
services.AddTransient<INavigationScope, NavigationScope>();
services.AddScoped<IComponentScopeCollection, ComponentScopeCollection>(provider => new ComponentScopeCollection
{
{ "Default", provider.GetRequiredService<IServiceProvider>() }
});
services.AddTransient<IComponentScopeProvider, ComponentScopeProvider>();
services.AddHandler<NavigateHandler>();
services.AddHandler<NavigateBackHandler>();
services.AddInitializer<ComponentInitializer>();
services.AddHostedService<AppService>();
});
}
}
+66
View File
@@ -0,0 +1,66 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Toolkit.Foundation;
public class DefaultHostBuilder :
HostBuilder
{
public static IHostBuilder Create()
{
return new HostBuilder()
.ConfigureServices((context, services) =>
{
services.AddScoped<IServiceFactory>(provider =>
new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type,
parameters?.Where(x => x is not null).ToArray()!)));
services.AddSingleton<IComponentHostCollection,
ComponentHostCollection>();
services.AddSingleton<IDisposer, Disposer>();
services.AddScoped<SubscriptionCollection>();
services.AddTransient<IHandlerProvider, HandlerProvider>();
services.AddScoped<ISubscriber, Subscriber>();
services.AddTransient<IPublisher, Publisher>();
services.AddTransient<IMediator, Mediator>();
services.AddTransient<IValidation, Validation>();
services.AddTransient<IValidatorCollection, ValidatorCollection>();
services.AddScoped<IProxyService<IPublisher>>(provider =>
new ProxyService<IPublisher>(provider.GetRequiredService<IPublisher>()));
services.AddScoped<IProxyService<INavigationRegionProvider>>(provider =>
new ProxyService<INavigationRegionProvider>(provider.GetRequiredService<INavigationRegionProvider>()));
services.AddScoped<IProxyService<IComponentHostCollection>>(provider =>
new ProxyService<IComponentHostCollection>(provider.GetRequiredService<IComponentHostCollection>()));
services.AddTransient<IContentFactory, ContentFactory>();
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>();
services.AddTransient<INavigationRegionProvider, NavigationRegionProvider>();
services.AddScoped<INavigation, Navigation>();
services.AddSingleton(new NamedComponent("Root"));
services.AddScoped<IComponentScopeCollection, ComponentScopeCollection>(provider =>
[
new ComponentScopeDescriptor("Root", provider.GetRequiredService<IServiceProvider>())
]);
services.AddTransient<IComponentFactory, ComponentFactory>();
services.AddTransient<IComponentScopeProvider, ComponentScopeProvider>();
services.AddHandler<NavigateHandler>();
services.AddHandler<NavigateBackHandler>();
services.AddInitialization<ComponentInitializer>();
services.AddHostedService<AppService>();
});
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Toolkit.Foundation;
public record Delete
{
public static DeleteEventArgs<TSender> As<TSender>(TSender sender) =>
new(sender);
public static DeleteEventArgs<TSender> As<TSender>() where TSender : new() =>
new(new TSender());
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record DeleteEventArgs<TSender>(TSender? Sender = default);
@@ -3,15 +3,15 @@ using System.Text.Json.Serialization;
namespace Toolkit.Foundation;
public class DictionaryStringObjectJsonConverter :
public class DictionaryStringObjectJsonConverter :
JsonConverter<Dictionary<string, object?>>
{
public override bool CanConvert(Type typeToConvert) =>
typeToConvert == typeof(Dictionary<string, object>) ||
typeToConvert == typeof(Dictionary<string, object?>);
public override Dictionary<string, object?> Read(ref Utf8JsonReader reader,
Type typeToConvert,
public override Dictionary<string, object?> Read(ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
Dictionary<string, object?> dictionary = [];
@@ -42,7 +42,7 @@ public class DictionaryStringObjectJsonConverter :
public override void Write(Utf8JsonWriter writer,
Dictionary<string, object?> value,
JsonSerializerOptions options) =>
JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, (IDictionary<string, object?>)value, options);
private object? ExtractValue(ref Utf8JsonReader reader,
@@ -56,20 +56,26 @@ public class DictionaryStringObjectJsonConverter :
return date;
}
return reader.GetString();
case JsonTokenType.False:
return false;
case JsonTokenType.True:
return true;
case JsonTokenType.Null:
return null;
case JsonTokenType.Number:
if (reader.TryGetInt64(out var result))
{
return result;
}
return reader.GetDecimal();
case JsonTokenType.StartObject:
return Read(ref reader, null!, options);
case JsonTokenType.StartArray:
List<object?> list = [];
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
@@ -77,6 +83,7 @@ public class DictionaryStringObjectJsonConverter :
list.Add(ExtractValue(ref reader, options));
}
return list;
default:
return default;
}
+56
View File
@@ -0,0 +1,56 @@
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
namespace Toolkit.Foundation;
public class DirectoryObserver
{
public static async Task<string[]> EnumerateFiles(string path,
string[] patterns,
int count,
CancellationToken cancellationToken = default)
{
string[] files = [];
List<Regex> regexPatterns = patterns.Select(pattern => new Regex(pattern, RegexOptions.IgnoreCase)).ToList();
bool IsBatchComplete()
{
files = Directory.EnumerateFiles(path, "*.*", SearchOption.TopDirectoryOnly)
.Where(file => regexPatterns.Any(regex => regex.IsMatch(Path.GetFileName(file))))
.ToArray();
if (files.Length != count)
{
return false;
}
ConcurrentBag<bool> fileAccessResults = [];
Parallel.ForEach(files, file =>
{
try
{
using FileStream fileStream = new(file, FileMode.Open, FileAccess.Read, FileShare.None);
fileAccessResults.Add(true);
}
catch (IOException)
{
fileAccessResults.Add(false);
}
});
return fileAccessResults.All(result => result);
}
await Task.Run(async () =>
{
while (!IsBatchComplete())
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(5000, cancellationToken);
}
}, cancellationToken);
return files;
}
}
+8 -8
View File
@@ -1,10 +1,10 @@
using System.Reactive.Disposables;
using System.Collections;
using System.Collections;
using System.Collections.Concurrent;
using System.Reactive.Disposables;
namespace Toolkit.Foundation;
public class Disposer :
public class Disposer :
IDisposer
{
private readonly ConcurrentDictionary<object, CompositeDisposable> subjects = [];
@@ -45,10 +45,10 @@ public class Disposer :
}
}
public TDisposable Replace<TDisposable>(object subject,
IDisposable disposer,
public TDisposable Replace<TDisposable>(object subject,
IDisposable disposer,
TDisposable replacement)
where TDisposable :
where TDisposable :
IDisposable
{
CompositeDisposable disposables = subjects.GetOrAdd(subject, args => new CompositeDisposable());
@@ -61,7 +61,7 @@ public class Disposer :
return replacement;
}
public void Remove(object subject,
public void Remove(object subject,
IDisposable disposer)
{
CompositeDisposable disposables = subjects.GetOrAdd(subject, args => new CompositeDisposable());
@@ -78,4 +78,4 @@ public class Disposer :
disposables?.Dispose();
}
}
}
}
-3
View File
@@ -1,3 +0,0 @@
namespace Toolkit.Foundation;
public record Enumerate<TValue>(object? Key = null) : INotification;
+14
View File
@@ -0,0 +1,14 @@
namespace Toolkit.Foundation;
public record Error(string Code, string Message)
{
public static readonly Error None = new(string.Empty, string.Empty);
public static readonly Error Null = new("Error.NullValue", "The specified result value is null.");
public static readonly Error ConditionNotMet = new("Error.ConditionNotMet", "The specified condition was not met.");
public static readonly Error Duplicated = new("Error.Duplicated", "The specified item already exists.");
public static readonly Error Failure = new("Error.Failure", "The operation has failed.");
}
+4
View File
@@ -0,0 +1,4 @@
namespace Toolkit.Foundation;
public record FileDescriptor(string Name, string Path, long Size) :
IFileDescriptor;
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record FileFilter(string Name, List<string> Extensions, bool AllowMultiple = false);
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record FolderFilter(bool AllowMultiple = false);
+2 -3
View File
@@ -1,5 +1,4 @@
namespace Toolkit.Foundation;
public delegate Task<TResponse> HandlerDelegate<TMessage, TResponse>(TMessage message,
CancellationToken cancellationToken)
where TMessage : IMessage;
public delegate Task<TResponse> HandlerDelegate<TRequest, TResponse>(TRequest request,
CancellationToken cancellationToken);
+25
View File
@@ -0,0 +1,25 @@
namespace Toolkit.Foundation;
public class HandlerProvider(SubscriptionCollection subscriptions) :
IHandlerProvider
{
public IEnumerable<object?> Get(object key)
{
var d = subscriptions;
if (subscriptions.TryGetValue(key, out List<WeakReference>? subscribers))
{
foreach (WeakReference weakRef in subscribers.ToArray())
{
object? target = weakRef.Target;
if (target != null)
{
yield return target;
}
else
{
subscribers.Remove(weakRef);
}
}
}
}
}
+10 -10
View File
@@ -1,25 +1,25 @@
namespace Toolkit.Foundation;
public class HandlerWrapper<TMessage, TReply>(IHandler<TMessage, TReply> handler,
IEnumerable<IPipelineBehavior<TMessage, TReply>> pipelineBehaviours)
where TMessage : class, IRequest<TReply>
public class HandlerWrapper<TRequest, TResponse>(IHandler<TRequest, TResponse> handler,
IEnumerable<IPipelineBehaviour<TRequest, TResponse>> pipelineBehaviours)
where TRequest : notnull
{
private readonly IEnumerable<IPipelineBehavior<TMessage, TReply>> pipelineBehaviours =
private readonly IEnumerable<IPipelineBehaviour<TRequest, TResponse>> pipelineBehaviours =
pipelineBehaviours.Reverse();
public async Task<TReply> Handle(TMessage message,
public async Task<TResponse> Handle(TRequest request,
CancellationToken cancellationToken)
{
HandlerDelegate<TMessage, TReply> currentHandler = handler.Handle;
foreach (IPipelineBehavior<TMessage, TReply> behavior in pipelineBehaviours)
HandlerDelegate<TRequest, TResponse> currentHandler = handler.Handle;
foreach (IPipelineBehaviour<TRequest, TResponse> behaviour in pipelineBehaviours)
{
HandlerDelegate<TMessage, TReply> previousHandler = currentHandler;
HandlerDelegate<TRequest, TResponse> previousHandler = currentHandler;
currentHandler = async (args, token) =>
{
return await behavior.Handle(args, previousHandler, token);
return await behaviour.Handle(args, previousHandler, token);
};
}
return await currentHandler(message, cancellationToken);
return await currentHandler(request, cancellationToken);
}
}
+1 -6
View File
@@ -2,10 +2,5 @@
public interface IActivated
{
Task Activated();
}
public interface IActivated<TResult>
{
Task Activated(TResult result);
Task OnActivated();
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public interface IActivation;
+6
View File
@@ -0,0 +1,6 @@
namespace Toolkit.Foundation;
public interface IActivityIndicator
{
bool IsActive { get; set; }
}
@@ -1,6 +1,6 @@
namespace Toolkit.Foundation;
public interface IInitializer
public interface IAsyncInitialization
{
Task Initialize();
}
+12 -5
View File
@@ -7,24 +7,31 @@ public interface ICache<TValue> :
void Clear();
bool Contains(TValue key);
int IndexOf(TValue value);
bool Remove(TValue value);
bool TryGetValue(TValue key, out TValue? item);
}
public interface ICache<TKey, TValue> :
IEnumerable<KeyValuePair<TKey, TValue>>
where TKey :
notnull
where TValue :
where TValue :
notnull
{
void Add(TKey key,
TValue value);
void Add(TKey key, TValue value);
void Clear();
bool ContainsKey(TKey key);
bool Contains(TKey key);
int IndexOf(TKey key);
bool Remove(TKey key);
bool TryGetValue(TKey key, out TValue? value);
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Toolkit.Foundation;
public interface IClipboardWriter
{
Task Write<TContent>(TContent content);
}
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public interface ICollectionSynchronization<TItem>;
@@ -0,0 +1,7 @@
namespace Toolkit.Foundation;
public static class ICollectionSynchronizationExtensions
{
public static int IndexOf<TItem>(this ICollectionSynchronization<TItem> synchronization,
TItem item) => synchronization is IList<TItem> collection ? collection.IndexOf(item) : -1;
}
+5 -1
View File
@@ -2,5 +2,9 @@
public interface IComponent
{
IComponentBuilder Create();
IComponentBuilder Configure(Action<IComponentBuilder>? configurationDelegate = null);
IComponentBuilder Configure<TConfiguration>(Action<IConfigurationBuilder<TConfiguration>>? configurationDelegate = null,
Action<IComponentBuilder>? componentDelegate = null)
where TConfiguration : class, new();
}
+14 -3
View File
@@ -4,10 +4,21 @@ namespace Toolkit.Foundation;
public interface IComponentBuilder
{
IComponentBuilder AddConfiguration<TConfiguration>(Action<TConfiguration> configurationDelegate)
where TConfiguration : ComponentConfiguration, new();
string ConfigurationFile { get; set; }
IComponentHost Build();
string ContentRoot { get; set; }
IComponentBuilder AddConfiguration<TConfiguration>(Action<TConfiguration> configurationDelegate)
where TConfiguration : class, new();
IComponentBuilder AddConfiguration<TConfiguration>(string section,
TConfiguration? configuration = null)
where TConfiguration : class, new();
IComponentBuilder AddConfiguration<TConfiguration>(string section)
where TConfiguration : class, new();
IComponentBuilder AddServices(Action<IServiceCollection> configureDelegate);
IComponentHost Build();
}
@@ -2,5 +2,4 @@
public interface IComponentConfigurationViewModel
{
}
}
+13
View File
@@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
namespace Toolkit.Foundation;
public interface IComponentFactory
{
IComponentHost? Create<TComponent, TConfiguration>(string key,
Action<IConfigurationBuilder<TConfiguration>>? configurationDelegate = null,
Action<IComponentBuilder>? componentDelegate = null,
Action<IServiceCollection>? servicesDelegate = null)
where TComponent : IComponent
where TConfiguration : class, new();
}
+4 -2
View File
@@ -2,8 +2,10 @@
namespace Toolkit.Foundation;
public interface IComponentHost :
public interface IComponentHost :
IHost
{
ComponentConfiguration? Configuration { get; }
TConfiguration? GetConfiguration<TConfiguration>()
where TConfiguration :
class;
}
@@ -1,5 +1,4 @@
namespace Toolkit.Foundation;
public interface IComponentScopeCollection :
IDictionary<string, IServiceProvider>;
public interface IComponentScopeCollection :
IList<ComponentScopeDescriptor>;
@@ -2,6 +2,5 @@
public interface IComponentScopeProvider
{
IServiceProvider? Get(string name);
}
ComponentScopeDescriptor? Get(string key);
}
-8
View File
@@ -1,8 +0,0 @@
namespace Toolkit.Foundation;
public interface IConfiguration<out TConfiguration>
where TConfiguration :
class
{
TConfiguration Value { get; }
}
@@ -0,0 +1,8 @@
namespace Toolkit.Foundation;
public interface IConfigurationBuilder<TConfiguration>
{
string? Section { get; set; }
TConfiguration? Configuration { get; set; }
}
@@ -0,0 +1,9 @@
namespace Toolkit.Foundation
{
public interface IConfigurationCache
{
bool Remove(string section);
void Set<TConfiguration>(string section, TConfiguration configuration);
bool TryGet<TConfiguration>(string section, out TConfiguration? configuration);
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
namespace Toolkit.Foundation;
public interface IConfigurationChanged<TConfiguration> :
IInitializer;
public interface IConfigurationChanged<TConfiguration> :
IInitialization;
@@ -0,0 +1,12 @@
namespace Toolkit.Foundation;
public interface IConfigurationDescriptor<out TConfiguration>
where TConfiguration :
class
{
string Name { get; }
string Section { get; }
TConfiguration Value { get; }
}
+2 -2
View File
@@ -1,7 +1,7 @@
namespace Toolkit.Foundation;
public interface IConfigurationFactory<TConfiguration>
where TConfiguration :
public interface IConfigurationFactory<TConfiguration>
where TConfiguration :
class
{
object Create();
+1 -1
View File
@@ -7,4 +7,4 @@ public interface IConfigurationFile<TConfiguration>
class
{
IFileInfo FileInfo { get; }
}
}
@@ -1,8 +1,8 @@
namespace Toolkit.Foundation;
public interface IConfigurationInitializer<TConfiguration>
public interface IConfigurationInitializer<TConfiguration>
where TConfiguration :
class
{
Task Initialize();
void Initialize();
}
+2 -3
View File
@@ -1,9 +1,8 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting;
namespace Toolkit.Foundation;
public interface IConfigurationMonitor<TConfiguration> :
public interface IConfigurationMonitor<TConfiguration> :
IHostedService
where TConfiguration :
class;

Some files were not shown because too many files have changed in this diff Show More