Here's something that might work, with a few caveats. Uniquely, this will handle different service lifetimes, so you won't saturate your db connections or lose shared data from a cache if your rule class is implemented by callers of differing lifetimes.
This code will also share instances of services limited by existing service lifetime rules. It does this by resolving the rule according to the original service descriptor, then casing that result to your validation class. If you have a singleton class that implements your rule, you will get the same instance using this that your other services are injecting.
You should make sure you call this last in your DI scope - it inspects the services registered in your service collection as of the time this code is run, and re-adds services that implement the one you want with the appropriate lifetime.
The use case for this is a validation routine on a user-configurable settings object. Various service classes of differing lifetimes and potentially custom factory methods made looking via assembly a nonstarter, and it would be too easy to miss an implementor if I did it manually.
drawbacks:
- uses factory methods to resolve back to the original service, then casts the result to the desired type, so not clear for tree shakers and can complicate the dependency graph
- doesn't get every implementor in the assembly, only those that have been registered as of when this method is called.
- will miss any service descriptor added after it is run, since it gets the service collection as of whenever you call it. In other words, call this last.
public static IServiceCollection AddSettingsValidation(this IServiceCollection services)
{
// omit unrelated code
var validatorInterface = typeof(IValidateSettings);
// find all registered services
var implementors = services
.Where(x => (x.ImplementationType ?? x.ServiceType).IsAssignableTo(validatorInterface))
.ToList();
foreach (var implementor in implementors)
{
var descriptor = implementor.IsKeyedService
? new ServiceDescriptor(
validatorInterface,
implementor.ServiceKey,
(provider, key) =>
provider.GetRequiredKeyedService(implementor.ServiceType, key),
implementor.Lifetime
)
: new ServiceDescriptor(
validatorInterface,
provider => provider.GetRequiredService(implementor.ServiceType),
implementor.Lifetime
);
services.TryAdd(descriptor);
}
return services;
}