diff --git a/Directory.Packages.support.props b/Directory.Packages.support.props index a90352c34..6e8500d5c 100644 --- a/Directory.Packages.support.props +++ b/Directory.Packages.support.props @@ -1,4 +1,4 @@ - + @@ -8,5 +8,6 @@ + diff --git a/sample/Sample.Restful.Client/Sample.Restful.Client.csproj b/sample/Sample.Restful.Client/Sample.Restful.Client.csproj index fedba3b4f..18b47c8ed 100644 --- a/sample/Sample.Restful.Client/Sample.Restful.Client.csproj +++ b/sample/Sample.Restful.Client/Sample.Restful.Client.csproj @@ -11,6 +11,10 @@ /> + public class FluentValidator : ComponentBase { - private static readonly char[] separators = { '.', '[' }; + private static readonly char[] _separators = { '.', '[' }; private static FieldIdentifier ToFieldIdentifier(EditContext editContext, string propertyPath) { @@ -27,7 +27,7 @@ private static FieldIdentifier ToFieldIdentifier(EditContext editContext, string while (true) { - var nextTokenEnd = propertyPath.IndexOfAny(separators); + var nextTokenEnd = propertyPath.IndexOfAny(_separators); if (nextTokenEnd < 0) { return new FieldIdentifier(obj, propertyPath); @@ -69,87 +69,83 @@ private static FieldIdentifier ToFieldIdentifier(EditContext editContext, string } } - /// - /// The validator to validate against - /// - [Parameter] - public IValidator Validator { get; set; } = null!; - - [Inject] private IValidatorFactory ValidatorFactory { get; set; } = null!; - - [CascadingParameter] private EditContext CurrentEditContext { get; set; } = null!; - - /// - protected override void OnInitialized() - { - if (CurrentEditContext == null) - { - throw new InvalidOperationException( - $"{nameof(FluentValidator)} requires a cascading " + - $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(FluentValidator)} " + - $"inside an {nameof(EditForm)}." - ); - } - - AddFluentValidation(Validator); - } - - private void AddFluentValidation(IValidator validator) + private static void AddFluentValidation(IValidator? validator, EditContext editContext, IServiceProvider services) { - var messages = new ValidationMessageStore(CurrentEditContext); + var messages = new ValidationMessageStore(editContext); - CurrentEditContext.OnValidationRequested += - (_, _) => ValidateModel(messages, validator); + editContext.OnValidationRequested += + (_, _) => ValidateModel(messages, editContext, validator ?? services.GetValidator(editContext.Model.GetType())); - CurrentEditContext.OnFieldChanged += - (_, eventArgs) => ValidateField(messages, eventArgs.FieldIdentifier, validator); + editContext.OnFieldChanged += + (_, eventArgs) => ValidateField(messages, editContext, eventArgs.FieldIdentifier, validator ?? services.GetValidator(editContext.Model.GetType())); } - private async void ValidateModel(ValidationMessageStore messages, IValidator? validator = null) + private static async void ValidateModel( + ValidationMessageStore messages, + EditContext editContext, + IValidator? validator = null + ) { - validator ??= GetValidatorForModel(CurrentEditContext.Model); - if (validator != null) { - var context = new ValidationContext(CurrentEditContext.Model); + var context = new ValidationContext(editContext.Model); var validationResults = await validator.ValidateAsync(context); messages.Clear(); foreach (var validationResult in validationResults.Errors) { - var fieldIdentifier = ToFieldIdentifier(CurrentEditContext, validationResult.PropertyName); + var fieldIdentifier = ToFieldIdentifier(editContext, validationResult.PropertyName); messages.Add(fieldIdentifier, validationResult.ErrorMessage); } - CurrentEditContext.NotifyValidationStateChanged(); + editContext.NotifyValidationStateChanged(); } } - private async void ValidateField( + private static async void ValidateField( ValidationMessageStore messages, + EditContext editContext, FieldIdentifier fieldIdentifier, IValidator? validator = null ) { - var properties = new[] { fieldIdentifier.FieldName }; - var context = new ValidationContext(fieldIdentifier.Model, new PropertyChain(), new MemberNameValidatorSelector(properties)); - - validator ??= GetValidatorForModel(fieldIdentifier.Model); - if (validator != null) { + var properties = new[] { fieldIdentifier.FieldName }; + var context = new ValidationContext(fieldIdentifier.Model, new PropertyChain(), new MemberNameValidatorSelector(properties)); var validationResults = await validator.ValidateAsync(context); messages.Clear(fieldIdentifier); messages.Add(fieldIdentifier, validationResults.Errors.Select(error => error.ErrorMessage)); - CurrentEditContext.NotifyValidationStateChanged(); + editContext.NotifyValidationStateChanged(); } } - private IValidator? GetValidatorForModel(object? model) + /// + /// The validator to validate against + /// + [Parameter] + [DisallowNull] + public IValidator? Validator { get; set; } = null!; + + [Inject] private IServiceProvider Services { get; set; } = null!; + + [CascadingParameter] private EditContext CurrentEditContext { get; set; } = null!; + + /// + protected override void OnInitialized() { - return model == null ? null : ValidatorFactory.GetValidator(model.GetType()); + if (CurrentEditContext == null) + { + throw new InvalidOperationException( + $"{nameof(FluentValidator)} requires a cascading " + + $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(FluentValidator)} " + + $"inside an {nameof(EditForm)}." + ); + } + + AddFluentValidation(Validator, CurrentEditContext, Services); } } diff --git a/src/AspNetCore/Conventions/AspNetCoreConvention.cs b/src/AspNetCore/Conventions/AspNetCoreConvention.cs index 8427985b1..86e83eeca 100644 --- a/src/AspNetCore/Conventions/AspNetCoreConvention.cs +++ b/src/AspNetCore/Conventions/AspNetCoreConvention.cs @@ -80,23 +80,6 @@ private static IEnumerable GetAssemblyClosure(Assembly assembly) } } - private readonly ValidatorConfiguration? _validatorConfiguration; - private readonly FluentValidationMvcConfiguration? _validationMvcConfiguration; - - /// - /// Configure aspnet with some logical defaults - /// - /// - /// - public AspNetCoreConvention( - ValidatorConfiguration? validatorConfiguration = null, - FluentValidationMvcConfiguration? validationMvcConfiguration = null - ) - { - _validatorConfiguration = validatorConfiguration; - _validationMvcConfiguration = validationMvcConfiguration; - } - /// /// Registers the specified context. /// diff --git a/src/AspNetCore/Conventions/FluentValidationConvention.cs b/src/AspNetCore/Conventions/FluentValidationConvention.cs index 56b92d0ef..7dd61a15d 100644 --- a/src/AspNetCore/Conventions/FluentValidationConvention.cs +++ b/src/AspNetCore/Conventions/FluentValidationConvention.cs @@ -117,11 +117,7 @@ static bool getNullableValue(Nullability nullability, Type propertyType) public void Register(IConventionContext context, IConfiguration configuration, IServiceCollection services) { services.WithMvcCore() - .AddFluentValidation( - config => config.ValidatorFactoryType ??= typeof(ValidatorFactory) - ); - services.RemoveAll(); - services.AddSingleton(); + .AddFluentValidation(); services.AddSingleton(); services .Configure(mvcOptions => mvcOptions.Filters.Insert(0, new ValidationExceptionFilter())) diff --git a/src/Foundation/Conventions/FluentValidationConvention.cs b/src/Foundation/Conventions/FluentValidationConvention.cs index f15667972..bdbb65668 100644 --- a/src/Foundation/Conventions/FluentValidationConvention.cs +++ b/src/Foundation/Conventions/FluentValidationConvention.cs @@ -54,12 +54,6 @@ public void Register(IConventionContext context, IConfiguration configuration, I services.TryAddEnumerable(ServiceDescriptor.Describe(item.InterfaceType, item.ValidatorType, ServiceLifetime.Singleton)); } - if (services.FirstOrDefault(z => z.ServiceType == typeof(IValidatorFactory)) is { } s && s.Lifetime != ServiceLifetime.Singleton) - { - services.Remove(s); - } - - services.TryAdd(ServiceDescriptor.Describe(typeof(IValidatorFactory), typeof(ValidatorFactory), ServiceLifetime.Singleton)); services.TryAddEnumerable(ServiceDescriptor.Describe(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>), _options.MediatorLifetime)); services.TryAddEnumerable( ServiceDescriptor.Describe(typeof(IStreamPipelineBehavior<,>), typeof(ValidationStreamPipelineBehavior<,>), _options.MediatorLifetime) diff --git a/src/Foundation/FluentValidationExtensions.cs b/src/Foundation/FluentValidationExtensions.cs index 56a4bd42d..f5138b2e8 100644 --- a/src/Foundation/FluentValidationExtensions.cs +++ b/src/Foundation/FluentValidationExtensions.cs @@ -61,4 +61,27 @@ this IRuleBuilder builder return builder.SetAsyncValidator(new PolymorphicPropertyValidator()); } + + /// + /// Get a fluent validation validator if defined. + /// + /// + /// + /// + public static IValidator? GetValidator(this IServiceProvider serviceProvider) + { + return GetValidator(serviceProvider, typeof(T)) as IValidator ?? null; + } + + /// + /// Get a fluent validation validator if defined. + /// + /// + /// + /// + public static IValidator? GetValidator(this IServiceProvider serviceProvider, Type? type) + { + if (type is null) return null; + return serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(type)) as IValidator ?? null; + } } diff --git a/src/Foundation/Validation/PolymorphicPropertyValidator.cs b/src/Foundation/Validation/PolymorphicPropertyValidator.cs index c3e270a2d..bd245f9cb 100644 --- a/src/Foundation/Validation/PolymorphicPropertyValidator.cs +++ b/src/Foundation/Validation/PolymorphicPropertyValidator.cs @@ -20,7 +20,7 @@ public override async Task IsValidAsync(ValidationContext context, TPro // bail out if the property is null if (value is not { }) return true; - var factory = context.GetServiceProvider().GetRequiredService(); + var factory = context.GetServiceProvider().GetService(); var validator = factory.GetValidator(value.GetType()); return validator is null || ( await validator.ValidateAsync(context, cancellation).ConfigureAwait(false) ).IsValid; } diff --git a/src/Foundation/Validation/ValidationPipelineBehavior.cs b/src/Foundation/Validation/ValidationPipelineBehavior.cs index 93d842717..0cd27e212 100644 --- a/src/Foundation/Validation/ValidationPipelineBehavior.cs +++ b/src/Foundation/Validation/ValidationPipelineBehavior.cs @@ -6,24 +6,20 @@ namespace Rocket.Surgery.LaunchPad.Foundation.Validation; internal class ValidationPipelineBehavior : IPipelineBehavior where T : IRequest { - private readonly IValidatorFactory _validatorFactory; - private readonly IServiceProvider _serviceProvider; + private readonly IValidator? _validator; - public ValidationPipelineBehavior(IValidatorFactory validatorFactory, IServiceProvider serviceProvider) + public ValidationPipelineBehavior(IValidator? validator = null) { - _validatorFactory = validatorFactory; - _serviceProvider = serviceProvider; + _validator = validator; } public async Task Handle(T request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - var validator = _validatorFactory.GetValidator(); - if (validator != null) + if (_validator is not null) { var context = new ValidationContext(request); - context.SetServiceProvider(_serviceProvider); - var response = await validator.ValidateAsync(context, cancellationToken).ConfigureAwait(false); + var response = await _validator.ValidateAsync(context, cancellationToken).ConfigureAwait(false); if (!response.IsValid) { throw new ValidationException(response.Errors); @@ -36,24 +32,20 @@ public async Task Handle(T request, CancellationToken cancellationToken, Requ internal class ValidationStreamPipelineBehavior : IStreamPipelineBehavior where T : IStreamRequest { - private readonly IValidatorFactory _validatorFactory; - private readonly IServiceProvider _serviceProvider; + private readonly IValidator? _validator; - public ValidationStreamPipelineBehavior(IValidatorFactory validatorFactory, IServiceProvider serviceProvider) + public ValidationStreamPipelineBehavior(IValidator? validator = null) { - _validatorFactory = validatorFactory; - _serviceProvider = serviceProvider; + _validator = validator; } public async IAsyncEnumerable Handle(T request, [EnumeratorCancellation] CancellationToken cancellationToken, StreamHandlerDelegate next) { - var validator = _validatorFactory.GetValidator(); - if (validator != null) + if (_validator is not null) { var context = new ValidationContext(request); - context.SetServiceProvider(_serviceProvider); - var response = await validator.ValidateAsync(context, cancellationToken).ConfigureAwait(false); + var response = await _validator.ValidateAsync(context, cancellationToken).ConfigureAwait(false); if (!response.IsValid) { throw new ValidationException(response.Errors); diff --git a/src/Foundation/Validation/ValidatorFactory.cs b/src/Foundation/Validation/ValidatorFactory.cs deleted file mode 100644 index 8fafd27d5..000000000 --- a/src/Foundation/Validation/ValidatorFactory.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Reflection; -using FluentValidation; -using Microsoft.Extensions.DependencyInjection; - -namespace Rocket.Surgery.LaunchPad.Foundation.Validation; - -/// -/// ValidatorFactory. -/// Implements the -/// -/// -internal class ValidatorFactory : ValidatorFactoryBase -{ - private static CompositeValidator CreateValidator(IEnumerable validators) - { - return new CompositeValidator(validators.OfType()); - } - - private static readonly MethodInfo CreateValidatorMethod = typeof(ValidatorFactory) - .GetMethod(nameof(CreateValidator), BindingFlags.Static | BindingFlags.NonPublic)!; - - private readonly IServiceProvider _context; - - /// - /// Initializes a new instance of the class. - /// - /// The context. - public ValidatorFactory(IServiceProvider context) - { - _context = context; - } - - /// - /// Creates the instance. - /// - /// Type of the validator. - /// IValidator. - public override IValidator CreateInstance(Type validatorType) - { - var services = _context.GetServices(validatorType).OfType().ToArray(); - if (services.Length > 0 && validatorType.IsGenericType && validatorType.GetGenericArguments().Length == 1) - { - return (IValidator)CreateValidatorMethod.MakeGenericMethod(validatorType.GetGenericArguments()[0]).Invoke( - null, new object[] - { - services.AsEnumerable() - } - )!; - } - - return null!; - } -} diff --git a/src/Grpc/Validation/ValidationInterceptor.cs b/src/Grpc/Validation/ValidationInterceptor.cs index ce1b96050..78bf43351 100644 --- a/src/Grpc/Validation/ValidationInterceptor.cs +++ b/src/Grpc/Validation/ValidationInterceptor.cs @@ -16,13 +16,11 @@ private static RpcException CreateException(ValidationResult results, string? me throw new RpcException(new Status(StatusCode.InvalidArgument, message), validationMetadata, message); } - private readonly IValidatorFactory _factory; private readonly IValidatorErrorMessageHandler _handler; private readonly IServiceProvider _serviceProvider; - public ValidationInterceptor(IValidatorFactory factory, IValidatorErrorMessageHandler handler, IServiceProvider serviceProvider) + public ValidationInterceptor(IValidatorErrorMessageHandler handler, IServiceProvider serviceProvider) { - _factory = factory; _handler = handler; _serviceProvider = serviceProvider; } @@ -33,12 +31,9 @@ public override async Task UnaryServerHandler( UnaryServerMethod continuation ) { - if (_factory.GetValidator() is { } validator) + if (_serviceProvider.GetValidator() is { } validator) { - var validationContext = new ValidationContext(request); - validationContext.SetServiceProvider(_serviceProvider); - - var results = await validator.ValidateAsync(validationContext, context.CancellationToken).ConfigureAwait(false); + var results = await validator.ValidateAsync(request, context.CancellationToken).ConfigureAwait(false); if (results.IsValid || !results.Errors.Any()) { @@ -58,7 +53,7 @@ public override AsyncUnaryCall AsyncUnaryCall( AsyncUnaryCallContinuation continuation ) { - if (_factory.GetValidator() is { } validator) + if (_serviceProvider.GetValidator() is { } validator) { var results = validator.Validate(request); @@ -80,7 +75,7 @@ public override TResponse BlockingUnaryCall( BlockingUnaryCallContinuation continuation ) { - if (_factory.GetValidator() is { } validator) + if (_serviceProvider.GetValidator() is { } validator) { var results = validator.Validate(request); diff --git a/src/HotChocolate/FairyBreadValidatorProvider.cs b/src/HotChocolate/FairyBreadValidatorProvider.cs deleted file mode 100644 index 55d51a398..000000000 --- a/src/HotChocolate/FairyBreadValidatorProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FairyBread; -using FluentValidation; -using HotChocolate.Resolvers; -using HotChocolate.Types; - -namespace Rocket.Surgery.LaunchPad.HotChocolate; - -internal class FairyBreadValidatorProvider : IValidatorProvider -{ - private readonly IValidatorFactory _factory; - - public FairyBreadValidatorProvider(IValidatorFactory factory) - { - _factory = factory; - } - - public IEnumerable GetValidators(IMiddlewareContext context, IInputField argument) - { - var validator = _factory.GetValidator(argument.RuntimeType); - if (validator is { }) - { - yield return new ResolvedValidator(validator); - } - } -} diff --git a/test/Extensions.Tests/Validation/ValidatorFactoryTests.cs b/test/Extensions.Tests/Validation/ValidatorFactoryTests.cs index c89d5a374..750a862df 100644 --- a/test/Extensions.Tests/Validation/ValidatorFactoryTests.cs +++ b/test/Extensions.Tests/Validation/ValidatorFactoryTests.cs @@ -11,19 +11,19 @@ namespace Extensions.Tests.Validation; public class ValidatorFactoryTests : AutoFakeTest { - [Fact] - public void Should_Aggregate_Validators() - { - var sp = A.Fake(); - A.CallTo(() => sp.GetService(typeof(IEnumerable>))) - .Returns(new IValidator[] { new ValidatorAb(), new ValidatorAa() }); - AutoFake.Provide(sp); - - var factory = AutoFake.Resolve(); - var validator = factory.GetValidator(); - var result = validator.Validate(new AModel()); - result.Errors.Should().HaveCount(2); - } +// [Fact] +// public void Should_Aggregate_Validators() +// { +// var sp = A.Fake(); +// A.CallTo(() => sp.GetService(typeof(IEnumerable>))) +// .Returns(new IValidator[] { new ValidatorAb(), new ValidatorAa() }); +// AutoFake.Provide(sp); +// +// var factory = AutoFake.Resolve(); +// var validator = factory.GetValidator(); +// var result = validator.Validate(new AModel()); +// result.Errors.Should().HaveCount(2); +// } public ValidatorFactoryTests(ITestOutputHelper outputHelper) : base(outputHelper, LogLevel.Information) {