Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
65eab33
Introduced the `FluentPropertyValidator<TObj, T>` and `FluentValidato…
kolan72 Mar 6, 2025
e262a03
Merge pull request #36 from kolan72/dev
kolan72 Mar 6, 2025
725270f
Merge pull request #42 from kolan72/dev-di
kolan72 Apr 9, 2025
62b260f
Package 0.3.5 version and update CHANGELOG.md.
kolan72 Apr 9, 2025
d04e2d1
Merge pull request #43 from kolan72/main
kolan72 Apr 10, 2025
6861af0
Improve performance by applying options in `ExpressValidator<TObj, TO…
kolan72 Apr 10, 2025
35fa236
Merge pull request #44 from kolan72/dev
kolan72 Apr 10, 2025
d00fa5b
Explicitly define a NotNull rule inside the `FluentPropertyValidator`…
kolan72 Apr 15, 2025
b3acecb
Add test to verify `FluentValidator` used via `SetValidator` for a co…
kolan72 Apr 15, 2025
66c7be2
Introduce the `Unit` readonly struct.
kolan72 Apr 23, 2025
8d23445
Introduced the `QuickValidator` class and its `Validate<T>(T, Action<…
kolan72 Apr 23, 2025
7766dff
Tests for property name accuracy in `QuickValidator`'s `ValidationRes…
kolan72 Apr 24, 2025
5bed32b
Change `QuickValidator`'s fallback property name to 'input' and add t…
kolan72 Apr 29, 2025
5f492de
Introduced the `ExpressValidator.QuickValidation` namespace and moved…
kolan72 Apr 30, 2025
8db6333
Change QuickValidator's fallback property name to 'Input'.
kolan72 Apr 30, 2025
10f6e56
Added `QuickValidator.Validate<T>()` method overload with `PropertyNa…
kolan72 May 3, 2025
616f947
Tests for `QuickValidator.Validate` overload using PropertyNameMode w…
kolan72 May 5, 2025
9dc4c8e
Add 'Nuances Of Using The Library' README Chapter.
kolan72 May 5, 2025
6e8ec66
Edit ' Nuances Of Using The Library' README Chapter.
kolan72 May 6, 2025
9d798db
Edit ' Nuances Of Using The Library' README Chapter -args clarification.
kolan72 May 6, 2025
fc5dcf6
Test for `QuickValidator.Validate` overload using string propName par…
kolan72 May 6, 2025
68f989c
Test for `QuickValidator.Validate` overload for complex type using `s…
kolan72 May 7, 2025
734cc87
Test for `QuickValidator.Validate` overload for complex type using `P…
kolan72 May 8, 2025
4e49dca
Adds optional `onSuccessValidation` callback (`Action<T>`) to `QuickV…
kolan72 May 12, 2025
85b1cfe
Adds an optional `onSuccessValidation` callback (`Action<T>`) to the …
kolan72 May 13, 2025
4533cf9
Merge branch 'dev-fv' into dev
kolan72 May 14, 2025
29cb7c3
Remove f. v. from dev so far.
kolan72 May 19, 2025
19bfb2a
Update NuGet.md.
kolan72 May 19, 2025
ec46457
Merge pull request #48 from kolan72/main
kolan72 May 20, 2025
9c0d0a5
Add 'Quick Validation' README Chapter.
kolan72 May 20, 2025
62f362e
Fix 'Nuances Of Using The Library' README example.
kolan72 May 20, 2025
eba4a60
Merge pull request #49 from kolan72/main
kolan72 May 20, 2025
a464d19
Add 'Quick Validation' NuGet README Chapter.
kolan72 May 20, 2025
be319e2
Edit 'Quick Validation' README Chapter.
kolan72 May 21, 2025
e3f8d11
Edit 'Quick Validation' NuGet README Chapter.
kolan72 May 21, 2025
41ef1e0
Update Microsoft nuget packages in 'ExpressValidator.Tests'.
kolan72 May 21, 2025
b33ebfb
Add 'Nuances Of Using The Library' NuGet README Chapter.
kolan72 May 21, 2025
0b98a36
Merge pull request #50 from kolan72/dev
kolan72 May 21, 2025
70d72f9
Update CHANGELOG.md.
kolan72 May 21, 2025
d445f8d
Package 0.9.0 version.
kolan72 May 21, 2025
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 0.9.0

- Added quick validation support via `QuickValidator` and its `Validate<T>` overloads.
- Improve performance by applying options in `ExpressValidator<TObj, TOptions>` during the `ExpressValidatorBuilder<TObj, TOptions>.Build` call instead of at validation time.
- Introduce the `Unit` readonly struct.
- Add 'Nuances Of Using The Library' README Chapter.
- Add 'Nuances Of Using The Library' NuGet README Chapter.
- Add 'Quick Validation' README Chapter.
- Add 'Quick Validation' NuGet README Chapter.


