From 6454e1bb6fd385beb63e95081139e0f18cb71b05 Mon Sep 17 00:00:00 2001 From: Dan Clark Date: Wed, 4 Dec 2024 22:35:58 +0000 Subject: [PATCH] Added Scoped Handlers --- .../ActionableInitializationScoped.cs | 7 + .../AsyncHandlerInitialization.cs | 4 +- .../AsyncHandlerKeyedInitialization.cs | 4 +- Toolkit.Foundation/HandlerInitialization.cs | 4 +- .../HandlerKeyedInitialization.cs | 4 +- Toolkit.Foundation/IInitialization.cs | 2 +- Toolkit.Foundation/IInitializationScoped.cs | 6 + ...Service.cs => IScopedServiceDescriptor.cs} | 2 +- .../IServiceCollectionExtensions.cs | 215 +++++++++++++++--- ...rService.cs => ScopedServiceDescriptor.cs} | 4 +- Toolkit.Foundation/ServiceScopeFactory.cs | 17 +- Toolkit.WinUI/ContentTemplate.cs | 22 +- 12 files changed, 235 insertions(+), 56 deletions(-) create mode 100644 Toolkit.Foundation/ActionableInitializationScoped.cs create mode 100644 Toolkit.Foundation/IInitializationScoped.cs rename Toolkit.Foundation/{IDecoratorService.cs => IScopedServiceDescriptor.cs} (65%) rename Toolkit.Foundation/{DecoratorService.cs => ScopedServiceDescriptor.cs} (63%) diff --git a/Toolkit.Foundation/ActionableInitializationScoped.cs b/Toolkit.Foundation/ActionableInitializationScoped.cs new file mode 100644 index 0000000..27e27d0 --- /dev/null +++ b/Toolkit.Foundation/ActionableInitializationScoped.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public class ActionableInitializationScoped(IServiceProvider provider, + Action delegateAction) : IInitializationScoped +{ + public void Initialize() => delegateAction.Invoke(provider); +} \ No newline at end of file diff --git a/Toolkit.Foundation/AsyncHandlerInitialization.cs b/Toolkit.Foundation/AsyncHandlerInitialization.cs index e170665..224ec2e 100644 --- a/Toolkit.Foundation/AsyncHandlerInitialization.cs +++ b/Toolkit.Foundation/AsyncHandlerInitialization.cs @@ -5,7 +5,7 @@ namespace Toolkit.Foundation; public class AsyncHandlerInitialization(IMessenger messenger, IServiceProvider provider) : - IInitialization where THandler : class, IAsyncHandler + IInitialization, IInitializationScoped where THandler : class, IAsyncHandler where TMessage : class { public void Initialize() @@ -33,7 +33,7 @@ public class AsyncHandlerInitialization(IMessenge public class AsyncHandlerInitialization(IMessenger messenger, IServiceProvider provider) : - IInitialization where THandler : class, IAsyncHandler + IInitialization, IInitializationScoped where THandler : class, IAsyncHandler where TMessage : class { public void Initialize() diff --git a/Toolkit.Foundation/AsyncHandlerKeyedInitialization.cs b/Toolkit.Foundation/AsyncHandlerKeyedInitialization.cs index f86ac94..dbc4e6f 100644 --- a/Toolkit.Foundation/AsyncHandlerKeyedInitialization.cs +++ b/Toolkit.Foundation/AsyncHandlerKeyedInitialization.cs @@ -6,7 +6,7 @@ namespace Toolkit.Foundation; public class AsyncHandlerKeyedInitialization(string key, IMessenger messenger, IServiceProvider provider) : - IInitialization where THandler : class, IAsyncHandler + IInitialization, IInitializationScoped where THandler : class, IAsyncHandler where TMessage : class { public void Initialize() @@ -34,7 +34,7 @@ public class AsyncHandlerKeyedInitialization(string key, public class AsyncHandlerKeyedInitialization(string key, IMessenger messenger, IServiceProvider provider) : - IInitialization where THandler : class, IAsyncHandler + IInitialization, IInitializationScoped where THandler : class, IAsyncHandler where TMessage : class { public void Initialize() diff --git a/Toolkit.Foundation/HandlerInitialization.cs b/Toolkit.Foundation/HandlerInitialization.cs index 5bd5954..df7c4a1 100644 --- a/Toolkit.Foundation/HandlerInitialization.cs +++ b/Toolkit.Foundation/HandlerInitialization.cs @@ -5,7 +5,7 @@ namespace Toolkit.Foundation; public class HandlerInitialization(IMessenger messenger, IServiceProvider provider) : - IInitialization where THandler : class, IHandler + IInitialization, IInitializationScoped where THandler : class, IHandler where TMessage : class { public void Initialize() @@ -34,7 +34,7 @@ public class HandlerInitialization(IMessenger mes public class HandlerInitialization(IMessenger messenger, IServiceProvider provider) : - IInitialization where THandler : class, IHandler + IInitialization, IInitializationScoped where THandler : class, IHandler where TMessage : class { public void Initialize() diff --git a/Toolkit.Foundation/HandlerKeyedInitialization.cs b/Toolkit.Foundation/HandlerKeyedInitialization.cs index 62f1741..eaffb2a 100644 --- a/Toolkit.Foundation/HandlerKeyedInitialization.cs +++ b/Toolkit.Foundation/HandlerKeyedInitialization.cs @@ -6,7 +6,7 @@ namespace Toolkit.Foundation; public class HandlerKeyedInitialization(string key, IMessenger messenger, IServiceProvider provider) : - IInitialization where THandler : class, IHandler + IInitialization, IInitializationScoped where THandler : class, IHandler where TMessage : class { public void Initialize() @@ -46,7 +46,7 @@ public class HandlerKeyedInitialization(string key, public class HandlerKeyedInitialization(string key, IMessenger messenger, IServiceProvider provider) : - IInitialization where THandler : class, IHandler + IInitialization, IInitializationScoped where THandler : class, IHandler where TMessage : class { public void Initialize() diff --git a/Toolkit.Foundation/IInitialization.cs b/Toolkit.Foundation/IInitialization.cs index c317689..3e08677 100644 --- a/Toolkit.Foundation/IInitialization.cs +++ b/Toolkit.Foundation/IInitialization.cs @@ -3,4 +3,4 @@ public interface IInitialization { void Initialize(); -} \ No newline at end of file +} diff --git a/Toolkit.Foundation/IInitializationScoped.cs b/Toolkit.Foundation/IInitializationScoped.cs new file mode 100644 index 0000000..7b4d892 --- /dev/null +++ b/Toolkit.Foundation/IInitializationScoped.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IInitializationScoped +{ + void Initialize(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IDecoratorService.cs b/Toolkit.Foundation/IScopedServiceDescriptor.cs similarity index 65% rename from Toolkit.Foundation/IDecoratorService.cs rename to Toolkit.Foundation/IScopedServiceDescriptor.cs index 1ed8d19..4dcedf4 100644 --- a/Toolkit.Foundation/IDecoratorService.cs +++ b/Toolkit.Foundation/IScopedServiceDescriptor.cs @@ -1,6 +1,6 @@ namespace Toolkit.Foundation; -public interface IDecoratorService +public interface IScopedServiceDescriptor { T? Value { get; } diff --git a/Toolkit.Foundation/IServiceCollectionExtensions.cs b/Toolkit.Foundation/IServiceCollectionExtensions.cs index a6d6cd7..4b747b3 100644 --- a/Toolkit.Foundation/IServiceCollectionExtensions.cs +++ b/Toolkit.Foundation/IServiceCollectionExtensions.cs @@ -50,6 +50,52 @@ public static class IServiceCollectionExtensions return services; } + public static IServiceCollection AddAsyncHandlerScoped(this IServiceCollection services, + string key) where THandler : class, IAsyncHandler + where TMessage : class => AddAsyncHandlerScoped(services, ServiceLifetime.Transient, key); + + public static IServiceCollection AddAsyncHandlerScoped(this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Transient, + string? key = null) where THandler : class, IAsyncHandler + where TMessage : class + { + if (key is { Length: > 0 }) + { + services.Add(new ServiceDescriptor(typeof(IAsyncHandler), key, typeof(THandler), lifetime)); + services.AddInitializationScoped>>(key); + } + else + { + services.Add(new ServiceDescriptor(typeof(IAsyncHandler), typeof(THandler), lifetime)); + services.AddInitializationScoped>>(); + } + + return services; + } + + public static IServiceCollection AddAsyncHandlerScoped(this IServiceCollection services, + string key) where THandler : class, IAsyncHandler + where TMessage : class => AddAsyncHandlerScoped(services, ServiceLifetime.Transient, key); + + public static IServiceCollection AddAsyncHandlerScoped(this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Transient, + string? key = null) where THandler : class, IAsyncHandler + where TMessage : class + { + if (key is { Length: > 0 }) + { + services.Add(new ServiceDescriptor(typeof(IAsyncHandler), key, typeof(THandler), lifetime)); + services.AddInitializationScoped>>(key); + } + else + { + services.Add(new ServiceDescriptor(typeof(IAsyncHandler), typeof(THandler), lifetime)); + services.AddInitializationScoped>>(); + } + + return services; + } + public static IServiceCollection AddAsyncInitialization(this IServiceCollection services) where TInitialization : class, IAsyncInitialization { @@ -57,6 +103,40 @@ public static class IServiceCollectionExtensions return services; } + public static IServiceCollection AddAsyncPipelineBehavior(this IServiceCollection services, + Type behaviorType, + string? key = null) + { + bool ImplementsInterface(Type type, Type interfaceType) => + type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType); + + if (ImplementsInterface(behaviorType, typeof(IAsyncPipelineBehavior<,>))) + { + if (key is { Length: > 0 }) + { + services.AddKeyedTransient(typeof(IAsyncPipelineBehavior<,>), key, behaviorType); + } + else + { + services.AddTransient(typeof(IAsyncPipelineBehavior<,>), behaviorType); + } + } + + if (ImplementsInterface(behaviorType, typeof(IAsyncPipelineBehavior<>))) + { + if (key is { Length: > 0 }) + { + services.AddKeyedTransient(typeof(IAsyncPipelineBehavior<>), key, behaviorType); + } + else + { + services.AddTransient(typeof(IAsyncPipelineBehavior<>), behaviorType); + } + } + + return services; + } + public static IServiceCollection AddCache(this IServiceCollection services) where TKey : notnull where TValue : notnull @@ -88,7 +168,7 @@ public static class IServiceCollectionExtensions public static IServiceCollection AddHandler(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Transient, - string? key = null) where THandler : class, IHandler + string? key = null) where THandler : class, IHandler where TMessage : class { if (key is { Length: > 0 }) @@ -128,6 +208,52 @@ public static class IServiceCollectionExtensions return services; } + public static IServiceCollection AddHandlerScoped(this IServiceCollection services, + string key) where THandler : class, IHandler + where TMessage : class => AddHandlerScoped(services, ServiceLifetime.Transient, key); + + public static IServiceCollection AddHandlerScoped(this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Transient, + string? key = null) where THandler : class, IHandler + where TMessage : class + { + if (key is { Length: > 0 }) + { + services.Add(new ServiceDescriptor(typeof(IHandler), key, typeof(THandler), lifetime)); + services.AddInitializationScoped>>(key); + } + else + { + services.Add(new ServiceDescriptor(typeof(IHandler), typeof(THandler), lifetime)); + services.AddInitializationScoped>>(); + } + + return services; + } + + public static IServiceCollection AddHandlerScoped(this IServiceCollection services, + string key) where THandler : class, IHandler + where TMessage : class => AddHandlerScoped(services, ServiceLifetime.Transient, key); + + public static IServiceCollection AddHandlerScoped(this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Transient, + string? key = null) where THandler : class, IHandler + where TMessage : class + { + if (key is { Length: > 0 }) + { + services.Add(new ServiceDescriptor(typeof(IHandler), key, typeof(THandler), lifetime)); + services.AddInitializationScoped>>(key); + } + else + { + services.Add(new ServiceDescriptor(typeof(IHandler), typeof(THandler), lifetime)); + services.AddInitializationScoped>>(); + } + + return services; + } + public static IServiceCollection AddInitialization(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Transient) where TInitialization : class, IInitialization @@ -155,45 +281,44 @@ public static class IServiceCollectionExtensions return services; } - public static IServiceCollection AddInitialization(this IServiceCollection services, + public static IServiceCollection AddInitialization(this IServiceCollection services, Action delegateAction) - { services.AddTransient(provider => new ActionableInitialization(provider, delegateAction)); return services; } - public static IServiceCollection AddAsyncPipelineBehavior(this IServiceCollection services, - Type behaviorType, - string? key = null) + public static IServiceCollection AddInitializationScoped(this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Transient) + where TInitialization : class, IInitialization + where TInitializationImplementation : class, IInitializationScoped { - bool ImplementsInterface(Type type, Type interfaceType) => - type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType); + services.Add(new ServiceDescriptor(typeof(IInitializationScoped), typeof(TInitializationImplementation), lifetime)); + services.AddTransient(provider => provider.GetRequiredService()); + return services; + } - if (ImplementsInterface(behaviorType, typeof(IAsyncPipelineBehavior<,>))) - { - if (key is { Length: > 0 }) - { - services.AddKeyedTransient(typeof(IAsyncPipelineBehavior<,>), key, behaviorType); - } - else - { - services.AddTransient(typeof(IAsyncPipelineBehavior<,>), behaviorType); - } - } - - if (ImplementsInterface(behaviorType, typeof(IAsyncPipelineBehavior<>))) - { - if (key is { Length: > 0 }) - { - services.AddKeyedTransient(typeof(IAsyncPipelineBehavior<>), key, behaviorType); - } - else - { - services.AddTransient(typeof(IAsyncPipelineBehavior<>), behaviorType); - } - } + public static IServiceCollection AddInitializationScoped(this IServiceCollection services) + where TInitialization : class, IInitializationScoped + { + services.AddTransient(); + return services; + } + public static IServiceCollection AddInitializationScoped(this IServiceCollection services, + params object[] parameters) + where TInitialization : class, IInitializationScoped + { + services.AddTransient(provider => provider.GetRequiredService() + .Create(parameters)); + + return services; + } + + public static IServiceCollection AddInitializationScoped(this IServiceCollection services, + Action delegateAction) + { + services.AddTransient(provider => new ActionableInitializationScoped(provider, delegateAction)); return services; } @@ -208,6 +333,34 @@ public static class IServiceCollectionExtensions return services; } + public static IServiceCollection AddServiceScope(this IServiceCollection services) + where TServiceScope : notnull + { + services.AddCache(); + services.AddTransient, ServiceScopeProvider>(); + services.AddTransient, ServiceScopeFactory>(); + + return services; + } + + public static IServiceCollection AddServiceScope(this IServiceCollection services, + Action providerDelegate) + where TServiceScope : notnull + { + services.AddCache(); + services.AddTransient, ServiceScopeProvider>(); + services.AddTransient, ServiceScopeFactory>(provider => + { + providerDelegate.Invoke(provider); + + IServiceScopeFactory factory = provider.GetRequiredService(); + ICache cache = provider.GetRequiredService>(); + + return new ServiceScopeFactory(factory, cache); + }); + + return services; + } public static IServiceCollection AddTemplate(this IServiceCollection services, object? key = null, ServiceLifetime serviceLifetime = ServiceLifetime.Transient, diff --git a/Toolkit.Foundation/DecoratorService.cs b/Toolkit.Foundation/ScopedServiceDescriptor.cs similarity index 63% rename from Toolkit.Foundation/DecoratorService.cs rename to Toolkit.Foundation/ScopedServiceDescriptor.cs index c16325b..27f2482 100644 --- a/Toolkit.Foundation/DecoratorService.cs +++ b/Toolkit.Foundation/ScopedServiceDescriptor.cs @@ -1,7 +1,7 @@ namespace Toolkit.Foundation; -public class DecoratorService : - IDecoratorService +public class ScopedServiceDescriptor : + IScopedServiceDescriptor { public T? Value { get; private set; } diff --git a/Toolkit.Foundation/ServiceScopeFactory.cs b/Toolkit.Foundation/ServiceScopeFactory.cs index 10efe92..92ac97b 100644 --- a/Toolkit.Foundation/ServiceScopeFactory.cs +++ b/Toolkit.Foundation/ServiceScopeFactory.cs @@ -2,20 +2,25 @@ namespace Toolkit.Foundation; -public class ServiceScopeFactory(IServiceScopeFactory serviceScopeFactory, - ICache cache) : - IServiceScopeFactory - where TService : notnull +public class ServiceScopeFactory(IServiceScopeFactory serviceScopeFactory, + ICache cache) : + IServiceScopeFactory + where TServiceScope : notnull { - public (IServiceScope, TService) Create(params object?[] parameters) + public (IServiceScope, TServiceScope) Create(params object?[] parameters) { if (serviceScopeFactory.CreateScope() is IServiceScope serviceScope) { if (serviceScope.ServiceProvider.GetService() is IServiceFactory factory) { - if (factory.Create(parameters) is TService service) + if (factory.Create(parameters) is TServiceScope service) { cache.Add(service, serviceScope); + foreach (IInitializationScoped initializationScoped in serviceScope.ServiceProvider.GetServices()) + { + initializationScoped.Initialize(); + } + return (serviceScope, service); } } diff --git a/Toolkit.WinUI/ContentTemplate.cs b/Toolkit.WinUI/ContentTemplate.cs index 914c45f..0cd55ad 100644 --- a/Toolkit.WinUI/ContentTemplate.cs +++ b/Toolkit.WinUI/ContentTemplate.cs @@ -10,6 +10,8 @@ public class ContentTemplate : DataTemplateSelector, IContentTemplate { + private readonly Dictionary _cache = []; + protected override DataTemplate? SelectTemplateCore(object item) { if (item is IObservableViewModel observableViewModel) @@ -17,10 +19,19 @@ public class ContentTemplate : if (observableViewModel.Provider is IServiceProvider provider) { Type itemType = item.GetType(); - if (provider.GetRequiredKeyedService(itemType.Name.Replace("ViewModel", "")) + string key = itemType.Name.Replace("ViewModel", ""); + + if (_cache.TryGetValue(key, out DataTemplate? cachedTemplate)) + { + return cachedTemplate; + } + + if (provider.GetRequiredKeyedService(key) is IContentTemplateDescriptor descriptor) { - return CreateDataTemplate(descriptor); + var newTemplate = CreateDataTemplate(descriptor); + _cache[key] = newTemplate; + return newTemplate; } } } @@ -29,10 +40,7 @@ public class ContentTemplate : } protected override DataTemplate? SelectTemplateCore(object item, - DependencyObject container) - { - return SelectTemplateCore(item); - } + DependencyObject container) => SelectTemplateCore(item); private static DataTemplate CreateDataTemplate(IContentTemplateDescriptor descriptor) { @@ -44,4 +52,4 @@ public class ContentTemplate : return (DataTemplate)XamlReader.Load(xamlString); } -} \ No newline at end of file +}