From 2673b9472767b27f65b51c3a16c2f88601e54ecb Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 10 Oct 2025 15:50:17 +0300 Subject: [PATCH 01/14] Package 0.3.12 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 9f9bb74..f051f0b 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.3.12 + +- Support .NET 8.0 and FluentValidation 12.0.0. +- Update Microsoft nuget packages. +- Update ExpressValidator NuGet package to v0.12.0. +- Update NUnit NuGet package to v4.4.0. +- Retarget ExpressValidator.Extensions.DependencyInjection.Sample to .NET 8.0. + + ## 0.3.9 - Update ExpressValidator nuget package. diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj index 4faeedf..9129223 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj +++ b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj @@ -3,7 +3,7 @@ netstandard2.0;net8.0 true - 0.3.9 + 0.3.12 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.9.0 + 0.3.12.0 From b188da2b5efc874e396fda89858d91e39ed09638 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 10 Oct 2025 16:29:47 +0300 Subject: [PATCH 02/14] Update Documentation README Chapter. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a0648cf..d13121c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,9 @@ ExpressValidator is a library that provides the ability to validate objects usin ## 📜 Documentation -For details, please check the [API documentation](https://www.tmfexplorer.com/ExpressValidator/api/ExpressValidator.html). +> See the [API documentation](https://www.tmfexplorer.com/ExpressValidator/api/ExpressValidator.html) for reference. +> +> Learn more on [DeepWiki](https://deepwiki.com/kolan72/ExpressValidator/2-core-library-%28expressvalidator%29). ## 🚀 Quick Start From 31fa4d8963bc7c4d03973c2cc109b91122e7f586 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 10 Oct 2025 16:47:24 +0300 Subject: [PATCH 03/14] Add `ExpressValidator.Extensions.DependencyInjection` 'Documentation' section to README. --- src/ExpressValidator.Extensions.DependencyInjection/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ExpressValidator.Extensions.DependencyInjection/README.md b/src/ExpressValidator.Extensions.DependencyInjection/README.md index 2db26f9..cb9343d 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/README.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/README.md @@ -6,6 +6,10 @@ - Additionally, the `IExpressValidatorBuilder` interface can be configured and registered to update the validator parameters when the `ValidationParametersOptions` change. - Ability to dynamically update the validator parameters from options bound to the configuration section without restarting the application by configuring the `IExpressValidatorWithReload` interface. +## 📜 Documentation + +Explore the API documentation and in-depth details on [DeepWiki](https://deepwiki.com/kolan72/ExpressValidator/3-dependency-injection-extension). + ## 🚀 Usage ```csharp From 07885915e4ebc18d0317aea92bd2a7abaa9b30ce Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 2 Nov 2025 16:25:41 +0300 Subject: [PATCH 04/14] Add a unit test that verifies `ExpressValidator` does not throw when members are null and no null-related validators are used. --- .../ExpressValidatorTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/ExpressValidator.Tests/ExpressValidatorTests.cs b/tests/ExpressValidator.Tests/ExpressValidatorTests.cs index 3976ca3..00a08cc 100644 --- a/tests/ExpressValidator.Tests/ExpressValidatorTests.cs +++ b/tests/ExpressValidator.Tests/ExpressValidatorTests.cs @@ -53,6 +53,21 @@ public void Should_Not_Invoke_SuccessValidationHandler_When_IsNotValid() Assert.That(result.IsValid, Is.False); } + [Test] + public void Should_NotThrow_When_MembersAreNull() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddField(o => o._sField) + .WithValidation(o => o.MinimumLength(1)) + .Build() + .Validate(new ObjWithTwoPublicProps()); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + } + [Test] public void Should_Work_When_IsValid_ForSubObjWithSimpleConditionForComplexProperty() { From d2d71ca2aad0013b796d87f981dddf02aebdd333 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Sun, 2 Nov 2025 17:56:59 +0300 Subject: [PATCH 05/14] Remove the eagerly allocated `NotNullValidationMessageProvider` during `ExpressValidatorBuilder` configuration, and instantiate `NotNullValidationMessageProvider` only when the value is null. Deprecate `NotNullValidationMessageProvider.GetMessage(ValidationContext)`. --- .../NotNullValidationMessageProvider.cs | 15 +++++++++++++++ .../TypeValidators/TypeValidatorBase.cs | 5 +---- .../ExpressValidatorTests.cs | 5 +++++ .../NotNullValidationMessageProviderTests.cs | 12 ++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/ExpressValidator/TypeValidators/NotNullValidationMessageProvider.cs b/src/ExpressValidator/TypeValidators/NotNullValidationMessageProvider.cs index 816746e..e69d166 100644 --- a/src/ExpressValidator/TypeValidators/NotNullValidationMessageProvider.cs +++ b/src/ExpressValidator/TypeValidators/NotNullValidationMessageProvider.cs @@ -1,5 +1,6 @@ using FluentValidation; using FluentValidation.Validators; +using System; namespace ExpressValidator { @@ -11,9 +12,23 @@ public NotNullValidationMessageProvider(string propName) _propName = propName; } + public string GetMessage() => GetDefaultMessageTemplate(null); + +#pragma warning disable S1133 // Deprecated code should be removed + [Obsolete("This method is obsolete")] +#pragma warning restore S1133 // Deprecated code should be removed public string GetMessage(ValidationContext context) { return context.MessageFormatter.AppendPropertyName(_propName).BuildMessage(GetDefaultMessageTemplate(null)); } } + + internal static class NullFallbackMessageProvider + { + public static string GetMessage(string propName, ValidationContext context) + { + var validator = new NotNullValidationMessageProvider(propName); + return context.MessageFormatter.AppendPropertyName(propName).BuildMessage(validator.GetMessage()); + } + } } diff --git a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs index 29ccab4..febaaea 100644 --- a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs +++ b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs @@ -11,7 +11,6 @@ namespace ExpressValidator internal abstract class TypeValidatorBase : AbstractValidator { protected IRuleBuilderOptions _ruleBuilderInitial; - private NotNullValidationMessageProvider _nullMessageProvider; private IValidationRule _rule; private string _propName; @@ -38,7 +37,7 @@ protected override bool PreValidate(ValidationContext context, ValidationResu { if (_shouldBeComparedToNull && EqualityComparer.Default.Equals(context.InstanceToValidate, default)) { - result.Errors.Add(new ValidationFailure(_propName, _nullMessageProvider.GetMessage(context))); + result.Errors.Add(new ValidationFailure(_propName, NullFallbackMessageProvider.GetMessage(_propName, context))); return false; } return true; @@ -55,8 +54,6 @@ public void SetValidation(Action> action, string propN _ruleBuilderInitial = _ruleBuilderInitial.OverridePropertyName(_propName); } - _nullMessageProvider = new NotNullValidationMessageProvider(_propName); - HasOnlyNullOrEmptyValidators = AllValidatorsAreNullOrEmpty(); } diff --git a/tests/ExpressValidator.Tests/ExpressValidatorTests.cs b/tests/ExpressValidator.Tests/ExpressValidatorTests.cs index 00a08cc..be3db07 100644 --- a/tests/ExpressValidator.Tests/ExpressValidatorTests.cs +++ b/tests/ExpressValidator.Tests/ExpressValidatorTests.cs @@ -64,8 +64,13 @@ public void Should_NotThrow_When_MembersAreNull() .Build() .Validate(new ObjWithTwoPublicProps()); + var em1 = NullFallbackMessageProvider.GetMessage("S", new ValidationContext(null)); + var em2 = NullFallbackMessageProvider.GetMessage("_sField", new ValidationContext(null)); + Assert.That(result.IsValid, Is.False); Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(em1)); + Assert.That(result.Errors[1].ErrorMessage, Is.EqualTo(em2)); } [Test] diff --git a/tests/ExpressValidator.Tests/NotNullValidationMessageProviderTests.cs b/tests/ExpressValidator.Tests/NotNullValidationMessageProviderTests.cs index 419f0e3..98580ce 100644 --- a/tests/ExpressValidator.Tests/NotNullValidationMessageProviderTests.cs +++ b/tests/ExpressValidator.Tests/NotNullValidationMessageProviderTests.cs @@ -1,10 +1,14 @@ using FluentValidation; using NUnit.Framework; +using System; namespace ExpressValidator.Tests { public class NotNullValidationMessageProviderTests { +#pragma warning disable S1133 // Deprecated code should be removed + [Obsolete("This test is obsolete")] +#pragma warning restore S1133 // Deprecated code should be removed [Test] public void Should_GetMessage_Returns_CorrectMessage_For_Null_Instance() { @@ -13,5 +17,13 @@ public void Should_GetMessage_Returns_CorrectMessage_For_Null_Instance() var res = notNullMsgProvider.GetMessage(new ValidationContext(null)); Assert.That(res.Contains(propName), Is.True); } + + [Test] + public void Should_NullFallbackMessageProvider_Returns_CorrectMessage_For_Null_Instance() + { + const string propName = "TestPropName"; + var res = NullFallbackMessageProvider.GetMessage(propName, new ValidationContext(null)); + Assert.That(res.Contains(propName), Is.True); + } } } From ee8914a9563606972c914a6719de908c30f80c11 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Mon, 3 Nov 2025 14:10:18 +0300 Subject: [PATCH 06/14] Rename `TypeValidatorBase.HasOnlyNullOrEmptyValidators` to `HasNonEmptyValidators` (inverting the boolean logic) and remove the negation of this property in the `ShouldValidate` method. --- src/ExpressValidator/TypeValidators/TypeValidatorBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs index febaaea..17c855e 100644 --- a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs +++ b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs @@ -54,7 +54,7 @@ public void SetValidation(Action> action, string propN _ruleBuilderInitial = _ruleBuilderInitial.OverridePropertyName(_propName); } - HasOnlyNullOrEmptyValidators = AllValidatorsAreNullOrEmpty(); + HasNonEmptyValidators = !AllValidatorsAreNullOrEmpty(); } public async Task<(bool IsValid, List Failures)> ValidateExAsync(T value, CancellationToken token = default) @@ -91,9 +91,9 @@ public void SetValidation(Action> action, string propN internal abstract bool? IsAsync { get; } - protected bool ShouldValidate(T value) =>!_shouldBeComparedToNull || !EqualityComparer.Default.Equals(value, default) || !HasOnlyNullOrEmptyValidators; + protected bool ShouldValidate(T value) =>!_shouldBeComparedToNull || !EqualityComparer.Default.Equals(value, default) || HasNonEmptyValidators; - private bool HasOnlyNullOrEmptyValidators { get; set; } + private bool HasNonEmptyValidators { get; set; } private bool AllValidatorsAreNullOrEmpty() { From eee698d3cd179a62e6c0c51727cb463acbc54b32 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Mon, 3 Nov 2025 15:58:01 +0300 Subject: [PATCH 07/14] Move the instance field `TypeValidatorBase._shouldBeComparedToNull` to a static readonly field (renamed to `_canBeNull`) to cache the reflection result per `TypeValidatorBase` type and eliminate redundant per-instance evaluations. --- .../TypeValidators/TypeValidatorBase.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs index 17c855e..dc7e550 100644 --- a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs +++ b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs @@ -15,12 +15,7 @@ internal abstract class TypeValidatorBase : AbstractValidator private IValidationRule _rule; private string _propName; - private readonly bool _shouldBeComparedToNull; - - protected TypeValidatorBase() - { - _shouldBeComparedToNull = !typeof(T).IsValueType || (Nullable.GetUnderlyingType(typeof(T)) != null); - } + private static readonly bool _canBeNull = !typeof(T).IsValueType || (Nullable.GetUnderlyingType(typeof(T)) != null); protected override void OnRuleAdded(IValidationRule rule) { @@ -35,7 +30,7 @@ protected override void OnRuleAdded(IValidationRule rule) /// protected override bool PreValidate(ValidationContext context, ValidationResult result) { - if (_shouldBeComparedToNull && EqualityComparer.Default.Equals(context.InstanceToValidate, default)) + if (_canBeNull && EqualityComparer.Default.Equals(context.InstanceToValidate, default)) { result.Errors.Add(new ValidationFailure(_propName, NullFallbackMessageProvider.GetMessage(_propName, context))); return false; @@ -91,7 +86,7 @@ public void SetValidation(Action> action, string propN internal abstract bool? IsAsync { get; } - protected bool ShouldValidate(T value) =>!_shouldBeComparedToNull || !EqualityComparer.Default.Equals(value, default) || HasNonEmptyValidators; + protected bool ShouldValidate(T value) =>!_canBeNull || !EqualityComparer.Default.Equals(value, default) || HasNonEmptyValidators; private bool HasNonEmptyValidators { get; set; } From cebb3106bc0afd55e08e12897637480fed47b620 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 4 Nov 2025 21:32:55 +0300 Subject: [PATCH 08/14] DRY refactor of null validation in `TypeValidatorBase`. --- src/ExpressValidator/TypeValidators/TypeValidatorBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs index dc7e550..12d33a8 100644 --- a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs +++ b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs @@ -30,7 +30,7 @@ protected override void OnRuleAdded(IValidationRule rule) /// protected override bool PreValidate(ValidationContext context, ValidationResult result) { - if (_canBeNull && EqualityComparer.Default.Equals(context.InstanceToValidate, default)) + if (IsValueNull(context.InstanceToValidate)) { result.Errors.Add(new ValidationFailure(_propName, NullFallbackMessageProvider.GetMessage(_propName, context))); return false; @@ -86,7 +86,12 @@ public void SetValidation(Action> action, string propN internal abstract bool? IsAsync { get; } - protected bool ShouldValidate(T value) =>!_canBeNull || !EqualityComparer.Default.Equals(value, default) || HasNonEmptyValidators; + protected bool ShouldValidate(T value) => !IsValueNull(value) || HasNonEmptyValidators; + + private static bool IsValueNull(T value) + { + return _canBeNull && EqualityComparer.Default.Equals(value, default); + } private bool HasNonEmptyValidators { get; set; } From 416773ceed4130f44d9e48100689578520aceebe Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 11 Nov 2025 11:54:38 +0300 Subject: [PATCH 09/14] Add tests for null-tolerance validation in `QuickValidator`. --- .../QuickValidatorTests.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index f4cba16..9399e01 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -286,6 +286,65 @@ public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForNonPrim } } + [Test] + public void Should_Fail_When_NonPrimitive_Value_Is_Null_With_NotNull_Rule() + { + var rule = GetRule(); + + var result = QuickValidator.Validate(null, rule); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Fail_When_NonPrimitive_Value_Is_Null_With_Mixed_Rules() + { + var rule = GetMixedWithNullRules(); + + var result = QuickValidator.Validate(null, rule); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Valid_When_NonPrimitive_Value_Is_Null_With_Null_Rules() + { + var rule = GetNullRules(); + + var result = QuickValidator.Validate(null, rule); + Assert.That(result.IsValid, Is.True); + } + + [Test] + public void Should_Fail_When_Nullable_Struct_Is_Null_With_NotNull_Rule() + { + var result = QuickValidator.Validate(null, + (opt) => opt.GreaterThan(10) + .GreaterThan(15)); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Fail_When_Nullable_Struct_Is_Null_With_Mixed_Rule() + { + var result = QuickValidator.Validate(null, + (opt) => + opt + .Null() + .GreaterThan(10) + .GreaterThan(15)); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Valid_When_Nullable_Struct_Is_Null_With_Null_Rules() + { + var result = QuickValidator.Validate(null, + (opt) => + opt + .Null() + .Empty()); + Assert.That(result.IsValid, Is.True); + } + [Test] [TestCase(PropertyNameMode.Default)] [TestCase(PropertyNameMode.TypeName)] @@ -468,6 +527,24 @@ private static Action> GetMixedWithNullRules() + { + return (opt) => + opt.Null() + .Empty() + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0)) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100)); + } + + private static Action> GetNullRules() + { + return (opt) => + opt.Null() + .Empty(); + } + private static Action> GetAsyncRule() { return (opt) => From 801957c028cfc720856914984a25eba1156488f7 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Tue, 11 Nov 2025 12:14:00 +0300 Subject: [PATCH 10/14] Update to FluentValidation 12.1.0. --- src/ExpressValidator/ExpressValidator.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExpressValidator/ExpressValidator.csproj b/src/ExpressValidator/ExpressValidator.csproj index 9cfb235..70c1eb9 100644 --- a/src/ExpressValidator/ExpressValidator.csproj +++ b/src/ExpressValidator/ExpressValidator.csproj @@ -24,7 +24,7 @@ - + From cd8ac67fffa3824806f24b69eecdf4f496684a56 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 12 Nov 2025 13:10:39 +0300 Subject: [PATCH 11/14] Add test for validating a primitive type using `ExpressValidatorBuilder`. --- .../ExpressValidatorTests.cs | 23 ++++++++++++++++++- .../QuickValidatorTests.cs | 4 ++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/ExpressValidator.Tests/ExpressValidatorTests.cs b/tests/ExpressValidator.Tests/ExpressValidatorTests.cs index be3db07..e0b4a0f 100644 --- a/tests/ExpressValidator.Tests/ExpressValidatorTests.cs +++ b/tests/ExpressValidator.Tests/ExpressValidatorTests.cs @@ -1,4 +1,5 @@ -using FluentValidation; +using ExpressValidator.Extensions; +using FluentValidation; using NUnit.Framework; using NUnit.Framework.Legacy; using System; @@ -369,5 +370,25 @@ public void Should_AddFunc_Preserve_Property_Name(SetPropertyNameType setPropert Assert.That(result.Errors.FirstOrDefault().ErrorMessage, Does.Contain("TestName")); } } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Validate_Primitive(bool valid) + { + int value; + + if(valid) + value = 3; + else + value = 1; + + var result = new ExpressValidatorBuilder() + .AddFunc(i => i, "value") + .WithValidation(o => o.GreaterThan(2)) + .BuildAndValidate(value); + + Assert.That(result.IsValid, Is.EqualTo(valid)); + } } } diff --git a/tests/ExpressValidator.Tests/QuickValidatorTests.cs b/tests/ExpressValidator.Tests/QuickValidatorTests.cs index 9399e01..bd205a4 100644 --- a/tests/ExpressValidator.Tests/QuickValidatorTests.cs +++ b/tests/ExpressValidator.Tests/QuickValidatorTests.cs @@ -326,7 +326,7 @@ public void Should_Fail_When_Nullable_Struct_Is_Null_With_NotNull_Rule() public void Should_Fail_When_Nullable_Struct_Is_Null_With_Mixed_Rule() { var result = QuickValidator.Validate(null, - (opt) => + (opt) => opt .Null() .GreaterThan(10) @@ -535,7 +535,7 @@ private static Action v.RuleFor(o => o.I) .GreaterThan(0)) .ChildRules((v) => v.RuleFor(o => o.PercentValue1) - .InclusiveBetween(0, 100)); + .InclusiveBetween(0, 100)); } private static Action> GetNullRules() From bc40cb4f261c089c0f8360e0bcfbdc8ed1116474 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 12 Nov 2025 13:15:50 +0300 Subject: [PATCH 12/14] Edit README.md and NuGet.md. --- README.md | 3 ++- src/ExpressValidator/docs/NuGet.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d13121c..5a2c94a 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,8 @@ var result = QuickValidator.Validate( .ChildRules((v) => v.RuleFor(o => o.PercentValue1).InclusiveBetween(0, 100)), nameof(obj)); ``` -The `QuickValidator` also provides a `ValidateAsync` method for asynchronous validation. +The `QuickValidator` also provides a `ValidateAsync` method for asynchronous validation. +It is also tolerant of `null` values, i.e., it avoids exceptions when the input is null. ## 🧩 Nuances Of Using The Library diff --git a/src/ExpressValidator/docs/NuGet.md b/src/ExpressValidator/docs/NuGet.md index ad050bb..5a58503 100644 --- a/src/ExpressValidator/docs/NuGet.md +++ b/src/ExpressValidator/docs/NuGet.md @@ -144,7 +144,8 @@ var result = QuickValidator.Validate( .ChildRules((v) => v.RuleFor(o => o.PercentValue1).InclusiveBetween(0, 100)), nameof(obj)); ``` -The `QuickValidator` also provides a `ValidateAsync` method for asynchronous validation. +The `QuickValidator` also provides a `ValidateAsync` method for asynchronous validation. +It is also tolerant of `null` values, i.e., it avoids exceptions when the input is null. ## Nuances Of Using The Library From 2bbce880fd5ffdd4a6744b4e8c7d413b19d20ab9 Mon Sep 17 00:00:00 2001 From: kolan72 Date: Wed, 12 Nov 2025 14:55:33 +0300 Subject: [PATCH 13/14] Edit 'Key Features' in README.md and NuGet.md. --- README.md | 2 +- src/ExpressValidator/docs/NuGet.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a2c94a..9e2f329 100644 --- a/README.md +++ b/README.md @@ -18,7 +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 and easy validation using the `QuickValidator`. +- Provides quick and easy validation using `QuickValidator`, with built-in tolerance for `null` values. - Supports asynchronous validation. - Targets .NET Standard 2.0+ diff --git a/src/ExpressValidator/docs/NuGet.md b/src/ExpressValidator/docs/NuGet.md index 5a58503..dfa8de4 100644 --- a/src/ExpressValidator/docs/NuGet.md +++ b/src/ExpressValidator/docs/NuGet.md @@ -8,7 +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 and easy validation using the `QuickValidator`. +- Provides quick and easy validation using `QuickValidator`, with built-in tolerance for `null` values. - Supports asynchronous validation. - Targets .NET Standard 2.0+ From 738ad9e9adbf7d49ac671ed760724232418b1d4b Mon Sep 17 00:00:00 2001 From: kolan72 Date: Fri, 14 Nov 2025 16:49:00 +0300 Subject: [PATCH 14/14] Package 0.12.2 version and update CHANGELOG.md. --- CHANGELOG.md | 14 ++++++++++++++ src/ExpressValidator/ExpressValidator.csproj | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2ee3ed..740112a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.12.2 + +- Move the instance field `TypeValidatorBase._shouldBeComparedToNull` to a static readonly field (renamed to `_canBeNull`) to cache the reflection result per `TypeValidatorBase` type and eliminate redundant per-instance evaluations. +- Remove the eagerly allocated `NotNullValidationMessageProvider` during `ExpressValidatorBuilder` configuration, and instantiate `NotNullValidationMessageProvider` only when the value is null. +Deprecate `NotNullValidationMessageProvider.GetMessage(ValidationContext)`. +- Update to FluentValidation 12.1.0. +- Rename private `TypeValidatorBase.HasOnlyNullOrEmptyValidators` to `HasNonEmptyValidators` (inverting the boolean logic) and remove the negation of this property in the `ShouldValidate` method. +- DRY refactor of null validation in `TypeValidatorBase`. +- Add tests for null-tolerance validation in `QuickValidator`. +- Add test for validating a primitive type using `ExpressValidatorBuilder`. +- Add a unit test that verifies `ExpressValidator` does not throw when members are null and no null-related validators are used. +- Edit README.md and NuGet.md. + + ## 0.12.0 - Support .NET 8.0 and FluentValidation 12.0.0. diff --git a/src/ExpressValidator/ExpressValidator.csproj b/src/ExpressValidator/ExpressValidator.csproj index 70c1eb9..1aa3f89 100644 --- a/src/ExpressValidator/ExpressValidator.csproj +++ b/src/ExpressValidator/ExpressValidator.csproj @@ -3,7 +3,7 @@ netstandard2.0;net8.0 true - 0.12.0 + 0.12.2 true Andrey Kolesnichenko ExpressValidator is a library that provides the ability to validate objects using the FluentValidation library, but without object inheritance from `AbstractValidator`. @@ -15,7 +15,7 @@ ExpressValidator.png NuGet.md - 0.12.0.0 + 0.12.2.0 0.0.0.0