## 0.5.0

- Introduced the `IExpressValidatorBuilder<TObj, TOptions>.BuildAndValidate<TObj, TOptions>(TObj, TOptions)` extension method.
Expand Down
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ExpressValidator is a library that provides the ability to validate objects usin
- Supports adding a property or field for validation.
- Verifies that a property expression is a property and a field expression is a field, and throws `ArgumentException` if it is not.
- Supports adding a `Func` that provides a value for validation.
- Provides quick validation (refers to ease of use).
- Supports asynchronous validation.
- Targets .NET Standard 2.0+

Expand Down Expand Up @@ -129,6 +130,49 @@ if(!result2.IsValid)
}
```

## ⏩ Quick Validation

Quick validation is convenient for primitive types or types without properties/fields (here, 'quick' refers to usability, not performance). Simply call `QuickValidator.Validate` on the object with a preconfigured rule:

```csharp
var value = 5;
// result.IsValid == false
// result.Errors[0].PropertyName == "value"
var result = QuickValidator.Validate(
value,
(opt) => opt.GreaterThan(10),
nameof(value));
```

For complex types, use FluentValidation's `ChildRules` method:

```csharp
var obj = new ObjToValidate() { I = -1, PercentValue1 = 101 };
// result.IsValid == false
// result.Errors.Count == 2
// result.Errors[0].PropertyName == "obj.I"; result.Errors[1].PropertyName == "obj.PercentValue1"
var result = QuickValidator.Validate(
obj,
(opt) =>
opt
.ChildRules((v) => v.RuleFor(o => o.I).GreaterThan(0))
.ChildRules((v) => v.RuleFor(o => o.PercentValue1).InclusiveBetween(0, 100)),
nameof(obj));
```

## 🧩 Nuances Of Using The Library

For `ExpressValidatorBuilder` methods (`AddFunc`, `AddProperty`, and `AddField`), the overridden property name (set via `FluentValidation`'s `OverridePropertyName` method in `With(Async)Validation`) takes precedence over the property name passed as a string or via `Expression` in `AddFunc`/`AddProperty`/`AddField`.
For example, for the `ObjToValidate` object from the 'Quick Start' chapter, `result.Errors[0].PropertyName` will equal "percentSum" (the property name overridden in the validation rule):
```csharp
// result.Errors[0].PropertyName == "percentSum"
var result = new ExpressValidatorBuilder<ObjToValidate>()
.AddFunc(o => o.PercentValue1 + o.PercentValue2, "sum")
.WithValidation((o) => o.InclusiveBetween(0, 100)
.OverridePropertyName("percentSum"))
.BuildAndValidate(new ObjToValidate() { PercentValue1 = 200});
```

## ❌ Drawbacks

- Non-canonical way of using of FluentValidation.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 0.3.5

- Reduced unnecessary updates to validator parameters by listening to `IOptionsMonitor.Change` with named validation options.
- Update 'ExpressValidator.Extensions.DependencyInjection' to ExpressValidator 0.5.0.
- Update Microsoft nuget packages.
- Update README with a libraries/dependencies table.
- Add a configuration section in 'ExpressValidator.Extensions.DependencyInjection.Sample' for testing purposes.


## 0.3.2

- Update to ExpressValidator 0.2.0.
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.2</Version>
<Version>0.3.5</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.2.0</AssemblyVersion>
<AssemblyVersion>0.3.5.0</AssemblyVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
13 changes: 5 additions & 8 deletions src/ExpressValidator/ExpressValidator.TOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ namespace ExpressValidator
/// <typeparam name="TOptions"></typeparam>
public class ExpressValidator<TObj, TOptions> : IExpressValidator<TObj>
{
private readonly TOptions _options;
private readonly IEnumerable<IObjectValidator<TObj, TOptions>> _validators;
private readonly OnFirstPropertyValidatorFailed _validationMode;

internal ExpressValidator(TOptions options, IEnumerable<IObjectValidator<TObj, TOptions>> validators, OnFirstPropertyValidatorFailed validationMode)
{
_options = options;
_validators = validators;
_validationMode = validationMode;

foreach (var validator in _validators)
{
validator.ApplyOptions(options);
}
}

public ValidationResult Validate(TObj obj)
Expand All @@ -44,8 +47,6 @@ private async Task<ValidationResult> ValidateWithBreakAsync(TObj obj, Cancellati
foreach (var validator in _validators)
{
token.ThrowIfCancellationRequested();

validator.ApplyOptions(_options);
var (IsValid, Failures) = await validator.ValidateAsync(obj, token).ConfigureAwait(false);
if (!IsValid)
{
Expand All @@ -61,8 +62,6 @@ private async Task<ValidationResult> ValidateWithContinueAsync(TObj obj, Cancell
foreach (var validator in _validators)
{
token.ThrowIfCancellationRequested();

validator.ApplyOptions(_options);
var (IsValid, Failures) = await validator.ValidateAsync(obj, token);
if (!IsValid)
{
Expand All @@ -76,7 +75,6 @@ private ValidationResult ValidateWithBreak(TObj obj)
{
foreach (var validator in _validators)
{
validator.ApplyOptions(_options);
var (IsValid, Failures) = validator.Validate(obj);
if (!IsValid)
{
Expand All @@ -91,7 +89,6 @@ private ValidationResult ValidateWithContinue(TObj obj)
var currentFailures = new List<ValidationFailure>();
foreach (var validator in _validators)
{
validator.ApplyOptions(_options);
var (IsValid, Failures) = validator.Validate(obj);
if (!IsValid)
{
Expand Down
4 changes: 2 additions & 2 deletions src/ExpressValidator/ExpressValidator.csproj
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.5.0</Version>
<Version>0.9.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,7 +15,7 @@
<PackageIcon>ExpressValidator.png</PackageIcon>
<PackageReadmeFile>NuGet.md</PackageReadmeFile>
<PackageIconUrl />
<AssemblyVersion>0.5.0.0</AssemblyVersion>
<AssemblyVersion>0.9.0.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal class ExpressPropertyValidator<TObj, TOptions, T> : IExpressPropertyVal
{
private readonly string _propName;
private readonly Func<TObj, T> _propertyFunc;
private TypeValidatorBase<T> _typeValidator;

private Action<TOptions, IRuleBuilderOptions<T, T>> _actionWithOptions;

Expand All @@ -30,9 +31,7 @@ public void SetValidation(Action<TOptions, IRuleBuilderOptions<T, T>> action)

public Task<(bool IsValid, List<ValidationFailure> Failures)> ValidateAsync(TObj obj, CancellationToken token = default)
{
var typeValidator = new TypeAsyncValidator<T>();
typeValidator.SetValidation(_action, _propName);
return typeValidator.ValidateExAsync(_propertyFunc(obj), token);
return _typeValidator.ValidateExAsync(_propertyFunc(obj), token);
}

public (bool IsValid, List<ValidationFailure> Failures) Validate(TObj obj)
Expand All @@ -41,14 +40,26 @@ public void SetValidation(Action<TOptions, IRuleBuilderOptions<T, T>> action)
{
throw new InvalidOperationException();
}
var typeValidator = new TypeValidator<T>();
typeValidator.SetValidation(_action, _propName);
return typeValidator.ValidateEx(_propertyFunc(obj));
return _typeValidator.ValidateEx(_propertyFunc(obj));
}

public void ApplyOptions(TOptions options)
{
_action =_actionWithOptions.Apply(options);
SetTypeValidator();
}

private void SetTypeValidator()
{
if (IsAsync)
{
_typeValidator = new TypeAsyncValidator<T>();
}
else
{
_typeValidator = new TypeValidator<T>();
}
_typeValidator.SetValidation(_action, _propName);
}

public bool IsAsync { get; }
Expand Down
18 changes: 18 additions & 0 deletions src/ExpressValidator/QuickValidation/PropertyNameMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace ExpressValidator.QuickValidation
{
/// <summary>
/// Determines how the property name is set when quick validation fails.
/// </summary>
public enum PropertyNameMode
{
/// <summary>
/// Use the literal string "Input" as the property name.
/// </summary>
Default,

/// <summary>
/// Use the type's name (typeof(T).Name) of the validated object as the property name.
/// </summary>
TypeName
}
}
57 changes: 57 additions & 0 deletions src/ExpressValidator/QuickValidation/QuickValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using ExpressValidator.Extensions;
using FluentValidation;
using FluentValidation.Results;
using System;

namespace ExpressValidator.QuickValidation
{
/// <summary>
/// Provides methods for quick validation.
/// </summary>
public static class QuickValidator
{
private const string FALLBACK_PROP_NAME = "Input";

/// <summary>
/// Validates the given object instance using <paramref name="action"/>.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
/// <param name="obj">The object to validate.</param>
/// <param name="action">Action to add validators.</param>
/// <param name="propName">The name of the property if the validation fails.
/// If <see langword="null"/>, "Input" will be used.</param>
/// <param name="onSuccessValidation">Specifies a method to execute when validation succeeds.</param>
/// <returns></returns>
public static ValidationResult Validate<T>(T obj, Action<IRuleBuilderOptions<T, T>> action, string propName, Action<T> onSuccessValidation = null)
{
return ValidateInner<T>(obj, action, propName ?? FALLBACK_PROP_NAME, onSuccessValidation);
}

/// <summary>
/// Validates the given object instance using <paramref name="action"/>.
/// </summary>
/// <typeparam name="T">The type of the object to validate.</typeparam>
/// <param name="obj">The object to validate.</param>
/// <param name="action">Action to add validators.</param>
/// <param name="mode"><see cref="PropertyNameMode"/>.
/// If <see cref="PropertyNameMode.Default"/>, "Input" will be used.</param>
/// <param name="onSuccessValidation">Specifies a method to execute when validation succeeds.</param>
/// <returns></returns>
public static ValidationResult Validate<T>(T obj, Action<IRuleBuilderOptions<T, T>> action, PropertyNameMode mode = PropertyNameMode.Default, Action<T> onSuccessValidation = null)
{
return ValidateInner<T>(obj, action, GetPropName<T>(mode), onSuccessValidation);
}

private static ValidationResult ValidateInner<T>(T obj, Action<IRuleBuilderOptions<T, T>> action, string propName, Action<T> onSuccessValidation = null)
{
var eb = new ExpressValidatorBuilder<Unit>();
return eb.AddFunc((_) => obj,
propName,
onSuccessValidation)
.WithValidation(action)
.BuildAndValidate(Unit.Default);
}

private static string GetPropName<T>(PropertyNameMode mode) => mode == PropertyNameMode.Default ? FALLBACK_PROP_NAME : typeof(T).Name;
}
}
7 changes: 7 additions & 0 deletions src/ExpressValidator/Unit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ExpressValidator
{
public readonly struct Unit
{
public static readonly Unit Default = new Unit();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public IBuilderWithPropValidator<TObj, T> AddField<T>(Expression<Func<TObj, T>>
/// <typeparam name="T">A type of value.</typeparam>
/// <param name="func">Func for object</param>
/// <param name="propName">A name of the property if the validation failed.</param>
/// <param name="onSuccessValidation">Func Result Validation Success Handler</param>
/// <param name="onSuccessValidation">Specifies a method to execute when validation succeeds.</param>
/// <returns></returns>
public IBuilderWithPropValidator<TObj, T> AddFunc<T>(Func<TObj, T> func, string propName, Action<T> onSuccessValidation = null)
{
Expand Down
46 changes: 46 additions & 0 deletions src/ExpressValidator/docs/NuGet.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ExpressValidator is a library that provides the ability to validate objects usin
- Supports adding a property or field for validation.
- Verifies that a property expression is a property and a field expression is a field, and throws `ArgumentException` if it is not.
- Supports adding a `Func` that provides a value for validation.
- Provides quick validation (refers to ease of use).
- Supports asynchronous validation.
- Targets .NET Standard 2.0+

Expand Down Expand Up @@ -51,6 +52,8 @@ if(!result.IsValid)
}
```

