-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
FluentValidation version
11.8.1
ASP.NET version
(ASP).NET Core 8
Summary
I honestly don't know if it is an issue, but it manifested as one to me when it occured.
2 (but actually any amount of) calls to AddValidatorsFromAssemblyContaining(<assembly>)
would register every validator in the matching assembly 2 times. Clients, that retrieve something like IEnumerable<IValidator<TTarget>>
via DI would get 2 instances of the same validator, potentially leading to executing it twice. Or thrice, or ... you get the point.
What sounds as an exotic use case in the first place, isn't too exotic for applications that make use of Vertical Slice Architecture. There the following structures are quite common:
- Feature A
-- Validators of Feature A (registered via FeatureA.ServiceCollectionExtensions.AddFeatureAServices) - Feature B
-- Validators of Feature B (registered via FeatureA.ServiceCollectionExtensions.AddFeatureBServices) - Feature C
-- ...
It basically boils down to the registrations in
FluentValidation/src/FluentValidation.DependencyInjectionExtensions/ServiceCollectionExtensions.cs
Lines 95 to 106 in 51e365b
services.Add( | |
new ServiceDescriptor( | |
serviceType: scanResult.InterfaceType, | |
implementationType: scanResult.ValidatorType, | |
lifetime: lifetime)); | |
//Register as self | |
services.Add( | |
new ServiceDescriptor( | |
serviceType: scanResult.ValidatorType, | |
implementationType: scanResult.ValidatorType, | |
lifetime: lifetime)); |
Here the descriptors are added via the
Add
method.Changing those to
TryAdd
, that verifies if a corresponding service descriptor is already contained in the service collection, would resolve this issue.
But as said, I can't tell if this is an inteded behavior for a use case that I currently cannot see. In that case a parameter for example could provice both options:
AddValidatorsFromAssemblyContaining(..., allowDuplicates: false) {
if (allowDuplicates)
services.Add(...)
else
services.TryAdd(...)
}
Steps to Reproduce
public class ArbitraryTarget{}
public class ArbitraryValidator : AbstractValidator<ArbitraryTarget> { ... }
//Program.cs
services.AddValidatorsFromAssemblyContaining<Program>();
services.AddValidatorsFromAssemblyContaining<Program>();
//Somewhere.cs
var provider = services.BuildServiceProvider();
var validators = provider.GetServices<IValidator<ArbitraryTarget>>();
//Inspect validators.Count();