-
Notifications
You must be signed in to change notification settings - Fork 2
Add generic factory registration for services #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| namespace InteractiveCLI.ServiceConfigurationHelpers; | ||
|
|
||
| public class DefaultServiceBuilder<TService>(IServiceCollection services, string name) : IServiceBuilder<TService> where TService : class | ||
| { | ||
| public string Name { get; } = name; | ||
| public IServiceCollection Services { get; } = services; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| using Microsoft.Extensions.Options; | ||
|
|
||
| namespace InteractiveCLI.ServiceConfigurationHelpers; | ||
|
|
||
| public class DefaultServiceFactory<TService>( | ||
| IServiceProvider serviceProvider, | ||
| IOptionsMonitor<ServiceFactoryOptions<TService>> optionsMonitor) : IServiceFactory<TService> where TService : class | ||
| { | ||
| private readonly IServiceProvider _serviceProvider = serviceProvider; | ||
| private readonly IOptionsMonitor<ServiceFactoryOptions<TService>> _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<TService> options) | ||
| { | ||
| var allArgs = new List<object>(); | ||
|
|
||
| foreach (var argProvider in options.ConstructorArgumentProviders) | ||
| { | ||
| var args = argProvider(_serviceProvider); | ||
| allArgs.AddRange(args); | ||
| } | ||
|
Comment on lines
+44
to
+48
|
||
|
|
||
| return allArgs.ToArray(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,83 @@ | ||||||||||||||||||||
| namespace InteractiveCLI.ServiceConfigurationHelpers; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public static class GenericServiceBuilderExtensions | ||||||||||||||||||||
| { | ||||||||||||||||||||
| public static IServiceBuilder<TService> ConfigureService<TService>( | ||||||||||||||||||||
| this IServiceBuilder<TService> builder, | ||||||||||||||||||||
| Action<ServiceFactoryOptions<TService>> configureOptions) | ||||||||||||||||||||
| where TService : class | ||||||||||||||||||||
| { | ||||||||||||||||||||
| builder.Services.Configure<ServiceFactoryOptions<TService>>(builder.Name, configureOptions); | ||||||||||||||||||||
| return builder; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public static IServiceBuilder<TService> ConfigureService<TService>( | ||||||||||||||||||||
| this IServiceBuilder<TService> builder, | ||||||||||||||||||||
| Action<ServiceFactoryOptions<TService>, IServiceProvider> configureOptions) | ||||||||||||||||||||
| where TService : class | ||||||||||||||||||||
| { | ||||||||||||||||||||
| builder.Services.Configure<ServiceFactoryOptions<TService>>(builder.Name, options => | ||||||||||||||||||||
| { | ||||||||||||||||||||
| options.ServiceActions.Add((service, serviceProvider) => | ||||||||||||||||||||
| { | ||||||||||||||||||||
| configureOptions(options, serviceProvider); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
Comment on lines
+19
to
+24
|
||||||||||||||||||||
| builder.Services.Configure<ServiceFactoryOptions<TService>>(builder.Name, options => | |
| { | |
| options.ServiceActions.Add((service, serviceProvider) => | |
| { | |
| configureOptions(options, serviceProvider); | |
| }); | |
| builder.Services.Configure<ServiceFactoryOptions<TService>>(builder.Name, (serviceProvider, options) => | |
| { | |
| configureOptions(options, serviceProvider); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bogdanstc-freemarket thoughts?
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| using Microsoft.Extensions.DependencyInjection.Extensions; | ||
|
|
||
| namespace InteractiveCLI.ServiceConfigurationHelpers; | ||
|
|
||
| public static class GenericServiceCollectionExtensions | ||
| { | ||
| public static IServiceBuilder<TService> AddServiceWithFactory<TService>( | ||
| this IServiceCollection services, | ||
| string name = "Default") | ||
| where TService : class | ||
| { | ||
| // Register the generic factory | ||
| services.TryAddSingleton<IServiceFactory<TService>, DefaultServiceFactory<TService>>(); | ||
|
|
||
| // Register named options for this service type | ||
| services.Configure<ServiceFactoryOptions<TService>>(name, options => { }); | ||
|
|
||
| // Register the service using the factory | ||
| services.TryAddTransient<TService>(serviceProvider => | ||
| { | ||
| var factory = serviceProvider.GetRequiredService<IServiceFactory<TService>>(); | ||
| return factory.CreateService(name); | ||
| }); | ||
|
|
||
| return new DefaultServiceBuilder<TService>(services, name); | ||
| } | ||
|
|
||
| public static IServiceBuilder<TService> AddServiceWithFactory<TService, TImplementation>( | ||
| this IServiceCollection services, | ||
| string name = "Default") | ||
| where TService : class | ||
| where TImplementation : class, TService | ||
| { | ||
| // Register the generic factory | ||
| services.TryAddSingleton<IServiceFactory<TService>, DefaultServiceFactory<TService>>(); | ||
|
|
||
| // Register named options | ||
| services.Configure<ServiceFactoryOptions<TService>>(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<TService>(serviceProvider => | ||
| { | ||
| var factory = serviceProvider.GetRequiredService<IServiceFactory<TService>>(); | ||
| return factory.CreateService(name); | ||
| }); | ||
|
|
||
| // Also register the concrete type | ||
| services.TryAddTransient<TImplementation>(serviceProvider => | ||
| (TImplementation)serviceProvider.GetRequiredService<TService>()); | ||
|
|
||
| return new DefaultServiceBuilder<TService>(services, name); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| namespace InteractiveCLI.ServiceConfigurationHelpers; | ||
|
|
||
| public interface IServiceBuilder<TService> where TService : class | ||
| { | ||
| public string Name { get; } | ||
| public IServiceCollection Services { get; } | ||
DevJonny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| namespace InteractiveCLI.ServiceConfigurationHelpers; | ||
|
|
||
| public interface IServiceFactory<TService> where TService : class | ||
| { | ||
| TService CreateService(string name = "Default"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| namespace InteractiveCLI.ServiceConfigurationHelpers; | ||
|
|
||
| public class ServiceFactoryOptions<TService> where TService : class | ||
| { | ||
| public List<Action<TService, IServiceProvider>> ServiceActions { get; set; } = new(); | ||
| public IList<Func<IServiceProvider, object[]>> ConstructorArgumentProviders { get; private set; } = new List<Func<IServiceProvider, object[]>>(); | ||
| public Func<IServiceProvider, object[], TService>? CustomFactory { get; set; } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
GatherConstructorArgumentsmethod is called twice whenCustomFactoryis null (lines 21 and 27), but only the second call is needed. Remove the duplicate call on line 21 or restructure the code to avoid redundant computation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bogdanstc-freemarket thoughts?