generated from Avanade/avanade-template
-
Notifications
You must be signed in to change notification settings - Fork 4
/
PropertyRuleBase.cs
119 lines (101 loc) · 4.63 KB
/
PropertyRuleBase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
using CoreEx.Localization;
using CoreEx.Validation.Clauses;
using CoreEx.Validation.Rules;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace CoreEx.Validation
{
/// <summary>
/// Represents a base validation rule for an entity property.
/// </summary>
/// <typeparam name="TEntity">The entity <see cref="Type"/>.</typeparam>
/// <typeparam name="TProperty">The property <see cref="Type"/>.</typeparam>
public abstract class PropertyRuleBase<TEntity, TProperty> : IPropertyRule<TEntity, TProperty> where TEntity : class
{
private readonly List<IValueRule<TEntity, TProperty>> _rules = [];
private readonly List<IPropertyRuleClause<TEntity>> _clauses = [];
/// <summary>
/// Initializes a new instance of the <see cref="PropertyRuleBase{TEntity, TProperty}"/> class.
/// </summary>
/// <param name="name">The property name.</param>
/// <param name="text">The friendly text name used in validation messages (defaults to <paramref name="name"/> as <see cref="Text.SentenceCase.ToSentenceCase(string)"/>).</param>
/// <param name="jsonName">The JSON property name (defaults to <paramref name="name"/>).</param>
protected PropertyRuleBase(string name, LText? text = null, string? jsonName = null)
{
Name = name.ThrowIfNullOrEmpty(nameof(name));
Text = text ?? Name.ToSentenceCase()!;
JsonName = string.IsNullOrEmpty(jsonName) ? Name : jsonName;
}
/// <inheritdoc/>
public string Name { get; internal set; }
/// <inheritdoc/>
public string JsonName { get; internal set; }
/// <inheritdoc/>
public virtual LText Text { get; set; }
/// <inheritdoc/>
public virtual LText? ErrorText { get; set; }
/// <inheritdoc/>
IPropertyRule<TEntity, TProperty> IPropertyRule<TEntity, TProperty>.WithMessage(LText errorText)
{
if (_rules.Count == 0)
ErrorText = errorText;
else
_rules.Last().ErrorText = errorText;
return this;
}
/// <inheritdoc/>
IPropertyRule<TEntity, TProperty> IPropertyRule<TEntity, TProperty>.AddRule(IValueRule<TEntity, TProperty> rule) => AddRule(rule);
/// <summary>
/// Adds a rule (<see cref="IValueRule{TEntity, TProperty}"/>) to the property.
/// </summary>
/// <param name="rule">The <see cref="IValueRule{TEntity, TProperty}"/>.</param>
/// <returns>The <see cref="PropertyRuleBase{TEntity, TProperty}"/>.</returns>
public PropertyRuleBase<TEntity, TProperty> AddRule(IValueRule<TEntity, TProperty> rule)
{
rule.ThrowIfNull(nameof(rule)).ErrorText ??= ErrorText; // Override the rule's error text where not already overridden.
_rules.Add(rule);
return this;
}
/// <summary>
/// Adds a clause (<see cref="IPropertyRuleClause{TEntity, TProperty}"/>) to the last rule added.
/// </summary>
/// <param name="clause">The <see cref="IPropertyRuleClause{TEntity, TProperty}"/>.</param>
public void AddClause(IPropertyRuleClause<TEntity> clause)
{
if (clause == null)
return;
if (_rules.Count == 0)
_clauses.Add(clause);
else
_rules.Last().AddClause(clause);
}
/// <summary>
/// Runs the configured clauses and rules.
/// </summary>
/// <param name="context">The <see cref="PropertyContext{TEntity, TProperty}"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
protected async Task InvokeAsync(PropertyContext<TEntity, TProperty> context, CancellationToken cancellationToken)
{
context.ThrowIfNull(nameof(context));
// Check all "this" clauses.
foreach (var clause in _clauses)
{
if (!clause.Check(context))
return;
}
// Check and execute all rules/clauses within the rules stack.
foreach (var rule in _rules)
{
if (rule.Check(context))
await rule.ValidateAsync(context, cancellationToken).ConfigureAwait(false);
// Stop validating after an error.
if (context.HasError || context.Parent.FailureResult.HasValue)
break;
}
}
}
}