Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Comment on lines +18 to +27
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GatherConstructorArguments method is called twice when CustomFactory is 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.

Suggested change
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);
var args = GatherConstructorArguments(options);
if (options.CustomFactory != null)
{
// Use custom factory if provided
service = options.CustomFactory(_serviceProvider, args);
}
else
{
// Use default creation with ActivatorUtilities

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


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
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configureOptions callback modifies options inside a service action that executes during service creation. This causes the configuration to be applied each time the service is created rather than during configuration time. The callback should be invoked directly without wrapping it in a service action: configureOptions(options, serviceProvider); should be called outside the ServiceActions.Add, or the method signature should be reconsidered to match the intended behavior.

Suggested change
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);

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

});
return builder;
}

public static IServiceBuilder<TService> WithConstructorArgs<TService>(
this IServiceBuilder<TService> builder,
params object[] args)
where TService : class
{
return builder.ConfigureService(options =>
{
options.ConstructorArgumentProviders.Add(_ => args);
});
}

public static IServiceBuilder<TService> WithConstructorArgs<TService>(
this IServiceBuilder<TService> builder,
Func<IServiceProvider, object[]> argsProvider)
where TService : class
{
return builder.ConfigureService(options =>
{
options.ConstructorArgumentProviders.Add(argsProvider);
});
}

public static IServiceBuilder<TService> AddServiceAction<TService>(
this IServiceBuilder<TService> builder,
Action<TService, IServiceProvider> action)
where TService : class
{
return builder.ConfigureService(options =>
{
options.ServiceActions.Add(action);
});
}

public static IServiceBuilder<TService> WithCustomFactory<TService>(
this IServiceBuilder<TService> builder,
Func<IServiceProvider, object[], TService> customFactory)
where TService : class
{
return builder.ConfigureService(options =>
{
options.CustomFactory = customFactory;
});
}

public static IServiceBuilder<TService> WithCustomFactory<TService>(
this IServiceBuilder<TService> builder,
Func<IServiceProvider, TService> customFactory)
where TService : class
{
return builder.ConfigureService(options =>
{
options.CustomFactory = (serviceProvider, _) => customFactory(serviceProvider);
});
}
}
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; }
}
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; }
}