Skip to content

Commit 0b98a36

Browse files
authored
Merge pull request #50 from kolan72/dev
Update main before release.
2 parents 62f362e + b33ebfb commit 0b98a36

12 files changed

Lines changed: 455 additions & 37 deletions

File tree

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ ExpressValidator is a library that provides the ability to validate objects usin
1818
- Supports adding a property or field for validation.
1919
- Verifies that a property expression is a property and a field expression is a field, and throws `ArgumentException` if it is not.
2020
- Supports adding a `Func` that provides a value for validation.
21+
- Provides quick validation (refers to ease of use).
2122
- Supports asynchronous validation.
2223
- Targets .NET Standard 2.0+
2324

@@ -129,6 +130,36 @@ if(!result2.IsValid)
129130
}
130131
```
131132

133+
## ⏩ Quick Validation
134+
135+
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:
136+
137+
```csharp
138+
var value = 5;
139+
// result.IsValid == false
140+
// result.Errors[0].PropertyName == "value"
141+
var result = QuickValidator.Validate(
142+
value,
143+
(opt) => opt.GreaterThan(10),
144+
nameof(value));
145+
```
146+
147+
For complex types, use FluentValidation's `ChildRules` method:
148+
149+
```csharp
150+
var obj = new ObjToValidate() { I = -1, PercentValue1 = 101 };
151+
// result.IsValid == false
152+
// result.Errors.Count == 2
153+
// result.Errors[0].PropertyName == "obj.I"; result.Errors[1].PropertyName == "obj.PercentValue1"
154+
var result = QuickValidator.Validate(
155+
obj,
156+
(opt) =>
157+
opt
158+
.ChildRules((v) => v.RuleFor(o => o.I).GreaterThan(0))
159+
.ChildRules((v) => v.RuleFor(o => o.PercentValue1).InclusiveBetween(0, 100)),
160+
nameof(obj));
161+
```
162+
132163
## 🧩 Nuances Of Using The Library
133164

134165
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`.

