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
+27
View File
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>TheXamlGuy.Framework.Avalonia</RootNamespace>
<AssemblyName>TheXamlGuy.Framework.Avalonia</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview3" />
<PackageReference Include="MicroCom.Runtime" Version="0.10.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\UI\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="FluentAvalonia">
<HintPath>References\FluentAvalonia.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
@@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using TheXamlGuy.Framework.Core;
namespace TheXamlGuy.Framework.Avalonia;
public static class IHostBuilderExtensions
{
public static IHostBuilder ConfigureTemplates(this IHostBuilder hostBuilder, Action<ITemplateBuilder> builderDelegate)
{
hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
{
TemplateBuilder? builder = new();
builderDelegate?.Invoke(builder);
serviceCollection
.AddSingleton(builder.Descriptors)
.AddSingleton<ITemplateDescriptorProvider, TemplateDescriptorProvider>()
.AddSingleton<ITemplateFactory, TemplateFactory>()
.AddSingleton<INamedTemplateFactory, NamedTemplateFactory>()
.AddSingleton<ITypedDataTemplateFactory, TypedDataTemplateFactory>()
.AddSingleton<INamedDataTemplateFactory, NamedDataTemplateFactory>()
.AddSingleton<ITemplateSelector, TemplateSelector>();
foreach (ITemplateDescriptor? descriptor in builder.Descriptors)
{
serviceCollection.Add(new ServiceDescriptor(descriptor.TemplateType, descriptor.TemplateType, descriptor.Lifetime));
serviceCollection.Add(new ServiceDescriptor(descriptor.DataType, descriptor.DataType, descriptor.Lifetime));
}
});
return hostBuilder;
}
}
@@ -0,0 +1,17 @@
using Microsoft.Extensions.DependencyInjection;
using TheXamlGuy.Framework.Core;
namespace TheXamlGuy.Framework.Avalonia;
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddRequiredAvalonia(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<IRouter, Router>()
.AddSingleton<IRouteDescriptorCollection, RouteDescriptorCollection>()
.AddSingleton<IRouterContext, RouterContext>()
.AddTransient<IMediatorHandler<Navigate>, NavigateHandler>()
.RegisterHandlers();
}
}
@@ -0,0 +1,453 @@
using Avalonia.Controls;
using Avalonia;
using Avalonia.Data;
using Avalonia.Markup.Xaml;
using TheXamlGuy.Framework.Core;
using TheXamlGuy.UI.Avalonia;
namespace TheXamlGuy.Framework.Avalonia;
public class NavigateExtension : TriggerExtension
{
private static readonly AttachedProperty<IEventAggregator> EventAggregatorProperty =
AvaloniaProperty.RegisterAttached<NavigateExtension, Control, IEventAggregator>("EventAggregator");
private static readonly AttachedProperty<object> ParameterProperty =
AvaloniaProperty.RegisterAttached<NavigateExtension, Control, object>("Parameter");
private static readonly AvaloniaProperty RouteProperty =
AvaloniaProperty.RegisterAttached<NavigateExtension, Control, object>("Route");
private static readonly AvaloniaProperty ToProperty =
AvaloniaProperty.RegisterAttached<NavigateExtension, Control, object>("To");
private readonly Binding eventAggregatorBinding;
private readonly List<object> parameters = new();
private readonly Binding toBinding;
private object? route;
private Binding? routeBinding;
public NavigateExtension(object eventAggregator,
object to)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
}
public NavigateExtension(object eventAggregator,
object to,
object args1)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7,
object args8)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
parameters.Add(args8 is MarkupExtension ? args8 : args8.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7,
object args8,
object args9)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
parameters.Add(args8 is MarkupExtension ? args8 : args8.ToBinding());
parameters.Add(args9 is MarkupExtension ? args9 : args9.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7,
object args8,
object args9,
object args10)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
parameters.Add(args8 is MarkupExtension ? args8 : args8.ToBinding());
parameters.Add(args9 is MarkupExtension ? args9 : args9.ToBinding());
parameters.Add(args10 is MarkupExtension ? args10 : args10.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7,
object args8,
object args9,
object args10,
object args11)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
parameters.Add(args8 is MarkupExtension ? args8 : args8.ToBinding());
parameters.Add(args9 is MarkupExtension ? args9 : args9.ToBinding());
parameters.Add(args10 is MarkupExtension ? args10 : args10.ToBinding());
parameters.Add(args11 is MarkupExtension ? args11 : args11.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7,
object args8,
object args9,
object args10,
object args11,
object args12)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
parameters.Add(args8 is MarkupExtension ? args8 : args8.ToBinding());
parameters.Add(args9 is MarkupExtension ? args9 : args9.ToBinding());
parameters.Add(args10 is MarkupExtension ? args10 : args10.ToBinding());
parameters.Add(args11 is MarkupExtension ? args11 : args11.ToBinding());
parameters.Add(args12 is MarkupExtension ? args12 : args12.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7,
object args8,
object args9,
object args10,
object args11,
object args12,
object args13)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
parameters.Add(args8 is MarkupExtension ? args8 : args8.ToBinding());
parameters.Add(args9 is MarkupExtension ? args9 : args9.ToBinding());
parameters.Add(args10 is MarkupExtension ? args10 : args10.ToBinding());
parameters.Add(args11 is MarkupExtension ? args11 : args11.ToBinding());
parameters.Add(args12 is MarkupExtension ? args12 : args12.ToBinding());
parameters.Add(args13 is MarkupExtension ? args13 : args13.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7,
object args8,
object args9,
object args10,
object args11,
object args12,
object args13,
object args14)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
parameters.Add(args8 is MarkupExtension ? args8 : args8.ToBinding());
parameters.Add(args9 is MarkupExtension ? args9 : args9.ToBinding());
parameters.Add(args10 is MarkupExtension ? args10 : args10.ToBinding());
parameters.Add(args11 is MarkupExtension ? args11 : args11.ToBinding());
parameters.Add(args12 is MarkupExtension ? args12 : args12.ToBinding());
parameters.Add(args13 is MarkupExtension ? args13 : args13.ToBinding());
parameters.Add(args14 is MarkupExtension ? args14 : args14.ToBinding());
}
public NavigateExtension(object eventAggregator,
object to,
object args1,
object args2,
object args3,
object args4,
object args5,
object args6,
object args7,
object args8,
object args9,
object args10,
object args11,
object args12,
object args13,
object args14,
object args15)
{
eventAggregatorBinding = eventAggregator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
parameters.Add(args3 is MarkupExtension ? args3 : args3.ToBinding());
parameters.Add(args4 is MarkupExtension ? args4 : args4.ToBinding());
parameters.Add(args5 is MarkupExtension ? args5 : args5.ToBinding());
parameters.Add(args6 is MarkupExtension ? args6 : args6.ToBinding());
parameters.Add(args7 is MarkupExtension ? args7 : args7.ToBinding());
parameters.Add(args8 is MarkupExtension ? args8 : args8.ToBinding());
parameters.Add(args9 is MarkupExtension ? args9 : args9.ToBinding());
parameters.Add(args10 is MarkupExtension ? args10 : args10.ToBinding());
parameters.Add(args11 is MarkupExtension ? args11 : args11.ToBinding());
parameters.Add(args12 is MarkupExtension ? args12 : args12.ToBinding());
parameters.Add(args13 is MarkupExtension ? args13 : args13.ToBinding());
parameters.Add(args14 is MarkupExtension ? args14 : args14.ToBinding());
parameters.Add(args15 is MarkupExtension ? args15 : args15.ToBinding());
}
public object? Route
{
get
{
return route;
}
set
{
route = value;
if (route is not null)
{
routeBinding = route.ToBinding();
}
}
}
protected override void OnInvoked(object sender, EventArgs args)
{
if (TargetObject is not null)
{
TargetObject.Bind(EventAggregatorProperty, eventAggregatorBinding);
if (TargetObject.GetValue(EventAggregatorProperty) is IEventAggregator eventAggregator)
{
TargetObject.Bind(ToProperty, toBinding);
if (TargetObject.GetValue(ToProperty) is { } to)
{
object? route = null;
if (routeBinding is not null)
{
TargetObject.Bind(RouteProperty, routeBinding);
route = TargetObject.GetValue(RouteProperty);
}
if (to is string name)
{
if (toBinding?.StringFormat is string format)
{
name = string.Format(format, name);
}
eventAggregator.Publish(new Navigate(name, parameters.ToArray()) { Route = route });
}
if (to is Type type)
{
eventAggregator.Publish(new Navigate(type, parameters.ToArray()) { Route = route });
}
}
}
base.OnInvoked(sender, args);
}
}
}
@@ -0,0 +1,86 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using TheXamlGuy.UI.Avalonia;
namespace TheXamlGuy.Framework.Avalonia;
public class RouteExtension : MarkupExtension
{
private static readonly AttachedProperty<object> RouteProperty =
AvaloniaProperty.RegisterAttached<RouteExtension, Control, object>("Route");
private readonly string name;
private readonly Binding routeBinding;
public RouteExtension(object route, string name)
{
routeBinding = route.ToBinding();
this.name = name;
}
private bool TryGetBinding(AvaloniaObject sender, out object? binding)
{
binding = sender.GetValue(StyledElement.DataContextProperty);
return binding is not null;
}
public override object? ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget target)
{
if (target.TargetObject is TemplatedControl control)
{
if (!TryGetBinding(control, out object? binding))
{
void HandleDataContextChanged(object? sender, EventArgs args)
{
if (TryGetBinding(control, out binding))
{
control.Loaded -= HandleLoaded;
control.Bind(RouteProperty, routeBinding);
if (control?.GetValue(RouteProperty) is IRouter route)
{
route.Add(name, control);
control.ClearValue(RouteProperty);
}
}
}
control.DataContextChanged += HandleDataContextChanged;
void HandleLoaded(object? sender, RoutedEventArgs args)
{
control.Loaded -= HandleLoaded;
if (TryGetBinding(control, out binding))
{
control.Bind(RouteProperty, routeBinding);
if (control?.GetValue(RouteProperty) is IRouter route)
{
route.Add(name, control);
control.ClearValue(RouteProperty);
}
}
}
control.Loaded += HandleLoaded;
}
else
{
control.Bind(RouteProperty, routeBinding);
if (control?.GetValue(RouteProperty) is IRouter route)
{
route.Add(name, control);
control.ClearValue(RouteProperty);
}
}
}
}
return null;
}
}
@@ -0,0 +1,3 @@
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "TheXamlGuy.Framework.Avalonia")]
Binary file not shown.
@@ -0,0 +1,30 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using FluentAvalonia.UI.Controls;
namespace TheXamlGuy.Framework.Avalonia;
public class ContentDialogRouteHandler : RouteHandler<ContentDialog>
{
public override async void Handle(Route<ContentDialog> request)
{
if (request.Template is ContentDialog contentDialog)
{
contentDialog.DataContext = request.Data;
await contentDialog.ShowAsync();
}
}
}
public class ContentControlRouteHandler : RouteHandler<ContentControl>
{
public override void Handle(Route<ContentControl> request)
{
if (request.Template is TemplatedControl control)
{
control.DataContext = request.Data;
request.Target.Content = control;
}
}
}
@@ -0,0 +1,26 @@
using Avalonia.Controls.Primitives;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
namespace TheXamlGuy.Framework.Avalonia;
public class FrameRouteHandler : RouteHandler<Frame>
{
public override void Handle(Route<Frame> request)
{
if (request.Template is Type type)
{
void HandleNavigated(object sender, NavigationEventArgs args)
{
request.Target.Navigated -= HandleNavigated;
if (request.Target.Content is TemplatedControl control)
{
control.DataContext = request.Data;
}
}
request.Target.Navigated += HandleNavigated;
request.Target.Navigate(type);
}
}
}
@@ -0,0 +1,8 @@
namespace TheXamlGuy.Framework.Avalonia;
public interface IRouteDescriptor
{
object Route { get; }
string? Name { get; }
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Avalonia;
public interface IRouteDescriptorCollection : IList<IRouteDescriptor>
{
}
+6
View File
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Avalonia;
public interface IRouter
{
void Add(string name, object route);
}
@@ -0,0 +1,19 @@
using TheXamlGuy.Framework.Core;
namespace TheXamlGuy.Framework.Avalonia
{
public class NavigateHandler : IMediatorHandler<Navigate>
{
private readonly IEventAggregator eventAggregator;
public NavigateHandler(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
}
public void Handle(Navigate request)
{
eventAggregator.Publish(request);
}
}
}
+30
View File
@@ -0,0 +1,30 @@
namespace TheXamlGuy.Framework.Avalonia;
public class Navigated<TContent, TDataContext> where TContent : class where TDataContext : class
{
public Navigated()
{
}
public Navigated(TContent content, TDataContext dataContext, IDictionary<string, object>? parameters = null)
{
Content = content;
DataContext = dataContext;
Parameters = parameters;
}
public TContent? Content { get; }
public TDataContext? DataContext { get; }
public IDictionary<string, object>? Parameters { get; }
}
public class Navigated
{
public static Navigated<TContent, TDataTemplate> Create<TContent, TDataTemplate>(TContent content, TDataTemplate dataContext, IDictionary<string, object>? parameters = null) where TContent : class where TDataTemplate : class
{
return new Navigated<TContent, TDataTemplate>(content, dataContext, parameters);
}
}
+5
View File
@@ -0,0 +1,5 @@
using Avalonia.Controls.Primitives;
namespace TheXamlGuy.Framework.Avalonia;
public record Route<TTarget>(TTarget Target, object? Data, object? Template) where TTarget : TemplatedControl;
@@ -0,0 +1,14 @@
namespace TheXamlGuy.Framework.Avalonia;
public record RouteDescriptor : IRouteDescriptor
{
public RouteDescriptor(string name, object route)
{
Name = name;
Route = route;
}
public string Name { get; }
public object Route { get; }
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.Framework.Avalonia;
public class RouteDescriptorCollection : List<IRouteDescriptor>, IRouteDescriptorCollection
{
public RouteDescriptorCollection(IEnumerable<IRouteDescriptor> collection) : base(collection)
{
}
}
@@ -0,0 +1,9 @@
using Avalonia.Controls.Primitives;
using TheXamlGuy.Framework.Core;
namespace TheXamlGuy.Framework.Avalonia;
public abstract class RouteHandler<TTarget> : IMediatorHandler<Route<TTarget>> where TTarget : TemplatedControl
{
public abstract void Handle(Route<TTarget> request);
}
+37
View File
@@ -0,0 +1,37 @@
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
namespace TheXamlGuy.Framework.Avalonia;
public class Router : IRouter
{
private readonly IRouteDescriptorCollection routes;
public Router(IRouteDescriptorCollection routes)
{
this.routes = routes;
}
public void Add(string name, object route)
{
if (route is TemplatedControl control)
{
void HandleUnloaded(object? sender, RoutedEventArgs args)
{
if (routes.FirstOrDefault(x => x.Route == sender) is IRouteDescriptor descriptor)
{
routes.Remove(descriptor);
}
}
control.Unloaded += HandleUnloaded;
}
if (routes.FirstOrDefault(x => x.Name == name) is IRouteDescriptor descriptor)
{
routes.Remove(descriptor);
}
routes.Add(new RouteDescriptor(name, route));
}
}
+133
View File
@@ -0,0 +1,133 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using FluentAvalonia.UI.Controls;
using TheXamlGuy.Framework.Core;
namespace TheXamlGuy.Framework.Avalonia;
public class RouterContext : IRouterContext
{
private readonly IRouteDescriptorCollection descriptors;
private readonly IDisposer disposer;
private readonly IEventAggregator eventAggregator;
private readonly IMediator mediator;
private readonly INamedDataTemplateFactory namedDataTemplateFactory;
private readonly INamedTemplateFactory namedTemplateFactory;
private readonly ITemplateDescriptorProvider templateDescriptorProvider;
private readonly ITemplateFactory templateFactory;
private readonly ITypedDataTemplateFactory typedDataTemplateFactory;
public RouterContext(ITemplateDescriptorProvider templateDescriptorProvider,
ITemplateFactory templateFactory,
INamedTemplateFactory namedTemplateFactory,
INamedDataTemplateFactory namedDataTemplateFactory,
ITypedDataTemplateFactory typedDataTemplateFactory,
IEventAggregator eventAggregator,
IMediator mediator,
IDisposer disposer,
IRouteDescriptorCollection descriptors)
{
this.templateDescriptorProvider = templateDescriptorProvider;
this.templateFactory = templateFactory;
this.namedTemplateFactory = namedTemplateFactory;
this.namedDataTemplateFactory = namedDataTemplateFactory;
this.typedDataTemplateFactory = typedDataTemplateFactory;
this.eventAggregator = eventAggregator;
this.mediator = mediator;
this.disposer = disposer;
this.descriptors = descriptors;
}
public async Task InitializeAsync()
{
disposer.Add(this, eventAggregator.Current.SubscribeUI<Navigate>(OnNavigate));
disposer.Add(this, eventAggregator.Current.SubscribeUI<NavigateBack>(OnNavigateBack));
await Task.CompletedTask;
}
private void OnNavigate(Navigate args)
{
object? data = null;
object? template = null;
Dictionary<string, object> keyedParameters = new();
List<object> parameters = new();
foreach (object? parameter in args.Parameters)
{
if (parameter is KeyValuePair<string, object> keyed)
{
keyedParameters.Add(keyed.Key, keyed.Value);
}
else
{
parameters.Add(parameter);
}
}
if (args.Name is { Length: > 0 } name)
{
data = namedDataTemplateFactory.Create(name, parameters.ToArray());
template = descriptors.FirstOrDefault(x => args.Route is string { } name && name == x.Name) is { Route: Frame } ? templateDescriptorProvider.Get(name)?.TemplateType : namedTemplateFactory.Create(name);
}
if (args.Type is Type type)
{
data = typedDataTemplateFactory.Create(type, parameters.ToArray());
template = descriptors.FirstOrDefault(x => args.Route is string { } name && name == x.Name) is { Route: Frame } ? templateDescriptorProvider.Get(type)?.TemplateType : templateFactory.Create(data);
}
if (template is not null)
{
if (template is ContentDialog contentDialog)
{
mediator.Handle(new Route<ContentDialog>(contentDialog, data, template));
}
else
{
if (descriptors.FirstOrDefault(x => args.Route is string { } name && name == x.Name) is RouteDescriptor descriptor)
{
switch (descriptor.Route)
{
case Frame frame:
mediator.Handle(new Route<Frame>(frame, data, template));
break;
case ContentControl contentControl:
mediator.Handle(new Route<ContentControl>(contentControl, data, template));
break;
}
}
}
}
else
{
if (descriptors.FirstOrDefault(x => args.Route is string { } name && name == x.Name) is RouteDescriptor descriptor)
{
if (descriptor.Route is ContentControl contentControl)
{
contentControl.Content = null;
}
}
}
}
private void OnNavigateBack(NavigateBack args)
{
if (descriptors.FirstOrDefault(x => args.Route is string { } name && name == x.Name) is RouteDescriptor descriptor)
{
if (descriptor.Route is ContentControl { Content: TemplatedControl content })
{
if (content.DataContext is IDisposable disposable)
{
disposable.Dispose();
}
}
if (descriptor.Route is Frame frame)
{
frame.GoBack();
}
}
}
}
@@ -0,0 +1,47 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml.Templates;
using FluentAvalonia.UI.Controls;
using TheXamlGuy.Framework.Core;
namespace TheXamlGuy.Framework.Avalonia;
public class TemplateSelector : DataTemplateSelector, ITemplateSelector
{
private readonly Dictionary<object, DataTemplate> dataTracking = new();
private readonly ITemplateFactory templateFactory;
private readonly IEventAggregator eventAggregator;
public TemplateSelector(ITemplateFactory templateFactory,
IEventAggregator eventAggregator)
{
this.templateFactory = templateFactory;
this.eventAggregator = eventAggregator;
}
protected override IDataTemplate SelectTemplateCore(object item, IControl container)
{
if (item is not null)
{
if (dataTracking.TryGetValue(item, out DataTemplate? cachedDataTemplate))
{
return cachedDataTemplate;
}
return new FuncDataTemplate(item.GetType(), (value, namescope) =>
{
if (templateFactory.Create(item) is TemplatedControl template)
{
return template;
}
return null;
});
}
return base.SelectTemplateCore(item, container);
}
}
+15
View File
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows10.0.18362.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0-rc.2.22472.3" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0-rc.2.22472.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Media\Capture\Capture.csproj" />
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
</Project>
+19
View File
@@ -0,0 +1,19 @@
using Microsoft.Extensions.Configuration;
using System.Collections.ObjectModel;
namespace TheXamlGuy.Framework.Camera;
public class CameraBuilder : ICameraBuilder
{
private readonly List<ICameraBuilderConfiguration> configurations = new();
public IReadOnlyCollection<ICameraBuilderConfiguration> Configurations => new ReadOnlyCollection<ICameraBuilderConfiguration>(configurations);
public ICameraBuilderConfiguration<TConfiguration> Add<TConfiguration>(IConfiguration configuration) where TConfiguration : IRemoteCameraConfiguration, new()
{
CameraBuilderConfiguration<TConfiguration>? builderConfiguration = new(configuration);
configurations.Add(builderConfiguration);
return builderConfiguration;
}
}
@@ -0,0 +1,16 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace TheXamlGuy.Framework.Camera;
public class CameraBuilderConfiguration<TConfiguration> : ICameraBuilderConfiguration<TConfiguration> where TConfiguration : INamedCameraConfiguration, new()
{
private readonly IConfiguration configuration;
public CameraBuilderConfiguration(IConfiguration configuration)
{
this.configuration = configuration;
}
public Func<IServiceProvider, ICameraContext> Factory => (IServiceProvider provider) => provider.GetService<ICameraFactory>()!.Create(configuration.Get<TConfiguration>());
}
+48
View File
@@ -0,0 +1,48 @@
using TheXamlGuy.Framework.Core;
using TheXamlGuy.Media.Capture;
namespace TheXamlGuy.Framework.Camera;
public class CameraContext : ICameraContext
{
private readonly IEventAggregator eventAggregator;
private ILowLagPhotoCapture? lowLagPhotoCapture;
private IMediaCapture? mediaCapture;
public CameraContext(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
}
public async Task InitializeAsync()
{
IReadOnlyList<IMediaFrameSource> sourceGroups = await MediaFrameSource.FindAllAsync();
if (sourceGroups.FirstOrDefault(x => x.DisplayName.Contains("USB", StringComparison.InvariantCultureIgnoreCase)) is IMediaFrameSource source)
{
if (source.SupportedFormats.OrderByDescending(x => x.Size.Width & x.Size.Height).FirstOrDefault() is MediaFrameFormat bestSupportedFormat)
{
source.SetFormat(bestSupportedFormat);
}
MediaCaptureInitializationSettings settings = new()
{
Source = source
};
mediaCapture = new MediaCapture();
mediaCapture.Initialize(settings);
lowLagPhotoCapture = await mediaCapture.PrepareLowLagPhotoCaptureAsync();
eventAggregator.SubscribeUI<Capture>(OnCapture);
}
}
private async void OnCapture(Capture args)
{
if (lowLagPhotoCapture is not null)
{
await lowLagPhotoCapture.CaptureAsync();
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using TheXamlGuy.Framework.Core;
namespace TheXamlGuy.Framework.Camera;
public class CameraFactory : ICameraFactory
{
private readonly Dictionary<INamedCameraConfiguration, ICameraContext> cache = new();
private readonly IServiceFactory factory;
public CameraFactory(IServiceFactory factory)
{
this.factory = factory;
}
public ICameraContext Create(INamedCameraConfiguration configuration)
{
if (cache.TryGetValue(configuration, out ICameraContext? context))
{
return context;
}
if (configuration is IRemoteCameraConfiguration)
{
context = factory.Create<RemoteCameraContext>();
}
else
{
context = factory.Create<CameraContext>();
}
cache.Add(configuration, context);
return context;
}
}
+3
View File
@@ -0,0 +1,3 @@
namespace TheXamlGuy.Framework.Camera;
public record Capture;
+12
View File
@@ -0,0 +1,12 @@
using System.Drawing;
namespace TheXamlGuy.Framework.Camera;
public record Captured
{
public Bitmap? Photo { get; init; }
public int Width { get; init; }
public int Height { get; init; }
}
+10
View File
@@ -0,0 +1,10 @@
using Microsoft.Extensions.Configuration;
namespace TheXamlGuy.Framework.Camera;
public interface ICameraBuilder
{
IReadOnlyCollection<ICameraBuilderConfiguration> Configurations { get; }
ICameraBuilderConfiguration<TConfiguration> Add<TConfiguration>(IConfiguration configuration) where TConfiguration : IRemoteCameraConfiguration, new();
}
@@ -0,0 +1,11 @@
namespace TheXamlGuy.Framework.Camera;
public interface ICameraBuilderConfiguration
{
Func<IServiceProvider, ICameraContext> Factory { get; }
}
public interface ICameraBuilderConfiguration<TConfiguration> : ICameraBuilderConfiguration where TConfiguration : INamedCameraConfiguration, new()
{
}
+5
View File
@@ -0,0 +1,5 @@
namespace TheXamlGuy.Framework.Camera;
public interface ICameraConfiguration : INamedCameraConfiguration
{
}
+8
View File
@@ -0,0 +1,8 @@
using TheXamlGuy.Framework.Core;
namespace TheXamlGuy.Framework.Camera;
public interface ICameraContext : IInitializer
{
}
+6
View File
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Camera;
public interface ICameraFactory
{
ICameraContext Create(INamedCameraConfiguration configuration);
}
@@ -0,0 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
namespace TheXamlGuy.Framework.Camera;
public static class IHostBuilderExtensions
{
public static IHostBuilder ConfigureCamera(this IHostBuilder hostBuilder, Action<HostBuilderContext, ICameraBuilder> builderDelegate)
{
hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
{
CameraBuilder? builder = new();
builderDelegate.Invoke(hostBuilderContext, builder);
serviceCollection.TryAddSingleton<ICameraFactory, CameraFactory>();
foreach (ICameraBuilderConfiguration configuration in builder.Configurations)
{
serviceCollection.AddSingleton(provider => configuration.Factory.Invoke(provider));
}
});
return hostBuilder;
}
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Framework.Camera;
public interface INamedCameraConfiguration
{
string Name { get; }
}
@@ -0,0 +1,5 @@
namespace TheXamlGuy.Framework.Camera;
public interface IRemoteCameraConfiguration : INamedCameraConfiguration
{
}
@@ -0,0 +1,9 @@
using System.Diagnostics.CodeAnalysis;
namespace TheXamlGuy.Framework.Camera;
public class RemoteCameraConfiguration : IRemoteCameraConfiguration
{
[NotNull]
public string? Name { get; set; }
}
+51
View File
@@ -0,0 +1,51 @@
using TheXamlGuy.Framework.Core;
using TheXamlGuy.Media.Capture;
namespace TheXamlGuy.Framework.Camera;
public class RemoteCameraContext : ICameraContext
{
private readonly IEventAggregator eventAggregator;
private ILowLagPhotoCapture? lowLagPhotoCapture;
private IRemoteMediaCapture? mediaCapture;
public RemoteCameraContext(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
}
public async Task InitializeAsync()
{
IReadOnlyList<IRemoteMediaFrameSource> sourceGroups = await RemoteMediaFrameSource.FindAllAsync();
if (sourceGroups.FirstOrDefault(x => x.DisplayName.Contains("DSC-HX60", StringComparison.InvariantCultureIgnoreCase)) is IRemoteMediaFrameSource source)
{
RemoteMediaCaptureInitializationSettings settings = new()
{
Source = source
};
mediaCapture = new RemoteMediaCapture();
mediaCapture.Initialize(settings);
lowLagPhotoCapture = await mediaCapture.PrepareLowLagPhotoCaptureAsync();
eventAggregator.SubscribeUI<Capture>(OnCapture);
}
}
private async void OnCapture(Capture args)
{
if (lowLagPhotoCapture is not null)
{
if (await lowLagPhotoCapture.CaptureAsync() is CapturedPhoto capturedPhoto)
{
eventAggregator.Publish(new Captured
{
Photo = capturedPhoto.Photo,
Width = capturedPhoto.Width,
Height = capturedPhoto.Height
});
}
}
}
}
@@ -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);
}
}

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