You can centralize the injection of common services like IValidator, ISecurity, and ILogger into your command/query handlers by using a "static service locator" with a delegate-based approach. This avoids modifying each handler's constructor.
Implementation:
public static class AppServices
{
private static Func<IServiceProvider>? _providerAccessor;
public static void Init(Func<IServiceProvider> providerAccessor)
{
_providerAccessor = providerAccessor ?? throw new ArgumentNullException(nameof(providerAccessor));
}
private static IServiceProvider CurrentProvider =>
_providerAccessor?.Invoke() ?? throw new InvalidOperationException("ServiceLocator not initialized");
public static IValidator Validator => CurrentProvider.GetRequiredService<IValidator>();
public static ISecurity Security => CurrentProvider.GetRequiredService<ISecurity>();
public static ILogger Logger => CurrentProvider.GetRequiredService<ILogger>();
}
Setup with IHttpContextAccessor:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpContextAccessor();
...
var app = builder.Build();
// Initialize the service locator once with a delegate
var accessor = app.Services.GetRequiredService<IHttpContextAccessor>();
AppServices.Init(() => accessor.HttpContext?.RequestServices ?? app.Services);
// Note: fallback to root provider ensures it works in background services or outside an HTTP request
app.MapControllers();
app.Run();
You can use AppServices in a handler like this:
public class MyCommandHandler
{
public void Handle(MyCommand command)
{
// Use services directly inline
if (!AppServices.Validator.Validate(command.Input))
{
AppServices.Logger.Log("Validation failed");
throw new InvalidOperationException("Invalid input");
}
var user = AppServices.Security.GetCurrentUser();
AppServices.Logger.Log($"Executing command for user {user.Name}");
// Command logic here...
}
}
The above approach will work, but it is not considered best practice. It hides dependencies and makes testing harder.
The recommended approach is to use constructor injection for your services or use patterns like decorator/middleware to provide cross-cutting concerns, as some of the other answers suggest.