diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dc581c..c2ee3ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.12.0 + +- Support .NET 8.0 and FluentValidation 12.0.0. +- Fix `ExpressPropertyValidator` to prevent calling `Func` propertyFunc twice when a success handler is present. +- Fix NU1504: Duplicate 'PackageReference' found +- Update NUnit NuGet package to v4.4.0. +- Add test for `ValidateAsync` with both `WithValidation` and `WithAsyncValidation` in `ExpressValidatorBuilder`. +- Add test to ensure synchronous Validate throws `AsyncValidatorInvokedSynchronouslyException` if the builder has async rules. +- Add a test for the `ValidateAsync` method with simulated external services. +- Add 'Asynchronous Validation' README Chapter. + + ## 0.10.0 - Introduced the `QuickValidator.ValidateAsync(T, Action>, string, Action, CancellationToken)` extension method. diff --git a/README.md b/README.md index a034748..a0648cf 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,28 @@ if(!result.IsValid) } ``` +## 💡 Asynchronous Validation + +If you want to add asynchronous FluentValidation rules such as `MustAsync` or `CustomAsync`, the recommended approach is to use the `WithAsyncValidation` method: +```csharp +//Checking if a user ID is already in use using an external web API: +var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.CustomerId) + .WithAsyncValidation(o => o.MustAsync(async (id, cancellation) => + + !await apiClient.IdExistsAsync(id, cancellation))) + + .Build() + .ValidateAsync(customer); +``` +Once you've used this method at least once within the `ExpressValidatorBuilder`, you must call the `ValidateAsync` method on the resulting `ExpressValidator`. + +Calling `Validate` instead will result in an `InvalidOperationException`. + +Note: You can still use the `WithValidation` method for asynchronous rules, but in that case, ensure you call only `ValidateAsync`; otherwise, FluentValidation will throw an `AsyncValidatorInvokedSynchronouslyException`. + +As with FluentValidation itself, you can safely call `ValidateAsync` when both synchronous and asynchronous rules are present. + ## ⚙️ Modifying FluentValidation Validator Parameters Using Options To dynamically change the parameters of the `FluentValidation` validators: diff --git a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md index e047ff9..9f9bb74 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.3.9 + +- Update ExpressValidator nuget package. +- Update Microsoft nuget packages. +- Update Microsoft NuGet packages for ExpressValidator.Extensions.DependencyInjection.Tests. + + ## 0.3.7 - Update ExpressValidator nuget package. diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj index 5a94f17..f3ae59b 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 true - 0.3.7 + 0.3.9 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.7.0 + 0.3.9.0 diff --git a/src/ExpressValidator/ExpressValidator.csproj b/src/ExpressValidator/ExpressValidator.csproj index 1fba684..9cfb235 100644 --- a/src/ExpressValidator/ExpressValidator.csproj +++ b/src/ExpressValidator/ExpressValidator.csproj @@ -1,9 +1,9 @@  - netstandard2.0 + netstandard2.0;net8.0 true - 0.10.0 + 0.12.0 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,20 +15,24 @@ ExpressValidator.png NuGet.md - 0.10.0.0 + 0.12.0.0 0.0.0.0 + + + + + + + + - - - - 1701;1702;1591 @@ -37,6 +41,10 @@ 1701;1702;1591 + + $(NoWarn);NU1504 + + <_Parameter1>ExpressValidator.Tests diff --git a/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.cs b/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.cs index 6bf297c..a0ad0f5 100644 --- a/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.cs +++ b/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.cs @@ -33,7 +33,7 @@ public void SetValidation(Action> action) if (_onSuccessValidation != null) { var value = _propertyFunc(obj); - var res = await _typeValidator.ValidateExAsync(_propertyFunc(obj), token); + var res = await _typeValidator.ValidateExAsync(value, token); if (res.IsValid) { _onSuccessValidation(value); diff --git a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj index faee7f7..2d107d5 100644 --- a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj +++ b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj @@ -1,6 +1,6 @@  - + @@ -45,11 +45,11 @@ ..\..\packages\FluentValidation.11.11.0\lib\netstandard2.0\FluentValidation.dll - - ..\..\packages\NUnit.4.3.2\lib\net462\nunit.framework.dll + + ..\..\packages\NUnit.4.4.0\lib\net462\nunit.framework.dll - - ..\..\packages\NUnit.4.3.2\lib\net462\nunit.framework.legacy.dll + + ..\..\packages\NUnit.4.4.0\lib\net462\nunit.framework.legacy.dll @@ -112,8 +112,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/tests/ExpressValidator.Tests/ExpressValidatorTests.ForAsync.Tests.cs b/tests/ExpressValidator.Tests/ExpressValidatorTests.ForAsync.Tests.cs index 38e7990..69040dc 100644 --- a/tests/ExpressValidator.Tests/ExpressValidatorTests.ForAsync.Tests.cs +++ b/tests/ExpressValidator.Tests/ExpressValidatorTests.ForAsync.Tests.cs @@ -22,6 +22,16 @@ public void Should_Validate_Throw_For_AsyncRule_ForProperty() Assert.That(exc.Message, Does.StartWith("Object validator has a property or field with asynchronous validation rules.")); } + [Test] + public void Should_Using_WithValidation_With_AsyncRule_Throw_AsyncValidatorInvokedSynchronouslyException_When_Validate() + { + var builder = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + Assert.Throws(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, S = "b" })); + } + [Test] public async Task Should_AsyncInvoke_SuccessValidationHandler_When_IsValid() { @@ -85,5 +95,41 @@ public async Task Should_ValidateAsync_Work_For_AsyncRules() ClassicAssert.AreEqual(true, result.IsValid); } + + [Test] + public async Task Should_ValidateAsync_When_Using_Combined_Validation_Strategy() + { + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return false; })) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation(o => o.InclusiveBetween(0, 100)) + .Build() + .ValidateAsync(new ObjWithTwoPublicProps() { I = 1, PercentValue1 = 200 }); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_ValidateAsync_When_Used_In_External_API(bool valid) + { + const int customerId = 1; + var apiClient = new SomeExternalWebApiClient(valid ? 2 : customerId); + var customer = new Customer() { CustomerId = customerId }; + + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.CustomerId) + .WithAsyncValidation(o => o.MustAsync(async (id, cancellation) => + + !await apiClient.IdExistsAsync(id, cancellation))) + + .Build() + .ValidateAsync(customer); + + Assert.That(result.IsValid, Is.EqualTo(valid)); + } } } diff --git a/tests/ExpressValidator.Tests/ObjectsToTests.cs b/tests/ExpressValidator.Tests/ObjectsToTests.cs index f80a877..30bd8db 100644 --- a/tests/ExpressValidator.Tests/ObjectsToTests.cs +++ b/tests/ExpressValidator.Tests/ObjectsToTests.cs @@ -1,5 +1,8 @@ using FluentValidation; +using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace ExpressValidator.Tests { @@ -69,4 +72,21 @@ public class Customer public decimal CustomerDiscount { get; set; } public bool IsPreferredCustomer { get; set; } } + + public class SomeExternalWebApiClient + { + private readonly int _existedId; + + public SomeExternalWebApiClient(int existedId) + { + _existedId = existedId; + } + + public async Task IdExistsAsync(int id, CancellationToken token) + { + await Task.Delay(TimeSpan.FromTicks(1), token); + return _existedId == id; + } + + } } diff --git a/tests/ExpressValidator.Tests/packages.config b/tests/ExpressValidator.Tests/packages.config index 1b3d233..3e49123 100644 --- a/tests/ExpressValidator.Tests/packages.config +++ b/tests/ExpressValidator.Tests/packages.config @@ -1,7 +1,7 @@  - +