Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 0.12.0

- Support .NET 8.0 and FluentValidation 12.0.0.
- Fix `ExpressPropertyValidator<TObj, T>` to prevent calling `Func<TObj, T>` 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>(T, Action<IRuleBuilderOptions<T, T>>, string, Action<T>, CancellationToken)` extension method.
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Customer>()
.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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.3.7</Version>
<Version>0.3.9</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Andrey Kolesnichenko</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand All @@ -15,7 +15,7 @@
<PackageTags>FluentValidation Validation DependencyInjection</PackageTags>
<Description>The ExpressValidator.Extensions.DependencyInjection package extends ExpressValidator to provide integration with Microsoft Dependency Injection.</Description>
<Copyright>Copyright 2024 Andrey Kolesnichenko</Copyright>
<AssemblyVersion>0.3.7.0</AssemblyVersion>
<AssemblyVersion>0.3.9.0</AssemblyVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
22 changes: 15 additions & 7 deletions src/ExpressValidator/ExpressValidator.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.10.0</Version>
<Version>0.12.0</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Andrey Kolesnichenko</Authors>
<Description>ExpressValidator is a library that provides the ability to validate objects using the FluentValidation library, but without object inheritance from `AbstractValidator`.</Description>
Expand All @@ -15,20 +15,24 @@
<PackageIcon>ExpressValidator.png</PackageIcon>
<PackageReadmeFile>NuGet.md</PackageReadmeFile>
<PackageIconUrl />
<AssemblyVersion>0.10.0.0</AssemblyVersion>
<AssemblyVersion>0.12.0.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="FluentValidation" Version="11.11.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="FluentValidation" Version="12.0.0" />
</ItemGroup>

<ItemGroup>
<Compile Remove="docs\ExpressValidator\**" />
<EmbeddedResource Remove="docs\ExpressValidator\**" />
<None Remove="docs\ExpressValidator\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.11.0" />
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
Expand All @@ -37,6 +41,10 @@
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>

<PropertyGroup>
<NoWarn>$(NoWarn);NU1504</NoWarn>
</PropertyGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>ExpressValidator.Tests</_Parameter1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void SetValidation(Action<IRuleBuilderOptions<T, T>> 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);
Expand Down
12 changes: 6 additions & 6 deletions tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\NUnit.4.3.2\build\NUnit.props" Condition="Exists('..\..\packages\NUnit.4.3.2\build\NUnit.props')" />
<Import Project="..\..\packages\NUnit.4.4.0\build\NUnit.props" Condition="Exists('..\..\packages\NUnit.4.4.0\build\NUnit.props')" />
<Import Project="..\..\packages\NUnit3TestAdapter.4.6.0\build\net462\NUnit3TestAdapter.props" Condition="Exists('..\..\packages\NUnit3TestAdapter.4.6.0\build\net462\NUnit3TestAdapter.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -45,11 +45,11 @@
<Reference Include="FluentValidation, Version=11.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0, processorArchitecture=MSIL">
<HintPath>..\..\packages\FluentValidation.11.11.0\lib\netstandard2.0\FluentValidation.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=4.3.2.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\..\packages\NUnit.4.3.2\lib\net462\nunit.framework.dll</HintPath>
<Reference Include="nunit.framework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\..\packages\NUnit.4.4.0\lib\net462\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="nunit.framework.legacy, Version=4.3.2.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\..\packages\NUnit.4.3.2\lib\net462\nunit.framework.legacy.dll</HintPath>
<Reference Include="nunit.framework.legacy, Version=4.4.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\..\packages\NUnit.4.4.0\lib\net462\nunit.framework.legacy.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
Expand Down Expand Up @@ -112,8 +112,8 @@
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\NUnit3TestAdapter.4.6.0\build\net462\NUnit3TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\NUnit3TestAdapter.4.6.0\build\net462\NUnit3TestAdapter.props'))" />
<Error Condition="!Exists('..\..\packages\NUnit.4.3.2\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\NUnit.4.3.2\build\NUnit.props'))" />
<Error Condition="!Exists('..\..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
<Error Condition="!Exists('..\..\packages\NUnit.4.4.0\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\NUnit.4.4.0\build\NUnit.props'))" />
</Target>
<Import Project="..\..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('..\..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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<ObjWithTwoPublicProps>()
.AddProperty(o => o.I)
.WithValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; }))
.Build();
Assert.Throws<AsyncValidatorInvokedSynchronouslyException>(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, S = "b" }));
}

[Test]
public async Task Should_AsyncInvoke_SuccessValidationHandler_When_IsValid()
{
Expand Down Expand Up @@ -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<ObjWithTwoPublicProps>()
.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<Customer>()
.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));
}
}
}
20 changes: 20 additions & 0 deletions tests/ExpressValidator.Tests/ObjectsToTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using FluentValidation;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ExpressValidator.Tests
{
Expand Down Expand Up @@ -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<bool> IdExistsAsync(int id, CancellationToken token)
{
await Task.Delay(TimeSpan.FromTicks(1), token);
return _existedId == id;
}

}
}
2 changes: 1 addition & 1 deletion tests/ExpressValidator.Tests/packages.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="FluentValidation" version="11.11.0" targetFramework="net48" />
<package id="NUnit" version="4.3.2" targetFramework="net48" />
<package id="NUnit" version="4.4.0" targetFramework="net48" />
<package id="NUnit3TestAdapter" version="4.6.0" targetFramework="net48" />
<package id="System.Buffers" version="4.6.1" targetFramework="net48" />
<package id="System.Memory" version="4.6.3" targetFramework="net48" />
Expand Down