如何在.NET依赖注入(结合Scrutor)中实现类似SimpleInjector的条件注册回退机制?
Great question! When moving from SimpleInjector to .NET's built-in DI (with Scrutor for advanced registration), you absolutely can replicate that conditional fallback behavior for your validators—you just need to approach it a bit differently since native DI doesn't have a direct equivalent to SimpleInjector's RegisterConditional method. Here's a clean, maintainable way to achieve the same result:
Core Idea
Leverage .NET DI's built-in behavior where concrete service registrations take priority over open-generic registrations. We'll first register our fallback NullValidator<T> as an open generic, then scan for and register all custom IValidator<T> implementations. This way, if a custom validator exists for a given T, it will be used; if not, the NullValidator<T> will be injected as a fallback.
Step 1: Register the Fallback NullValidator
Start by registering your NullValidator<T> as an open-generic transient service. This will be our default when no custom validator exists:
services.AddTransient(typeof(IValidator<>), typeof(NullValidator<>));
Step 2: Scan for Custom Validators with Scrutor
Use Scrutor's scanning capabilities to automatically register all your custom IValidator<T> implementations (those inheriting from AbstractValidator<T>) across your assemblies:
services.Scan(scan => scan .FromAssembliesOf(typeof(YourAssemblyMarker)) // Replace with a type from your validator assembly .AddClasses(classes => classes.AssignableTo(typeof(IValidator<>))) .AsImplementedInterfaces() .WithTransientLifetime());
Since these are concrete, closed-generic registrations, they will override the open-generic NullValidator<T> for their specific T types.
Step 3: Register Command/Query Handlers and Decorators
Next, register your command/query handlers and apply the FluentValidation decorator using Scrutor:
// Register all command handlers services.Scan(scan => scan .FromAssembliesOf(typeof(CreateUserCommandHandler)) // Replace with a command handler type .AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>))) .AsImplementedInterfaces() .WithTransientLifetime()); // Apply the FluentValidation decorator to all command handlers services.Decorate(typeof(ICommandHandler<>), typeof(FluentValidationCommandHandlerDecorator<>)); // Repeat the same pattern for query handlers if needed services.Scan(scan => scan .FromAssembliesOf(typeof(GetUserQueryHandler)) // Replace with a query handler type .AddClasses(classes => classes.AssignableTo(typeof(IQueryHandler<,>))) .AsImplementedInterfaces() .WithTransientLifetime()); // Add your query validation decorator here if you have one // services.Decorate(typeof(IQueryHandler<,>), typeof(FluentValidationQueryHandlerDecorator<,>));
How It Works
When the DI container resolves an IValidator<TCommand> for your decorator:
- If a custom
IValidator<TCommand>exists (e.g.,CreateUserCommandValidator), that concrete implementation is returned. - If no custom validator exists, the open-generic registration kicks in, creating and returning a
NullValidator<TCommand>instance.
This matches exactly the behavior you had with SimpleInjector's conditional registration—your decorator will always receive a non-null IValidator<T> instance, eliminating the need for null checks (though your existing null guard clauses are still good practice!).
Bonus: Verify the Behavior
To confirm this works as expected, you can test resolving a validator for a command with no custom validator:
var validator = serviceProvider.GetRequiredService<IValidator<NoValidationCommand>>(); Debug.Assert(validator is NullValidator<NoValidationCommand>);
内容的提问来源于stack exchange,提问作者M.E.