## Modifying FluentValidation Validator Parameters Using Options

To dynamically change the parameters of the `FluentValidation` validators:

1. Create an options object that contains the parameters of validators.
Expand Down Expand Up @@ -111,3 +114,46 @@ if(!result2.IsValid)
...
}
```

## Quick Validation

Quick validation is convenient for primitive types or types without properties/fields (here, 'quick' refers to usability, not performance). Simply call `QuickValidator.Validate` on the object with a preconfigured rule:

```csharp
var value = 5;
// result.IsValid == false
// result.Errors[0].PropertyName == "value"
var result = QuickValidator.Validate(
value,
(opt) => opt.GreaterThan(10),
nameof(value));
```

For complex types, use FluentValidation's `ChildRules` method:

```csharp
var obj = new ObjToValidate() { I = -1, PercentValue1 = 101 };
// result.IsValid == false
// result.Errors.Count == 2
// result.Errors[0].PropertyName == "obj.I"; result.Errors[1].PropertyName == "obj.PercentValue1"
var result = QuickValidator.Validate(
obj,
(opt) =>
opt
.ChildRules((v) => v.RuleFor(o => o.I).GreaterThan(0))
.ChildRules((v) => v.RuleFor(o => o.PercentValue1).InclusiveBetween(0, 100)),
nameof(obj));
```

## Nuances Of Using The Library

For `ExpressValidatorBuilder` methods (`AddFunc`, `AddProperty`, and `AddField`), the overridden property name (set via `FluentValidation`'s `OverridePropertyName` method in `With(Async)Validation`) takes precedence over the property name passed as a string or via `Expression` in `AddFunc`/`AddProperty`/`AddField`.
For example, for the `ObjToValidate` object from the 'Quick Start' chapter, `result.Errors[0].PropertyName` will equal "percentSum" (the property name overridden in the validation rule):
```csharp
// result.Errors[0].PropertyName == "percentSum"
var result = new ExpressValidatorBuilder<ObjToValidate>()
.AddFunc(o => o.PercentValue1 + o.PercentValue2, "sum")
.WithValidation((o) => o.InclusiveBetween(0, 100)
.OverridePropertyName("percentSum"))
.BuildAndValidate(new ObjToValidate() { PercentValue1 = 200});
```
Loading