src/ExpressValidator/ExpressValidator.TOptions.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ namespace ExpressValidator
1414
/// <typeparam name="TOptions"></typeparam>
1515
public class ExpressValidator<TObj, TOptions> : IExpressValidator<TObj>
1616
{
17-
private readonly TOptions _options;
1817
private readonly IEnumerable<IObjectValidator<TObj, TOptions>> _validators;
1918
private readonly OnFirstPropertyValidatorFailed _validationMode;
2019

2120
internal ExpressValidator(TOptions options, IEnumerable<IObjectValidator<TObj, TOptions>> validators, OnFirstPropertyValidatorFailed validationMode)
2221
{
23-
_options = options;
2422
_validators = validators;
2523
_validationMode = validationMode;
24+
25+
foreach (var validator in _validators)
26+
{
27+
validator.ApplyOptions(options);
28+
}
2629
}
2730

2831
public ValidationResult Validate(TObj obj)
@@ -44,8 +47,6 @@ private async Task<ValidationResult> ValidateWithBreakAsync(TObj obj, Cancellati
4447
foreach (var validator in _validators)
4548
{
4649
token.ThrowIfCancellationRequested();
47-
48-
validator.ApplyOptions(_options);
4950
var (IsValid, Failures) = await validator.ValidateAsync(obj, token).ConfigureAwait(false);
5051
if (!IsValid)
5152
{
@@ -61,8 +62,6 @@ private async Task<ValidationResult> ValidateWithContinueAsync(TObj obj, Cancell
6162
foreach (var validator in _validators)
6263
{
6364
token.ThrowIfCancellationRequested();
64-
65-
validator.ApplyOptions(_options);
6665
var (IsValid, Failures) = await validator.ValidateAsync(obj, token);
6766
if (!IsValid)
6867
{
@@ -76,7 +75,6 @@ private ValidationResult ValidateWithBreak(TObj obj)
7675
{
7776
foreach (var validator in _validators)
7877
{
79-
validator.ApplyOptions(_options);
8078
var (IsValid, Failures) = validator.Validate(obj);
8179
if (!IsValid)
8280
{
@@ -91,7 +89,6 @@ private ValidationResult ValidateWithContinue(TObj obj)
9189
var currentFailures = new List<ValidationFailure>();
9290
foreach (var validator in _validators)
9391
{
94-
validator.ApplyOptions(_options);
9592
var (IsValid, Failures) = validator.Validate(obj);
9693
if (!IsValid)
9794
{

src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal class ExpressPropertyValidator<TObj, TOptions, T> : IExpressPropertyVal
1111
{
1212
private readonly string _propName;
1313
private readonly Func<TObj, T> _propertyFunc;
14+
private TypeValidatorBase<T> _typeValidator;
1415

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

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

3132
public Task<(bool IsValid, List<ValidationFailure> Failures)> ValidateAsync(TObj obj, CancellationToken token = default)
3233
{
33-
var typeValidator = new TypeAsyncValidator<T>();
34-
typeValidator.SetValidation(_action, _propName);
35-
return typeValidator.ValidateExAsync(_propertyFunc(obj), token);
34+
return _typeValidator.ValidateExAsync(_propertyFunc(obj), token);
3635
}
3736

3837
public (bool IsValid, List<ValidationFailure> Failures) Validate(TObj obj)
@@ -41,14 +40,26 @@ public void SetValidation(Action<TOptions, IRuleBuilderOptions<T, T>> action)
4140
{
4241
throw new InvalidOperationException();
4342
}
44-
var typeValidator = new TypeValidator<T>();
45-
typeValidator.SetValidation(_action, _propName);
46-
return typeValidator.ValidateEx(_propertyFunc(obj));
43+
return _typeValidator.ValidateEx(_propertyFunc(obj));
4744
}
4845

4946
public void ApplyOptions(TOptions options)
5047
{
5148
_action =_actionWithOptions.Apply(options);
49+
SetTypeValidator();
50+
}
51+
52+
private void SetTypeValidator()
53+
{
54+
if (IsAsync)
55+
{
56+
_typeValidator = new TypeAsyncValidator<T>();
57+
}
58+
else
59+
{
60+
_typeValidator = new TypeValidator<T>();
61+
}
62+
_typeValidator.SetValidation(_action, _propName);
5263
}
5364

5465
public bool IsAsync { get; }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace ExpressValidator.QuickValidation
2+
{
3+
/// <summary>
4+
/// Determines how the property name is set when quick validation fails.
5+
/// </summary>
6+
public enum PropertyNameMode
7+
{
8+
/// <summary>
9+
/// Use the literal string "Input" as the property name.
10+
/// </summary>
11+
Default,
12+
13+
/// <summary>
14+
/// Use the type's name (typeof(T).Name) of the validated object as the property name.
15+
/// </summary>
16+
TypeName
17+
}
18+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using ExpressValidator.Extensions;
2+
using FluentValidation;
3+
using FluentValidation.Results;
4+
using System;
5+
6+
namespace ExpressValidator.QuickValidation
7+
{
8+
/// <summary>
9+
/// Provides methods for quick validation.
10+
/// </summary>
11+
public static class QuickValidator
12+
{
13+
private const string FALLBACK_PROP_NAME = "Input";
14+
15+
/// <summary>
16+
/// Validates the given object instance using <paramref name="action"/>.
17+
/// </summary>
18+
/// <typeparam name="T">The type of the object to validate.</typeparam>
19+
/// <param name="obj">The object to validate.</param>
20+
/// <param name="action">Action to add validators.</param>
21+
/// <param name="propName">The name of the property if the validation fails.
22+
/// If <see langword="null"/>, "Input" will be used.</param>
23+
/// <param name="onSuccessValidation">Specifies a method to execute when validation succeeds.</param>
24+
/// <returns></returns>
25+
public static ValidationResult Validate<T>(T obj, Action<IRuleBuilderOptions<T, T>> action, string propName, Action<T> onSuccessValidation = null)
26+
{
27+
return ValidateInner<T>(obj, action, propName ?? FALLBACK_PROP_NAME, onSuccessValidation);
28+
}
29+
30+
/// <summary>
31+
/// Validates the given object instance using <paramref name="action"/>.
32+
/// </summary>
33+
/// <typeparam name="T">The type of the object to validate.</typeparam>
34+
/// <param name="obj">The object to validate.</param>
35+
/// <param name="action">Action to add validators.</param>
36+
/// <param name="mode"><see cref="PropertyNameMode"/>.
37+
/// If <see cref="PropertyNameMode.Default"/>, "Input" will be used.</param>
38+
/// <param name="onSuccessValidation">Specifies a method to execute when validation succeeds.</param>
39+
/// <returns></returns>
40+
public static ValidationResult Validate<T>(T obj, Action<IRuleBuilderOptions<T, T>> action, PropertyNameMode mode = PropertyNameMode.Default, Action<T> onSuccessValidation = null)
41+
{
42+
return ValidateInner<T>(obj, action, GetPropName<T>(mode), onSuccessValidation);
43+
}
44+
45+
private static ValidationResult ValidateInner<T>(T obj, Action<IRuleBuilderOptions<T, T>> action, string propName, Action<T> onSuccessValidation = null)
46+
{
47+
var eb = new ExpressValidatorBuilder<Unit>();
48+
return eb.AddFunc((_) => obj,
49+
propName,
50+
onSuccessValidation)
51+
.WithValidation(action)
52+
.BuildAndValidate(Unit.Default);
53+
}
54+
55+
private static string GetPropName<T>(PropertyNameMode mode) => mode == PropertyNameMode.Default ? FALLBACK_PROP_NAME : typeof(T).Name;
56+
}
57+
}

src/ExpressValidator/Unit.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace ExpressValidator
2+
{
3+
public readonly struct Unit
4+
{
5+
public static readonly Unit Default = new Unit();
6+
}
7+
}

src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public IBuilderWithPropValidator<TObj, T> AddField<T>(Expression<Func<TObj, T>>
5050
/// <typeparam name="T">A type of value.</typeparam>
5151
/// <param name="func">Func for object</param>
5252
/// <param name="propName">A name of the property if the validation failed.</param>
53-
/// <param name="onSuccessValidation">Func Result Validation Success Handler</param>
53+
/// <param name="onSuccessValidation">Specifies a method to execute when validation succeeds.</param>
5454
/// <returns></returns>
5555
public IBuilderWithPropValidator<TObj, T> AddFunc<T>(Func<TObj, T> func, string propName, Action<T> onSuccessValidation = null)
5656
{

src/ExpressValidator/docs/NuGet.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ExpressValidator is a library that provides the ability to validate objects usin
88
- Supports adding a property or field for validation.
99
- Verifies that a property expression is a property and a field expression is a field, and throws `ArgumentException` if it is not.
1010
- Supports adding a `Func` that provides a value for validation.
11+
- Provides quick validation (refers to ease of use).
1112
- Supports asynchronous validation.
1213
- Targets .NET Standard 2.0+
1314

@@ -50,6 +51,7 @@ if(!result.IsValid)
5051
//As usual with validation result...
5152
}
5253
```
54+
5355
## Modifying FluentValidation Validator Parameters Using Options
5456

5557
To dynamically change the parameters of the `FluentValidation` validators:
@@ -112,3 +114,46 @@ if(!result2.IsValid)
112114
...
113115
}
114116
```
117+
118+
## Quick Validation
119+
120+
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:
121+
122+
```csharp
123+
var value = 5;
124+
// result.IsValid == false
125+
// result.Errors[0].PropertyName == "value"
126+
var result = QuickValidator.Validate(
127+
value,
128+
(opt) => opt.GreaterThan(10),
129+
nameof(value));
130+
```
131+
132+
For complex types, use FluentValidation's `ChildRules` method:
133+
134+
```csharp
135+
var obj = new ObjToValidate() { I = -1, PercentValue1 = 101 };
136+
// result.IsValid == false
137+
// result.Errors.Count == 2
138+
// result.Errors[0].PropertyName == "obj.I"; result.Errors[1].PropertyName == "obj.PercentValue1"
139+
var result = QuickValidator.Validate(
140+
obj,
141+
(opt) =>
142+
opt
143+
.ChildRules((v) => v.RuleFor(o => o.I).GreaterThan(0))
144+
.ChildRules((v) => v.RuleFor(o => o.PercentValue1).InclusiveBetween(0, 100)),
145+
nameof(obj));
146+
```
147+
148+
## Nuances Of Using The Library
149+
150+
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`.
151+
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):
152+
```csharp
153+
// result.Errors[0].PropertyName == "percentSum"
154+
var result = new ExpressValidatorBuilder<ObjToValidate>()
155+
.AddFunc(o => o.PercentValue1 + o.PercentValue2, "sum")
156+
.WithValidation((o) => o.InclusiveBetween(0, 100)
157+
.OverridePropertyName("percentSum"))
158+
.BuildAndValidate(new ObjToValidate() { PercentValue1 = 200});
159+
```

tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,22 @@
5252
<HintPath>..\..\packages\NUnit.4.3.2\lib\net462\nunit.framework.legacy.dll</HintPath>
5353
</Reference>
5454
<Reference Include="System" />
55-
<Reference Include="System.Buffers, Version=4.0.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
56-
<HintPath>..\..\packages\System.Buffers.4.6.0\lib\net462\System.Buffers.dll</HintPath>
55+
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
56+
<HintPath>..\..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
5757
</Reference>
5858
<Reference Include="System.Core" />
59-
<Reference Include="System.Memory, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
60-
<HintPath>..\..\packages\System.Memory.4.6.0\lib\net462\System.Memory.dll</HintPath>
59+
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
60+
<HintPath>..\..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
6161
</Reference>
6262
<Reference Include="System.Numerics" />
63-
<Reference Include="System.Numerics.Vectors, Version=4.1.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
64-
<HintPath>..\..\packages\System.Numerics.Vectors.4.6.0\lib\net462\System.Numerics.Vectors.dll</HintPath>
63+
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
64+
<HintPath>..\..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
6565
</Reference>
66-
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
67-
<HintPath>..\..\packages\System.Runtime.CompilerServices.Unsafe.6.1.0\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
66+
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
67+
<HintPath>..\..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
6868
</Reference>
69-
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
70-
<HintPath>..\..\packages\System.Threading.Tasks.Extensions.4.6.0\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
71-
</Reference>
72-
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
73-
<HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
69+
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
70+
<HintPath>..\..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
7471
</Reference>
7572
<Reference Include="System.Xml.Linq" />
7673
<Reference Include="System.Data.DataSetExtensions" />
@@ -84,6 +81,7 @@
8481
<Compile Include="ExpressValidatorExtensionsTests.cs" />
8582
<Compile Include="ExpressValidatorWithOptionsTests.cs" />
8683
<Compile Include="ExpressValidatorWithOptionsTests.ForAsync.Tests.cs" />
84+
<Compile Include="QuickValidatorTests.cs" />
8785
<Compile Include="TypeAsyncValidatorTests.cs" />
8886
<Compile Include="TypeAsyncValidatorTests.ForNullOrEmptyValidator.cs" />
8987
<Compile Include="ExpressAsyncValidatorTests.cs" />
@@ -115,5 +113,7 @@
115113
</PropertyGroup>
116114
<Error Condition="!Exists('..\..\packages\NUnit3TestAdapter.4.6.0\build\net462\NUnit3TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\NUnit3TestAdapter.4.6.0\build\net462\NUnit3TestAdapter.props'))" />
117115
<Error Condition="!Exists('..\..\packages\NUnit.4.3.2\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\NUnit.4.3.2\build\NUnit.props'))" />
116+
<Error Condition="!Exists('..\..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
118117
</Target>
118+
<Import Project="..\..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('..\..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
119119
</Project>

0 commit comments

Comments
 (0)