From 65eab3336f1d5756e3fc76a247d19df2de8675eb Mon Sep 17 00:00:00 2001 From: kolan72 Date: Thu, 6 Mar 2025 16:07:53 +0300 Subject: [PATCH 01/32] Introduced the `FluentPropertyValidator` and `FluentValidator` classes. --- src/ExpressValidator/FluentValidator.cs | 16 ++++++++ .../FluentPropertyValidator.cs | 13 +++++++ .../ExpressValidator.Tests.csproj | 2 + .../FluentPropertyValidatorTests.cs | 33 +++++++++++++++++ .../FluentValidatorTests.cs | 37 +++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 src/ExpressValidator/FluentValidator.cs create mode 100644 src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs create mode 100644 tests/ExpressValidator.Tests/FluentPropertyValidatorTests.cs create mode 100644 tests/ExpressValidator.Tests/FluentValidatorTests.cs diff --git a/src/ExpressValidator/FluentValidator.cs b/src/ExpressValidator/FluentValidator.cs new file mode 100644 index 0000000..235f955 --- /dev/null +++ b/src/ExpressValidator/FluentValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; +using System.Collections.Generic; + +namespace ExpressValidator +{ + public class FluentValidator : AbstractValidator + { + internal FluentValidator(IEnumerable> validators) + { + foreach (var v in validators) + { + Include(v); + } + } + } +} diff --git a/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs b/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs new file mode 100644 index 0000000..2594f5f --- /dev/null +++ b/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using System; + +namespace ExpressValidator +{ + internal class FluentPropertyValidator : AbstractValidator + { + public FluentPropertyValidator(Func propertyFunc, string propName, TypeValidatorBase typeValidator) + { + RuleFor((obj) => propertyFunc(obj)).SetValidator(typeValidator).OverridePropertyName(propName); + } + } +} diff --git a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj index bcef3cb..dc415ad 100644 --- a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj +++ b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj @@ -84,6 +84,8 @@ + + diff --git a/tests/ExpressValidator.Tests/FluentPropertyValidatorTests.cs b/tests/ExpressValidator.Tests/FluentPropertyValidatorTests.cs new file mode 100644 index 0000000..e56a709 --- /dev/null +++ b/tests/ExpressValidator.Tests/FluentPropertyValidatorTests.cs @@ -0,0 +1,33 @@ +using NUnit.Framework; +using System; +using FluentValidation; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests +{ + internal class FluentPropertyValidatorTests + { + [Test] + public void Should_Be_Initialized_Correctly_By_Ctor() + { + static decimal funcForField(Customer c) => c.CustomerDiscount; + const string propName = "CustomerDiscount"; + var validator = new TypeValidator(); + validator.SetValidation((opt) => opt.GreaterThan(0), propName); + + var fluentValidator = new FluentPropertyValidator(funcForField, propName, validator); + Assert.That(fluentValidator.Count(), Is.EqualTo(1)); + Assert.That(fluentValidator.FirstOrDefault().PropertyName, Is.EqualTo(propName)); + + var notValidCustomer = new Customer(); + var failedValidationResult = fluentValidator.Validate(notValidCustomer); + Assert.That(failedValidationResult.IsValid, Is.False); + Assert.That(failedValidationResult.Errors.Count, Is.EqualTo(1)); + + Assert.That(fluentValidator.Validate(new Customer() { CustomerDiscount = 1 }).IsValid, Is.True); + } + } +} diff --git a/tests/ExpressValidator.Tests/FluentValidatorTests.cs b/tests/ExpressValidator.Tests/FluentValidatorTests.cs new file mode 100644 index 0000000..375b52f --- /dev/null +++ b/tests/ExpressValidator.Tests/FluentValidatorTests.cs @@ -0,0 +1,37 @@ +using FluentValidation; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ExpressValidator.Tests +{ + internal class FluentValidatorTests + { + [Test] + public void Should_Be_Correctly_Initialized_By_FluentPropertyValidators() + { + static decimal funcForDiscount(Customer c) => c.CustomerDiscount; + const string propName = "CustomerDiscount"; + var validator = new TypeValidator(); + validator.SetValidation((opt) => opt.GreaterThan(0), propName); + + var propForDiscount = new FluentPropertyValidator(funcForDiscount, propName, validator); + + static int funcForId(Customer c) => c.CustomerId; + const string propNameId = "CustomerId"; + var validatorId = new TypeValidator(); + validatorId.SetValidation((opt) => opt.GreaterThan(1), propNameId); + + var propForId = new FluentPropertyValidator(funcForId, propName, validatorId); + + var fv = new FluentValidator(new List>() { propForDiscount, propForId }); + Assert.That(fv.Count(), Is.EqualTo(2)); + + var vr = fv.Validate(new Customer()); + + Assert.That(vr.IsValid, Is.False); + Assert.That(vr.Errors.Count, Is.EqualTo(2)); + } + } +} From 62b260fc4bbb9a7a2f9f280c7269197fe95d6580 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 9 Apr 2025 17:14:41 +0300 Subject: [PATCH 02/32] Package 0.3.5 version and update CHANGELOG.md. --- .../CHANGELOG.md | 9 +++++++++ ...xpressValidator.Extensions.DependencyInjection.csproj | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md index fefdd67..951392a 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md @@ -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. diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj index fd61276..9a0dd09 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.2 + 0.3.5 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.2.0 + 0.3.5.0 From 6861af0bff820e863205c08555a7baea228af56a Mon Sep 17 00:00:00 2001 From: kolan72 Date: Thu, 10 Apr 2025 13:56:48 +0300 Subject: [PATCH 03/32] Improve performance by applying options in `ExpressValidator` during the `ExpressValidatorBuilder.Build` call instead of at validation time. --- .../ExpressValidator.TOptions.cs | 13 ++++------- .../ExpressPropertyValidator.TOptions.cs | 23 ++++++++++++++----- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/ExpressValidator/ExpressValidator.TOptions.cs b/src/ExpressValidator/ExpressValidator.TOptions.cs index a20fe71..93fc22d 100644 --- a/src/ExpressValidator/ExpressValidator.TOptions.cs +++ b/src/ExpressValidator/ExpressValidator.TOptions.cs @@ -14,15 +14,18 @@ namespace ExpressValidator /// public class ExpressValidator : IExpressValidator { - private readonly TOptions _options; private readonly IEnumerable> _validators; private readonly OnFirstPropertyValidatorFailed _validationMode; internal ExpressValidator(TOptions options, IEnumerable> validators, OnFirstPropertyValidatorFailed validationMode) { - _options = options; _validators = validators; _validationMode = validationMode; + + foreach (var validator in _validators) + { + validator.ApplyOptions(options); + } } public ValidationResult Validate(TObj obj) @@ -44,8 +47,6 @@ private async Task 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) { @@ -61,8 +62,6 @@ private async Task ValidateWithContinueAsync(TObj obj, Cancell foreach (var validator in _validators) { token.ThrowIfCancellationRequested(); - - validator.ApplyOptions(_options); var (IsValid, Failures) = await validator.ValidateAsync(obj, token); if (!IsValid) { @@ -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) { @@ -91,7 +89,6 @@ private ValidationResult ValidateWithContinue(TObj obj) var currentFailures = new List(); foreach (var validator in _validators) { - validator.ApplyOptions(_options); var (IsValid, Failures) = validator.Validate(obj); if (!IsValid) { diff --git a/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs b/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs index 342f959..8a9faa0 100644 --- a/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs +++ b/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs @@ -11,6 +11,7 @@ internal class ExpressPropertyValidator : IExpressPropertyVal { private readonly string _propName; private readonly Func _propertyFunc; + private TypeValidatorBase _typeValidator; private Action> _actionWithOptions; @@ -30,9 +31,7 @@ public void SetValidation(Action> action) public Task<(bool IsValid, List Failures)> ValidateAsync(TObj obj, CancellationToken token = default) { - var typeValidator = new TypeAsyncValidator(); - typeValidator.SetValidation(_action, _propName); - return typeValidator.ValidateExAsync(_propertyFunc(obj), token); + return _typeValidator.ValidateExAsync(_propertyFunc(obj), token); } public (bool IsValid, List Failures) Validate(TObj obj) @@ -41,14 +40,26 @@ public void SetValidation(Action> action) { throw new InvalidOperationException(); } - var typeValidator = new TypeValidator(); - 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(); + } + else + { + _typeValidator = new TypeValidator(); + } + _typeValidator.SetValidation(_action, _propName); } public bool IsAsync { get; } From d00fa5bb123bff506d28c080bb73da4735e64dd9 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 15 Apr 2025 15:06:58 +0300 Subject: [PATCH 04/32] Explicitly define a NotNull rule inside the `FluentPropertyValidator`, update test. --- .../FluentPropertyValidator.cs | 2 +- .../FluentValidatorTests.cs | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs b/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs index 2594f5f..921d641 100644 --- a/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs +++ b/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs @@ -7,7 +7,7 @@ internal class FluentPropertyValidator : AbstractValidator { public FluentPropertyValidator(Func propertyFunc, string propName, TypeValidatorBase typeValidator) { - RuleFor((obj) => propertyFunc(obj)).SetValidator(typeValidator).OverridePropertyName(propName); + RuleFor((obj) => propertyFunc(obj)).NotNull().SetValidator(typeValidator).OverridePropertyName(propName); } } } diff --git a/tests/ExpressValidator.Tests/FluentValidatorTests.cs b/tests/ExpressValidator.Tests/FluentValidatorTests.cs index 375b52f..062ed83 100644 --- a/tests/ExpressValidator.Tests/FluentValidatorTests.cs +++ b/tests/ExpressValidator.Tests/FluentValidatorTests.cs @@ -12,26 +12,33 @@ internal class FluentValidatorTests public void Should_Be_Correctly_Initialized_By_FluentPropertyValidators() { static decimal funcForDiscount(Customer c) => c.CustomerDiscount; - const string propName = "CustomerDiscount"; + const string propNameDiscount = "CustomerDiscount"; var validator = new TypeValidator(); - validator.SetValidation((opt) => opt.GreaterThan(0), propName); + validator.SetValidation((opt) => opt.GreaterThan(0), propNameDiscount); - var propForDiscount = new FluentPropertyValidator(funcForDiscount, propName, validator); + var propForDiscount = new FluentPropertyValidator(funcForDiscount, propNameDiscount, validator); static int funcForId(Customer c) => c.CustomerId; const string propNameId = "CustomerId"; var validatorId = new TypeValidator(); validatorId.SetValidation((opt) => opt.GreaterThan(1), propNameId); - var propForId = new FluentPropertyValidator(funcForId, propName, validatorId); + var propForId = new FluentPropertyValidator(funcForId, propNameId, validatorId); - var fv = new FluentValidator(new List>() { propForDiscount, propForId }); - Assert.That(fv.Count(), Is.EqualTo(2)); + static string funcForName(Customer c) => c.Name; + const string propNameName = "Name"; + var nameValidator = new TypeValidator(); + nameValidator.SetValidation((opt) => opt.Length(2), propNameName); - var vr = fv.Validate(new Customer()); + var propForName = new FluentPropertyValidator(funcForName, propNameName, nameValidator); + + var fv = new FluentValidator(new List>() { propForDiscount, propForId, propForName }); + Assert.That(fv.Count(), Is.EqualTo(3)); + + var vr = fv.Validate(new Customer() ); Assert.That(vr.IsValid, Is.False); - Assert.That(vr.Errors.Count, Is.EqualTo(2)); + Assert.That(vr.Errors.Count, Is.EqualTo(3)); } } } From b3acecb0f8d7344c41a27ad956d6d557e2adb50a Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 15 Apr 2025 15:19:00 +0300 Subject: [PATCH 05/32] Add test to verify `FluentValidator` used via `SetValidator` for a collection property. --- .../FluentValidatorTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/ExpressValidator.Tests/FluentValidatorTests.cs b/tests/ExpressValidator.Tests/FluentValidatorTests.cs index 062ed83..d4b389f 100644 --- a/tests/ExpressValidator.Tests/FluentValidatorTests.cs +++ b/tests/ExpressValidator.Tests/FluentValidatorTests.cs @@ -40,5 +40,39 @@ public void Should_Be_Correctly_Initialized_By_FluentPropertyValidators() Assert.That(vr.IsValid, Is.False); Assert.That(vr.Errors.Count, Is.EqualTo(3)); } + + [Test] + public void Should_Apply_FluentValidator_To_Each_Item_In_Collection() + { + static string funcForName(Contact c) => c.Name; + const string propName = "Name"; + var nameValidator = new TypeValidator(); + nameValidator.SetValidation((opt) => opt.Length(2), propName); + + var propForName = new FluentPropertyValidator(funcForName, propName, nameValidator); + + static string funcForEmail(Contact c) => c.Email; + const string propEmail = "Email"; + var emailValidator = new TypeValidator(); + emailValidator.SetValidation((opt) => opt.EmailAddress(), propEmail); + + var propForEmail = new FluentPropertyValidator(funcForEmail, propEmail, emailValidator); + + var fv = new FluentValidator(new List>() { propForName, propForEmail }); + Assert.That(fv.Count(), Is.EqualTo(2)); + + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.Contacts) + .WithValidation(o => o.ForEach(o1 => o1.SetValidator(fv))) + .Build() + .Validate(new SubObjWithComplexCollectionProperty() + { + I = 1, + S = "b", + Contacts = new List() { new Contact() { Name = "A", Email = "a"}, new Contact() { Name = "K", Email = "b"} } + }); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(4)); + } } } From 66c7be2a4768dc0814b6a693bc452a96911bbe45 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 23 Apr 2025 16:12:19 +0300 Subject: [PATCH 06/32] Introduce the `Unit` readonly struct. --- src/ExpressValidator/Unit.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/ExpressValidator/Unit.cs diff --git a/src/ExpressValidator/Unit.cs b/src/ExpressValidator/Unit.cs new file mode 100644 index 0000000..ac6385d --- /dev/null +++ b/src/ExpressValidator/Unit.cs @@ -0,0 +1,7 @@ +namespace ExpressValidator +{ + public readonly struct Unit + { + public static readonly Unit Default = new Unit(); + } +} \ No newline at end of file From 8d234458398beae04f3c0b7dd06980bbeeaf0427 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 23 Apr 2025 18:19:07 +0300 Subject: [PATCH 07/32] Introduced the `QuickValidator` class and its `Validate(T, Action>, string)` method. --- src/ExpressValidator/QuickValidator.cs | 33 +++++++++++++++++++ .../ExpressValidator.Tests.csproj | 1 + .../QuickValidatorTests.cs | 29 ++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/ExpressValidator/QuickValidator.cs create mode 100644 tests/ExpressValidator.Tests/QuickValidatorTests.cs diff --git a/src/ExpressValidator/QuickValidator.cs b/src/ExpressValidator/QuickValidator.cs new file mode 100644 index 0000000..8086d69 --- /dev/null +++ b/src/ExpressValidator/QuickValidator.cs @@ -0,0 +1,33 @@ +using ExpressValidator.Extensions; +using FluentValidation; +using FluentValidation.Results; +using System; + +namespace ExpressValidator +{ + /// + /// Provides methods for quick validation. + /// + public static class QuickValidator + { + /// + /// Validates the given object instance using . + /// + /// The type of the object to validate. + /// The object to validate. + /// Action to add validators. + /// The name of the property if the validation fails. + /// If , "input value" will be used. + /// + public static ValidationResult Validate(T obj, Action> action, string propName = null) + { + var eb = new ExpressValidatorBuilder(); + return eb.AddFunc((_) => obj, + GetPropName(propName)) + .WithValidation(action) + .BuildAndValidate(Unit.Default); + } + + private static string GetPropName(string propName = null) => propName ?? "input value"; + } +} diff --git a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj index bcef3cb..4d17cf4 100644 --- a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj +++ b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj @@ -84,6 +84,7 @@ + diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs new file mode 100644 index 0000000..add97e0 --- /dev/null +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using NUnit.Framework; + +namespace ExpressValidator.Tests +{ + internal class QuickValidatorTests + { + [Test] + public void Should_Fail_Validation_When_NotValid() + { + const int valueToTest = 5; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10) + .GreaterThan(15)); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + } + + [Test] + public void Should_Pass_Validation_When_Valid() + { + const int valueToTest = 25; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10) + .InclusiveBetween(15, 25)); + Assert.That(result.IsValid, Is.True); + } + } +} From 7766dffafc5890ee6e2e13706c303dc4e7898690 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Thu, 24 Apr 2025 18:09:07 +0300 Subject: [PATCH 08/32] Tests for property name accuracy in `QuickValidator`'s `ValidationResult`. --- src/ExpressValidator/QuickValidator.cs | 4 ++- .../QuickValidatorTests.cs | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/ExpressValidator/QuickValidator.cs b/src/ExpressValidator/QuickValidator.cs index 8086d69..54793c3 100644 --- a/src/ExpressValidator/QuickValidator.cs +++ b/src/ExpressValidator/QuickValidator.cs @@ -10,6 +10,8 @@ namespace ExpressValidator /// public static class QuickValidator { + private const string FALLBACK_PROP_NAME = "input value"; + /// /// Validates the given object instance using . /// @@ -28,6 +30,6 @@ public static ValidationResult Validate(T obj, Action propName ?? "input value"; + private static string GetPropName(string propName = null) => propName ?? FALLBACK_PROP_NAME; } } diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index add97e0..5c9f5a9 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -1,5 +1,7 @@ using FluentValidation; +using FluentValidation.Results; using NUnit.Framework; +using System.Linq; namespace ExpressValidator.Tests { @@ -16,6 +18,29 @@ public void Should_Fail_Validation_When_NotValid() Assert.That(result.Errors.Count, Is.EqualTo(2)); } + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_FailValidation_WhenInputIsInvalid_HasCorrectPropertyName(bool withPropertyName) + { + const int valueToTest = 5; + ValidationResult result = null; + if (withPropertyName) + { + result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10), + nameof(valueToTest)); + } + else + { + result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10)); + } + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo(withPropertyName ? nameof(valueToTest) : "input value")); + } + [Test] public void Should_Pass_Validation_When_Valid() { From 5bed32bcd1f72955e19308fa5970ce14d4f25046 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 29 Apr 2025 21:06:06 +0300 Subject: [PATCH 09/32] Change `QuickValidator`'s fallback property name to 'input' and add tests for quick validation of non-primitive types. --- src/ExpressValidator/QuickValidator.cs | 2 +- .../QuickValidatorTests.cs | 40 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/ExpressValidator/QuickValidator.cs b/src/ExpressValidator/QuickValidator.cs index 54793c3..20e0031 100644 --- a/src/ExpressValidator/QuickValidator.cs +++ b/src/ExpressValidator/QuickValidator.cs @@ -10,7 +10,7 @@ namespace ExpressValidator /// public static class QuickValidator { - private const string FALLBACK_PROP_NAME = "input value"; + private const string FALLBACK_PROP_NAME = "input"; /// /// Validates the given object instance using . diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index 5c9f5a9..7990d64 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -1,6 +1,7 @@ using FluentValidation; using FluentValidation.Results; using NUnit.Framework; +using System; using System.Linq; namespace ExpressValidator.Tests @@ -38,7 +39,44 @@ public void Should_FailValidation_WhenInputIsInvalid_HasCorrectPropertyName(bool } Assert.That(result.IsValid, Is.False); - Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo(withPropertyName ? nameof(valueToTest) : "input value")); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(withPropertyName ? nameof(valueToTest) : "input")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_FailValidation_WhenNonPrimitiveInputIsInvalid_HasCorrectPropertyName(bool withPropertyName) + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetRule(); + ValidationResult result = null; + if (withPropertyName) + { + result = QuickValidator.Validate(objToQuick, + rule, + nameof(objToQuick)); + } + else + { + result = QuickValidator.Validate(objToQuick, + rule); + } + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(withPropertyName ? nameof(objToQuick) + "." + nameof(objToQuick.I) + : "input." + nameof(objToQuick.I))); + + Assert.That(result.Errors[1].PropertyName, Is.EqualTo(withPropertyName ? nameof(objToQuick) + "." + nameof(objToQuick.PercentValue1) + : "input." + nameof(objToQuick.PercentValue1))); + + static Action> GetRule() + { + return (opt) => + opt + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0)) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100)); + } } [Test] From 5f492dea17aaeedfb76b8a93603016f98c408aa3 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 30 Apr 2025 16:41:57 +0300 Subject: [PATCH 10/32] Introduced the `ExpressValidator.QuickValidation` namespace and moved the `QuickValidator` class into it. --- src/ExpressValidator/{ => QuickValidation}/QuickValidator.cs | 2 +- tests/ExpressValidator.Tests/QuickValidatorTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/ExpressValidator/{ => QuickValidation}/QuickValidator.cs (96%) diff --git a/src/ExpressValidator/QuickValidator.cs b/src/ExpressValidator/QuickValidation/QuickValidator.cs similarity index 96% rename from src/ExpressValidator/QuickValidator.cs rename to src/ExpressValidator/QuickValidation/QuickValidator.cs index 20e0031..9e00313 100644 --- a/src/ExpressValidator/QuickValidator.cs +++ b/src/ExpressValidator/QuickValidation/QuickValidator.cs @@ -3,7 +3,7 @@ using FluentValidation.Results; using System; -namespace ExpressValidator +namespace ExpressValidator.QuickValidation { /// /// Provides methods for quick validation. diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index 7990d64..97de11f 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -1,8 +1,8 @@ -using FluentValidation; +using ExpressValidator.QuickValidation; +using FluentValidation; using FluentValidation.Results; using NUnit.Framework; using System; -using System.Linq; namespace ExpressValidator.Tests { From 8db63336e2925ff40f70ddc36783a7de9a6f5915 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 30 Apr 2025 16:45:03 +0300 Subject: [PATCH 11/32] Change QuickValidator's fallback property name to 'Input'. --- src/ExpressValidator/QuickValidation/QuickValidator.cs | 2 +- tests/ExpressValidator.Tests/QuickValidatorTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ExpressValidator/QuickValidation/QuickValidator.cs b/src/ExpressValidator/QuickValidation/QuickValidator.cs index 9e00313..db7f06c 100644 --- a/src/ExpressValidator/QuickValidation/QuickValidator.cs +++ b/src/ExpressValidator/QuickValidation/QuickValidator.cs @@ -10,7 +10,7 @@ namespace ExpressValidator.QuickValidation /// public static class QuickValidator { - private const string FALLBACK_PROP_NAME = "input"; + private const string FALLBACK_PROP_NAME = "Input"; /// /// Validates the given object instance using . diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index 97de11f..b1fb354 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -39,7 +39,7 @@ public void Should_FailValidation_WhenInputIsInvalid_HasCorrectPropertyName(bool } Assert.That(result.IsValid, Is.False); - Assert.That(result.Errors[0].PropertyName, Is.EqualTo(withPropertyName ? nameof(valueToTest) : "input")); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(withPropertyName ? nameof(valueToTest) : "Input")); } [Test] @@ -63,10 +63,10 @@ public void Should_FailValidation_WhenNonPrimitiveInputIsInvalid_HasCorrectPrope } Assert.That(result.IsValid, Is.False); Assert.That(result.Errors[0].PropertyName, Is.EqualTo(withPropertyName ? nameof(objToQuick) + "." + nameof(objToQuick.I) - : "input." + nameof(objToQuick.I))); + : "Input." + nameof(objToQuick.I))); Assert.That(result.Errors[1].PropertyName, Is.EqualTo(withPropertyName ? nameof(objToQuick) + "." + nameof(objToQuick.PercentValue1) - : "input." + nameof(objToQuick.PercentValue1))); + : "Input." + nameof(objToQuick.PercentValue1))); static Action> GetRule() { From 10f6e56993de0650c8b2437ed35435c304252b26 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sat, 3 May 2025 18:09:51 +0300 Subject: [PATCH 12/32] Added `QuickValidator.Validate()` method overload with `PropertyNameMode` support: `Validate(T, Action>, PropertyNameMode)`. --- .../QuickValidation/PropertyNameMode.cs | 18 ++++ .../QuickValidation/QuickValidator.cs | 30 +++++- .../QuickValidatorTests.cs | 92 +++++++++++-------- 3 files changed, 95 insertions(+), 45 deletions(-) create mode 100644 src/ExpressValidator/QuickValidation/PropertyNameMode.cs diff --git a/src/ExpressValidator/QuickValidation/PropertyNameMode.cs b/src/ExpressValidator/QuickValidation/PropertyNameMode.cs new file mode 100644 index 0000000..30bd02f --- /dev/null +++ b/src/ExpressValidator/QuickValidation/PropertyNameMode.cs @@ -0,0 +1,18 @@ +namespace ExpressValidator.QuickValidation +{ + /// + /// Determines how the property name is set when quick validation fails. + /// + public enum PropertyNameMode + { + /// + /// Use the literal string "Input" as the property name. + /// + Default, + + /// + /// Use the type's name (typeof(T).Name) of the validated object as the property name. + /// + TypeName + } +} diff --git a/src/ExpressValidator/QuickValidation/QuickValidator.cs b/src/ExpressValidator/QuickValidation/QuickValidator.cs index db7f06c..f895eb5 100644 --- a/src/ExpressValidator/QuickValidation/QuickValidator.cs +++ b/src/ExpressValidator/QuickValidation/QuickValidator.cs @@ -13,23 +13,43 @@ public static class QuickValidator private const string FALLBACK_PROP_NAME = "Input"; /// - /// Validates the given object instance using . + /// Validates the given object instance using . /// /// The type of the object to validate. /// The object to validate. /// Action to add validators. /// The name of the property if the validation fails. - /// If , "input value" will be used. + /// If , "Input" will be used. /// - public static ValidationResult Validate(T obj, Action> action, string propName = null) + public static ValidationResult Validate(T obj, Action> action, string propName) + { + return ValidateInner(obj, action, propName ?? FALLBACK_PROP_NAME); + } + + /// + /// Validates the given object instance using . + /// + /// The type of the object to validate. + /// The object to validate. + /// Action to add validators. + /// . + /// If , "Input" will be used. + /// + /// + public static ValidationResult Validate(T obj, Action> action, PropertyNameMode mode = PropertyNameMode.Default) + { + return ValidateInner(obj, action, GetPropName(mode)); + } + + private static ValidationResult ValidateInner(T obj, Action> action, string propName) { var eb = new ExpressValidatorBuilder(); return eb.AddFunc((_) => obj, - GetPropName(propName)) + propName) .WithValidation(action) .BuildAndValidate(Unit.Default); } - private static string GetPropName(string propName = null) => propName ?? FALLBACK_PROP_NAME; + private static string GetPropName(PropertyNameMode mode) => mode == PropertyNameMode.Default ? FALLBACK_PROP_NAME : typeof(T).Name; } } diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index b1fb354..5b1e6be 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -1,6 +1,5 @@ using ExpressValidator.QuickValidation; using FluentValidation; -using FluentValidation.Results; using NUnit.Framework; using System; @@ -9,73 +8,76 @@ namespace ExpressValidator.Tests internal class QuickValidatorTests { [Test] - public void Should_Fail_Validation_When_NotValid() + public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForPrimitiveType_UsingOverload_WithPropertyName() { const int valueToTest = 5; var result = QuickValidator.Validate(valueToTest, (opt) => opt.GreaterThan(10) - .GreaterThan(15)); + .GreaterThan(15), + nameof(valueToTest)); Assert.That(result.IsValid, Is.False); Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(valueToTest))); } [Test] - [TestCase(true)] - [TestCase(false)] - public void Should_FailValidation_WhenInputIsInvalid_HasCorrectPropertyName(bool withPropertyName) + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) { const int valueToTest = 5; - ValidationResult result = null; - if (withPropertyName) + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10) + .GreaterThan(15), + mode); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) { - result = QuickValidator.Validate(valueToTest, - (opt) => opt.GreaterThan(10), - nameof(valueToTest)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input")); } else { - result = QuickValidator.Validate(valueToTest, - (opt) => opt.GreaterThan(10)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(typeof(int).Name)); } - - Assert.That(result.IsValid, Is.False); - Assert.That(result.Errors[0].PropertyName, Is.EqualTo(withPropertyName ? nameof(valueToTest) : "Input")); } [Test] - [TestCase(true)] - [TestCase(false)] - public void Should_FailValidation_WhenNonPrimitiveInputIsInvalid_HasCorrectPropertyName(bool withPropertyName) + public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyName() { var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; var rule = GetRule(); - ValidationResult result = null; - if (withPropertyName) - { - result = QuickValidator.Validate(objToQuick, + + var result = QuickValidator.Validate(objToQuick, rule, nameof(objToQuick)); - } - else - { - result = QuickValidator.Validate(objToQuick, - rule); - } + Assert.That(result.IsValid, Is.False); - Assert.That(result.Errors[0].PropertyName, Is.EqualTo(withPropertyName ? nameof(objToQuick) + "." + nameof(objToQuick.I) - : "Input." + nameof(objToQuick.I))); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick)+"." + nameof(ObjWithTwoPublicProps.I))); + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetRule(); - Assert.That(result.Errors[1].PropertyName, Is.EqualTo(withPropertyName ? nameof(objToQuick) + "." + nameof(objToQuick.PercentValue1) - : "Input." + nameof(objToQuick.PercentValue1))); + var result = QuickValidator.Validate(objToQuick, + rule, + mode); - static Action> GetRule() + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input." + nameof(ObjWithTwoPublicProps.I))); + } + else { - return (opt) => - opt - .ChildRules((v) => v.RuleFor(o => o.I) - .GreaterThan(0)) - .ChildRules((v) => v.RuleFor(o => o.PercentValue1) - .InclusiveBetween(0, 100)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(ObjWithTwoPublicProps) + "." + nameof(ObjWithTwoPublicProps.I))); } } @@ -88,5 +90,15 @@ public void Should_Pass_Validation_When_Valid() .InclusiveBetween(15, 25)); Assert.That(result.IsValid, Is.True); } + + private static Action> GetRule() + { + return (opt) => + opt + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0)) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100)); + } } } From 616f947e852f01e5f44240f68744790e25905878 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Mon, 5 May 2025 12:10:53 +0300 Subject: [PATCH 13/32] Tests for `QuickValidator.Validate` overload using PropertyNameMode with overridden property name. --- .../QuickValidatorTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index 5b1e6be..f80e875 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -20,6 +20,25 @@ public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForPrimiti Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(valueToTest))); } + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + const int valueToTest = 5; + const string propName = "MyPropName"; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt + .OverridePropertyName(propName) + .GreaterThan(10) + .GreaterThan(15), + mode); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(propName)); + Assert.That(result.Errors[1].PropertyName, Is.EqualTo(propName)); + } + [Test] [TestCase(PropertyNameMode.Default)] [TestCase(PropertyNameMode.TypeName)] From 9dc4c8e326872d169cbc00e4b195feefba46edf5 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Mon, 5 May 2025 18:19:28 +0300 Subject: [PATCH 14/32] Add 'Nuances Of Using The Library' README Chapter. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index ba94950..51dc7a1 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,19 @@ if(!result2.IsValid) } ``` +## 🧩 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 initially provided in `AddFunc`/`AddProperty`/`AddField`. +For example, in this case `result.Errors[0].PropertyName` will equal "percentSum" (the property name overridden in the validation rule): +```csharp +// result0.Errors[0].PropertyName == "percentSum" +var result = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "sum") + .WithValidation((o) => o.InclusiveBetween(0, 100) + .OverridePropertyName("percentSum")) + .BuildAndValidate(new ObjToValidateOptions() { PercentValue1 = 200}); +``` + ## ❌ Drawbacks - Non-canonical way of using of FluentValidation. From 6e8ec66859a24e1cc91e6d34cb8fbdea9c0bce04 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 6 May 2025 14:41:57 +0300 Subject: [PATCH 15/32] Edit ' Nuances Of Using The Library' README Chapter. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 51dc7a1..eabb5a3 100644 --- a/README.md +++ b/README.md @@ -132,14 +132,14 @@ if(!result2.IsValid) ## 🧩 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 initially provided in `AddFunc`/`AddProperty`/`AddField`. -For example, in this case `result.Errors[0].PropertyName` will equal "percentSum" (the property name overridden in the validation rule): +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 // result0.Errors[0].PropertyName == "percentSum" -var result = new ExpressValidatorBuilder() +var result = new ExpressValidatorBuilder() .AddFunc(o => o.PercentValue1 + o.PercentValue2, "sum") .WithValidation((o) => o.InclusiveBetween(0, 100) .OverridePropertyName("percentSum")) - .BuildAndValidate(new ObjToValidateOptions() { PercentValue1 = 200}); + .BuildAndValidate(new ObjToValidate() { PercentValue1 = 200}); ``` ## ❌ Drawbacks From 9d798db594d45c909ad78307f58b2e29d8fec342 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 6 May 2025 15:19:01 +0300 Subject: [PATCH 16/32] Edit ' Nuances Of Using The Library' README Chapter -args clarification. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eabb5a3..740693e 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ if(!result2.IsValid) ## 🧩 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 initially provided in `AddFunc`/`AddProperty`/`AddField`. +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 // result0.Errors[0].PropertyName == "percentSum" From fc5dcf629820e8a4242f5abd61eb1d33529e0dfc Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 6 May 2025 16:11:11 +0300 Subject: [PATCH 17/32] Test for `QuickValidator.Validate` overload using string propName parameter with FV overridden property name. --- .../QuickValidatorTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index f80e875..254ca12 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -20,6 +20,22 @@ public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForPrimiti Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(valueToTest))); } + [Test] + public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForPrimitiveType_UsingOverload_WithPropertyName() + { + const int valueToTest = 5; + const string propName = "MyPropName"; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt + .OverridePropertyName(propName) + .GreaterThan(10) + .GreaterThan(15), + nameof(valueToTest)); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(propName)); + } + [Test] [TestCase(PropertyNameMode.Default)] [TestCase(PropertyNameMode.TypeName)] From 68f989cf3eec1bf4962cba92e927d14c05934f3b Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 7 May 2025 18:47:44 +0300 Subject: [PATCH 18/32] Test for `QuickValidator.Validate` overload for complex type using `string` `propName` parameter with FV overridden property name. --- .../QuickValidatorTests.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index 254ca12..e4c5c34 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -89,7 +89,22 @@ public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForNonPrim Assert.That(result.IsValid, Is.False); Assert.That(result.Errors.Count, Is.EqualTo(2)); - Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick)+"." + nameof(ObjWithTwoPublicProps.I))); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick) + "." + nameof(ObjWithTwoPublicProps.I))); + } + + [Test] + public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyName() + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetRuleWithOverriddenPropertyName(); + + var result = QuickValidator.Validate(objToQuick, + rule, + nameof(objToQuick)); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick) + ".MyPropNameI")); } [Test] @@ -135,5 +150,15 @@ private static Action v.RuleFor(o => o.PercentValue1) .InclusiveBetween(0, 100)); } + + private static Action> GetRuleWithOverriddenPropertyName() + { + return (opt) => + opt + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0).OverridePropertyName("MyPropNameI")) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100)); + } } } From 734cc87d42b7ccda1fabaef9f5ee29d205d7135d Mon Sep 17 00:00:00 2001 From: kolan72 Date: Thu, 8 May 2025 18:53:15 +0300 Subject: [PATCH 19/32] Test for `QuickValidator.Validate` overload for complex type using `PropertyNameMode` parameter with FV overridden property name. --- .../QuickValidatorTests.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index e4c5c34..fa91c8e 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -107,6 +107,30 @@ public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForNonPr Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick) + ".MyPropNameI")); } + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetRuleWithOverriddenPropertyName(); + + var result = QuickValidator.Validate(objToQuick, + rule, + mode); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input.MyPropNameI")); + } + else + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(ObjWithTwoPublicProps) + ".MyPropNameI")); + } + } + [Test] [TestCase(PropertyNameMode.Default)] [TestCase(PropertyNameMode.TypeName)] @@ -131,6 +155,7 @@ public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForNonPrim } } + [Test] public void Should_Pass_Validation_When_Valid() { From 4e49dcae5b1560c374a99f5d71562b6d3f173de1 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Mon, 12 May 2025 13:19:40 +0300 Subject: [PATCH 20/32] Adds optional `onSuccessValidation` callback (`Action`) to `QuickValidator.Validate`. --- .../QuickValidation/QuickValidator.cs | 10 +++--- .../ExpressValidatorBuilder.cs | 2 +- .../QuickValidatorTests.cs | 33 ++++++++++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/ExpressValidator/QuickValidation/QuickValidator.cs b/src/ExpressValidator/QuickValidation/QuickValidator.cs index f895eb5..bbc5dab 100644 --- a/src/ExpressValidator/QuickValidation/QuickValidator.cs +++ b/src/ExpressValidator/QuickValidation/QuickValidator.cs @@ -20,10 +20,11 @@ public static class QuickValidator /// Action to add validators. /// The name of the property if the validation fails. /// If , "Input" will be used. + /// Specifies a method to execute when validation succeeds. /// - public static ValidationResult Validate(T obj, Action> action, string propName) + public static ValidationResult Validate(T obj, Action> action, string propName, Action onSuccessValidation = null) { - return ValidateInner(obj, action, propName ?? FALLBACK_PROP_NAME); + return ValidateInner(obj, action, propName ?? FALLBACK_PROP_NAME, onSuccessValidation); } /// @@ -41,11 +42,12 @@ public static ValidationResult Validate(T obj, Action(obj, action, GetPropName(mode)); } - private static ValidationResult ValidateInner(T obj, Action> action, string propName) + private static ValidationResult ValidateInner(T obj, Action> action, string propName, Action onSuccessValidation = null) { var eb = new ExpressValidatorBuilder(); return eb.AddFunc((_) => obj, - propName) + propName, + onSuccessValidation) .WithValidation(action) .BuildAndValidate(Unit.Default); } diff --git a/src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.cs b/src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.cs index 9985aff..cc745c2 100644 --- a/src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.cs +++ b/src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.cs @@ -50,7 +50,7 @@ public IBuilderWithPropValidator AddField(Expression> /// A type of value. /// Func for object /// A name of the property if the validation failed. - /// Func Result Validation Success Handler + /// Specifies a method to execute when validation succeeds. /// public IBuilderWithPropValidator AddFunc(Func func, string propName, Action onSuccessValidation = null) { diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index fa91c8e..141ac23 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -155,7 +155,6 @@ public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForNonPrim } } - [Test] public void Should_Pass_Validation_When_Valid() { @@ -166,6 +165,38 @@ public void Should_Pass_Validation_When_Valid() Assert.That(result.IsValid, Is.True); } + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Call_OnSuccess_When_Validation_Succeeds(bool isValid) + { + int valueFromHandler = 0; + int valueToTest; + if (isValid) + { + valueToTest = 25; + } + else + { + valueToTest = 5; + } + + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10), + "vv", + (v) => valueFromHandler = v); + if (isValid) + { + Assert.That(result.IsValid, Is.True); + Assert.That(valueFromHandler, Is.EqualTo(25)); + } + else + { + Assert.That(result.IsValid, Is.False); + Assert.That(valueFromHandler, Is.EqualTo(0)); + } + } + private static Action> GetRule() { return (opt) => From 85b1cfe32b509fae2000e764dfa66f4a9c4fdca3 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 13 May 2025 18:31:40 +0300 Subject: [PATCH 21/32] Adds an optional `onSuccessValidation` callback (`Action`) to the `QuickValidator.Validate` overload that includes a `PropertyNameMode` param. --- .../QuickValidation/QuickValidator.cs | 8 ++--- .../QuickValidatorTests.cs | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/ExpressValidator/QuickValidation/QuickValidator.cs b/src/ExpressValidator/QuickValidation/QuickValidator.cs index bbc5dab..6426b7d 100644 --- a/src/ExpressValidator/QuickValidation/QuickValidator.cs +++ b/src/ExpressValidator/QuickValidation/QuickValidator.cs @@ -34,12 +34,12 @@ public static ValidationResult Validate(T obj, ActionThe object to validate. /// Action to add validators. /// . - /// If , "Input" will be used. - /// + /// If , "Input" will be used. + /// Specifies a method to execute when validation succeeds. /// - public static ValidationResult Validate(T obj, Action> action, PropertyNameMode mode = PropertyNameMode.Default) + public static ValidationResult Validate(T obj, Action> action, PropertyNameMode mode = PropertyNameMode.Default, Action onSuccessValidation = null) { - return ValidateInner(obj, action, GetPropName(mode)); + return ValidateInner(obj, action, GetPropName(mode), onSuccessValidation); } private static ValidationResult ValidateInner(T obj, Action> action, string propName, Action onSuccessValidation = null) diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index 141ac23..ee97206 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -197,6 +197,38 @@ public void Should_Call_OnSuccess_When_Validation_Succeeds(bool isValid) } } + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Call_OnSuccess_When_Validation_Succeeds_UsingOverload_WithPropertyNameMode(bool isValid) + { + int valueFromHandler = 0; + int valueToTest; + if (isValid) + { + valueToTest = 25; + } + else + { + valueToTest = 5; + } + + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10), + PropertyNameMode.TypeName, + (v) => valueFromHandler = v); + if (isValid) + { + Assert.That(result.IsValid, Is.True); + Assert.That(valueFromHandler, Is.EqualTo(25)); + } + else + { + Assert.That(result.IsValid, Is.False); + Assert.That(valueFromHandler, Is.EqualTo(0)); + } + } + private static Action> GetRule() { return (opt) => From 29cb7c323669fe7796e573caee08e3e285e064d3 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Mon, 19 May 2025 16:40:05 +0300 Subject: [PATCH 22/32] Remove f. v. from dev so far. --- src/ExpressValidator/FluentValidator.cs | 16 ---- .../FluentPropertyValidator.cs | 13 ---- .../ExpressValidator.Tests.csproj | 2 - .../FluentPropertyValidatorTests.cs | 33 -------- .../FluentValidatorTests.cs | 78 ------------------- 5 files changed, 142 deletions(-) delete mode 100644 src/ExpressValidator/FluentValidator.cs delete mode 100644 src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs delete mode 100644 tests/ExpressValidator.Tests/FluentPropertyValidatorTests.cs delete mode 100644 tests/ExpressValidator.Tests/FluentValidatorTests.cs diff --git a/src/ExpressValidator/FluentValidator.cs b/src/ExpressValidator/FluentValidator.cs deleted file mode 100644 index 235f955..0000000 --- a/src/ExpressValidator/FluentValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using FluentValidation; -using System.Collections.Generic; - -namespace ExpressValidator -{ - public class FluentValidator : AbstractValidator - { - internal FluentValidator(IEnumerable> validators) - { - foreach (var v in validators) - { - Include(v); - } - } - } -} diff --git a/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs b/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs deleted file mode 100644 index 921d641..0000000 --- a/src/ExpressValidator/PropertyValidators/FluentPropertyValidator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FluentValidation; -using System; - -namespace ExpressValidator -{ - internal class FluentPropertyValidator : AbstractValidator - { - public FluentPropertyValidator(Func propertyFunc, string propName, TypeValidatorBase typeValidator) - { - RuleFor((obj) => propertyFunc(obj)).NotNull().SetValidator(typeValidator).OverridePropertyName(propName); - } - } -} diff --git a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj index 7a11b02..4d17cf4 100644 --- a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj +++ b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj @@ -85,8 +85,6 @@ - - diff --git a/tests/ExpressValidator.Tests/FluentPropertyValidatorTests.cs b/tests/ExpressValidator.Tests/FluentPropertyValidatorTests.cs deleted file mode 100644 index e56a709..0000000 --- a/tests/ExpressValidator.Tests/FluentPropertyValidatorTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using NUnit.Framework; -using System; -using FluentValidation; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ExpressValidator.Tests -{ - internal class FluentPropertyValidatorTests - { - [Test] - public void Should_Be_Initialized_Correctly_By_Ctor() - { - static decimal funcForField(Customer c) => c.CustomerDiscount; - const string propName = "CustomerDiscount"; - var validator = new TypeValidator(); - validator.SetValidation((opt) => opt.GreaterThan(0), propName); - - var fluentValidator = new FluentPropertyValidator(funcForField, propName, validator); - Assert.That(fluentValidator.Count(), Is.EqualTo(1)); - Assert.That(fluentValidator.FirstOrDefault().PropertyName, Is.EqualTo(propName)); - - var notValidCustomer = new Customer(); - var failedValidationResult = fluentValidator.Validate(notValidCustomer); - Assert.That(failedValidationResult.IsValid, Is.False); - Assert.That(failedValidationResult.Errors.Count, Is.EqualTo(1)); - - Assert.That(fluentValidator.Validate(new Customer() { CustomerDiscount = 1 }).IsValid, Is.True); - } - } -} diff --git a/tests/ExpressValidator.Tests/FluentValidatorTests.cs b/tests/ExpressValidator.Tests/FluentValidatorTests.cs deleted file mode 100644 index d4b389f..0000000 --- a/tests/ExpressValidator.Tests/FluentValidatorTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using FluentValidation; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace ExpressValidator.Tests -{ - internal class FluentValidatorTests - { - [Test] - public void Should_Be_Correctly_Initialized_By_FluentPropertyValidators() - { - static decimal funcForDiscount(Customer c) => c.CustomerDiscount; - const string propNameDiscount = "CustomerDiscount"; - var validator = new TypeValidator(); - validator.SetValidation((opt) => opt.GreaterThan(0), propNameDiscount); - - var propForDiscount = new FluentPropertyValidator(funcForDiscount, propNameDiscount, validator); - - static int funcForId(Customer c) => c.CustomerId; - const string propNameId = "CustomerId"; - var validatorId = new TypeValidator(); - validatorId.SetValidation((opt) => opt.GreaterThan(1), propNameId); - - var propForId = new FluentPropertyValidator(funcForId, propNameId, validatorId); - - static string funcForName(Customer c) => c.Name; - const string propNameName = "Name"; - var nameValidator = new TypeValidator(); - nameValidator.SetValidation((opt) => opt.Length(2), propNameName); - - var propForName = new FluentPropertyValidator(funcForName, propNameName, nameValidator); - - var fv = new FluentValidator(new List>() { propForDiscount, propForId, propForName }); - Assert.That(fv.Count(), Is.EqualTo(3)); - - var vr = fv.Validate(new Customer() ); - - Assert.That(vr.IsValid, Is.False); - Assert.That(vr.Errors.Count, Is.EqualTo(3)); - } - - [Test] - public void Should_Apply_FluentValidator_To_Each_Item_In_Collection() - { - static string funcForName(Contact c) => c.Name; - const string propName = "Name"; - var nameValidator = new TypeValidator(); - nameValidator.SetValidation((opt) => opt.Length(2), propName); - - var propForName = new FluentPropertyValidator(funcForName, propName, nameValidator); - - static string funcForEmail(Contact c) => c.Email; - const string propEmail = "Email"; - var emailValidator = new TypeValidator(); - emailValidator.SetValidation((opt) => opt.EmailAddress(), propEmail); - - var propForEmail = new FluentPropertyValidator(funcForEmail, propEmail, emailValidator); - - var fv = new FluentValidator(new List>() { propForName, propForEmail }); - Assert.That(fv.Count(), Is.EqualTo(2)); - - var result = new ExpressValidatorBuilder() - .AddProperty(o => o.Contacts) - .WithValidation(o => o.ForEach(o1 => o1.SetValidator(fv))) - .Build() - .Validate(new SubObjWithComplexCollectionProperty() - { - I = 1, - S = "b", - Contacts = new List() { new Contact() { Name = "A", Email = "a"}, new Contact() { Name = "K", Email = "b"} } - }); - Assert.That(result.IsValid, Is.False); - Assert.That(result.Errors.Count, Is.EqualTo(4)); - } - } -} From 19bfb2ab4f17f304e2d743f96f02891ba6eebd23 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Mon, 19 May 2025 17:36:47 +0300 Subject: [PATCH 23/32] Update NuGet.md. --- src/ExpressValidator/docs/NuGet.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ExpressValidator/docs/NuGet.md b/src/ExpressValidator/docs/NuGet.md index 7bbdb60..7868253 100644 --- a/src/ExpressValidator/docs/NuGet.md +++ b/src/ExpressValidator/docs/NuGet.md @@ -50,6 +50,7 @@ if(!result.IsValid) //As usual with validation result... } ``` +## Modifying FluentValidation Validator Parameters Using Options To dynamically change the parameters of the `FluentValidation` validators: From 9c0d0a51a479c4a2e668ebc0031ee100f7e20afd Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 20 May 2025 15:35:32 +0300 Subject: [PATCH 24/32] Add 'Quick Validation' README Chapter. --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 740693e..913fe40 100644 --- a/README.md +++ b/README.md @@ -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+ @@ -142,6 +143,21 @@ var result = new ExpressValidatorBuilder() .BuildAndValidate(new ObjToValidate() { PercentValue1 = 200}); ``` +## ⏩ 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)); +``` + + ## ❌ Drawbacks - Non-canonical way of using of FluentValidation. From 62f362ed1aed253e4a34178f1d454733a1bc7159 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 20 May 2025 15:37:57 +0300 Subject: [PATCH 25/32] Fix 'Nuances Of Using The Library' README example. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 740693e..df868cb 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ if(!result2.IsValid) 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 -// result0.Errors[0].PropertyName == "percentSum" +// result.Errors[0].PropertyName == "percentSum" var result = new ExpressValidatorBuilder() .AddFunc(o => o.PercentValue1 + o.PercentValue2, "sum") .WithValidation((o) => o.InclusiveBetween(0, 100) From a464d19abf50036efd001acd3ee4e7850855db6f Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 20 May 2025 16:23:10 +0300 Subject: [PATCH 26/32] Add 'Quick Validation' NuGet README Chapter. --- README.md | 2 +- src/ExpressValidator/docs/NuGet.md | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d2e979..7294383 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ var value = 5; // result.Errors[0].PropertyName == "value" var result = QuickValidator.Validate( value, - (opt) => opt.GreaterThan(10) + (opt) => opt.GreaterThan(10), nameof(value)); ``` diff --git a/src/ExpressValidator/docs/NuGet.md b/src/ExpressValidator/docs/NuGet.md index 7868253..042fe05 100644 --- a/src/ExpressValidator/docs/NuGet.md +++ b/src/ExpressValidator/docs/NuGet.md @@ -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+ @@ -112,3 +113,16 @@ 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)); +``` \ No newline at end of file From be319e22eaac4829972f4beccf124f32b69876d8 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 21 May 2025 14:29:54 +0300 Subject: [PATCH 27/32] Edit 'Quick Validation' README Chapter. --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 7294383..aadb518 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,21 @@ var result = QuickValidator.Validate( 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)); +``` ## ❌ Drawbacks From e3f8d1148fe8b1aa2b26f4d0208451c025883ea4 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 21 May 2025 14:36:15 +0300 Subject: [PATCH 28/32] Edit 'Quick Validation' NuGet README Chapter. --- src/ExpressValidator/docs/NuGet.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/ExpressValidator/docs/NuGet.md b/src/ExpressValidator/docs/NuGet.md index 042fe05..26550e4 100644 --- a/src/ExpressValidator/docs/NuGet.md +++ b/src/ExpressValidator/docs/NuGet.md @@ -51,6 +51,7 @@ if(!result.IsValid) //As usual with validation result... } ``` + ## Modifying FluentValidation Validator Parameters Using Options To dynamically change the parameters of the `FluentValidation` validators: @@ -113,6 +114,7 @@ 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: @@ -125,4 +127,20 @@ 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)); ``` \ No newline at end of file From 41ef1e0f6ae7b0e70170936601cb0195bc168591 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 21 May 2025 14:50:07 +0300 Subject: [PATCH 29/32] Update Microsoft nuget packages in 'ExpressValidator.Tests'. --- .../ExpressValidator.Tests.csproj | 25 +++++++++---------- tests/ExpressValidator.Tests/app.config | 6 ++--- tests/ExpressValidator.Tests/packages.config | 12 ++++----- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj index 4d17cf4..faee7f7 100644 --- a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj +++ b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj @@ -52,25 +52,22 @@ ..\..\packages\NUnit.4.3.2\lib\net462\nunit.framework.legacy.dll - - ..\..\packages\System.Buffers.4.6.0\lib\net462\System.Buffers.dll + + ..\..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll - - ..\..\packages\System.Memory.4.6.0\lib\net462\System.Memory.dll + + ..\..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll - - ..\..\packages\System.Numerics.Vectors.4.6.0\lib\net462\System.Numerics.Vectors.dll + + ..\..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.6.1.0\lib\net462\System.Runtime.CompilerServices.Unsafe.dll + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll - - ..\..\packages\System.Threading.Tasks.Extensions.4.6.0\lib\net462\System.Threading.Tasks.Extensions.dll - - - ..\..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + ..\..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll @@ -116,5 +113,7 @@ + + \ No newline at end of file diff --git a/tests/ExpressValidator.Tests/app.config b/tests/ExpressValidator.Tests/app.config index d207bec..1983c0c 100644 --- a/tests/ExpressValidator.Tests/app.config +++ b/tests/ExpressValidator.Tests/app.config @@ -8,15 +8,15 @@ - + - + - + diff --git a/tests/ExpressValidator.Tests/packages.config b/tests/ExpressValidator.Tests/packages.config index b2a4321..1b3d233 100644 --- a/tests/ExpressValidator.Tests/packages.config +++ b/tests/ExpressValidator.Tests/packages.config @@ -3,10 +3,10 @@ - - - - - - + + + + + + \ No newline at end of file From b33ebfb50394730012019e2f8865eca8681b4fef Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 21 May 2025 15:18:24 +0300 Subject: [PATCH 30/32] Add 'Nuances Of Using The Library' NuGet README Chapter. --- README.md | 26 +++++++++++++------------- src/ExpressValidator/docs/NuGet.md | 13 +++++++++++++ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index aadb518..98e6dab 100644 --- a/README.md +++ b/README.md @@ -130,19 +130,6 @@ if(!result2.IsValid) } ``` -## 🧩 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() - .AddFunc(o => o.PercentValue1 + o.PercentValue2, "sum") - .WithValidation((o) => o.InclusiveBetween(0, 100) - .OverridePropertyName("percentSum")) - .BuildAndValidate(new ObjToValidate() { PercentValue1 = 200}); -``` - ## ⏩ 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: @@ -173,6 +160,19 @@ var result = QuickValidator.Validate( 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() + .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. diff --git a/src/ExpressValidator/docs/NuGet.md b/src/ExpressValidator/docs/NuGet.md index 26550e4..2d7fa16 100644 --- a/src/ExpressValidator/docs/NuGet.md +++ b/src/ExpressValidator/docs/NuGet.md @@ -143,4 +143,17 @@ var result = QuickValidator.Validate( .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() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "sum") + .WithValidation((o) => o.InclusiveBetween(0, 100) + .OverridePropertyName("percentSum")) + .BuildAndValidate(new ObjToValidate() { PercentValue1 = 200}); ``` \ No newline at end of file From 70d72f9e31691763e247b35133fa55aa4a501db8 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 21 May 2025 16:02:22 +0300 Subject: [PATCH 31/32] Update CHANGELOG.md. --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d1c860..5563acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.9.0 + +- Added quick validation support via `QuickValidator` and its `Validate` overloads. +- Improve performance by applying options in `ExpressValidator` during the `ExpressValidatorBuilder.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.BuildAndValidate(TObj, TOptions)` extension method. From d445f8d40cd424a76a1a900fe38f84daffe5e699 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 21 May 2025 16:03:13 +0300 Subject: [PATCH 32/32] Package 0.9.0 version. --- src/ExpressValidator/ExpressValidator.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ExpressValidator/ExpressValidator.csproj b/src/ExpressValidator/ExpressValidator.csproj index 255b004..2e9f057 100644 --- a/src/ExpressValidator/ExpressValidator.csproj +++ b/src/ExpressValidator/ExpressValidator.csproj @@ -3,7 +3,7 @@ netstandard2.0 true - 0.5.0 + 0.9.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,7 +15,7 @@ ExpressValidator.png NuGet.md - 0.5.0.0 + 0.9.0.0 0.0.0.0