You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
8.4 KiB
274 lines
8.4 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics.CodeAnalysis; |
|
using Avalonia.Controls; |
|
using Microsoft.Extensions.DependencyInjection; |
|
using StabilityMatrix.Avalonia.Controls; |
|
using StabilityMatrix.Core.Attributes; |
|
|
|
namespace StabilityMatrix.Avalonia.Services; |
|
|
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
|
public class ServiceManager<T> |
|
{ |
|
// Holds providers |
|
private readonly Dictionary<Type, Func<T>> providers = new(); |
|
|
|
// Holds singleton instances |
|
private readonly Dictionary<Type, T> instances = new(); |
|
|
|
/// <summary> |
|
/// Register a new dialog view model (singleton instance) |
|
/// </summary> |
|
public ServiceManager<T> Register<TService>(TService instance) |
|
where TService : T |
|
{ |
|
if (instance is null) |
|
throw new ArgumentNullException(nameof(instance)); |
|
|
|
lock (instances) |
|
{ |
|
if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService))) |
|
{ |
|
throw new ArgumentException( |
|
$"Service of type {typeof(TService)} is already registered for {typeof(T)}" |
|
); |
|
} |
|
|
|
instances[instance.GetType()] = instance; |
|
} |
|
|
|
return this; |
|
} |
|
|
|
/// <summary> |
|
/// Register a new dialog view model provider action (called on each dialog creation) |
|
/// </summary> |
|
public ServiceManager<T> Register<TService>(Func<TService> provider) |
|
where TService : T |
|
{ |
|
lock (providers) |
|
{ |
|
if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService))) |
|
{ |
|
throw new ArgumentException( |
|
$"Service of type {typeof(TService)} is already registered for {typeof(T)}" |
|
); |
|
} |
|
|
|
// Return type is wrong during build with method group syntax |
|
// ReSharper disable once RedundantCast |
|
providers[typeof(TService)] = () => (TService)provider(); |
|
} |
|
|
|
return this; |
|
} |
|
|
|
/// <summary> |
|
/// Register a new dialog view model instance using a service provider |
|
/// Equal to Register[TService](serviceProvider.GetRequiredService[TService]) |
|
/// </summary> |
|
public ServiceManager<T> RegisterProvider<TService>(IServiceProvider provider) |
|
where TService : notnull, T |
|
{ |
|
lock (providers) |
|
{ |
|
if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService))) |
|
{ |
|
throw new ArgumentException( |
|
$"Service of type {typeof(TService)} is already registered for {typeof(T)}" |
|
); |
|
} |
|
|
|
// Return type is wrong during build with method group syntax |
|
// ReSharper disable once RedundantCast |
|
providers[typeof(TService)] = () => (TService)provider.GetRequiredService<TService>(); |
|
} |
|
|
|
return this; |
|
} |
|
|
|
/// <summary> |
|
/// Get a view model instance from runtime type |
|
/// </summary> |
|
[SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] |
|
public T Get(Type serviceType) |
|
{ |
|
if (!serviceType.IsAssignableTo(typeof(T))) |
|
{ |
|
throw new ArgumentException( |
|
$"Service type {serviceType} is not assignable to {typeof(T)}" |
|
); |
|
} |
|
|
|
if (instances.TryGetValue(serviceType, out var instance)) |
|
{ |
|
if (instance is null) |
|
{ |
|
throw new ArgumentException( |
|
$"Service of type {serviceType} was registered as null" |
|
); |
|
} |
|
return (T)instance; |
|
} |
|
|
|
if (providers.TryGetValue(serviceType, out var provider)) |
|
{ |
|
if (provider is null) |
|
{ |
|
throw new ArgumentException( |
|
$"Service of type {serviceType} was registered as null" |
|
); |
|
} |
|
var result = provider(); |
|
if (result is null) |
|
{ |
|
throw new ArgumentException( |
|
$"Service provider for type {serviceType} returned null" |
|
); |
|
} |
|
return (T)result; |
|
} |
|
|
|
throw new ArgumentException( |
|
$"Service of type {serviceType} is not registered for {typeof(T)}" |
|
); |
|
} |
|
|
|
/// <summary> |
|
/// Get a view model instance |
|
/// </summary> |
|
[SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] |
|
public TService Get<TService>() |
|
where TService : T |
|
{ |
|
if (instances.TryGetValue(typeof(TService), out var instance)) |
|
{ |
|
if (instance is null) |
|
{ |
|
throw new ArgumentException( |
|
$"Service of type {typeof(TService)} was registered as null" |
|
); |
|
} |
|
return (TService)instance; |
|
} |
|
|
|
if (providers.TryGetValue(typeof(TService), out var provider)) |
|
{ |
|
if (provider is null) |
|
{ |
|
throw new ArgumentException( |
|
$"Service of type {typeof(TService)} was registered as null" |
|
); |
|
} |
|
var result = provider(); |
|
if (result is null) |
|
{ |
|
throw new ArgumentException( |
|
$"Service provider for type {typeof(TService)} returned null" |
|
); |
|
} |
|
return (TService)result; |
|
} |
|
|
|
throw new ArgumentException( |
|
$"Service of type {typeof(TService)} is not registered for {typeof(T)}" |
|
); |
|
} |
|
|
|
/// <summary> |
|
/// Get a view model instance with an initializer parameter |
|
/// </summary> |
|
public TService Get<TService>(Func<TService, TService> initializer) |
|
where TService : T |
|
{ |
|
var instance = Get<TService>(); |
|
return initializer(instance); |
|
} |
|
|
|
/// <summary> |
|
/// Get a view model instance with an initializer for a mutable instance |
|
/// </summary> |
|
public TService Get<TService>(Action<TService> initializer) |
|
where TService : T |
|
{ |
|
var instance = Get<TService>(); |
|
initializer(instance); |
|
return instance; |
|
} |
|
|
|
/// <summary> |
|
/// Get a view model instance, set as DataContext of its View, and return |
|
/// a BetterContentDialog with that View as its Content |
|
/// </summary> |
|
public BetterContentDialog GetDialog<TService>() |
|
where TService : T |
|
{ |
|
var instance = Get<TService>()!; |
|
|
|
if ( |
|
Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) |
|
is not ViewAttribute viewAttr |
|
) |
|
{ |
|
throw new InvalidOperationException( |
|
$"View not found for {instance.GetType().FullName}" |
|
); |
|
} |
|
|
|
if (Activator.CreateInstance(viewAttr.ViewType) is not Control view) |
|
{ |
|
throw new NullReferenceException( |
|
$"Unable to create instance for {instance.GetType().FullName}" |
|
); |
|
} |
|
|
|
return new BetterContentDialog { Content = view }; |
|
} |
|
|
|
/// <summary> |
|
/// Get a view model instance with initializer, set as DataContext of its View, and return |
|
/// a BetterContentDialog with that View as its Content |
|
/// </summary> |
|
public BetterContentDialog GetDialog<TService>(Action<TService> initializer) |
|
where TService : T |
|
{ |
|
var instance = Get(initializer)!; |
|
|
|
if ( |
|
Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) |
|
is not ViewAttribute viewAttr |
|
) |
|
{ |
|
throw new InvalidOperationException( |
|
$"View not found for {instance.GetType().FullName}" |
|
); |
|
} |
|
|
|
if (Activator.CreateInstance(viewAttr.ViewType) is not Control view) |
|
{ |
|
throw new NullReferenceException( |
|
$"Unable to create instance for {instance.GetType().FullName}" |
|
); |
|
} |
|
|
|
view.DataContext = instance; |
|
|
|
return new BetterContentDialog { Content = view }; |
|
} |
|
|
|
public void Register(Type type, Func<T> providerFunc) |
|
{ |
|
lock (providers) |
|
{ |
|
if (instances.ContainsKey(type) || providers.ContainsKey(type)) |
|
{ |
|
throw new ArgumentException( |
|
$"Service of type {type} is already registered for {typeof(T)}" |
|
); |
|
} |
|
|
|
providers[type] = providerFunc; |
|
} |
|
} |
|
}
|
|
|