diff --git a/FluentValidation.AutoValidation.Endpoints/src/Configuration/AutoValidationEndpointsConfiguration.cs b/FluentValidation.AutoValidation.Endpoints/src/Configuration/AutoValidationEndpointsConfiguration.cs
index 4e82152..d70afcf 100644
--- a/FluentValidation.AutoValidation.Endpoints/src/Configuration/AutoValidationEndpointsConfiguration.cs
+++ b/FluentValidation.AutoValidation.Endpoints/src/Configuration/AutoValidationEndpointsConfiguration.cs
@@ -2,24 +2,41 @@
using Microsoft.AspNetCore.Http.HttpResults;
using SharpGrip.FluentValidation.AutoValidation.Endpoints.Results;
-namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Configuration
+namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Configuration;
+
+public class AutoValidationEndpointsConfiguration
{
- public class AutoValidationEndpointsConfiguration
+ ///
+ /// Gets a value indicating whether the validation process should look for validators
+ /// registered for interfaces or base types when a validator for the concrete type is not found.
+ ///
+ public bool UseBaseTypeValidations { get; private set; }
+
+ ///
+ /// Holds the overridden result factory. This property is meant for infrastructure and should not be used by application code.
+ ///
+ public Type? OverriddenResultFactory { get; private set; }
+
+ ///
+ /// Overrides the default result factory with a custom result factory. Custom result factories are required to implement .
+ /// The default result factory returns the validation errors wrapped in a object.
+ ///
+ ///
+ /// The custom result factory implementing .
+ public AutoValidationEndpointsConfiguration OverrideDefaultResultFactoryWith() where TResultFactory : IFluentValidationAutoValidationResultFactory
{
- ///
- /// Holds the overridden result factory. This property is meant for infrastructure and should not be used by application code.
- ///
- public Type? OverriddenResultFactory { get; private set; }
+ OverriddenResultFactory = typeof(TResultFactory);
+ return this;
+ }
- ///
- /// Overrides the default result factory with a custom result factory. Custom result factories are required to implement .
- /// The default result factory returns the validation errors wrapped in a object.
- ///
- ///
- /// The custom result factory implementing .
- public void OverrideDefaultResultFactoryWith() where TResultFactory : IFluentValidationAutoValidationResultFactory
- {
- OverriddenResultFactory = typeof(TResultFactory);
- }
+ ///
+ /// Enables the fallback mechanism to search for validators in the type hierarchy (interfaces and base classes)
+ /// if no specific validator is registered for the primary parameter type.
+ ///
+ /// The current instance for fluent chaining.
+ public AutoValidationEndpointsConfiguration WithBaseTypeValidations()
+ {
+ UseBaseTypeValidations = true;
+ return this;
}
}
\ No newline at end of file
diff --git a/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs b/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs
index 8520683..24195e6 100644
--- a/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs
+++ b/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs
@@ -1,15 +1,19 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using FluentValidation;
+using FluentValidation.Results;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using SharpGrip.FluentValidation.AutoValidation.Endpoints.Configuration;
using SharpGrip.FluentValidation.AutoValidation.Endpoints.Interceptors;
using SharpGrip.FluentValidation.AutoValidation.Endpoints.Results;
using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions;
namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Filters
{
- public class FluentValidationAutoValidationEndpointFilter(ILogger logger) : IEndpointFilter
+ public class FluentValidationAutoValidationEndpointFilter(ILogger logger, IOptions options) : IEndpointFilter
{
public async ValueTask InvokeAsync(EndpointFilterInvocationContext endpointFilterInvocationContext, EndpointFilterDelegate next)
{
@@ -17,66 +21,80 @@ public class FluentValidationAutoValidationEndpointFilter(ILogger();
+ var validationResult = await ExecuteValidation(endpointFilterInvocationContext, validator, serviceProvider, argument);
- IValidationContext validationContext = new ValidationContext(argument);
+ if (!validationResult.IsValid) return CreateAInvalidResult(endpointFilterInvocationContext, argument, validationResult, serviceProvider);
- if (validatorInterceptor != null)
- {
- logger.LogDebug("Invoking validator interceptor BeforeValidation for argument '{Argument}'.", argument.GetType().Name);
- validationContext = await validatorInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext;
- }
+ logger.LogDebug("Validation result valid for argument '{Argument}'.", argument.GetType().Name);
+ }
- if (globalValidationInterceptor != null)
- {
- logger.LogDebug("Invoking global validation interceptor BeforeValidation for argument '{Argument}'.", argument.GetType().Name);
- validationContext = await globalValidationInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext;
- }
+ return await next(endpointFilterInvocationContext);
+ }
- var validationResult = await validator.ValidateAsync(validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted);
+ private object CreateAInvalidResult(EndpointFilterInvocationContext endpointFilterInvocationContext, object argument, ValidationResult validationResult, IServiceProvider serviceProvider)
+ {
+ logger.LogDebug("Validation result not valid for argument '{Argument}': {ErrorCount} validation error(s) found.", argument.GetType().Name, validationResult.Errors.Count);
- if (validatorInterceptor != null)
- {
- logger.LogDebug("Invoking validator interceptor AfterValidation for argument '{Argument}'.", argument.GetType().Name);
- validationResult = await validatorInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult;
- }
+ var fluentValidationAutoValidationResultFactory = serviceProvider.GetService();
- if (globalValidationInterceptor != null)
- {
- logger.LogDebug("Invoking global validation interceptor AfterValidation for argument '{Argument}'.", argument.GetType().Name);
- validationResult = await globalValidationInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult;
- }
+ logger.LogDebug("Creating result for path '{Path}'.", endpointFilterInvocationContext.HttpContext.Request.Path);
- if (!validationResult.IsValid)
- {
- logger.LogDebug("Validation result not valid for argument '{Argument}': {ErrorCount} validation error(s) found.", argument.GetType().Name, validationResult.Errors.Count);
+ if (fluentValidationAutoValidationResultFactory != null)
+ {
+ logger.LogTrace("Creating result for path '{Path}' using a custom result factory.", endpointFilterInvocationContext.HttpContext.Request.Path);
- var fluentValidationAutoValidationResultFactory = serviceProvider.GetService();
+ return fluentValidationAutoValidationResultFactory.CreateResult(endpointFilterInvocationContext, validationResult);
+ }
- logger.LogDebug("Creating result for path '{Path}'.", endpointFilterInvocationContext.HttpContext.Request.Path);
+ logger.LogTrace("Creating result for path '{Path}' using the default result factory.", endpointFilterInvocationContext.HttpContext.Request.Path);
- if (fluentValidationAutoValidationResultFactory != null)
- {
- logger.LogTrace("Creating result for path '{Path}' using a custom result factory.", endpointFilterInvocationContext.HttpContext.Request.Path);
+ return new FluentValidationAutoValidationDefaultResultFactory().CreateResult(endpointFilterInvocationContext, validationResult);
+ }
- return fluentValidationAutoValidationResultFactory.CreateResult(endpointFilterInvocationContext, validationResult);
- }
+ private async ValueTask ExecuteValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidator validator, IServiceProvider serviceProvider, object argument)
+ {
+ var validatorInterceptor = validator as IValidatorInterceptor;
+ var globalValidationInterceptor = serviceProvider.GetService();
- logger.LogTrace("Creating result for path '{Path}' using the default result factory.", endpointFilterInvocationContext.HttpContext.Request.Path);
+ IValidationContext validationContext = new ValidationContext(argument);
- return new FluentValidationAutoValidationDefaultResultFactory().CreateResult(endpointFilterInvocationContext, validationResult);
- }
+ if (validatorInterceptor != null)
+ {
+ logger.LogDebug("Invoking validator interceptor BeforeValidation for argument '{Argument}'.", argument.GetType().Name);
+ validationContext = await validatorInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext;
+ }
- logger.LogDebug("Validation result valid for argument '{Argument}'.", argument.GetType().Name);
- }
+ if (globalValidationInterceptor != null)
+ {
+ logger.LogDebug("Invoking global validation interceptor BeforeValidation for argument '{Argument}'.", argument.GetType().Name);
+ validationContext = await globalValidationInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext;
}
- return await next(endpointFilterInvocationContext);
+ var validationResult = await validator.ValidateAsync(validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted);
+
+ if (validatorInterceptor != null)
+ {
+ logger.LogDebug("Invoking validator interceptor AfterValidation for argument '{Argument}'.", argument.GetType().Name);
+ validationResult = await validatorInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult;
+ }
+
+ if (globalValidationInterceptor != null)
+ {
+ logger.LogDebug("Invoking global validation interceptor AfterValidation for argument '{Argument}'.", argument.GetType().Name);
+ validationResult = await globalValidationInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult;
+ }
+
+ return validationResult;
}
}
}
\ No newline at end of file
diff --git a/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs b/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs
index 98e94a0..0d8b6b1 100644
--- a/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs
+++ b/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs
@@ -61,7 +61,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
var hasAutoValidateAlwaysAttribute = parameterInfo?.HasCustomAttribute() ?? false;
var hasAutoValidateNeverAttribute = parameterInfo?.HasCustomAttribute() ?? false;
- if (subject != null && parameterType != null && parameterType.IsCustomType() && !hasAutoValidateNeverAttribute && (hasAutoValidateAlwaysAttribute || HasValidBindingSource(bindingSource)) && serviceProvider.GetValidator(parameterType) is IValidator validator)
+ if (subject != null && parameterType != null && parameterType.IsCustomType() && !hasAutoValidateNeverAttribute && (hasAutoValidateAlwaysAttribute || HasValidBindingSource(bindingSource)) && serviceProvider.GetValidator(parameterType, false) is IValidator validator)
{
logger.LogDebug("Validating parameter '{Parameter}' of type '{Type}' for action '{Action}' on controller '{Controller}'.", parameter.Name, parameterType.Name, controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName);
diff --git a/FluentValidation.AutoValidation.Shared/src/Extensions/ServiceProviderExtensions.cs b/FluentValidation.AutoValidation.Shared/src/Extensions/ServiceProviderExtensions.cs
index b15530f..93fca6c 100644
--- a/FluentValidation.AutoValidation.Shared/src/Extensions/ServiceProviderExtensions.cs
+++ b/FluentValidation.AutoValidation.Shared/src/Extensions/ServiceProviderExtensions.cs
@@ -1,13 +1,46 @@
using System;
using FluentValidation;
-namespace SharpGrip.FluentValidation.AutoValidation.Shared.Extensions
+namespace SharpGrip.FluentValidation.AutoValidation.Shared.Extensions;
+
+public static class ServiceProviderExtensions
{
- public static class ServiceProviderExtensions
+ public static object? GetValidator(this IServiceProvider serviceProvider, Type type, bool useBaseTypeValidations)
+ {
+ var validator = serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(type));
+ if (validator is not null) return validator;
+ if (!useBaseTypeValidations) return null;
+
+ return GetValidatorFromBaseClasses(serviceProvider, type)
+ ?? GetValidatorFromInterfaces(serviceProvider, type);
+ }
+
+ private static object? GetValidatorFromBaseClasses(IServiceProvider serviceProvider, Type type)
{
- public static object? GetValidator(this IServiceProvider serviceProvider, Type type)
+ var baseType = type.BaseType;
+ while (baseType is not null && baseType != typeof(object))
{
- return serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(type));
+ var baseValidatorType = typeof(IValidator<>).MakeGenericType(baseType);
+ var baseValidator = serviceProvider.GetService(baseValidatorType);
+
+ if (baseValidator is not null) return baseValidator;
+
+ baseType = baseType.BaseType;
}
+
+ return null;
+ }
+
+ private static object? GetValidatorFromInterfaces(IServiceProvider serviceProvider, Type type)
+ {
+ foreach (var interfaceType in type.GetInterfaces())
+ {
+ var interfaceValidatorType = typeof(IValidator<>).MakeGenericType(interfaceType);
+ var interfaceValidator = serviceProvider.GetService(interfaceValidatorType);
+
+ if (interfaceValidator is not null) return interfaceValidator;
+ }
+
+ return null;
}
}
\ No newline at end of file
diff --git a/FluentValidation.AutoValidation.Shared/src/Extensions/TypeExtensions.cs b/FluentValidation.AutoValidation.Shared/src/Extensions/TypeExtensions.cs
index 019bd3c..ab0ecbc 100644
--- a/FluentValidation.AutoValidation.Shared/src/Extensions/TypeExtensions.cs
+++ b/FluentValidation.AutoValidation.Shared/src/Extensions/TypeExtensions.cs
@@ -7,6 +7,20 @@ namespace SharpGrip.FluentValidation.AutoValidation.Shared.Extensions
{
public static class TypeExtensions
{
+ private static readonly HashSet builtInTypes =
+ [
+ typeof(string),
+ typeof(decimal),
+ typeof(DateTime),
+ typeof(DateTimeOffset),
+ typeof(TimeSpan),
+ typeof(DateOnly),
+ typeof(TimeOnly),
+ typeof(Uri),
+ typeof(Guid),
+ typeof(Enum)
+ ];
+
public static bool IsCustomType(this Type? type)
{
if (type == null || type.IsEnum || type.IsPrimitive)
@@ -14,20 +28,6 @@ public static bool IsCustomType(this Type? type)
return false;
}
- var builtInTypes = new HashSet
- {
- typeof(string),
- typeof(decimal),
- typeof(DateTime),
- typeof(DateTimeOffset),
- typeof(TimeSpan),
- typeof(DateOnly),
- typeof(TimeOnly),
- typeof(Uri),
- typeof(Guid),
- typeof(Enum)
- };
-
if (builtInTypes.Contains(type))
{
return false;
diff --git a/Tests/src/FluentValidation.AutoValidation.Endpoints/Filters/FluentValidationAutoValidationEndpointFilterTest.cs b/Tests/src/FluentValidation.AutoValidation.Endpoints/Filters/FluentValidationAutoValidationEndpointFilterTest.cs
index 1836bab..5863025 100644
--- a/Tests/src/FluentValidation.AutoValidation.Endpoints/Filters/FluentValidationAutoValidationEndpointFilterTest.cs
+++ b/Tests/src/FluentValidation.AutoValidation.Endpoints/Filters/FluentValidationAutoValidationEndpointFilterTest.cs
@@ -10,9 +10,12 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using NSubstitute;
+using SharpGrip.FluentValidation.AutoValidation.Endpoints.Configuration;
using SharpGrip.FluentValidation.AutoValidation.Endpoints.Filters;
using SharpGrip.FluentValidation.AutoValidation.Endpoints.Interceptors;
+using SharpGrip.FluentValidation.AutoValidation.Tests.Shared.Stubs;
using Xunit;
namespace SharpGrip.FluentValidation.AutoValidation.Tests.FluentValidation.AutoValidation.Endpoints.Filters;
@@ -32,6 +35,8 @@ public async Task TestInvokeAsync_ValidatorFound()
var logger = Substitute.For>();
var serviceProvider = Substitute.For();
var endpointFilterInvocationContext = Substitute.For();
+ var configuration = Substitute.For>();
+ configuration.Value.Returns(new AutoValidationEndpointsConfiguration());
endpointFilterInvocationContext.HttpContext.Returns(new DefaultHttpContext {RequestServices = serviceProvider});
endpointFilterInvocationContext.Arguments.Returns(new List {new TestModel {Parameter1 = "Value 1", Parameter2 = "Value 2", Parameter3 = "Value 3"}});
@@ -40,7 +45,7 @@ public async Task TestInvokeAsync_ValidatorFound()
var validationFailuresValues = ValidationFailures.Values.ToList();
- var endpointFilter = new FluentValidationAutoValidationEndpointFilter(logger);
+ var endpointFilter = new FluentValidationAutoValidationEndpointFilter(logger, configuration);
var result = (ValidationProblem) (await endpointFilter.InvokeAsync(endpointFilterInvocationContext, _ => ValueTask.FromResult(new object())!))!;
var problemDetailsErrorValues = result.ProblemDetails.Errors.ToList();
@@ -56,18 +61,68 @@ public async Task TestInvokeAsync_ValidatorNotFound()
var logger = Substitute.For>();
var serviceProvider = Substitute.For();
var endpointFilterInvocationContext = Substitute.For();
+ var configuration = Substitute.For>();
+ configuration.Value.Returns(new AutoValidationEndpointsConfiguration());
endpointFilterInvocationContext.HttpContext.Returns(new DefaultHttpContext {RequestServices = serviceProvider});
endpointFilterInvocationContext.Arguments.Returns(new List {new TestModel {Parameter1 = "Value 1", Parameter2 = "Value 2", Parameter3 = "Value 3"}});
serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(TestModel))).Returns(null);
serviceProvider.GetService(typeof(IGlobalValidationInterceptor)).Returns(null);
- var endpointFilter = new FluentValidationAutoValidationEndpointFilter(logger);
+ var endpointFilter = new FluentValidationAutoValidationEndpointFilter(logger, configuration);
var result = await endpointFilter.InvokeAsync(endpointFilterInvocationContext, _ => ValueTask.FromResult(new object())!);
Assert.IsType(result);
}
+
+ [Fact]
+ public async Task TestInvokeAsync_BaseTypeValidatorNotFound()
+ {
+ var logger = Substitute.For>();
+ var serviceProvider = Substitute.For();
+ var endpointFilterInvocationContext = Substitute.For();
+ var configuration = Substitute.For>();
+ configuration.Value.Returns(new AutoValidationEndpointsConfiguration());
+
+ endpointFilterInvocationContext.HttpContext.Returns(new DefaultHttpContext {RequestServices = serviceProvider});
+ endpointFilterInvocationContext.Arguments.Returns(new List {new CreatePostRequest()});
+ serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(CreatePostRequest))).Returns(null);
+ serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(ICreatePost))).Returns(new CreatePostValidator());
+ serviceProvider.GetService(typeof(IGlobalValidationInterceptor)).Returns(null);
+
+ var endpointFilter = new FluentValidationAutoValidationEndpointFilter(logger, configuration);
+
+ var result = await endpointFilter.InvokeAsync(endpointFilterInvocationContext, _ => ValueTask.FromResult(new object())!);
+
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public async Task TestInvokeAsync_BaseTypeValidatorFound()
+ {
+ var logger = Substitute.For>();
+ var serviceProvider = Substitute.For();
+ var endpointFilterInvocationContext = Substitute.For();
+ var configuration = Substitute.For>();
+ configuration.Value.Returns(new AutoValidationEndpointsConfiguration().WithBaseTypeValidations());
+
+ endpointFilterInvocationContext.HttpContext.Returns(new DefaultHttpContext {RequestServices = serviceProvider});
+ endpointFilterInvocationContext.Arguments.Returns(new List {new CreatePostRequest()});
+ serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(CreatePostRequest))).Returns(null);
+ serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(ICreatePost))).Returns(new CreatePostValidator());
+ serviceProvider.GetService(typeof(IGlobalValidationInterceptor)).Returns(new GlobalValidationInterceptor());
+
+ var endpointFilter = new FluentValidationAutoValidationEndpointFilter(logger, configuration);
+
+ var result = await endpointFilter.InvokeAsync(endpointFilterInvocationContext, _ => ValueTask.FromResult(new object()));
+ Assert.IsType(result, false);
+ var problem = result as ValidationProblem;
+ var problemDetailsErrorValues = problem.ProblemDetails.Errors.ToList();
+
+ Assert.Contains(problemDetailsErrorValues, x => x.Value.Contains("Title cannot be null or empty."));
+ Assert.Contains(problemDetailsErrorValues, x => x.Value.Contains("Body cannot be null or empty."));
+ }
private class TestModel
{
diff --git a/Tests/src/FluentValidation.AutoValidation.Shared/Extensions/ServiceProviderExtensionsTest.cs b/Tests/src/FluentValidation.AutoValidation.Shared/Extensions/ServiceProviderExtensionsTest.cs
index dd60a3c..811e7d3 100644
--- a/Tests/src/FluentValidation.AutoValidation.Shared/Extensions/ServiceProviderExtensionsTest.cs
+++ b/Tests/src/FluentValidation.AutoValidation.Shared/Extensions/ServiceProviderExtensionsTest.cs
@@ -4,6 +4,7 @@
using FluentValidation;
using NSubstitute;
using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions;
+using SharpGrip.FluentValidation.AutoValidation.Tests.Shared.Stubs;
using Xunit;
namespace SharpGrip.FluentValidation.AutoValidation.Tests.FluentValidation.AutoValidation.Shared.Extensions;
@@ -20,11 +21,29 @@ public void Test_GetValidator()
serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(testModel.GetType())).Returns(testModelValidator);
- var validator = serviceProvider.GetValidator(testModel.GetType());
+ var validator = serviceProvider.GetValidator(testModel.GetType(), false);
Assert.Equal(testModelValidator, validator);
}
+ [Fact]
+ public void Test_GetValidator_WithBaseTypeValidator()
+ {
+ var serviceProvider = Substitute.For();
+
+ var testModel = new CreatePostRequest();
+ var testModelValidator = new CreatePostValidator();
+
+ serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(testModel.GetType())).Returns(null);
+ serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(ICreatePost))).Returns(testModelValidator);
+
+ var validator = serviceProvider.GetValidator(testModel.GetType(), false);
+ Assert.Null(validator);
+
+ validator = serviceProvider.GetValidator(testModel.GetType(), true);
+ Assert.Equal(testModelValidator, validator);
+ }
+
private class TestModel;
private class TestModelValidator;
diff --git a/Tests/src/Shared/GlobalSetup.cs b/Tests/src/Shared/GlobalSetup.cs
new file mode 100644
index 0000000..73bcd64
--- /dev/null
+++ b/Tests/src/Shared/GlobalSetup.cs
@@ -0,0 +1,20 @@
+using System.Globalization;
+using System.Threading;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+[assembly: Xunit.TestFramework("SharpGrip.FluentValidation.AutoValidation.Tests.Shared.GlobalSetup", "FluentValidation.AutoValidation.Tests")]
+namespace SharpGrip.FluentValidation.AutoValidation.Tests.Shared;
+
+public class GlobalSetup : XunitTestFramework
+{
+ public GlobalSetup(IMessageSink messageSink) : base(messageSink)
+ {
+ SetupEnvironment();
+ }
+
+ private static void SetupEnvironment()
+ {
+ Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
+ }
+}
\ No newline at end of file
diff --git a/Tests/src/Shared/Stubs/CreatePostRequest.cs b/Tests/src/Shared/Stubs/CreatePostRequest.cs
new file mode 100644
index 0000000..7228be5
--- /dev/null
+++ b/Tests/src/Shared/Stubs/CreatePostRequest.cs
@@ -0,0 +1,24 @@
+using FluentValidation;
+
+namespace SharpGrip.FluentValidation.AutoValidation.Tests.Shared.Stubs;
+
+public interface ICreatePost
+{
+ string Body { get; }
+ string Title { get; }
+}
+
+public class CreatePostRequest : ICreatePost
+{
+ public string Body { get; set; } = null!;
+ public string Title { get; set; } = null!;
+}
+
+public class CreatePostValidator : AbstractValidator
+{
+ public CreatePostValidator()
+ {
+ RuleFor(req => req.Title).NotEmpty().WithMessage("Title cannot be null or empty.");
+ RuleFor(req => req.Body).NotEmpty().WithMessage("Body cannot be null or empty.");
+ }
+}
\ No newline at end of file
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..7734148
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+