From ce503a4cde9ea4c320f9589e13c5f5d1bc69d46c Mon Sep 17 00:00:00 2001 From: Bogdan Stoica Date: Wed, 1 Oct 2025 10:54:52 +0100 Subject: [PATCH 1/4] Add generic factory registration for services --- .../DefaultServiceBuilder.cs | 7 ++ .../DefaultServiceFactory.cs | 52 ++++++++++++ .../GenericServiceBuilderExtentions.cs | 83 +++++++++++++++++++ .../GenericServiceCollectionExtentions.cs | 58 +++++++++++++ .../IServiceBuilder.cs | 7 ++ .../IServiceFactory.cs | 6 ++ .../ServiceFactoryOptions.cs | 8 ++ 7 files changed, 221 insertions(+) create mode 100644 src/InteractiveCLI/ServiceConfigurationHelpers/DefaultServiceBuilder.cs create mode 100644 src/InteractiveCLI/ServiceConfigurationHelpers/DefaultServiceFactory.cs create mode 100644 src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceBuilderExtentions.cs create mode 100644 src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceCollectionExtentions.cs create mode 100644 src/InteractiveCLI/ServiceConfigurationHelpers/IServiceBuilder.cs create mode 100644 src/InteractiveCLI/ServiceConfigurationHelpers/IServiceFactory.cs create mode 100644 src/InteractiveCLI/ServiceConfigurationHelpers/ServiceFactoryOptions.cs diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/DefaultServiceBuilder.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/DefaultServiceBuilder.cs new file mode 100644 index 0000000..f496758 --- /dev/null +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/DefaultServiceBuilder.cs @@ -0,0 +1,7 @@ +namespace InteractiveCLI.ServiceConfigurationHelpers; + +public class DefaultServiceBuilder(IServiceCollection services, string name) : IServiceBuilder where TService : class +{ + public string Name { get; } = name; + public IServiceCollection Services { get; } = services; +} diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/DefaultServiceFactory.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/DefaultServiceFactory.cs new file mode 100644 index 0000000..c60f3e8 --- /dev/null +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/DefaultServiceFactory.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Options; + +namespace InteractiveCLI.ServiceConfigurationHelpers; + +public class DefaultServiceFactory( + IServiceProvider serviceProvider, + IOptionsMonitor> optionsMonitor) : IServiceFactory where TService : class +{ + private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly IOptionsMonitor> _optionsMonitor = optionsMonitor; + + public TService CreateService(string name = "Default") + { + var options = _optionsMonitor.Get(name); + + TService service; + + if (options.CustomFactory != null) + { + // Use custom factory if provided + var args = GatherConstructorArguments(options); + service = options.CustomFactory(_serviceProvider, args); + } + else + { + // Use default creation with ActivatorUtilities + var args = GatherConstructorArguments(options); + service = (TService)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(TService), args); + } + + // Apply registered service actions + foreach (var action in options.ServiceActions) + { + action(service, _serviceProvider); + } + + return service; + } + + private object[] GatherConstructorArguments(ServiceFactoryOptions options) + { + var allArgs = new List(); + + foreach (var argProvider in options.ConstructorArgumentProviders) + { + var args = argProvider(_serviceProvider); + allArgs.AddRange(args); + } + + return allArgs.ToArray(); + } +} diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceBuilderExtentions.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceBuilderExtentions.cs new file mode 100644 index 0000000..aed13b9 --- /dev/null +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceBuilderExtentions.cs @@ -0,0 +1,83 @@ +namespace InteractiveCLI.ServiceConfigurationHelpers; + +public static class GenericServiceBuilderExtentions +{ + public static IServiceBuilder ConfigureService( + this IServiceBuilder builder, + Action> configureOptions) + where TService : class + { + builder.Services.Configure>(builder.Name, configureOptions); + return builder; + } + + public static IServiceBuilder ConfigureService( + this IServiceBuilder builder, + Action, IServiceProvider> configureOptions) + where TService : class + { + builder.Services.Configure>(builder.Name, options => + { + options.ServiceActions.Add((service, serviceProvider) => + { + configureOptions(options, serviceProvider); + }); + }); + return builder; + } + + public static IServiceBuilder WithConstructorArgs( + this IServiceBuilder builder, + params object[] args) + where TService : class + { + return builder.ConfigureService(options => + { + options.ConstructorArgumentProviders.Add(_ => args); + }); + } + + public static IServiceBuilder WithConstructorArgs( + this IServiceBuilder builder, + Func argsProvider) + where TService : class + { + return builder.ConfigureService(options => + { + options.ConstructorArgumentProviders.Add(argsProvider); + }); + } + + public static IServiceBuilder AddServiceAction( + this IServiceBuilder builder, + Action action) + where TService : class + { + return builder.ConfigureService(options => + { + options.ServiceActions.Add(action); + }); + } + + public static IServiceBuilder WithCustomFactory( + this IServiceBuilder builder, + Func customFactory) + where TService : class + { + return builder.ConfigureService(options => + { + options.CustomFactory = customFactory; + }); + } + + public static IServiceBuilder WithCustomFactory( + this IServiceBuilder builder, + Func customFactory) + where TService : class + { + return builder.ConfigureService(options => + { + options.CustomFactory = (serviceProvider, _) => customFactory(serviceProvider); + }); + } +} diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceCollectionExtentions.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceCollectionExtentions.cs new file mode 100644 index 0000000..bd9f819 --- /dev/null +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceCollectionExtentions.cs @@ -0,0 +1,58 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace InteractiveCLI.ServiceConfigurationHelpers; + +public static class GenericServiceCollectionExtentions +{ + public static IServiceBuilder AddServiceWithFactory( + this IServiceCollection services, + string name = "Default") + where TService : class + { + // Register the generic factory + services.TryAddSingleton, DefaultServiceFactory>(); + + // Register named options for this service type + services.Configure>(name, options => { }); + + // Register the service using the factory + services.TryAddTransient(serviceProvider => + { + var factory = serviceProvider.GetRequiredService>(); + return factory.CreateService(name); + }); + + return new DefaultServiceBuilder(services, name); + } + + public static IServiceBuilder AddServiceWithFactory( + this IServiceCollection services, + string name = "Default") + where TService : class + where TImplementation : class, TService + { + // Register the generic factory + services.TryAddSingleton, DefaultServiceFactory>(); + + // Register named options + services.Configure>(name, options => + { + // Set custom factory to create TImplementation + options.CustomFactory = (serviceProvider, args) => + (TService)ActivatorUtilities.CreateInstance(serviceProvider, typeof(TImplementation), args); + }); + + // Register the service interface + services.TryAddTransient(serviceProvider => + { + var factory = serviceProvider.GetRequiredService>(); + return factory.CreateService(name); + }); + + // Also register the concrete type + services.TryAddTransient(serviceProvider => + (TImplementation)serviceProvider.GetRequiredService()); + + return new DefaultServiceBuilder(services, name); + } +} diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/IServiceBuilder.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/IServiceBuilder.cs new file mode 100644 index 0000000..d174179 --- /dev/null +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/IServiceBuilder.cs @@ -0,0 +1,7 @@ +namespace InteractiveCLI.ServiceConfigurationHelpers; + +public interface IServiceBuilder where TService : class +{ + public string Name { get; } + public IServiceCollection Services { get; } +} diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/IServiceFactory.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/IServiceFactory.cs new file mode 100644 index 0000000..52e700e --- /dev/null +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/IServiceFactory.cs @@ -0,0 +1,6 @@ +namespace InteractiveCLI.ServiceConfigurationHelpers; + +public interface IServiceFactory where TService : class +{ + TService CreateService(string name = "Default"); +} diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/ServiceFactoryOptions.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/ServiceFactoryOptions.cs new file mode 100644 index 0000000..ed91252 --- /dev/null +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/ServiceFactoryOptions.cs @@ -0,0 +1,8 @@ +namespace InteractiveCLI.ServiceConfigurationHelpers; + +public class ServiceFactoryOptions where TService : class +{ + public List> ServiceActions { get; set; } = new(); + public List> ConstructorArgumentProviders { get; set; } = new(); + public Func? CustomFactory { get; set; } +} From 462b29394a7cbf8c160e5f98091efbe67ff36855 Mon Sep 17 00:00:00 2001 From: Jonny Olliff-Lee Date: Thu, 30 Oct 2025 08:45:46 +0000 Subject: [PATCH 2/4] fix: Typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../GenericServiceCollectionExtentions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceCollectionExtentions.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceCollectionExtentions.cs index bd9f819..cb60c15 100644 --- a/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceCollectionExtentions.cs +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceCollectionExtentions.cs @@ -2,7 +2,7 @@ namespace InteractiveCLI.ServiceConfigurationHelpers; -public static class GenericServiceCollectionExtentions +public static class GenericServiceCollectionExtensions { public static IServiceBuilder AddServiceWithFactory( this IServiceCollection services, From dd865ee8e4145e9b0671beb0b6028811529c63ce Mon Sep 17 00:00:00 2001 From: Jonny Olliff-Lee Date: Thu, 30 Oct 2025 08:45:58 +0000 Subject: [PATCH 3/4] fix: Typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../GenericServiceBuilderExtentions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceBuilderExtentions.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceBuilderExtentions.cs index aed13b9..6266360 100644 --- a/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceBuilderExtentions.cs +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/GenericServiceBuilderExtentions.cs @@ -1,6 +1,6 @@ namespace InteractiveCLI.ServiceConfigurationHelpers; -public static class GenericServiceBuilderExtentions +public static class GenericServiceBuilderExtensions { public static IServiceBuilder ConfigureService( this IServiceBuilder builder, From da05757f956f0a869b06773a427fc4249a79c88c Mon Sep 17 00:00:00 2001 From: Jonny Olliff-Lee Date: Thu, 30 Oct 2025 08:48:02 +0000 Subject: [PATCH 4/4] fix: Cos Copilot told me to Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ServiceConfigurationHelpers/ServiceFactoryOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InteractiveCLI/ServiceConfigurationHelpers/ServiceFactoryOptions.cs b/src/InteractiveCLI/ServiceConfigurationHelpers/ServiceFactoryOptions.cs index ed91252..b981004 100644 --- a/src/InteractiveCLI/ServiceConfigurationHelpers/ServiceFactoryOptions.cs +++ b/src/InteractiveCLI/ServiceConfigurationHelpers/ServiceFactoryOptions.cs @@ -3,6 +3,6 @@ public class ServiceFactoryOptions where TService : class { public List> ServiceActions { get; set; } = new(); - public List> ConstructorArgumentProviders { get; set; } = new(); + public IList> ConstructorArgumentProviders { get; private set; } = new List>(); public Func? CustomFactory { get; set; } }