Improve scoped service

This commit is contained in:
Dan Clark
2025-02-11 19:45:02 +00:00
parent 66d61a3917
commit 92ea28d647
25 changed files with 149 additions and 111 deletions
@@ -4,10 +4,10 @@ using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class FileProvider(ITopLevelProvider topLevelProvider) :
IFileProvider
public class FilePicker(ITopLevelProvider topLevelProvider) :
IFilePicker
{
public async Task<IReadOnlyCollection<string>> SelectFiles(FileFilter filter)
public async Task<IReadOnlyCollection<string>> Get(FilePickerFilter filter)
{
if (topLevelProvider.Get() is TopLevel topLevel)
{
@@ -4,10 +4,10 @@ using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class FolderProvider(ITopLevelProvider topLevelProvider) :
IFolderProvider
public class FolderPicker(ITopLevelProvider topLevelProvider) :
IFolderPicker
{
public async Task<IReadOnlyCollection<string>> SelectFolders(FolderFilter filter)
public async Task<IReadOnlyCollection<string>> Get(FolderPickerPicker filter)
{
if (topLevelProvider.Get() is TopLevel topLevel)
{
@@ -12,8 +12,8 @@ public static class IServiceCollectionExtensions
public static IServiceCollection AddAvalonia(this IServiceCollection services)
{
services.AddTransient<ITopLevelProvider, TopLevelProvider>();
services.AddTransient<IFileProvider, FileProvider>();
services.AddTransient<IFolderProvider, FolderProvider>();
services.AddTransient<IFilePicker, FilePicker>();
services.AddTransient<IFolderPicker, FolderPicker>();
services.AddTransient<IClipboardWriter, ClipboardWriter>();
@@ -27,8 +27,8 @@ public static class IServiceCollectionExtensions
services.AddTransient<INavigationRegion, NavigationRegion>();
services.AddAsyncHandler<WriteEventArgs<Clipboard<object>>, WriteClipboardHandler>();
services.AddAsyncHandler<SelectionEventArgs<FolderFilter>, IReadOnlyCollection<string>?, SelectFoldersHandler>();
services.AddAsyncHandler<SelectionEventArgs<FileFilter>, IReadOnlyCollection<string>?, SelectFilesHandler>();
services.AddAsyncHandler<SelectionEventArgs<FolderPickerPicker>, IReadOnlyCollection<string>?, SelectFoldersHandler>();
services.AddAsyncHandler<SelectionEventArgs<FilePickerFilter>, IReadOnlyCollection<string>?, SelectFilesHandler>();
services.AddHandler<NavigateTemplateEventArgs, ClassicDesktopStyleApplicationHandler>(nameof(IClassicDesktopStyleApplicationLifetime));
services.AddHandler<NavigateTemplateEventArgs, SingleViewApplicationHandler>(nameof(ISingleViewApplicationLifetime));
@@ -51,8 +51,8 @@ public static class IServiceCollectionExtensions
new ProxyServiceCollection<IComponentBuilder>(services =>
{
services.AddTransient<ITopLevelProvider, TopLevelProvider>();
services.AddTransient<IFileProvider, FileProvider>();
services.AddTransient<IFolderProvider, FolderProvider>();
services.AddTransient<IFilePicker, FilePicker>();
services.AddTransient<IFolderPicker, FolderPicker>();
services.AddTransient<IClipboardWriter, ClipboardWriter>();
@@ -66,8 +66,8 @@ public static class IServiceCollectionExtensions
services.AddTransient<INavigationRegion, NavigationRegion>();
services.AddAsyncHandler<WriteEventArgs<Clipboard<object>>, WriteClipboardHandler>();
services.AddAsyncHandler<SelectionEventArgs<FolderFilter>, IReadOnlyCollection<string>?, SelectFoldersHandler>();
services.AddAsyncHandler<SelectionEventArgs<FileFilter>, IReadOnlyCollection<string>?, SelectFilesHandler>();
services.AddAsyncHandler<SelectionEventArgs<FolderPickerPicker>, IReadOnlyCollection<string>?, SelectFoldersHandler>();
services.AddAsyncHandler<SelectionEventArgs<FilePickerFilter>, IReadOnlyCollection<string>?, SelectFilesHandler>();
services.AddHandler<NavigateTemplateEventArgs, ContentControlHandler>(nameof(ContentControl));
+4 -2
View File
@@ -52,8 +52,10 @@ public class DefaultHostBuilder :
services.AddTransient<IComponentFactory, ComponentFactory>();
services.AddTransient<IComponentScopeProvider, ComponentScopeProvider>();
services.AddHandlerScoped<NavigateEventArgs, NavigateHandler>();
services.AddHandlerScoped<NavigateBackEventArgs, NavigateBackHandler>();
services.AddScopedHandler<NavigateEventArgs, NavigateHandler>();
services.AddScopedHandler<NavigateBackEventArgs, NavigateBackHandler>();
services.AddTransient<IFileProvider, FileProvider>();
services.AddInitialization<ComponentInitializer>();
});
-4
View File
@@ -1,4 +0,0 @@
namespace Toolkit.Foundation;
public record FileDescriptor(string Name, string Path, long Size) :
IFileDescriptor;
-3
View File
@@ -1,3 +0,0 @@
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 FilePickerFilter(string Name, List<string> Extensions, bool AllowMultiple = false);
+27
View File
@@ -0,0 +1,27 @@
namespace Toolkit.Foundation;
public class FileProvider :
IFileProvider
{
public IReadOnlyCollection<string> Get(string path,
FileProviderFilter filter)
{
if (!Directory.Exists(path))
{
return [];
}
List<string> searchPatterns = filter.Extensions.Count > 0
? filter.Extensions.Select(ext => $"*.{ext}").ToList()
: ["*.*"];
List<string> files = [];
foreach (string pattern in searchPatterns)
{
files.AddRange(Directory.EnumerateFiles(path, pattern, SearchOption.TopDirectoryOnly));
}
return files;
}
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record FileProviderFilter(List<string> Extensions);
-3
View File
@@ -1,3 +0,0 @@
namespace Toolkit.Foundation;
public record FolderFilter(bool AllowMultiple = false);
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record FolderPickerPicker(bool AllowMultiple = false);
-10
View File
@@ -1,10 +0,0 @@
namespace Toolkit.Foundation;
public interface IFileDescriptor
{
string Name { get; }
string Path { get; }
long Size { get; }
}
+6
View File
@@ -0,0 +1,6 @@
namespace Toolkit.Foundation;
public interface IFilePicker
{
Task<IReadOnlyCollection<string>> Get(FilePickerFilter filter);
}
+6 -4
View File
@@ -1,6 +1,8 @@
namespace Toolkit.Foundation;
public interface IFileProvider
namespace Toolkit.Foundation
{
Task<IReadOnlyCollection<string>> SelectFiles(FileFilter filter);
public interface IFileProvider
{
IReadOnlyCollection<string> Get(string path, FileProviderFilter filter);
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Toolkit.Foundation;
public interface IFolderPicker
{
Task<IReadOnlyCollection<string>> Get(FolderPickerPicker filter);
}
-6
View File
@@ -1,6 +0,0 @@
namespace Toolkit.Foundation;
public interface IFolderProvider
{
Task<IReadOnlyCollection<string>> SelectFolders(FolderFilter filter);
}
@@ -2,7 +2,7 @@
namespace Toolkit.Foundation;
public interface IServiceScopeFactory<TService>
public interface IScopedServiceFactory<TService>
{
(IServiceScope, TService) Create(params object?[] parameters);
}
@@ -2,7 +2,7 @@
namespace Toolkit.Foundation;
public interface IServiceScopeProvider<TService>
public interface IScopedServiceProvider<TService>
{
bool TryGet(TService service, out IServiceScope? serviceScope);
}
@@ -216,11 +216,11 @@ public static class IServiceCollectionExtensions
return services;
}
public static IServiceCollection AddHandlerScoped<TMessage, THandler>(this IServiceCollection services,
public static IServiceCollection AddScopedHandler<TMessage, THandler>(this IServiceCollection services,
string key) where THandler : class, IHandler<TMessage>
where TMessage : class => AddHandlerScoped<TMessage, THandler>(services, ServiceLifetime.Transient, key);
where TMessage : class => AddScopedHandler<TMessage, THandler>(services, ServiceLifetime.Transient, key);
public static IServiceCollection AddHandlerScoped<TMessage, THandler>(this IServiceCollection services,
public static IServiceCollection AddScopedHandler<TMessage, THandler>(this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Transient,
string? key = null) where THandler : class, IHandler<TMessage>
where TMessage : class
@@ -243,11 +243,11 @@ public static class IServiceCollectionExtensions
return services;
}
public static IServiceCollection AddHandlerScoped<TMessage, TResponse, THandler>(this IServiceCollection services,
public static IServiceCollection AddScopedHandler<TMessage, TResponse, THandler>(this IServiceCollection services,
string key) where THandler : class, IHandler<TMessage, TResponse>
where TMessage : class => AddHandlerScoped<TMessage, TResponse, THandler>(services, ServiceLifetime.Transient, key);
where TMessage : class => AddScopedHandler<TMessage, TResponse, THandler>(services, ServiceLifetime.Transient, key);
public static IServiceCollection AddHandlerScoped<TMessage, TResponse, THandler>(this IServiceCollection services,
public static IServiceCollection AddScopedHandler<TMessage, TResponse, THandler>(this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Transient,
string? key = null) where THandler : class, IHandler<TMessage, TResponse>
where TMessage : class
@@ -351,30 +351,38 @@ public static class IServiceCollectionExtensions
return services;
}
public static IServiceCollection AddServiceScope<TServiceScope>(this IServiceCollection services)
where TServiceScope : notnull
public static IServiceCollection AddScopedService<TScopedService>(this IServiceCollection services)
where TScopedService : class
{
services.AddCache<TServiceScope, IServiceScope>();
services.AddTransient<IServiceScopeProvider<TServiceScope>, ServiceScopeProvider<TServiceScope>>();
services.AddTransient<IServiceScopeFactory<TServiceScope>, ServiceScopeFactory<TServiceScope>>();
services.AddScoped<IScopedServiceDescriptor<TScopedService>, ScopedServiceDescriptor<TScopedService>>();
services.AddScoped(provider => provider.GetRequiredService<IScopedServiceDescriptor<TScopedService>>().Value!);
services.AddCache<TScopedService, IServiceScope>();
services.AddTransient<IScopedServiceProvider<TScopedService>, ScopedServiceProvider<TScopedService>>();
services.AddTransient<IScopedServiceFactory<TScopedService>, ScopedServiceFactory<TScopedService>>();
return services;
}
public static IServiceCollection AddServiceScope<TServiceScope>(this IServiceCollection services,
public static IServiceCollection AddScopedService<TScopedService>(this IServiceCollection services,
Action<IServiceProvider> providerDelegate)
where TServiceScope : notnull
where TScopedService : class
{
services.AddCache<TServiceScope, IServiceScope>();
services.AddTransient<IServiceScopeProvider<TServiceScope>, ServiceScopeProvider<TServiceScope>>();
services.AddTransient<IServiceScopeFactory<TServiceScope>, ServiceScopeFactory<TServiceScope>>(provider =>
services.AddScoped<IScopedServiceDescriptor<TScopedService>, ScopedServiceDescriptor<TScopedService>>();
services.AddScoped(provider => provider.GetRequiredService<IScopedServiceDescriptor<TScopedService>>().Value!);
services.AddCache<TScopedService, IServiceScope>();
services.AddTransient<IScopedServiceProvider<TScopedService>, ScopedServiceProvider<TScopedService>>();
services.AddTransient<IScopedServiceFactory<TScopedService>, ScopedServiceFactory<TScopedService>>(provider =>
{
providerDelegate.Invoke(provider);
IServiceScopeFactory factory = provider.GetRequiredService<IServiceScopeFactory>();
ICache<TServiceScope, IServiceScope> cache = provider.GetRequiredService<ICache<TServiceScope, IServiceScope>>();
ICache<TScopedService, IServiceScope> cache = provider.GetRequiredService<ICache<TScopedService, IServiceScope>>();
return new ServiceScopeFactory<TServiceScope>(factory, cache);
return new ScopedServiceFactory<TScopedService>(factory, cache);
});
return services;
@@ -0,0 +1,35 @@
using Microsoft.Extensions.DependencyInjection;
namespace Toolkit.Foundation;
public class ScopedServiceFactory<TScopedService>(IServiceScopeFactory serviceScopeFactory,
ICache<TScopedService, IServiceScope> cache) :
IScopedServiceFactory<TScopedService>
where TScopedService : notnull
{
public (IServiceScope, TScopedService) Create(params object?[] parameters)
{
if (serviceScopeFactory.CreateScope() is IServiceScope serviceScope)
{
IServiceProvider serviceProvider = serviceScope.ServiceProvider;
if (serviceProvider.GetRequiredService<IServiceFactory>() is IServiceFactory factory)
{
if (factory.Create<TScopedService>(parameters) is TScopedService service)
{
serviceProvider.GetRequiredService<IScopedServiceDescriptor<TScopedService>>().Set(service);
cache.Add(service, serviceScope);
foreach (IInitializationScoped initializationScoped in serviceScope.ServiceProvider.GetServices<IInitializationScoped>())
{
initializationScoped.Initialize();
}
return (serviceScope, service);
}
}
}
return default;
}
}
@@ -2,8 +2,8 @@
namespace Toolkit.Foundation;
public class ServiceScopeProvider<TService>(ICache<TService, IServiceScope> cache) :
IServiceScopeProvider<TService>
public class ScopedServiceProvider<TService>(ICache<TService, IServiceScope> cache) :
IScopedServiceProvider<TService>
where TService : notnull
{
public bool TryGet(TService service,
+5 -5
View File
@@ -1,14 +1,14 @@
namespace Toolkit.Foundation;
public class SelectFilesHandler(IFileProvider fileProvider) :
IAsyncHandler<SelectionEventArgs<FileFilter>, IReadOnlyCollection<string>?>
public class SelectFilesHandler(IFilePicker fileProvider) :
IAsyncHandler<SelectionEventArgs<FilePickerFilter>, IReadOnlyCollection<string>?>
{
public async Task<IReadOnlyCollection<string>?> Handle(SelectionEventArgs<FileFilter> args,
public async Task<IReadOnlyCollection<string>?> Handle(SelectionEventArgs<FilePickerFilter> args,
CancellationToken cancellationToken)
{
if (args.Value is FileFilter filter)
if (args.Value is FilePickerFilter filter)
{
if (await fileProvider.SelectFiles(filter)
if (await fileProvider.Get(filter)
is { Count: > 0 } files)
{
return files;
+5 -5
View File
@@ -1,15 +1,15 @@
namespace Toolkit.Foundation;
public class SelectFoldersHandler(IFolderProvider folderProvider) :
IAsyncHandler<SelectionEventArgs<FolderFilter>, IReadOnlyCollection<string>?>
public class SelectFoldersHandler(IFolderPicker folderProvider) :
IAsyncHandler<SelectionEventArgs<FolderPickerPicker>, IReadOnlyCollection<string>?>
{
public async Task<IReadOnlyCollection<string>?> Handle(SelectionEventArgs<FolderFilter> args,
public async Task<IReadOnlyCollection<string>?> Handle(SelectionEventArgs<FolderPickerPicker> args,
CancellationToken cancellationToken = default)
{
if (args.Value is FolderFilter filter)
if (args.Value is FolderPickerPicker filter)
{
if (await folderProvider.SelectFolders(filter)
if (await folderProvider.Get(filter)
is { Count: > 0 } folders)
{
return folders;
-31
View File
@@ -1,31 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
namespace Toolkit.Foundation;
public class ServiceScopeFactory<TServiceScope>(IServiceScopeFactory serviceScopeFactory,
ICache<TServiceScope, IServiceScope> cache) :
IServiceScopeFactory<TServiceScope>
where TServiceScope : notnull
{
public (IServiceScope, TServiceScope) Create(params object?[] parameters)
{
if (serviceScopeFactory.CreateScope() is IServiceScope serviceScope)
{
if (serviceScope.ServiceProvider.GetService<IServiceFactory>() is IServiceFactory factory)
{
if (factory.Create<TServiceScope>(parameters) is TServiceScope service)
{
cache.Add(service, serviceScope);
foreach (IInitializationScoped initializationScoped in serviceScope.ServiceProvider.GetServices<IInitializationScoped>())
{
initializationScoped.Initialize();
}
return (serviceScope, service);
}
}
}
return default;
}
}
@@ -16,8 +16,8 @@ public static class IServiceCollectionExtensions
services.AddTransient<IContentTemplate, ContentTemplate>();
services.AddTransient<INavigationRegion, NavigationRegion>();
services.AddHandlerScoped<NavigateTemplateEventArgs, ContentControlHandler>(nameof(ContentControl));
services.AddHandlerScoped<NavigateTemplateEventArgs, ContentDialogHandler>(nameof(ContentDialog));
services.AddScopedHandler<NavigateTemplateEventArgs, ContentControlHandler>(nameof(ContentControl));
services.AddScopedHandler<NavigateTemplateEventArgs, ContentDialogHandler>(nameof(ContentDialog));
services.AddTransient((Func<IServiceProvider, IProxyServiceCollection<IComponentBuilder>>)(provider =>
new ProxyServiceCollection<IComponentBuilder>(services =>