From 738ad9e9adbf7d49ac671ed760724232418b1d4b Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 14 Nov 2025 16:49:00 +0300 Subject: [PATCH 01/22] Package 0.12.2 version and update CHANGELOG.md. --- CHANGELOG.md | 14 ++++++++++++++ src/ExpressValidator/ExpressValidator.csproj | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2ee3ed..740112a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.12.2 + +- Move the instance field `TypeValidatorBase._shouldBeComparedToNull` to a static readonly field (renamed to `_canBeNull`) to cache the reflection result per `TypeValidatorBase` type and eliminate redundant per-instance evaluations. +- Remove the eagerly allocated `NotNullValidationMessageProvider` during `ExpressValidatorBuilder` configuration, and instantiate `NotNullValidationMessageProvider` only when the value is null. +Deprecate `NotNullValidationMessageProvider.GetMessage(ValidationContext)`. +- Update to FluentValidation 12.1.0. +- Rename private `TypeValidatorBase.HasOnlyNullOrEmptyValidators` to `HasNonEmptyValidators` (inverting the boolean logic) and remove the negation of this property in the `ShouldValidate` method. +- DRY refactor of null validation in `TypeValidatorBase`. +- Add tests for null-tolerance validation in `QuickValidator`. +- Add test for validating a primitive type using `ExpressValidatorBuilder`. +- Add a unit test that verifies `ExpressValidator` does not throw when members are null and no null-related validators are used. +- Edit README.md and NuGet.md. + + ## 0.12.0 - Support .NET 8.0 and FluentValidation 12.0.0. diff --git a/src/ExpressValidator/ExpressValidator.csproj b/src/ExpressValidator/ExpressValidator.csproj index 70c1eb9..1aa3f89 100644 --- a/src/ExpressValidator/ExpressValidator.csproj +++ b/src/ExpressValidator/ExpressValidator.csproj @@ -3,7 +3,7 @@ netstandard2.0;net8.0 true - 0.12.0 + 0.12.2 true Andrey Kolesnichenko ExpressValidator is a library that provides the ability to validate objects using the FluentValidation library, but without object inheritance from `AbstractValidator`. @@ -15,7 +15,7 @@ ExpressValidator.png NuGet.md - 0.12.0.0 + 0.12.2.0 0.0.0.0 From 26652d0e00fec7a2aa7d932a9d56875f78f63bb2 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 23 Nov 2025 21:30:28 +0300 Subject: [PATCH 02/22] Introduce the `ExpressConfigurator` abstract class, add `IServiceCollection.AddExpressValidation` to register all `ExpressConfigurator` implementations in Microsoft DI, and register `ProxyValidator` as the open-generic implementation of `IExpressValidator<>`. --- .../ExpressConfigurator.cs | 20 ++ .../IExpressConfigurator.cs | 7 + .../ProxyValidator.cs | 25 ++ .../ServiceCollectionExtensions.cs | 41 +++ .../AddExpressValidationIntegrationTests.cs | 318 ++++++++++++++++++ ...xtensions.DependencyInjection.Tests.csproj | 2 + ...ionExtensionsTests.AddExpressValidation.cs | 156 +++++++++ .../ServiceCollectionExtensionsTests.cs | 2 +- 8 files changed, 570 insertions(+), 1 deletion(-) create mode 100644 src/ExpressValidator.Extensions.DependencyInjection/ExpressConfigurator.cs create mode 100644 src/ExpressValidator.Extensions.DependencyInjection/IExpressConfigurator.cs create mode 100644 src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs create mode 100644 tests/ExpressValidator.Extensions.DependencyInjection.Tests/AddExpressValidationIntegrationTests.cs create mode 100644 tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.AddExpressValidation.cs diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressConfigurator.cs b/src/ExpressValidator.Extensions.DependencyInjection/ExpressConfigurator.cs new file mode 100644 index 0000000..0c5fc30 --- /dev/null +++ b/src/ExpressValidator.Extensions.DependencyInjection/ExpressConfigurator.cs @@ -0,0 +1,20 @@ +namespace ExpressValidator.Extensions.DependencyInjection +{ + public abstract class ExpressConfigurator : IExpressConfigurator + { + private readonly ExpressValidatorBuilder _validatorBuilder; + protected ExpressConfigurator(ExpressValidatorOptions expressValidatorOptions = null) + { + expressValidatorOptions = expressValidatorOptions ?? new ExpressValidatorOptions() { OnFirstPropertyValidatorFailed = OnFirstPropertyValidatorFailed.Continue }; + _validatorBuilder = new ExpressValidatorBuilder(expressValidatorOptions.OnFirstPropertyValidatorFailed); + } + + public abstract void Configure(ExpressValidatorBuilder expressValidatorBuilder); + + IExpressValidator IExpressConfigurator.Build() + { + Configure(_validatorBuilder); + return _validatorBuilder.Build(); + } + } +} diff --git a/src/ExpressValidator.Extensions.DependencyInjection/IExpressConfigurator.cs b/src/ExpressValidator.Extensions.DependencyInjection/IExpressConfigurator.cs new file mode 100644 index 0000000..bcd9670 --- /dev/null +++ b/src/ExpressValidator.Extensions.DependencyInjection/IExpressConfigurator.cs @@ -0,0 +1,7 @@ +namespace ExpressValidator.Extensions.DependencyInjection +{ + internal interface IExpressConfigurator + { + IExpressValidator Build(); + } +} diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs b/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs new file mode 100644 index 0000000..3938ad2 --- /dev/null +++ b/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation.Results; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ExpressValidator.Extensions.DependencyInjection +{ + internal class ProxyValidator : IExpressValidator + { + private readonly IExpressValidator _innerValidator; + public ProxyValidator(IServiceProvider serviceProvider) + { + var innerConfigurator = serviceProvider.GetRequiredService>(); + _innerValidator = innerConfigurator.Build(); + } + + public ValidationResult Validate(T obj) => _innerValidator.Validate(obj); + + public Task ValidateAsync(T obj, CancellationToken token = default) => _innerValidator.ValidateAsync(obj, token); + } +} diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs index 2603d49..0223639 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs @@ -1,11 +1,21 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System; +using System.Linq; +using System.Reflection; namespace ExpressValidator.Extensions.DependencyInjection { public static class ServiceCollectionExtensions { + public static IServiceCollection AddExpressValidation(this IServiceCollection services, Assembly assemblyToScan, ServiceLifetime lifetime = ServiceLifetime.Transient) + { + assemblyToScan = assemblyToScan ?? Assembly.GetExecutingAssembly(); + services.AddAllConfigurators(assemblyToScan, lifetime); + services.Add(new ServiceDescriptor(typeof(IExpressValidator<>), typeof(ProxyValidator<>), lifetime)); + return services; + } + public static IServiceCollection AddExpressValidator(this IServiceCollection services, Action> configure, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { return AddExpressValidator(services, configure, new ExpressValidatorOptions() { OnFirstPropertyValidatorFailed = OnFirstPropertyValidatorFailed.Continue }, serviceLifetime); @@ -88,5 +98,36 @@ ExpressValidatorWithReload func(IServiceProvider sp) services.AddSingleton, OptionsMonitorContext >(); return services; } + + internal static IServiceCollection AddAllConfigurators( + this IServiceCollection services, + Assembly assemblyToScan, + ServiceLifetime lifetime = ServiceLifetime.Transient) + { + // 1. Define the open generic interface type to search for. + var openGenericInterface = typeof(IExpressConfigurator<>); + + // 2. Scan the assembly for all types that are concrete classes and implement IExpressConfigurator. + var builderTypes = assemblyToScan.GetTypes() + .Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericTypeDefinition) + .Select(t => new + { + ImplementationType = t, + InterfaceType = Array.Find(t.GetInterfaces(), + i => i.IsGenericType && i.GetGenericTypeDefinition() == openGenericInterface) + }) + .Where(x => x.InterfaceType != null); + + // 3. Register each implementation. + foreach (var builderRegistration in builderTypes) + { + var serviceType = builderRegistration.InterfaceType; + var implementationType = builderRegistration.ImplementationType; + var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime); + services.Add(descriptor); + } + + return services; + } } } diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/AddExpressValidationIntegrationTests.cs b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/AddExpressValidationIntegrationTests.cs new file mode 100644 index 0000000..3d3d276 --- /dev/null +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/AddExpressValidationIntegrationTests.cs @@ -0,0 +1,318 @@ +using FluentValidation.Results; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace ExpressValidator.Extensions.DependencyInjection.Tests +{ + [TestFixture] + public class AddExpressValidationIntegrationTests + { + private ServiceCollection _services; + private IServiceProvider _serviceProvider; + + [SetUp] + public void SetUp() + { + _services = new ServiceCollection(); + } + + [TearDown] + public void TearDown() + { + (_serviceProvider as IDisposable)?.Dispose(); + } + + [Test] + public void Should_ResolveValidator_WhenConfiguratorIsRegistered() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var validator = _serviceProvider.GetService>(); + + Assert.That(validator, Is.Not.Null); + Assert.That(validator, Is.InstanceOf>()); + } + + [Test] + public void Should_BuildValidatorFromConfigurator_WhenResolved() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var validator = _serviceProvider.GetService>(); + var testPerson = new TestPersonModel { Name = "John", Age = 25 }; + + var result = validator.Validate(testPerson); + + Assert.That(result, Is.Not.Null); + } + + [Test] + public async Task Should_ValidateAsynchronously_WhenValidatorIsResolved() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var validator = _serviceProvider.GetService>(); + var testPerson = new TestPersonModel { Name = "Jane", Age = 30 }; + + var result = await validator.ValidateAsync(testPerson); + + Assert.That(result, Is.Not.Null); + } + + [Test] + public void Should_CreateNewInstancePerRequest_WhenLifetimeIsTransient() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly(), ServiceLifetime.Transient); + _serviceProvider = _services.BuildServiceProvider(); + + var validator1 = _serviceProvider.GetService>(); + var validator2 = _serviceProvider.GetService>(); + + Assert.That(validator1, Is.Not.Null); + Assert.That(validator2, Is.Not.Null); + Assert.That(validator1, Is.Not.SameAs(validator2)); + } + + [Test] + public void Should_ReturnSameInstance_WhenLifetimeIsSingleton() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly(), ServiceLifetime.Singleton); + _serviceProvider = _services.BuildServiceProvider(); + + var validator1 = _serviceProvider.GetService>(); + var validator2 = _serviceProvider.GetService>(); + + Assert.That(validator1, Is.Not.Null); + Assert.That(validator2, Is.Not.Null); + Assert.That(validator1, Is.SameAs(validator2)); + } + + [Test] + public void Should_ReturnSameInstancePerScope_WhenLifetimeIsScoped() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly(), ServiceLifetime.Scoped); + _serviceProvider = _services.BuildServiceProvider(); + + using (var scope1 = _serviceProvider.CreateScope()) + using (var scope2 = _serviceProvider.CreateScope()) + { + var validator1a = scope1.ServiceProvider.GetService>(); + var validator1b = scope1.ServiceProvider.GetService>(); + var validator2 = scope2.ServiceProvider.GetService>(); + + Assert.That(validator1a, Is.SameAs(validator1b)); + Assert.That(validator1a, Is.Not.SameAs(validator2)); + } + } + + [Test] + public void Should_ResolveMultipleValidators_WhenMultipleConfiguratorsExist() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var personValidator = _serviceProvider.GetService>(); + var productValidator = _serviceProvider.GetService>(); + + Assert.That(personValidator, Is.Not.Null); + Assert.That(productValidator, Is.Not.Null); + } + + [Test] + public void Should_ExecuteConfigureMethod_WhenValidatorIsBuilt() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var validator = _serviceProvider.GetService>(); + var testPerson = new TestPersonModel { Name = "", Age = -1 }; + + var result = validator.Validate(testPerson); + + // The configurator should have added validation rules + Assert.That(result, Is.Not.Null); + } + + [Test] + public void Should_ThrowException_WhenConfiguratorDoesNotExist() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + Assert.Throws(() => + _serviceProvider.GetRequiredService>()); + } + + [Test] + public void Should_ResolveConfigurator_WhenRequestedDirectly() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var configurator = _serviceProvider.GetService>(); + + Assert.That(configurator, Is.Not.Null); + Assert.That(configurator, Is.InstanceOf()); + } + + [Test] + public void Should_BuildValidatorMultipleTimes_WhenConfiguratorIsTransient() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly(), ServiceLifetime.Transient); + _serviceProvider = _services.BuildServiceProvider(); + + var validator1 = _serviceProvider.GetService>(); + var validator2 = _serviceProvider.GetService>(); + + var testPerson = new TestPersonModel { Name = "Test", Age = 20 }; + var result1 = validator1.Validate(testPerson); + var result2 = validator2.Validate(testPerson); + + Assert.That(result1, Is.Not.Null); + Assert.That(result2, Is.Not.Null); + } + + [Test] + public async Task Should_HandleConcurrentValidation_WhenMultipleThreadsValidate() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var validator = _serviceProvider.GetService>(); + var tasks = new Task[10]; + + for (int i = 0; i < tasks.Length; i++) + { + var testPerson = new TestPersonModel { Name = $"Person{i}", Age = 20 + i }; + tasks[i] = validator.ValidateAsync(testPerson); + } + + var results = await Task.WhenAll(tasks); + + Assert.That(results, Has.Length.EqualTo(10)); + Assert.That(results, Has.All.Not.Null); + } + + [Test] + public void Should_WorkWithDifferentAssemblies_WhenSpecified() + { + var currentAssembly = Assembly.GetExecutingAssembly(); + _services.AddExpressValidation(currentAssembly); + _serviceProvider = _services.BuildServiceProvider(); + + var validator = _serviceProvider.GetService>(); + + Assert.That(validator, Is.Not.Null); + } + + [Test] + public void Should_AllowMultipleRegistrations_WhenCalledMultipleTimes() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var validators = _serviceProvider.GetServices>(); + + Assert.That(validators, Is.Not.Null); + Assert.That(validators.Count(), Is.GreaterThan(0)); + } + + [Test] + public void Should_DisposeProperlyInScope_WhenScopedLifetimeUsed() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly(), ServiceLifetime.Scoped); + _serviceProvider = _services.BuildServiceProvider(); + + IExpressValidator validator; + + using (var scope = _serviceProvider.CreateScope()) + { + validator = scope.ServiceProvider.GetService>(); + Assert.That(validator, Is.Not.Null); + } + + // Validator should have been disposed with scope + Assert.DoesNotThrow(() => validator.Validate(new TestPersonModel())); + } + + [Test] + public void Should_UseExpressValidatorOptions_WhenConfiguratorHasOptions() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _serviceProvider = _services.BuildServiceProvider(); + + var validator = _serviceProvider.GetService>(); + var testPerson = new TestPersonWithOptionsModel { Name = "", Age = -1 }; + + var result = validator.Validate(testPerson); + + Assert.That(result, Is.Not.Null); + } + } + + // Test Models + public class TestPersonModel + { + public string Name { get; set; } + public int Age { get; set; } + } + + public class TestProductModel + { + public string ProductName { get; set; } + public decimal Price { get; set; } + } + + public class TestPersonWithOptionsModel + { + public string Name { get; set; } + public int Age { get; set; } + } + + public class NonExistentModel + { + public string Value { get; set; } + } + + // Test Configurators + public class TestPersonModelConfigurator : ExpressConfigurator + { + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + { + // Add some validation rules for testing + } + } + + public class TestProductModelConfigurator : ExpressConfigurator + { + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + { + // Add some validation rules for testing + } + } + + public class TestPersonWithOptionsModelConfigurator : ExpressConfigurator + { + public TestPersonWithOptionsModelConfigurator() + : base(new ExpressValidatorOptions + { + OnFirstPropertyValidatorFailed = OnFirstPropertyValidatorFailed.Continue + }) + { + } + + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + { + // Add some validation rules for testing + } + } +} diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj index 996e294..410e409 100644 --- a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj @@ -132,9 +132,11 @@ + + diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.AddExpressValidation.cs b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.AddExpressValidation.cs new file mode 100644 index 0000000..d5a76ee --- /dev/null +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.AddExpressValidation.cs @@ -0,0 +1,156 @@ +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using System.Linq; +using System.Reflection; + +namespace ExpressValidator.Extensions.DependencyInjection.Tests +{ + internal partial class ServiceCollectionExtensionsTests + { + private IServiceCollection _services; + + [SetUp] + public void SetUp() + { + _services = new ServiceCollection(); + } + + [Test] + public void Should_ReturnServiceCollection_WhenCalled() + { + var result = _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + + Assert.That(result, Is.SameAs(_services)); + } + + [Test] + public void Should_UseExecutingAssembly_WhenAssemblyIsNull() + { + var result = _services.AddExpressValidation(null); + + Assert.That(result, Is.Not.Null); + Assert.That(result, Is.SameAs(_services)); + } + + [Test] + public void Should_RegisterProxyValidatorAsTransient_WhenLifetimeIsNotSpecified() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + + var descriptor = _services.FirstOrDefault(sd => + sd.ServiceType.IsGenericType && + sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressValidator<>)); + + Assert.That(descriptor, Is.Not.Null); + Assert.That(descriptor.ImplementationType.GetGenericTypeDefinition(), Is.EqualTo(typeof(ProxyValidator<>))); + Assert.That(descriptor.Lifetime, Is.EqualTo(ServiceLifetime.Transient)); + } + + [Test] + public void Should_RegisterProxyValidatorAsSingleton_WhenLifetimeIsSingleton() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly(), ServiceLifetime.Singleton); + + var descriptor = _services.FirstOrDefault(sd => + sd.ServiceType.IsGenericType && + sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressValidator<>)); + + Assert.That(descriptor, Is.Not.Null); + Assert.That(descriptor.Lifetime, Is.EqualTo(ServiceLifetime.Singleton)); + } + + [Test] + public void Should_RegisterProxyValidatorAsScoped_WhenLifetimeIsScoped() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly(), ServiceLifetime.Scoped); + + var descriptor = _services.FirstOrDefault(sd => + sd.ServiceType.IsGenericType && + sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressValidator<>)); + + Assert.That(descriptor, Is.Not.Null); + Assert.That(descriptor.Lifetime, Is.EqualTo(ServiceLifetime.Scoped)); + } + + [Test] + public void Should_RegisterAllConfiguratorsFromAssembly_WhenCalled() + { + var assembly = Assembly.GetExecutingAssembly(); + + _services.AddExpressValidation(assembly); + + var configuratorDescriptors = _services.Where(sd => + sd.ServiceType.IsGenericType && + sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressConfigurator<>)); + + Assert.That(configuratorDescriptors, Is.Not.Empty); + } + + [Test] + public void Should_RegisterConfiguratorsWithSpecifiedLifetime_WhenLifetimeIsProvided() + { + var assembly = Assembly.GetExecutingAssembly(); + + _services.AddExpressValidation(assembly, ServiceLifetime.Singleton); + + var configuratorDescriptors = _services.Where(sd => + sd.ServiceType.IsGenericType && + sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressConfigurator<>)); + + Assert.That(configuratorDescriptors.All(d => d.Lifetime == ServiceLifetime.Singleton), Is.True); + } + + [Test] + public void Should_RegisterBothConfiguratorsAndValidator_WhenCalled() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + + var hasConfigurators = _services.Any(sd => + sd.ServiceType.IsGenericType && + sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressConfigurator<>)); + + var hasValidator = _services.Any(sd => + sd.ServiceType.IsGenericType && + sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressValidator<>)); + + Assert.That(hasConfigurators || hasValidator, Is.True); + Assert.That(hasValidator, Is.True); + } + + [Test] + public void Should_NotThrowException_WhenAssemblyHasNoConfigurators() + { + var emptyAssembly = typeof(object).Assembly; // mscorlib has no configurators + + Assert.DoesNotThrow(() => _services.AddExpressValidation(emptyAssembly)); + } + + [Test] + public void Should_RegisterOnlyOneProxyValidator_WhenCalledMultipleTimes() + { + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + _services.AddExpressValidation(Assembly.GetExecutingAssembly()); + + var validatorDescriptors = _services.Where(sd => + sd.ServiceType.IsGenericType && + sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressValidator<>) && + sd.ImplementationType?.GetGenericTypeDefinition() == typeof(ProxyValidator<>)); + + Assert.That(validatorDescriptors.Count(), Is.EqualTo(2)); // Each call adds one + } + } + + // Test fixture helper classes for testing configurator discovery + public class TestModel + { + public string Name { get; set; } + } + + public class TestModelConfigurator : ExpressConfigurator + { + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + { + // Test implementation + } + } +} diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs index 5cab846..9f12697 100644 --- a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs @@ -6,7 +6,7 @@ namespace ExpressValidator.Extensions.DependencyInjection.Tests { - internal class ServiceCollectionExtensionsTests + internal partial class ServiceCollectionExtensionsTests { [Test] public void Should_AddExpressValidator_Register() From 1d2dc5452e6d84d55c7a0f4bc5ed46a17c27a736 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 30 Nov 2025 20:40:18 +0300 Subject: [PATCH 03/22] Move `ExpressValidator.Extensions.DependencyInjection.Sample.csproj` into a new `QuickStart` folder and rename it to `QuickStart.csproj`. --- ...lidator.Extensions.DependencyInjection.Sample.sln | 12 ++++++------ .../{ => QuickStart}/Program.cs | 0 .../QuickStart.csproj} | 2 +- .../{ => QuickStart}/appsettings.Development.json | 0 .../{ => QuickStart}/appsettings.json | 0 5 files changed, 7 insertions(+), 7 deletions(-) rename samples/ExpressValidator.Extensions.DependencyInjection.Sample/{ => QuickStart}/Program.cs (100%) rename samples/ExpressValidator.Extensions.DependencyInjection.Sample/{ExpressValidator.Extensions.DependencyInjection.Sample.csproj => QuickStart/QuickStart.csproj} (61%) rename samples/ExpressValidator.Extensions.DependencyInjection.Sample/{ => QuickStart}/appsettings.Development.json (100%) rename samples/ExpressValidator.Extensions.DependencyInjection.Sample/{ => QuickStart}/appsettings.json (100%) diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln index b7f3dc7..87ef382 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln @@ -3,24 +3,24 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressValidator.Extensions.DependencyInjection.Sample", "ExpressValidator.Extensions.DependencyInjection.Sample.csproj", "{C934656A-07B3-4909-9C4E-0DC71D2FD62F}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressValidator.Extensions.DependencyInjection", "..\..\src\ExpressValidator.Extensions.DependencyInjection\ExpressValidator.Extensions.DependencyInjection.csproj", "{FF31B329-336C-47F0-BA60-55893A6FBCED}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickStart", "QuickStart\QuickStart.csproj", "{6C698590-8D21-5D95-CEA6-33121ACA75F1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C934656A-07B3-4909-9C4E-0DC71D2FD62F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C934656A-07B3-4909-9C4E-0DC71D2FD62F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C934656A-07B3-4909-9C4E-0DC71D2FD62F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C934656A-07B3-4909-9C4E-0DC71D2FD62F}.Release|Any CPU.Build.0 = Release|Any CPU {FF31B329-336C-47F0-BA60-55893A6FBCED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FF31B329-336C-47F0-BA60-55893A6FBCED}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF31B329-336C-47F0-BA60-55893A6FBCED}.Release|Any CPU.ActiveCfg = Release|Any CPU {FF31B329-336C-47F0-BA60-55893A6FBCED}.Release|Any CPU.Build.0 = Release|Any CPU + {6C698590-8D21-5D95-CEA6-33121ACA75F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C698590-8D21-5D95-CEA6-33121ACA75F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C698590-8D21-5D95-CEA6-33121ACA75F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C698590-8D21-5D95-CEA6-33121ACA75F1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs similarity index 100% rename from samples/ExpressValidator.Extensions.DependencyInjection.Sample/Program.cs rename to samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/QuickStart.csproj similarity index 61% rename from samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.csproj rename to samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/QuickStart.csproj index ff2118b..67f89bf 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.csproj +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/QuickStart.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/appsettings.Development.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/appsettings.Development.json similarity index 100% rename from samples/ExpressValidator.Extensions.DependencyInjection.Sample/appsettings.Development.json rename to samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/appsettings.Development.json diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/appsettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/appsettings.json similarity index 100% rename from samples/ExpressValidator.Extensions.DependencyInjection.Sample/appsettings.json rename to samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/appsettings.json From 1343480fd136817a939c0ddf96e1504b4f7705e7 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 3 Dec 2025 10:59:18 +0300 Subject: [PATCH 04/22] Provide an XML summary comment for the `AddExpressValidation` method. --- .../ServiceCollectionExtensions.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs index 0223639..e7b0d2b 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs @@ -8,6 +8,18 @@ namespace ExpressValidator.Extensions.DependencyInjection { public static class ServiceCollectionExtensions { + /// + /// Registers all concrete, non-abstract, non-generic classes that inherit from + /// into the Microsoft Dependency Injection container. + ///
+ /// Behind the scenes, for every configurator type ExpressConfigurator<T> found, + /// the DI container will also expose a proxy implementation of , + /// enabling validation logic to be resolved transparently via the service provider. + ///
+ /// The to add the services to. + /// The assembly to scan for configurator types. + /// The service lifetime for the registered configurators. Defaults to . + /// The for chaining. public static IServiceCollection AddExpressValidation(this IServiceCollection services, Assembly assemblyToScan, ServiceLifetime lifetime = ServiceLifetime.Transient) { assemblyToScan = assemblyToScan ?? Assembly.GetExecutingAssembly(); From cc245c8f9b867c6514b630a800727185aa345a05 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Thu, 4 Dec 2025 19:11:28 +0300 Subject: [PATCH 05/22] Add `ValidatorBuilderWithOptions.csproj` to the `ExpressValidator.Extensions.DependencyInjection.Sample.sln` solution to demonstrate usage of `IServiceCollection.AddExpressValidatorBuilder()`. --- ....Extensions.DependencyInjection.Sample.sln | 8 ++- .../QuickStart/Properties/launchSettings.json | 12 +++++ .../IGuessTheNumberService.cs | 50 +++++++++++++++++++ .../ValidatorBuilderWithOptions/Program.cs | 49 ++++++++++++++++++ .../Properties/launchSettings.json | 14 ++++++ .../ValidatorBuilderWithOptions.csproj | 14 ++++++ .../appsettings.Development.json | 8 +++ .../appsettings.json | 12 +++++ 8 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Properties/launchSettings.json create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Properties/launchSettings.json create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/ValidatorBuilderWithOptions.csproj create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/appsettings.Development.json create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/appsettings.json diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln index 87ef382..83ecf0e 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln @@ -5,7 +5,9 @@ VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressValidator.Extensions.DependencyInjection", "..\..\src\ExpressValidator.Extensions.DependencyInjection\ExpressValidator.Extensions.DependencyInjection.csproj", "{FF31B329-336C-47F0-BA60-55893A6FBCED}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickStart", "QuickStart\QuickStart.csproj", "{6C698590-8D21-5D95-CEA6-33121ACA75F1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickStart", "QuickStart\QuickStart.csproj", "{6C698590-8D21-5D95-CEA6-33121ACA75F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValidatorBuilderWithOptions", "ValidatorBuilderWithOptions\ValidatorBuilderWithOptions.csproj", "{FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +23,10 @@ Global {6C698590-8D21-5D95-CEA6-33121ACA75F1}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C698590-8D21-5D95-CEA6-33121ACA75F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C698590-8D21-5D95-CEA6-33121ACA75F1}.Release|Any CPU.Build.0 = Release|Any CPU + {FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Properties/launchSettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Properties/launchSettings.json new file mode 100644 index 0000000..8e623f4 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "QuickStart": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:56889;http://localhost:56890" + } + } +} \ No newline at end of file diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs new file mode 100644 index 0000000..2256e9a --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs @@ -0,0 +1,50 @@ +using ExpressValidator; +using Microsoft.Extensions.Options; + +namespace ValidatorBuilderWithOptions +{ + public interface IGuessTheNumberService + { + (bool Result, string Message) ComplexGuess(); + } + + public class GuessTheNumberService : IGuessTheNumberService + { + private readonly ValidationParametersOptions _validateOptions; + private readonly IExpressValidatorBuilder _expressValidatorBuilder; + + private const string WIN_PHRASE = "The rules have changed in the middle of the game, but you still win!"; + private const string LOSE_PHRASE = "Sorry, the rules changed in the middle of the game."; + + public GuessTheNumberService(IExpressValidatorBuilder expressValidatorBuilder, + IOptions validateOptions) + { + _validateOptions = validateOptions.Value; + _expressValidatorBuilder = expressValidatorBuilder; + } + public (bool Result, string Message) ComplexGuess() + { + var i = Random.Shared.Next(1, 11); + var objToValidate = new ObjToValidate() { I = i }; + + ChangeValidateOptions(); + + var vr = _expressValidatorBuilder.Build(_validateOptions).Validate(objToValidate); + if (vr.IsValid) + { + return (true, WIN_PHRASE + " " + + $"You guessed {i} and it is correct because it's greater than {_validateOptions.IGreaterThanValue}."); + } + else + { + return (false, LOSE_PHRASE + " " + + $"You have chosen {i} and it is wrong. " + vr.ToString()); + } + } + + private void ChangeValidateOptions() + { + _validateOptions.IGreaterThanValue = Random.Shared.Next(2, 10); + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs new file mode 100644 index 0000000..fccc4f8 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs @@ -0,0 +1,49 @@ +using ExpressValidator.Extensions.DependencyInjection; +using FluentValidation; + +namespace ValidatorBuilderWithOptions +{ + public static class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddExpressValidatorBuilder(b => + b.AddProperty(o => o.I) + .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) + .WithMessage($"Must be greater than {to.IGreaterThanValue}!"))); + + builder.Services.AddTransient(); + + builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); + + var app = builder.Build(); + + app.MapGet("/complexguess", (IGuessTheNumberService service) => + { + var (Result, Message) = service.ComplexGuess(); + if (!Result) + { + return Results.BadRequest(Message); + } + else + { + return Results.Ok(Message); + } + }); + + app.Run(); + } + } + + public class ValidationParametersOptions + { + public int IGreaterThanValue { get; set; } + } + + public class ObjToValidate + { + public int I { get; set; } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Properties/launchSettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Properties/launchSettings.json new file mode 100644 index 0000000..74b5669 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "ValidatorBuilderWithOptions": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "complexguess", + "applicationUrl": "http://localhost:5251", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/ValidatorBuilderWithOptions.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/ValidatorBuilderWithOptions.csproj new file mode 100644 index 0000000..059c64a --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/ValidatorBuilderWithOptions.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + true + + + + + + + diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/appsettings.Development.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/appsettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/appsettings.json new file mode 100644 index 0000000..7083bd9 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ValidationParameters": { + "IGreaterThanValue": 5 + } +} From d1c4c06d6f7c624019cfa92b9af113b80772d362 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 19 Dec 2025 16:34:21 +0300 Subject: [PATCH 06/22] Rename `IExpressConfigurator` to `IValidatorConfigurator` and `ExpressConfigurator` to `ValidatorConfigurator`. --- ...{IExpressConfigurator.cs => IValidatorConfigurator.cs} | 2 +- .../ProxyValidator.cs | 2 +- .../ServiceCollectionExtensions.cs | 4 ++-- .../{ExpressConfigurator.cs => ValidatorConfigurator.cs} | 6 +++--- .../AddExpressValidationIntegrationTests.cs | 8 ++++---- ...rviceCollectionExtensionsTests.AddExpressValidation.cs | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) rename src/ExpressValidator.Extensions.DependencyInjection/{IExpressConfigurator.cs => IValidatorConfigurator.cs} (69%) rename src/ExpressValidator.Extensions.DependencyInjection/{ExpressConfigurator.cs => ValidatorConfigurator.cs} (72%) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/IExpressConfigurator.cs b/src/ExpressValidator.Extensions.DependencyInjection/IValidatorConfigurator.cs similarity index 69% rename from src/ExpressValidator.Extensions.DependencyInjection/IExpressConfigurator.cs rename to src/ExpressValidator.Extensions.DependencyInjection/IValidatorConfigurator.cs index bcd9670..b1126ab 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/IExpressConfigurator.cs +++ b/src/ExpressValidator.Extensions.DependencyInjection/IValidatorConfigurator.cs @@ -1,6 +1,6 @@ namespace ExpressValidator.Extensions.DependencyInjection { - internal interface IExpressConfigurator + internal interface IValidatorConfigurator { IExpressValidator Build(); } diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs b/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs index 3938ad2..e4bb06d 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs +++ b/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs @@ -14,7 +14,7 @@ internal class ProxyValidator : IExpressValidator private readonly IExpressValidator _innerValidator; public ProxyValidator(IServiceProvider serviceProvider) { - var innerConfigurator = serviceProvider.GetRequiredService>(); + var innerConfigurator = serviceProvider.GetRequiredService>(); _innerValidator = innerConfigurator.Build(); } diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs index e7b0d2b..d25ccb0 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/ExpressValidator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs @@ -9,7 +9,7 @@ namespace ExpressValidator.Extensions.DependencyInjection public static class ServiceCollectionExtensions { /// - /// Registers all concrete, non-abstract, non-generic classes that inherit from + /// Registers all concrete, non-abstract, non-generic classes that inherit from /// into the Microsoft Dependency Injection container. ///
/// Behind the scenes, for every configurator type ExpressConfigurator<T> found, @@ -117,7 +117,7 @@ internal static IServiceCollection AddAllConfigurators( ServiceLifetime lifetime = ServiceLifetime.Transient) { // 1. Define the open generic interface type to search for. - var openGenericInterface = typeof(IExpressConfigurator<>); + var openGenericInterface = typeof(IValidatorConfigurator<>); // 2. Scan the assembly for all types that are concrete classes and implement IExpressConfigurator. var builderTypes = assemblyToScan.GetTypes() diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressConfigurator.cs b/src/ExpressValidator.Extensions.DependencyInjection/ValidatorConfigurator.cs similarity index 72% rename from src/ExpressValidator.Extensions.DependencyInjection/ExpressConfigurator.cs rename to src/ExpressValidator.Extensions.DependencyInjection/ValidatorConfigurator.cs index 0c5fc30..c56d664 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ExpressConfigurator.cs +++ b/src/ExpressValidator.Extensions.DependencyInjection/ValidatorConfigurator.cs @@ -1,9 +1,9 @@ namespace ExpressValidator.Extensions.DependencyInjection { - public abstract class ExpressConfigurator : IExpressConfigurator + public abstract class ValidatorConfigurator : IValidatorConfigurator { private readonly ExpressValidatorBuilder _validatorBuilder; - protected ExpressConfigurator(ExpressValidatorOptions expressValidatorOptions = null) + protected ValidatorConfigurator(ExpressValidatorOptions expressValidatorOptions = null) { expressValidatorOptions = expressValidatorOptions ?? new ExpressValidatorOptions() { OnFirstPropertyValidatorFailed = OnFirstPropertyValidatorFailed.Continue }; _validatorBuilder = new ExpressValidatorBuilder(expressValidatorOptions.OnFirstPropertyValidatorFailed); @@ -11,7 +11,7 @@ protected ExpressConfigurator(ExpressValidatorOptions expressValidatorOptions = public abstract void Configure(ExpressValidatorBuilder expressValidatorBuilder); - IExpressValidator IExpressConfigurator.Build() + IExpressValidator IValidatorConfigurator.Build() { Configure(_validatorBuilder); return _validatorBuilder.Build(); diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/AddExpressValidationIntegrationTests.cs b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/AddExpressValidationIntegrationTests.cs index 3d3d276..d056bb2 100644 --- a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/AddExpressValidationIntegrationTests.cs +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/AddExpressValidationIntegrationTests.cs @@ -157,7 +157,7 @@ public void Should_ResolveConfigurator_WhenRequestedDirectly() _services.AddExpressValidation(Assembly.GetExecutingAssembly()); _serviceProvider = _services.BuildServiceProvider(); - var configurator = _serviceProvider.GetService>(); + var configurator = _serviceProvider.GetService>(); Assert.That(configurator, Is.Not.Null); Assert.That(configurator, Is.InstanceOf()); @@ -284,7 +284,7 @@ public class NonExistentModel } // Test Configurators - public class TestPersonModelConfigurator : ExpressConfigurator + public class TestPersonModelConfigurator : ValidatorConfigurator { public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) { @@ -292,7 +292,7 @@ public override void Configure(ExpressValidatorBuilder expressV } } - public class TestProductModelConfigurator : ExpressConfigurator + public class TestProductModelConfigurator : ValidatorConfigurator { public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) { @@ -300,7 +300,7 @@ public override void Configure(ExpressValidatorBuilder express } } - public class TestPersonWithOptionsModelConfigurator : ExpressConfigurator + public class TestPersonWithOptionsModelConfigurator : ValidatorConfigurator { public TestPersonWithOptionsModelConfigurator() : base(new ExpressValidatorOptions diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.AddExpressValidation.cs b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.AddExpressValidation.cs index d5a76ee..7362d28 100644 --- a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.AddExpressValidation.cs +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.AddExpressValidation.cs @@ -81,7 +81,7 @@ public void Should_RegisterAllConfiguratorsFromAssembly_WhenCalled() var configuratorDescriptors = _services.Where(sd => sd.ServiceType.IsGenericType && - sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressConfigurator<>)); + sd.ServiceType.GetGenericTypeDefinition() == typeof(IValidatorConfigurator<>)); Assert.That(configuratorDescriptors, Is.Not.Empty); } @@ -95,7 +95,7 @@ public void Should_RegisterConfiguratorsWithSpecifiedLifetime_WhenLifetimeIsProv var configuratorDescriptors = _services.Where(sd => sd.ServiceType.IsGenericType && - sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressConfigurator<>)); + sd.ServiceType.GetGenericTypeDefinition() == typeof(IValidatorConfigurator<>)); Assert.That(configuratorDescriptors.All(d => d.Lifetime == ServiceLifetime.Singleton), Is.True); } @@ -107,7 +107,7 @@ public void Should_RegisterBothConfiguratorsAndValidator_WhenCalled() var hasConfigurators = _services.Any(sd => sd.ServiceType.IsGenericType && - sd.ServiceType.GetGenericTypeDefinition() == typeof(IExpressConfigurator<>)); + sd.ServiceType.GetGenericTypeDefinition() == typeof(IValidatorConfigurator<>)); var hasValidator = _services.Any(sd => sd.ServiceType.IsGenericType && @@ -146,7 +146,7 @@ public class TestModel public string Name { get; set; } } - public class TestModelConfigurator : ExpressConfigurator + public class TestModelConfigurator : ValidatorConfigurator { public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) { From cde505a32132a1a3adaeb8adec1cdee9a5c89c68 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 19 Dec 2025 17:56:27 +0300 Subject: [PATCH 07/22] Remove the `/complexguess` endpoint from the `QuickStart.csproj` project. --- .../QuickStart/Program.cs | 62 +------------------ .../QuickStart/Properties/launchSettings.json | 1 + 2 files changed, 4 insertions(+), 59 deletions(-) diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs index 7a9cb17..3eb2dee 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs @@ -1,27 +1,20 @@ using ExpressValidator; using ExpressValidator.Extensions.DependencyInjection; using FluentValidation; -using Microsoft.Extensions.Options; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddExpressValidator(b => +builder.Services.AddExpressValidator(b => b.AddProperty(o => o.I) .WithValidation(o => o.GreaterThan(5) .WithMessage("Must be greater than 5!"))); -builder.Services.AddExpressValidatorBuilder(b => - b.AddProperty(o => o.I) - .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) - .WithMessage($"Must be greater than {to.IGreaterThanValue}!"))); - builder.Services.AddExpressValidatorWithReload(b => b.AddProperty(o => o.I) .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) .WithMessage($"Must be greater than {to.IGreaterThanValue}!")), "ValidationParameters"); - builder.Services.AddTransient(); builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); @@ -41,19 +34,6 @@ } }); -app.MapGet("/complexguess", (IGuessTheNumberService service) => -{ - var (Result, Message) = service.ComplexGuess(); - if (!Result) - { - return Results.BadRequest(Message); - } - else - { - return Results.Ok(Message); - } -}); - app.MapGet("/guesswithreload", (IGuessTheNumberService service) => { var (Result, Message) = service.GuessWithReload(); @@ -87,7 +67,6 @@ public interface IGuessTheNumberService { (bool Result, string Message) Guess(); - (bool Result, string Message) ComplexGuess(); (bool Result, string Message) GuessWithReload(); Task<(bool Result, string Message)> GuessWithReloadAsync(); } @@ -95,22 +74,12 @@ public interface IGuessTheNumberService public class GuessTheNumberService : IGuessTheNumberService { private readonly IExpressValidator _expressValidator; - private readonly IExpressValidatorBuilder _expressValidatorBuilder; private readonly IExpressValidatorWithReload _expressValidatorWithReload; - private readonly ValidationParametersOptions _validateOptions; - - private const string WIN_PHRASE = "The rules have changed in the middle of the game, but you still win!"; - private const string LOSE_PHRASE = "Sorry, the rules changed in the middle of the game."; - - public GuessTheNumberService(IExpressValidator expressValidator, - IExpressValidatorBuilder expressValidatorBuilder, - IExpressValidatorWithReload expressValidatorWithReload, - IOptions validateOptions) + public GuessTheNumberService(IExpressValidator expressValidator, + IExpressValidatorWithReload expressValidatorWithReload) { _expressValidator = expressValidator; - _validateOptions = validateOptions.Value; - _expressValidatorBuilder = expressValidatorBuilder; _expressValidatorWithReload = expressValidatorWithReload; } @@ -158,31 +127,6 @@ public GuessTheNumberService(IExpressValidator expressValidator, return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); } } - - public (bool Result, string Message) ComplexGuess() - { - var i = Random.Shared.Next(1, 11); - var objToValidate = new ObjToValidate() { I = i }; - - ChangeValidateOptions(); - - var vr = _expressValidatorBuilder.Build(_validateOptions).Validate(objToValidate); - if (vr.IsValid) - { - return (true, WIN_PHRASE + " " + - $"You guessed {i} and it is correct because it's greater than {_validateOptions.IGreaterThanValue}."); - } - else - { - return (false, LOSE_PHRASE + " " + - $"You have chosen {i} and it is wrong. " + vr.ToString()); - } - } - - private void ChangeValidateOptions() - { - _validateOptions.IGreaterThanValue = Random.Shared.Next(2, 10); - } } public class ObjToValidate diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Properties/launchSettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Properties/launchSettings.json index 8e623f4..3fb3225 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Properties/launchSettings.json +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Properties/launchSettings.json @@ -3,6 +3,7 @@ "QuickStart": { "commandName": "Project", "launchBrowser": true, + "launchUrl": "guess", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, From 14d115147ec1173d8885df2bf6e37aaee6e2e466 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 21 Dec 2025 21:46:22 +0300 Subject: [PATCH 08/22] Add `ConfiguratorDemo.csproj` to the `ExpressValidator.Extensions.DependencyInjection.Sample.sln` solution to demonstrate usage of `IServiceCollection.AddExpressValidation`. --- .../ConfiguratorDemo/ConfiguratorDemo.csproj | 22 +++++++++++ .../GuessValidatorConfigurator.cs | 14 +++++++ .../IGuessTheNumberService.cs | 33 ++++++++++++++++ .../ConfiguratorDemo/Program.cs | 39 +++++++++++++++++++ .../Properties/launchSettings.json | 13 +++++++ .../appsettings.Development.json | 8 ++++ .../ConfiguratorDemo/appsettings.json | 12 ++++++ ....Extensions.DependencyInjection.Sample.sln | 6 +++ .../ValidatorBuilderWithOptions/Program.cs | 4 ++ 9 files changed, 151 insertions(+) create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/ConfiguratorDemo.csproj create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/GuessValidatorConfigurator.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Properties/launchSettings.json create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.Development.json create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.json diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/ConfiguratorDemo.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/ConfiguratorDemo.csproj new file mode 100644 index 0000000..8091ec4 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/ConfiguratorDemo.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/GuessValidatorConfigurator.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/GuessValidatorConfigurator.cs new file mode 100644 index 0000000..2352f65 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/GuessValidatorConfigurator.cs @@ -0,0 +1,14 @@ +using ExpressValidator.Extensions.DependencyInjection; +using ExpressValidator; +using FluentValidation; + +namespace ConfiguratorDemo +{ + public class GuessValidatorConfigurator : ValidatorConfigurator + { + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + => expressValidatorBuilder + .AddProperty(o => o.I) + .WithValidation((o) => o.GreaterThan(5)); + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs new file mode 100644 index 0000000..1193c49 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs @@ -0,0 +1,33 @@ +using ExpressValidator; + +namespace ConfiguratorDemo +{ + public interface IGuessTheNumberService + { + (bool Result, string Message) Guess(); + } + + public class GuessTheNumberService : IGuessTheNumberService + { + private readonly IExpressValidator _expressValidator; + public GuessTheNumberService(IExpressValidator expressValidator) + { + _expressValidator = expressValidator; + } + + public (bool Result, string Message) Guess() + { + var i = Random.Shared.Next(1, 11); + var objToValidate = new ObjToValidate() { I = i }; + var vr = _expressValidator.Validate(objToValidate); + if (vr.IsValid) + { + return (true, $"You guessed {i} and it is correct!"); + } + else + { + return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); + } + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs new file mode 100644 index 0000000..dcc6537 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs @@ -0,0 +1,39 @@ +using ExpressValidator.Extensions.DependencyInjection; +using System.Reflection; + +namespace ConfiguratorDemo +{ + public static class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddExpressValidation(Assembly.GetExecutingAssembly()); + + builder.Services.AddTransient(); + + var app = builder.Build(); + + app.MapGet("/guess", (IGuessTheNumberService service) => + { + var (Result, Message) = service.Guess(); + if (!Result) + { + return Results.BadRequest(Message); + } + else + { + return Results.Ok(Message); + } + }); + + app.Run(); + } + } + + public class ObjToValidate + { + public int I { get; set; } + } +} \ No newline at end of file diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Properties/launchSettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Properties/launchSettings.json new file mode 100644 index 0000000..cfa5164 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "ConfiguratorDemo": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "guess", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:56889;http://localhost:56890" + } + } +} \ No newline at end of file diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.Development.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.json new file mode 100644 index 0000000..7083bd9 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ValidationParameters": { + "IGreaterThanValue": 5 + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln index 83ecf0e..2fec1e3 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickStart", "QuickStart\Qu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValidatorBuilderWithOptions", "ValidatorBuilderWithOptions\ValidatorBuilderWithOptions.csproj", "{FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfiguratorDemo", "ConfiguratorDemo\ConfiguratorDemo.csproj", "{B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC01EFE1-3E2D-48C7-9A5B-0F14E892F0C3}.Release|Any CPU.Build.0 = Release|Any CPU + {B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs index fccc4f8..a11609a 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs @@ -1,5 +1,7 @@ +using ExpressValidator; using ExpressValidator.Extensions.DependencyInjection; using FluentValidation; +using System.Reflection; namespace ValidatorBuilderWithOptions { @@ -9,6 +11,8 @@ public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); + builder.Services.AddExpressValidation(Assembly.GetExecutingAssembly()); + builder.Services.AddExpressValidatorBuilder(b => b.AddProperty(o => o.I) .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) From 6df01c9a5f296898301e863854c46a81f86971f6 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 28 Dec 2025 20:01:18 +0300 Subject: [PATCH 09/22] Add `Shared.csproj` to the `ExpressValidator.Extensions.DependencyInjection.Sample.sln` solution. --- .../ConfiguratorDemo/ConfiguratorDemo.csproj | 1 + .../ConfiguratorDemo/GuessValidatorConfigurator.cs | 1 + .../ConfiguratorDemo/IGuessTheNumberService.cs | 1 + .../ConfiguratorDemo/Program.cs | 5 ----- ...ssValidator.Extensions.DependencyInjection.Sample.sln | 6 ++++++ .../QuickStart/Program.cs | 6 +----- .../QuickStart/QuickStart.csproj | 3 ++- .../Shared/ObjToValidate.cs | 9 +++++++++ .../Shared/Shared.csproj | 7 +++++++ 9 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ObjToValidate.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Shared.csproj diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/ConfiguratorDemo.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/ConfiguratorDemo.csproj index 8091ec4..bae089d 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/ConfiguratorDemo.csproj +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/ConfiguratorDemo.csproj @@ -13,6 +13,7 @@ + diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/GuessValidatorConfigurator.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/GuessValidatorConfigurator.cs index 2352f65..808c5e8 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/GuessValidatorConfigurator.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/GuessValidatorConfigurator.cs @@ -1,6 +1,7 @@ using ExpressValidator.Extensions.DependencyInjection; using ExpressValidator; using FluentValidation; +using Shared; namespace ConfiguratorDemo { diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs index 1193c49..4012c60 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs @@ -1,4 +1,5 @@ using ExpressValidator; +using Shared; namespace ConfiguratorDemo { diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs index dcc6537..f220b7a 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs @@ -31,9 +31,4 @@ public static void Main(string[] args) app.Run(); } } - - public class ObjToValidate - { - public int I { get; set; } - } } \ No newline at end of file diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln index 2fec1e3..71eb22f 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValidatorBuilderWithOptions EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfiguratorDemo", "ConfiguratorDemo\ConfiguratorDemo.csproj", "{B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{773987E2-48B5-4E06-A211-568C85655B72}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7782C5A-65E8-4FB9-B0CF-4BC0657CB294}.Release|Any CPU.Build.0 = Release|Any CPU + {773987E2-48B5-4E06-A211-568C85655B72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {773987E2-48B5-4E06-A211-568C85655B72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {773987E2-48B5-4E06-A211-568C85655B72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {773987E2-48B5-4E06-A211-568C85655B72}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs index 3eb2dee..5e475c5 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs @@ -1,6 +1,7 @@ using ExpressValidator; using ExpressValidator.Extensions.DependencyInjection; using FluentValidation; +using Shared; var builder = WebApplication.CreateBuilder(args); @@ -129,11 +130,6 @@ public GuessTheNumberService(IExpressValidator expressValidator, } } - public class ObjToValidate - { - public int I { get; set; } - } - public class ValidationParametersOptions { public int IGreaterThanValue { get; set; } diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/QuickStart.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/QuickStart.csproj index 67f89bf..05623f3 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/QuickStart.csproj +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/QuickStart.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -8,6 +8,7 @@ + diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ObjToValidate.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ObjToValidate.cs new file mode 100644 index 0000000..064163a --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ObjToValidate.cs @@ -0,0 +1,9 @@ +using System; + +namespace Shared +{ + public class ObjToValidate + { + public int I { get; set; } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Shared.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Shared.csproj new file mode 100644 index 0000000..dbdcea4 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Shared.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + From 88b237835145e29122c9535a69f4124acae4adcc Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 9 Jan 2026 13:35:19 +0300 Subject: [PATCH 10/22] Split `ValidatorWithReload.csproj` from `QuickStart.csproj`. --- .../ConfiguratorDemo/appsettings.json | 4 - ....Extensions.DependencyInjection.Sample.sln | 6 + .../QuickStart/IGuessTheNumberService.cs | 35 ++++++ .../QuickStart/Program.cs | 111 +----------------- .../QuickStart/appsettings.json | 7 -- .../Shared/ValidationParametersOptions.cs | 7 ++ .../IGuessTheNumberService.cs | 1 + .../ValidatorBuilderWithOptions/Program.cs | 12 +- .../ValidatorBuilderWithOptions.csproj | 3 +- .../IGuessTheNumberService.cs | 51 ++++++++ .../ValidatorWithReload/Program.cs | 46 ++++++++ .../Properties/launchSettings.json | 14 +++ .../ValidatorWithReload.csproj | 14 +++ .../appsettings.Development.json | 8 ++ .../ValidatorWithReload/appsettings.json | 15 +++ 15 files changed, 202 insertions(+), 132 deletions(-) create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/IGuessTheNumberService.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ValidationParametersOptions.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IGuessTheNumberService.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Program.cs create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Properties/launchSettings.json create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/ValidatorWithReload.csproj create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/appsettings.Development.json create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/appsettings.json diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.json index 7083bd9..0c208ae 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.json +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/appsettings.json @@ -4,9 +4,5 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - }, - "AllowedHosts": "*", - "ValidationParameters": { - "IGreaterThanValue": 5 } } diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln index 71eb22f..c7f2fd7 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ExpressValidator.Extensions.DependencyInjection.Sample.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfiguratorDemo", "Configu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{773987E2-48B5-4E06-A211-568C85655B72}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValidatorWithReload", "ValidatorWithReload\ValidatorWithReload.csproj", "{2F1D27FB-7F89-4502-AEC9-50E1737769B7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {773987E2-48B5-4E06-A211-568C85655B72}.Debug|Any CPU.Build.0 = Debug|Any CPU {773987E2-48B5-4E06-A211-568C85655B72}.Release|Any CPU.ActiveCfg = Release|Any CPU {773987E2-48B5-4E06-A211-568C85655B72}.Release|Any CPU.Build.0 = Release|Any CPU + {2F1D27FB-7F89-4502-AEC9-50E1737769B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F1D27FB-7F89-4502-AEC9-50E1737769B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F1D27FB-7F89-4502-AEC9-50E1737769B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F1D27FB-7F89-4502-AEC9-50E1737769B7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/IGuessTheNumberService.cs new file mode 100644 index 0000000..6c8222f --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/IGuessTheNumberService.cs @@ -0,0 +1,35 @@ +using ExpressValidator; +using Shared; + +namespace QuickStart +{ + public interface IGuessTheNumberService + { + (bool Result, string Message) Guess(); + } + + public class GuessTheNumberService : IGuessTheNumberService + { + private readonly IExpressValidator _expressValidator; + + public GuessTheNumberService(IExpressValidator expressValidator) + { + _expressValidator = expressValidator; + } + + public (bool Result, string Message) Guess() + { + var i = Random.Shared.Next(1, 11); + var objToValidate = new ObjToValidate() { I = i }; + var vr = _expressValidator.Validate(objToValidate); + if (vr.IsValid) + { + return (true, $"You guessed {i} and it is correct!"); + } + else + { + return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); + } + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs index 5e475c5..a4c541d 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs @@ -1,6 +1,6 @@ -using ExpressValidator; using ExpressValidator.Extensions.DependencyInjection; using FluentValidation; +using QuickStart; using Shared; var builder = WebApplication.CreateBuilder(args); @@ -10,20 +10,13 @@ .WithValidation(o => o.GreaterThan(5) .WithMessage("Must be greater than 5!"))); -builder.Services.AddExpressValidatorWithReload(b => - b.AddProperty(o => o.I) - .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) - .WithMessage($"Must be greater than {to.IGreaterThanValue}!")), - "ValidationParameters"); - builder.Services.AddTransient(); -builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); - var app = builder.Build(); app.MapGet("/guess", (IGuessTheNumberService service) => { + var (Result, Message) = service.Guess(); if (!Result) { @@ -35,104 +28,4 @@ } }); -app.MapGet("/guesswithreload", (IGuessTheNumberService service) => -{ - var (Result, Message) = service.GuessWithReload(); - if (!Result) - { - return Results.BadRequest(Message); - } - else - { - return Results.Ok(Message); - } -}); - -app.MapGet("/guesswithreloadasync", async (IGuessTheNumberService service) => -{ - var (Result, Message) = await service.GuessWithReloadAsync(); - if (!Result) - { - return Results.BadRequest(Message); - } - else - { - return Results.Ok(Message); - } -}); - await app.RunAsync(); - - -#pragma warning disable S3903 // Types should be defined in named namespaces -public interface IGuessTheNumberService -{ - (bool Result, string Message) Guess(); - (bool Result, string Message) GuessWithReload(); - Task<(bool Result, string Message)> GuessWithReloadAsync(); -} - -public class GuessTheNumberService : IGuessTheNumberService -{ - private readonly IExpressValidator _expressValidator; - private readonly IExpressValidatorWithReload _expressValidatorWithReload; - - public GuessTheNumberService(IExpressValidator expressValidator, - IExpressValidatorWithReload expressValidatorWithReload) - { - _expressValidator = expressValidator; - _expressValidatorWithReload = expressValidatorWithReload; - } - - public (bool Result, string Message) Guess() - { - var i = Random.Shared.Next(1, 11); - var objToValidate = new ObjToValidate() { I = i }; - var vr = _expressValidator.Validate(objToValidate); - if (vr.IsValid) - { - return (true, $"You guessed {i} and it is correct!"); - } - else - { - return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); - } - } - - public (bool Result, string Message) GuessWithReload() - { - var i = Random.Shared.Next(1, 11); - var objToValidate = new ObjToValidate() { I = i }; - var vr = _expressValidatorWithReload.Validate(objToValidate); - if (vr.IsValid) - { - return (true, $"You guessed {i} and it is correct!"); - } - else - { - return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); - } - } - - public async Task<(bool Result, string Message)> GuessWithReloadAsync() - { - var i = Random.Shared.Next(1, 11); - var objToValidate = new ObjToValidate() { I = i }; - var vr = await _expressValidatorWithReload.ValidateAsync(objToValidate).ConfigureAwait(false); - if (vr.IsValid) - { - return (true, $"You guessed {i} and it is correct!"); - } - else - { - return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); - } - } -} - -public class ValidationParametersOptions -{ - public int IGreaterThanValue { get; set; } -} -#pragma warning restore S3903 // Types should be defined in named namespaces - diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/appsettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/appsettings.json index 91c4ab2..0c208ae 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/appsettings.json +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/appsettings.json @@ -4,12 +4,5 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - }, - "AllowedHosts": "*", - "ValidationParameters": { - "IGreaterThanValue": 5 - }, - "OtherParameters": { - "OtherParameter": 4 } } diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ValidationParametersOptions.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ValidationParametersOptions.cs new file mode 100644 index 0000000..957e9b5 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ValidationParametersOptions.cs @@ -0,0 +1,7 @@ +namespace Shared +{ + public class ValidationParametersOptions + { + public int IGreaterThanValue { get; set; } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs index 2256e9a..6ea78df 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs @@ -1,5 +1,6 @@ using ExpressValidator; using Microsoft.Extensions.Options; +using Shared; namespace ValidatorBuilderWithOptions { diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs index a11609a..a2c4396 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs @@ -1,6 +1,6 @@ -using ExpressValidator; using ExpressValidator.Extensions.DependencyInjection; using FluentValidation; +using Shared; using System.Reflection; namespace ValidatorBuilderWithOptions @@ -40,14 +40,4 @@ public static void Main(string[] args) app.Run(); } } - - public class ValidationParametersOptions - { - public int IGreaterThanValue { get; set; } - } - - public class ObjToValidate - { - public int I { get; set; } - } } diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/ValidatorBuilderWithOptions.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/ValidatorBuilderWithOptions.csproj index 059c64a..cddae74 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/ValidatorBuilderWithOptions.csproj +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/ValidatorBuilderWithOptions.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -9,6 +9,7 @@ + diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IGuessTheNumberService.cs new file mode 100644 index 0000000..e5a8419 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IGuessTheNumberService.cs @@ -0,0 +1,51 @@ +using ExpressValidator.Extensions.DependencyInjection; +using Shared; + +namespace ValidatorWithReload +{ + public interface IGuessTheNumberService + { + (bool Result, string Message) GuessWithReload(); + Task<(bool Result, string Message)> GuessWithReloadAsync(); + } + + public class GuessTheNumberService : IGuessTheNumberService + { + private readonly IExpressValidatorWithReload _expressValidatorWithReload; + + public GuessTheNumberService(IExpressValidatorWithReload expressValidatorWithReload) + { + _expressValidatorWithReload = expressValidatorWithReload; + } + + public (bool Result, string Message) GuessWithReload() + { + var i = Random.Shared.Next(1, 11); + var objToValidate = new ObjToValidate() { I = i }; + var vr = _expressValidatorWithReload.Validate(objToValidate); + if (vr.IsValid) + { + return (true, $"You guessed {i} and it is correct!"); + } + else + { + return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); + } + } + + public async Task<(bool Result, string Message)> GuessWithReloadAsync() + { + var i = Random.Shared.Next(1, 11); + var objToValidate = new ObjToValidate() { I = i }; + var vr = await _expressValidatorWithReload.ValidateAsync(objToValidate).ConfigureAwait(false); + if (vr.IsValid) + { + return (true, $"You guessed {i} and it is correct!"); + } + else + { + return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); + } + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Program.cs new file mode 100644 index 0000000..77ce44d --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Program.cs @@ -0,0 +1,46 @@ +using ExpressValidator.Extensions.DependencyInjection; +using FluentValidation; +using Shared; +using ValidatorWithReload; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddExpressValidatorWithReload(b => + b.AddProperty(o => o.I) + .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) + .WithMessage($"Must be greater than {to.IGreaterThanValue}!")), + "ValidationParameters"); + +builder.Services.AddTransient(); + +builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); + +var app = builder.Build(); + +app.MapGet("/guesswithreload", (IGuessTheNumberService service) => +{ + var (Result, Message) = service.GuessWithReload(); + if (!Result) + { + return Results.BadRequest(Message); + } + else + { + return Results.Ok(Message); + } +}); + +app.MapGet("/guesswithreloadasync", async (IGuessTheNumberService service) => +{ + var (Result, Message) = await service.GuessWithReloadAsync(); + if (!Result) + { + return Results.BadRequest(Message); + } + else + { + return Results.Ok(Message); + } +}); + +await app.RunAsync(); diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Properties/launchSettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Properties/launchSettings.json new file mode 100644 index 0000000..c283bf8 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "ValidatorBuilderWithOptions": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "guesswithreload", + "applicationUrl": "http://localhost:5251", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/ValidatorWithReload.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/ValidatorWithReload.csproj new file mode 100644 index 0000000..fcfd6b0 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/ValidatorWithReload.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/appsettings.Development.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/appsettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/appsettings.json new file mode 100644 index 0000000..91c4ab2 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ValidationParameters": { + "IGreaterThanValue": 5 + }, + "OtherParameters": { + "OtherParameter": 4 + } +} From 6d7240230871524a983d1ea97ad0e2f41f640887 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 9 Jan 2026 14:13:23 +0300 Subject: [PATCH 11/22] Update to ExpressValidator 0.12.2 and FluentValidation 12.1.0. --- .../ExpressValidator.Extensions.DependencyInjection.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj index 9129223..a651daf 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj +++ b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj @@ -23,7 +23,7 @@ - + @@ -39,7 +39,7 @@ - + From ac7da6f162cefc66d4d6a520add147533fbb35af Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 9 Jan 2026 15:14:34 +0300 Subject: [PATCH 12/22] Update ExpressValidator package for ExpressValidator.Extensions.DependencyInjection.Tests. --- ...pressValidator.Extensions.DependencyInjection.Tests.csproj | 4 ++-- .../packages.config | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj index 410e409..053e80e 100644 --- a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj @@ -39,8 +39,8 @@ 4 - - ..\..\packages\ExpressValidator.0.12.0\lib\netstandard2.0\ExpressValidator.dll + + ..\..\packages\ExpressValidator.0.12.2\lib\netstandard2.0\ExpressValidator.dll ..\..\packages\FluentValidation.11.11.0\lib\netstandard2.0\FluentValidation.dll diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/packages.config b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/packages.config index 24eebd6..c517967 100644 --- a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/packages.config +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/packages.config @@ -1,6 +1,6 @@  - + From d66c76e371d0e70ddeed34c3e8557f2ce32dd3ce Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 9 Jan 2026 15:55:43 +0300 Subject: [PATCH 13/22] Update Microsoft nuget packages. --- .../Properties/launchSettings.json | 2 +- ...ator.Extensions.DependencyInjection.csproj | 4 +- ...xtensions.DependencyInjection.Tests.csproj | 72 +++++++++---------- .../packages.config | 36 +++++----- 4 files changed, 57 insertions(+), 57 deletions(-) diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Properties/launchSettings.json b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Properties/launchSettings.json index c283bf8..7c6f810 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Properties/launchSettings.json +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "ValidatorBuilderWithOptions": { + "ValidatorWithReload": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj index a651daf..c561f19 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj +++ b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj @@ -40,8 +40,8 @@ - - + + diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj index 053e80e..4e1c6d5 100644 --- a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/ExpressValidator.Extensions.DependencyInjection.Tests.csproj @@ -45,50 +45,50 @@ ..\..\packages\FluentValidation.11.11.0\lib\netstandard2.0\FluentValidation.dll - - ..\..\packages\Microsoft.Bcl.AsyncInterfaces.9.0.9\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + ..\..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.1\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll - - ..\..\packages\Microsoft.Extensions.Configuration.9.0.9\lib\net462\Microsoft.Extensions.Configuration.dll + + ..\..\packages\Microsoft.Extensions.Configuration.10.0.1\lib\net462\Microsoft.Extensions.Configuration.dll - - ..\..\packages\Microsoft.Extensions.Configuration.Abstractions.9.0.9\lib\net462\Microsoft.Extensions.Configuration.Abstractions.dll + + ..\..\packages\Microsoft.Extensions.Configuration.Abstractions.10.0.1\lib\net462\Microsoft.Extensions.Configuration.Abstractions.dll - - ..\..\packages\Microsoft.Extensions.Configuration.Binder.9.0.9\lib\net462\Microsoft.Extensions.Configuration.Binder.dll + + ..\..\packages\Microsoft.Extensions.Configuration.Binder.10.0.1\lib\net462\Microsoft.Extensions.Configuration.Binder.dll - - ..\..\packages\Microsoft.Extensions.Configuration.EnvironmentVariables.9.0.9\lib\net462\Microsoft.Extensions.Configuration.EnvironmentVariables.dll + + ..\..\packages\Microsoft.Extensions.Configuration.EnvironmentVariables.10.0.1\lib\net462\Microsoft.Extensions.Configuration.EnvironmentVariables.dll - - ..\..\packages\Microsoft.Extensions.Configuration.FileExtensions.9.0.9\lib\net462\Microsoft.Extensions.Configuration.FileExtensions.dll + + ..\..\packages\Microsoft.Extensions.Configuration.FileExtensions.10.0.1\lib\net462\Microsoft.Extensions.Configuration.FileExtensions.dll - - ..\..\packages\Microsoft.Extensions.Configuration.Json.9.0.9\lib\net462\Microsoft.Extensions.Configuration.Json.dll + + ..\..\packages\Microsoft.Extensions.Configuration.Json.10.0.1\lib\net462\Microsoft.Extensions.Configuration.Json.dll - - ..\..\packages\Microsoft.Extensions.DependencyInjection.9.0.9\lib\net462\Microsoft.Extensions.DependencyInjection.dll + + ..\..\packages\Microsoft.Extensions.DependencyInjection.10.0.1\lib\net462\Microsoft.Extensions.DependencyInjection.dll - - ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.9.0.9\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.10.0.1\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - ..\..\packages\Microsoft.Extensions.FileProviders.Abstractions.9.0.9\lib\net462\Microsoft.Extensions.FileProviders.Abstractions.dll + + ..\..\packages\Microsoft.Extensions.FileProviders.Abstractions.10.0.1\lib\net462\Microsoft.Extensions.FileProviders.Abstractions.dll - - ..\..\packages\Microsoft.Extensions.FileProviders.Physical.9.0.9\lib\net462\Microsoft.Extensions.FileProviders.Physical.dll + + ..\..\packages\Microsoft.Extensions.FileProviders.Physical.10.0.1\lib\net462\Microsoft.Extensions.FileProviders.Physical.dll - - ..\..\packages\Microsoft.Extensions.FileSystemGlobbing.9.0.9\lib\net462\Microsoft.Extensions.FileSystemGlobbing.dll + + ..\..\packages\Microsoft.Extensions.FileSystemGlobbing.10.0.1\lib\net462\Microsoft.Extensions.FileSystemGlobbing.dll - - ..\..\packages\Microsoft.Extensions.Options.9.0.9\lib\net462\Microsoft.Extensions.Options.dll + + ..\..\packages\Microsoft.Extensions.Options.10.0.1\lib\net462\Microsoft.Extensions.Options.dll - - ..\..\packages\Microsoft.Extensions.Options.ConfigurationExtensions.9.0.9\lib\net462\Microsoft.Extensions.Options.ConfigurationExtensions.dll + + ..\..\packages\Microsoft.Extensions.Options.ConfigurationExtensions.10.0.1\lib\net462\Microsoft.Extensions.Options.ConfigurationExtensions.dll - - ..\..\packages\Microsoft.Extensions.Primitives.9.0.9\lib\net462\Microsoft.Extensions.Primitives.dll + + ..\..\packages\Microsoft.Extensions.Primitives.10.0.1\lib\net462\Microsoft.Extensions.Primitives.dll ..\..\packages\NUnit.4.4.0\lib\net462\nunit.framework.dll @@ -102,8 +102,8 @@ - - ..\..\packages\System.IO.Pipelines.9.0.9\lib\net462\System.IO.Pipelines.dll + + ..\..\packages\System.IO.Pipelines.10.0.1\lib\net462\System.IO.Pipelines.dll ..\..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll @@ -115,11 +115,11 @@ ..\..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll - - ..\..\packages\System.Text.Encodings.Web.9.0.9\lib\net462\System.Text.Encodings.Web.dll + + ..\..\packages\System.Text.Encodings.Web.10.0.1\lib\net462\System.Text.Encodings.Web.dll - - ..\..\packages\System.Text.Json.9.0.9\lib\net462\System.Text.Json.dll + + ..\..\packages\System.Text.Json.10.0.1\lib\net462\System.Text.Json.dll ..\..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll diff --git a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/packages.config b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/packages.config index c517967..8081237 100644 --- a/tests/ExpressValidator.Extensions.DependencyInjection.Tests/packages.config +++ b/tests/ExpressValidator.Extensions.DependencyInjection.Tests/packages.config @@ -2,30 +2,30 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - + + \ No newline at end of file From 04cc7edba487e4e6df73cfddb01de4c2288000fa Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sat, 10 Jan 2026 14:31:47 +0300 Subject: [PATCH 14/22] Rename service names in `ValidatorBuilderWithOptions.csproj` and `ValidatorWithReload.csproj`. --- ...heNumberService.cs => IAdvancedNumberGuessingService.cs} | 6 +++--- .../ValidatorBuilderWithOptions/Program.cs | 4 ++-- ...NumberService.cs => IReloadableNumberGuessingService.cs} | 6 +++--- .../ValidatorWithReload/Program.cs | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) rename samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/{IGuessTheNumberService.cs => IAdvancedNumberGuessingService.cs} (84%) rename samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/{IGuessTheNumberService.cs => IReloadableNumberGuessingService.cs} (83%) diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IAdvancedNumberGuessingService.cs similarity index 84% rename from samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs rename to samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IAdvancedNumberGuessingService.cs index 6ea78df..e9ec0b3 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IGuessTheNumberService.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/IAdvancedNumberGuessingService.cs @@ -4,12 +4,12 @@ namespace ValidatorBuilderWithOptions { - public interface IGuessTheNumberService + public interface IAdvancedNumberGuessingService { (bool Result, string Message) ComplexGuess(); } - public class GuessTheNumberService : IGuessTheNumberService + public class AdvancedNumberGuessingService : IAdvancedNumberGuessingService { private readonly ValidationParametersOptions _validateOptions; private readonly IExpressValidatorBuilder _expressValidatorBuilder; @@ -17,7 +17,7 @@ public class GuessTheNumberService : IGuessTheNumberService private const string WIN_PHRASE = "The rules have changed in the middle of the game, but you still win!"; private const string LOSE_PHRASE = "Sorry, the rules changed in the middle of the game."; - public GuessTheNumberService(IExpressValidatorBuilder expressValidatorBuilder, + public AdvancedNumberGuessingService(IExpressValidatorBuilder expressValidatorBuilder, IOptions validateOptions) { _validateOptions = validateOptions.Value; diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs index a2c4396..1b56b9b 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs @@ -18,13 +18,13 @@ public static void Main(string[] args) .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) .WithMessage($"Must be greater than {to.IGreaterThanValue}!"))); - builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); var app = builder.Build(); - app.MapGet("/complexguess", (IGuessTheNumberService service) => + app.MapGet("/complexguess", (IAdvancedNumberGuessingService service) => { var (Result, Message) = service.ComplexGuess(); if (!Result) diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IReloadableNumberGuessingService.cs similarity index 83% rename from samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IGuessTheNumberService.cs rename to samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IReloadableNumberGuessingService.cs index e5a8419..d430389 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IGuessTheNumberService.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/IReloadableNumberGuessingService.cs @@ -3,17 +3,17 @@ namespace ValidatorWithReload { - public interface IGuessTheNumberService + public interface IReloadableNumberGuessingService { (bool Result, string Message) GuessWithReload(); Task<(bool Result, string Message)> GuessWithReloadAsync(); } - public class GuessTheNumberService : IGuessTheNumberService + public class ReloadableNumberGuessingService : IReloadableNumberGuessingService { private readonly IExpressValidatorWithReload _expressValidatorWithReload; - public GuessTheNumberService(IExpressValidatorWithReload expressValidatorWithReload) + public ReloadableNumberGuessingService(IExpressValidatorWithReload expressValidatorWithReload) { _expressValidatorWithReload = expressValidatorWithReload; } diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Program.cs index 77ce44d..41208d8 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorWithReload/Program.cs @@ -11,13 +11,13 @@ .WithMessage($"Must be greater than {to.IGreaterThanValue}!")), "ValidationParameters"); -builder.Services.AddTransient(); +builder.Services.AddTransient(); builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); var app = builder.Build(); -app.MapGet("/guesswithreload", (IGuessTheNumberService service) => +app.MapGet("/guesswithreload", (IReloadableNumberGuessingService service) => { var (Result, Message) = service.GuessWithReload(); if (!Result) @@ -30,7 +30,7 @@ } }); -app.MapGet("/guesswithreloadasync", async (IGuessTheNumberService service) => +app.MapGet("/guesswithreloadasync", async (IReloadableNumberGuessingService service) => { var (Result, Message) = await service.GuessWithReloadAsync(); if (!Result) From ebbbe2d990b15bd2b57241051cb42cb0be769f20 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sat, 10 Jan 2026 18:25:37 +0300 Subject: [PATCH 15/22] Move `GuessTheNumberService` to `Shared.csproj`. --- .../IGuessTheNumberService.cs | 34 ------------------- .../ConfiguratorDemo/Program.cs | 1 + .../QuickStart/Program.cs | 2 -- .../IGuessTheNumberService.cs | 6 ++-- .../Shared/ObjToValidate.cs | 4 +-- .../Shared/Randomizer.cs | 13 +++++++ .../Shared/Shared.csproj | 5 +++ 7 files changed, 23 insertions(+), 42 deletions(-) delete mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs rename samples/ExpressValidator.Extensions.DependencyInjection.Sample/{QuickStart => Shared}/IGuessTheNumberService.cs (91%) create mode 100644 samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Randomizer.cs diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs deleted file mode 100644 index 4012c60..0000000 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/IGuessTheNumberService.cs +++ /dev/null @@ -1,34 +0,0 @@ -using ExpressValidator; -using Shared; - -namespace ConfiguratorDemo -{ - public interface IGuessTheNumberService - { - (bool Result, string Message) Guess(); - } - - public class GuessTheNumberService : IGuessTheNumberService - { - private readonly IExpressValidator _expressValidator; - public GuessTheNumberService(IExpressValidator expressValidator) - { - _expressValidator = expressValidator; - } - - public (bool Result, string Message) Guess() - { - var i = Random.Shared.Next(1, 11); - var objToValidate = new ObjToValidate() { I = i }; - var vr = _expressValidator.Validate(objToValidate); - if (vr.IsValid) - { - return (true, $"You guessed {i} and it is correct!"); - } - else - { - return (false, $"You have chosen {i} and it is wrong. " + vr.ToString()); - } - } - } -} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs index f220b7a..a0ae29a 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ConfiguratorDemo/Program.cs @@ -1,4 +1,5 @@ using ExpressValidator.Extensions.DependencyInjection; +using Shared; using System.Reflection; namespace ConfiguratorDemo diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs index a4c541d..59ee9ec 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/Program.cs @@ -1,6 +1,5 @@ using ExpressValidator.Extensions.DependencyInjection; using FluentValidation; -using QuickStart; using Shared; var builder = WebApplication.CreateBuilder(args); @@ -16,7 +15,6 @@ app.MapGet("/guess", (IGuessTheNumberService service) => { - var (Result, Message) = service.Guess(); if (!Result) { diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/IGuessTheNumberService.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/IGuessTheNumberService.cs similarity index 91% rename from samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/IGuessTheNumberService.cs rename to samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/IGuessTheNumberService.cs index 6c8222f..0f4c5e7 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/QuickStart/IGuessTheNumberService.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/IGuessTheNumberService.cs @@ -1,7 +1,7 @@ using ExpressValidator; -using Shared; +using System; -namespace QuickStart +namespace Shared { public interface IGuessTheNumberService { @@ -19,7 +19,7 @@ public GuessTheNumberService(IExpressValidator expressValidator) public (bool Result, string Message) Guess() { - var i = Random.Shared.Next(1, 11); + var i = Randomizer.Next(1, 11); var objToValidate = new ObjToValidate() { I = i }; var vr = _expressValidator.Validate(objToValidate); if (vr.IsValid) diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ObjToValidate.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ObjToValidate.cs index 064163a..4c43098 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ObjToValidate.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/ObjToValidate.cs @@ -1,6 +1,4 @@ -using System; - -namespace Shared +namespace Shared { public class ObjToValidate { diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Randomizer.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Randomizer.cs new file mode 100644 index 0000000..1d58dd7 --- /dev/null +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Randomizer.cs @@ -0,0 +1,13 @@ +using System; + +namespace Shared +{ + public static class Randomizer + { + private static readonly Random _rnd = new(); + public static int Next(int minInclusive, int maxExclusive) + { + return _rnd.Next(minInclusive, maxExclusive); + } + } +} diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Shared.csproj b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Shared.csproj index dbdcea4..a6c27f2 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Shared.csproj +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/Shared/Shared.csproj @@ -2,6 +2,11 @@ netstandard2.0 + latest + + + + From 333b0501ba9ee6af64a28e296b9198fcf18699c4 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sat, 10 Jan 2026 19:31:46 +0300 Subject: [PATCH 16/22] Remove the redundant `AddExpressValidation` call from `Program.Main` in `ValidatorBuilderWithOptions.csproj`. --- .../ValidatorBuilderWithOptions/Program.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs index 1b56b9b..97199fc 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/ValidatorBuilderWithOptions/Program.cs @@ -1,7 +1,6 @@ using ExpressValidator.Extensions.DependencyInjection; using FluentValidation; using Shared; -using System.Reflection; namespace ValidatorBuilderWithOptions { @@ -11,8 +10,6 @@ public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); - builder.Services.AddExpressValidation(Assembly.GetExecutingAssembly()); - builder.Services.AddExpressValidatorBuilder(b => b.AddProperty(o => o.I) .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) From c050fc68de25afca69c0080107bf8a94fc80d561 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 11 Jan 2026 13:04:12 +0300 Subject: [PATCH 17/22] Edit NuGet README. --- .../docs/NuGet.md | 259 +++++++++++++----- 1 file changed, 194 insertions(+), 65 deletions(-) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/docs/NuGet.md b/src/ExpressValidator.Extensions.DependencyInjection/docs/NuGet.md index 93d050d..6eb725f 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/docs/NuGet.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/docs/NuGet.md @@ -6,7 +6,9 @@ - Additionally, the `IExpressValidatorBuilder` interface can be configured and registered to update the validator parameters when the `ValidationParametersOptions` change. - Ability to dynamically update the validator parameters from options bound to the configuration section without restarting the application by configuring the `IExpressValidatorWithReload` interface. -## Usage +## Quick Start + +Register an `IExpressValidator` implementation in the dependency injection (DI) container using the `AddExpressValidator` method, then inject and use it in a consuming service: ```csharp using ExpressValidator; @@ -15,113 +17,240 @@ using FluentValidation; var builder = WebApplication.CreateBuilder(args); +// Registers the validator for ObjToValidate with specified validation rules. builder.Services.AddExpressValidator(b => b.AddProperty(o => o.I) .WithValidation(o => o.GreaterThan(5) .WithMessage("Must be greater than 5!"))); -builder.Services.AddExpressValidatorBuilder - (b => b.AddProperty(o => o.I) - .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) - .WithMessage($"Must be greater than {to.IGreaterThanValue}!"))); +// Registers the service that will use the validator. +builder.Services.AddTransient(); -builder.Services.AddExpressValidatorWithReload(b => - b.AddProperty(o => o.I) - .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) - .WithMessage($"Must be greater than {to.IGreaterThanValue}!")), - //Configuration section path - "ValidationParameters"); +var app = builder.Build(); -builder.Services.AddTransient(); +app.MapGet("/guess", (IGuessTheNumberService service) => +{ + var (Result, Message) = service.Guess(); + if (!Result) + { + return Results.BadRequest(Message); + } + // Additional logic here... +}); -var app = builder.Build(); +await app.RunAsync(); -... +// ... (Other code omitted for brevity) -interface ISomeServiceThatUseIExpressValidator +// Service interface definition. +public interface IGuessTheNumberService { - void ValidateByValidator(ObjToValidate objToValidate); - void ValidateByBuilder(ObjToValidate objToValidate); - void ValidateByValidatorWithReload(ObjToValidate objToValidate); + (bool Result, string Message) Guess(); } -class SomeServiceThatUseIExpressValidator : ISomeServiceThatUseIExpressValidator +// Service implementation that uses the validator. +public class GuessTheNumberService : IGuessTheNumberService { private readonly IExpressValidator _expressValidator; - private readonly IExpressValidatorBuilder _expressValidatorBuilder; - private readonly IExpressValidatorWithReload _expressValidatorWithReload; - - private readonly ValidationParametersOptions _validateOptions; - public SomeServiceThatUseIExpressValidator( - IExpressValidator expressValidator, - IExpressValidatorBuilder expressValidatorBuilder, - IExpressValidatorWithReload expressValidatorWithReload - IOptions validateOptions) + public GuessTheNumberService(IExpressValidator expressValidator) { _expressValidator = expressValidator; - _expressValidatorBuilder = expressValidatorBuilder; - _expressValidatorWithReload = expressValidatorWithReload; - _validateOptions = validateOptions.Value; } - public void ValidateByValidator(ObjToValidate objToValidate) + public (bool Result, string Message) Guess() { + ... var vr = _expressValidator.Validate(objToValidate); - if(vr.IsValid) + if (!vr.IsValid) { - ... + ... } + // ... (Additional logic) } +} +// ... (Other code omitted for brevity) +``` + +## Quick Start: Using a `ValidatorConfigurator` (Alternative Approach) - public void ValidateByBuilder(ObjToValidate objToValidate) +As an alternative to inline configuration, you can define validation rules by creating a dedicated configurator class that inherits from `ValidatorConfigurator`, where `T` is the type being validated: + +```csharp +/// +/// Configures validation rules for ObjToValidate. +/// +public class GuessValidatorConfigurator : ValidatorConfigurator +{ + /// + /// Configures the validator builder with rules. + /// + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + => expressValidatorBuilder + .AddProperty(o => o.I) + .WithValidation((o) => o.GreaterThan(5)); +} +``` + +Then use `AddExpressValidation` method to register the configurator in DI: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Scans the assembly and registers validators from configurators. +builder.Services.AddExpressValidation(Assembly.GetExecutingAssembly()); + +// Registers the service that will use the validator. +builder.Services.AddTransient(); + +// ... (Application build and run code omitted; same as in Quick Start) + +// The GuessTheNumberService implementation remains the same as in the Quick Start example. +// ... (Other code omitted for brevity) +``` + +## Validation with Options + +In this approach, register an `IExpressValidatorBuilder` implementation (instead of `IExpressValidator`) in the DI container by calling the `AddExpressValidatorBuilder` method. + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Registers the validator builder with options-dependent rules. +builder.Services.AddExpressValidatorBuilder(b => + b.AddProperty(o => o.I) + .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) + .WithMessage($"Must be greater than {to.IGreaterThanValue}!"))); + +builder.Services.AddTransient(); + +// Configures options from the application settings. +builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); + +var app = builder.Build(); + +app.MapGet("/complexguess", (IAdvancedNumberGuessingService service) => +{ + var (Result, Message) = service.ComplexGuess(); + if (!Result) { - ChangeOptions(); - var vr = _expressValidatorBuilder - .Build(_validateOptions) - .Validate(objToValidate); - if(vr.IsValid) - { - ... - } + return Results.BadRequest(Message); + } + // Additional logic here... +}); + +app.Run(); + +// Service interface definition: +public interface IAdvancedNumberGuessingService +{ + (bool Result, string Message) ComplexGuess(); +} + +// Service implementation that builds and uses the validator with options. +public class AdvancedNumberGuessingService : IAdvancedNumberGuessingService +{ + private readonly ValidationParametersOptions _validateOptions; + private readonly IExpressValidatorBuilder _expressValidatorBuilder; + + public AdvancedNumberGuessingService(IExpressValidatorBuilder expressValidatorBuilder, + IOptions validateOptions) + { + _validateOptions = validateOptions.Value; + _expressValidatorBuilder = expressValidatorBuilder; } - //Change the options in the configuration section path named "ValidationParameters" - //and use this method to revalidate the object without restarting the application. - public void ValidateByValidatorWithReload(ObjToValidate objToValidate) + //Updates options, rebuilds the validator, and validates. + public (bool Result, string Message) ComplexGuess() { - var vr = _expressValidatorWithReload.Validate(objToValidate); - if(vr.IsValid) - { ... + ChangeValidateOptions(); + + var vr = _expressValidatorBuilder.Build(_validateOptions).Validate(objToValidate); + if (!vr.IsValid) + { + // ... (Handle invalid case) } + // ... (Additional logic) } - private void ChangeOptions() + private void ChangeValidateOptions() { - _validateOptions.IGreaterThanValue = ...; + // ... (Option update logic omitted) } } - -class ObjToValidate -{ - public int I { get; set; } -} - -class ValidationParametersOptions -{ - public int IGreaterThanValue { get; set; } -} ``` - In the *appsettings.json* ```csharp { -... +// ... (Other settings omitted) "ValidationParameters": { "IGreaterThanValue": 5 } -... } -``` \ No newline at end of file +``` + +## Validation with Automatic Reload on Configuration Changes + +To validate options when configuration changes - without restarting the application - use the `AddExpressValidatorWithReload` method: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Registers a reloadable validator that updates on configuration changes. +builder.Services.AddExpressValidatorWithReload(b => + b.AddProperty(o => o.I) + .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) + .WithMessage($"Must be greater than {to.IGreaterThanValue}!")), + "ValidationParameters"); + +// Registers the reloadable service. +builder.Services.AddTransient(); + +// Configures options from the application settings. +builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); + +var app = builder.Build(); + +app.MapGet("/guesswithreload", (IReloadableNumberGuessingService service) => +{ + var (Result, Message) = service.GuessWithReload(); + if (!Result) + { + return Results.BadRequest(Message); + } + // Additional logic here... +}); + +// Service interface definition. +public interface IReloadableNumberGuessingService +{ + (bool Result, string Message) GuessWithReload(); +} + +// Service implementation that uses the reloadable validator. +public class ReloadableNumberGuessingService : IReloadableNumberGuessingService +{ + private readonly IExpressValidatorWithReload _expressValidatorWithReload; + + public ReloadableNumberGuessingService(IExpressValidatorWithReload expressValidatorWithReload) + { + _expressValidatorWithReload = expressValidatorWithReload; + } + + public (bool Result, string Message) GuessWithReload() + { + ... + var vr = _expressValidatorWithReload.Validate(objToValidate); + if (!vr.IsValid) + { + ... + } + } +} +``` + +## Samples + +See samples folder for concrete example. \ No newline at end of file From 01a607a8be7a8e2a7c1551f7dd69896eab1023d9 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 11 Jan 2026 15:23:11 +0300 Subject: [PATCH 18/22] Edit README.md. --- .../README.md | 254 +++++++++++++----- 1 file changed, 190 insertions(+), 64 deletions(-) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/README.md b/src/ExpressValidator.Extensions.DependencyInjection/README.md index cb9343d..9bbb08b 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/README.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/README.md @@ -10,7 +10,9 @@ Explore the API documentation and in-depth details on [DeepWiki](https://deepwiki.com/kolan72/ExpressValidator/3-dependency-injection-extension). -## 🚀 Usage +## 🚀 Quick Start + +Register an `IExpressValidator` implementation in the dependency injection (DI) container using the `AddExpressValidator` method, then inject and use it in a consuming service: ```csharp using ExpressValidator; @@ -19,116 +21,240 @@ using FluentValidation; var builder = WebApplication.CreateBuilder(args); +// Registers the validator for ObjToValidate with specified validation rules. builder.Services.AddExpressValidator(b => b.AddProperty(o => o.I) .WithValidation(o => o.GreaterThan(5) .WithMessage("Must be greater than 5!"))); -builder.Services.AddExpressValidatorBuilder - (b => b.AddProperty(o => o.I) - .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) - .WithMessage($"Must be greater than {to.IGreaterThanValue}!"))); +// Registers the service that will use the validator. +builder.Services.AddTransient(); -builder.Services.AddExpressValidatorWithReload(b => - b.AddProperty(o => o.I) - .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) - .WithMessage($"Must be greater than {to.IGreaterThanValue}!")), - //Configuration section path - "ValidationParameters"); +var app = builder.Build(); -builder.Services.AddTransient(); +app.MapGet("/guess", (IGuessTheNumberService service) => +{ + var (Result, Message) = service.Guess(); + if (!Result) + { + return Results.BadRequest(Message); + } + // Additional logic here... +}); -var app = builder.Build(); +await app.RunAsync(); -... +// ... (Other code omitted for brevity) -interface ISomeServiceThatUseIExpressValidator +// Service interface definition. +public interface IGuessTheNumberService { - void ValidateByValidator(ObjToValidate objToValidate); - void ValidateByBuilder(ObjToValidate objToValidate); - void ValidateByValidatorWithReload(ObjToValidate objToValidate); + (bool Result, string Message) Guess(); } -class SomeServiceThatUseIExpressValidator : ISomeServiceThatUseIExpressValidator +// Service implementation that uses the validator. +public class GuessTheNumberService : IGuessTheNumberService { private readonly IExpressValidator _expressValidator; - private readonly IExpressValidatorBuilder _expressValidatorBuilder; - private readonly IExpressValidatorWithReload _expressValidatorWithReload; - - private readonly ValidationParametersOptions _validateOptions; - public SomeServiceThatUseIExpressValidator( - IExpressValidator expressValidator, - IExpressValidatorBuilder expressValidatorBuilder, - IExpressValidatorWithReload expressValidatorWithReload - IOptions validateOptions) + public GuessTheNumberService(IExpressValidator expressValidator) { _expressValidator = expressValidator; - _expressValidatorBuilder = expressValidatorBuilder; - _expressValidatorWithReload = expressValidatorWithReload; - _validateOptions = validateOptions.Value; } - public void ValidateByValidator(ObjToValidate objToValidate) + public (bool Result, string Message) Guess() { + ... var vr = _expressValidator.Validate(objToValidate); - if(vr.IsValid) + if (!vr.IsValid) { - ... + ... } + // ... (Additional logic) } +} +// ... (Other code omitted for brevity) +``` + +## 🛠️ Quick Start: Using a `ValidatorConfigurator` (Alternative Approach) + +As an alternative to inline configuration, you can define validation rules by creating a dedicated configurator class that inherits from `ValidatorConfigurator`, where `T` is the type being validated: + +```csharp +/// +/// Configures validation rules for ObjToValidate. +/// +public class GuessValidatorConfigurator : ValidatorConfigurator +{ + /// + /// Configures the validator builder with rules. + /// + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + => expressValidatorBuilder + .AddProperty(o => o.I) + .WithValidation((o) => o.GreaterThan(5)); +} +``` + +Then use `AddExpressValidation` method to register the configurator in DI: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Scans the assembly and registers validators from configurators. +builder.Services.AddExpressValidation(Assembly.GetExecutingAssembly()); + +// Registers the service that will use the validator. +builder.Services.AddTransient(); + +// ... (Application build and run code omitted; same as in Quick Start) + +// The GuessTheNumberService implementation remains the same as in the Quick Start example. +// ... (Other code omitted for brevity) +``` + +## ⚙️ Validation with Options + +In this approach, register an `IExpressValidatorBuilder` implementation (instead of `IExpressValidator`) in the DI container by calling the `AddExpressValidatorBuilder` method. + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Registers the validator builder with options-dependent rules. +builder.Services.AddExpressValidatorBuilder(b => + b.AddProperty(o => o.I) + .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) + .WithMessage($"Must be greater than {to.IGreaterThanValue}!"))); - public void ValidateByBuilder(ObjToValidate objToValidate) +builder.Services.AddTransient(); + +// Configures options from the application settings. +builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); + +var app = builder.Build(); + +app.MapGet("/complexguess", (IAdvancedNumberGuessingService service) => +{ + var (Result, Message) = service.ComplexGuess(); + if (!Result) { - ChangeOptions(); - var vr = _expressValidatorBuilder - .Build(_validateOptions) - .Validate(objToValidate); - if(vr.IsValid) - { - ... - } + return Results.BadRequest(Message); } + // Additional logic here... +}); - //Change the options in the configuration section path named "ValidationParameters" - //and use this method to revalidate the object without restarting the application. - public void ValidateByValidatorWithReload(ObjToValidate objToValidate) +app.Run(); + +// Service interface definition: +public interface IAdvancedNumberGuessingService +{ + (bool Result, string Message) ComplexGuess(); +} + +// Service implementation that builds and uses the validator with options. +public class AdvancedNumberGuessingService : IAdvancedNumberGuessingService +{ + private readonly ValidationParametersOptions _validateOptions; + private readonly IExpressValidatorBuilder _expressValidatorBuilder; + + public AdvancedNumberGuessingService(IExpressValidatorBuilder expressValidatorBuilder, + IOptions validateOptions) + { + _validateOptions = validateOptions.Value; + _expressValidatorBuilder = expressValidatorBuilder; + } + + //Updates options, rebuilds the validator, and validates. + public (bool Result, string Message) ComplexGuess() { - var vr = _expressValidatorWithReload.Validate(objToValidate); - if(vr.IsValid) - { ... + ChangeValidateOptions(); + + var vr = _expressValidatorBuilder.Build(_validateOptions).Validate(objToValidate); + if (!vr.IsValid) + { + // ... (Handle invalid case) } + // ... (Additional logic) } - private void ChangeOptions() + private void ChangeValidateOptions() { - _validateOptions.IGreaterThanValue = ...; + // ... (Option update logic omitted) } } - -class ObjToValidate -{ - public int I { get; set; } -} - -class ValidationParametersOptions -{ - public int IGreaterThanValue { get; set; } -} ``` - In the *appsettings.json* ```csharp { -... +// ... (Other settings omitted) "ValidationParameters": { "IGreaterThanValue": 5 } -... } ``` +## 🔥 Validation with Automatic Reload on Configuration Changes + +To validate options when configuration changes - without restarting the application - use the `AddExpressValidatorWithReload` method: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Registers a reloadable validator that updates on configuration changes. +builder.Services.AddExpressValidatorWithReload(b => + b.AddProperty(o => o.I) + .WithValidation((to, rbo) => rbo.GreaterThan(to.IGreaterThanValue) + .WithMessage($"Must be greater than {to.IGreaterThanValue}!")), + "ValidationParameters"); + +// Registers the reloadable service. +builder.Services.AddTransient(); + +// Configures options from the application settings. +builder.Services.Configure(builder.Configuration.GetSection("ValidationParameters")); + +var app = builder.Build(); + +app.MapGet("/guesswithreload", (IReloadableNumberGuessingService service) => +{ + var (Result, Message) = service.GuessWithReload(); + if (!Result) + { + return Results.BadRequest(Message); + } + // Additional logic here... +}); + +// Service interface definition. +public interface IReloadableNumberGuessingService +{ + (bool Result, string Message) GuessWithReload(); +} + +// Service implementation that uses the reloadable validator. +public class ReloadableNumberGuessingService : IReloadableNumberGuessingService +{ + private readonly IExpressValidatorWithReload _expressValidatorWithReload; + + public ReloadableNumberGuessingService(IExpressValidatorWithReload expressValidatorWithReload) + { + _expressValidatorWithReload = expressValidatorWithReload; + } + + public (bool Result, string Message) GuessWithReload() + { + ... + var vr = _expressValidatorWithReload.Validate(objToValidate); + if (!vr.IsValid) + { + ... + } + } +} +``` + +## 🏆 Sample See samples folder for concrete example. [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../../samples/ExpressValidator.Extensions.DependencyInjection.Sample) From 527758f41fbc3a8c449c4c582e8257cbcfafac55 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 11 Jan 2026 15:47:35 +0300 Subject: [PATCH 19/22] Edit Samples README.md. --- .../README.md | 134 +++++++++++++++++- 1 file changed, 131 insertions(+), 3 deletions(-) diff --git a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/README.md b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/README.md index 798032d..7299eb7 100644 --- a/samples/ExpressValidator.Extensions.DependencyInjection.Sample/README.md +++ b/samples/ExpressValidator.Extensions.DependencyInjection.Sample/README.md @@ -1,3 +1,131 @@ -The application tries to "/guess" the number for you, validates it and gives you response whether it is correct or not. -The "/complexguess" endpoint validates the number by `IExpressValidatorBuilder` using options that change dynamically. -Use "/guesswithreload" or "/guesswithreloadasync" to change the parameters of the `FluentValidation` validators in real time after configuration changes, without having to restart your application. \ No newline at end of file + +This folder contains **sample applications** demonstrating different ways to use **ExpressValidator.Extensions.DependencyInjection** in real-world scenarios. + +--- + +## 🎯 Basic Guess Validation (`/guess`) + +This sample demonstrates the **simplest usage** of `ExpressValidator.Extensions.DependencyInjection`. + +> The application tries to **guess a number** for you, validates it, and returns a response indicating whether the guess is correct or not. + +### What it shows + +* Registering an `IExpressValidator` using `AddExpressValidator` +* Defining validation rules at application startup +* Injecting and using the validator in a consuming service +* Returning validation results from an API endpoint + +**Endpoint** + +``` +GET /guess +``` + +**Best for** + +* Static validation rules +* Quick setup +* Learning the basics of ExpressValidator +--- + +## 🧩 Validation Using `ValidatorConfigurator` + +This sample demonstrates how to define validation rules in a **dedicated configurator class** instead of configuring them inline at startup. + +> The application validates the guessed number using an `IExpressValidator` that is configured through a `ValidatorConfigurator` implementation and registered automatically via assembly scanning. + +### What it shows + +* Creating a class that inherits from `ValidatorConfigurator` +* Centralizing validation rules in a reusable and testable component +* Registering validators using `AddExpressValidation` +* Keeping `Program.cs` clean and focused on application wiring + +### How it works + +1. A `ValidatorConfigurator` defines all validation rules for the target type. +2. The application registers all configurators from the assembly at startup. +3. An `IExpressValidator` is resolved from DI and used in the service. +4. The endpoint returns a validation result based on the configured rules. + +### Endpoint + +``` +GET /guess +``` + +*(Uses the same endpoint as the basic sample, but with a different validator registration strategy.)* + +### Best for + +* Clean architecture and separation of concerns +* Larger projects with many validators +* Shared validation logic across multiple services + +--- + +## ⚙️ Advanced Validation with Options (`/complexguess`) + +This sample demonstrates **dynamic validation rules** driven by runtime options. + +> The `/complexguess` endpoint validates the number using +> `IExpressValidatorBuilder`, +> where validation parameters can change dynamically. + +### What it shows + +* Using `AddExpressValidatorBuilder` +* Building validators per request using options +* Injecting `IOptions` into services +* Adjusting validation behavior without redeploying code + +**Endpoint** + +``` +GET /complexguess +``` + +**Best for** + +* Configurable business rules +* Feature flags +* Per-request validation logic + +--- + +## 🔄 Validation with Live Configuration Reload + +### (`/guesswithreload`, `/guesswithreloadasync`) + +This sample demonstrates **real-time validation updates** when configuration changes. + +> Use `/guesswithreload` or `/guesswithreloadasync` to change the parameters of the FluentValidation validators **in real time**, without restarting the application. + +### What it shows + +* Using `AddExpressValidatorWithReload` +* Automatically reloading validation rules on configuration changes +* Sync and async validation APIs +* Integration with `IOptionsMonitor` + +**Endpoints** + +``` +GET /guesswithreload +GET /guesswithreloadasync +``` + +**Best for** + +* Live configuration updates + +## ▶️ Running the Samples + +1. Navigate to the desired sample project +2. Run the application: + + ``` + dotnet run + ``` +3. Call the endpoints using a browser, curl, or Swagger UI From bc42f88b6e9ace8277f0d1003db7bc1e93def2db Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 11 Jan 2026 23:01:42 +0300 Subject: [PATCH 20/22] Edit 'Key Features' README chapter. --- .../README.md | 7 ++++--- .../docs/NuGet.md | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/README.md b/src/ExpressValidator.Extensions.DependencyInjection/README.md index 9bbb08b..45cb5f5 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/README.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/README.md @@ -2,9 +2,10 @@ ## 🔑 Key Features -- Configures and adds the `IExpressValidator` interface in Microsoft's Dependency Injection (DI) container. -- Additionally, the `IExpressValidatorBuilder` interface can be configured and registered to update the validator parameters when the `ValidationParametersOptions` change. -- Ability to dynamically update the validator parameters from options bound to the configuration section without restarting the application by configuring the `IExpressValidatorWithReload` interface. +- **Automatic DI Registration**: Configures and registers `IExpressValidator` with Microsoft's Dependency Injection container. +- **Class-Based Configuration**: Define validation rules via dedicated configurator classes inheriting from `ValidatorConfigurator`, providing an alternative to inline configuration. +- **Dynamic Parameter Updates**: Registers `IExpressValidatorBuilder` to automatically update validation parameters when configuration options change. +- **Automatic Reload Capability**: Automatically reload validation rules when configuration changes using `IExpressValidatorWithReload`. ## 📜 Documentation diff --git a/src/ExpressValidator.Extensions.DependencyInjection/docs/NuGet.md b/src/ExpressValidator.Extensions.DependencyInjection/docs/NuGet.md index 6eb725f..3dca396 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/docs/NuGet.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/docs/NuGet.md @@ -2,9 +2,10 @@ ## Key Features -- Configures and adds the `IExpressValidator` interface in Microsoft's Dependency Injection (DI) container. -- Additionally, the `IExpressValidatorBuilder` interface can be configured and registered to update the validator parameters when the `ValidationParametersOptions` change. -- Ability to dynamically update the validator parameters from options bound to the configuration section without restarting the application by configuring the `IExpressValidatorWithReload` interface. +- **Automatic DI Registration**: Configures and registers `IExpressValidator` with Microsoft's Dependency Injection container. +- **Class-Based Configuration**: Define validation rules via dedicated configurator classes inheriting from `ValidatorConfigurator`, providing an alternative to inline configuration. +- **Dynamic Parameter Updates**: Registers `IExpressValidatorBuilder` to automatically update validation parameters when configuration options change. +- **Automatic Reload Capability**: Automatically reload validation rules when configuration changes using `IExpressValidatorWithReload`. ## Quick Start From 745872209726cf383968e12ca43f20184bc490e1 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 13 Jan 2026 15:05:28 +0300 Subject: [PATCH 21/22] Remove unnecessary using. --- .../ProxyValidator.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs b/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs index e4bb06d..22a56e2 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs +++ b/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs @@ -1,15 +1,12 @@ using FluentValidation.Results; using Microsoft.Extensions.DependencyInjection; using System; -using System.Collections.Generic; -using System.Data; -using System.Text; using System.Threading; using System.Threading.Tasks; namespace ExpressValidator.Extensions.DependencyInjection { - internal class ProxyValidator : IExpressValidator + internal class ProxyValidator : IExpressValidator { private readonly IExpressValidator _innerValidator; public ProxyValidator(IServiceProvider serviceProvider) From ac4a88a74494289a6d96ad219ab2b283250dcafb Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 13 Jan 2026 15:34:28 +0300 Subject: [PATCH 22/22] Package 0.4.0 version and update CHANGELOG.md. --- .../CHANGELOG.md | 11 +++++++++++ ...essValidator.Extensions.DependencyInjection.csproj | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md index f051f0b..98c9a85 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.4.0 + +- Introduced class-based validation configuration with dedicated configurator classes inheriting from `ValidatorConfigurator` and registered through `AddExpressValidation`. +- Update to ExpressValidator 0.12.2 and FluentValidation 12.1.0. +- Update Microsoft nuget packages. +- Edit NuGet README. +- Edit README.md. +- Add Shared.csproj to the ExpressValidator.Extensions.DependencyInjection.Sample.sln solution. +- Split sample project into multiple projects illustrating README-described features. + + ## 0.3.12 - Support .NET 8.0 and FluentValidation 12.0.0. diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj index c561f19..2467860 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj +++ b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj @@ -3,7 +3,7 @@ netstandard2.0;net8.0 true - 0.3.12 + 0.4.0 true Andrey Kolesnichenko MIT @@ -15,7 +15,7 @@ FluentValidation Validation DependencyInjection The ExpressValidator.Extensions.DependencyInjection package extends ExpressValidator to provide integration with Microsoft Dependency Injection. Copyright 2024 Andrey Kolesnichenko - 0.3.12.0 + 0.4.0.0