generated from Avanade/avanade-template
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathPropertyContext.cs
244 lines (212 loc) · 10.7 KB
/
PropertyContext.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
using CoreEx.Abstractions.Reflection;
using CoreEx.Entities;
using CoreEx.Localization;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CoreEx.Validation
{
/// <summary>
/// Provides a validation context for a property.
/// </summary>
/// <typeparam name="TEntity">The entity <see cref="Type"/>.</typeparam>
/// <typeparam name="TProperty">The property <see cref="Type"/>.</typeparam>
public class PropertyContext<TEntity, TProperty> : IPropertyContext<TEntity, TProperty> where TEntity : class
{
private readonly bool _doNotAppendName = false;
/// <summary>
/// Initializes a new instance of the <see cref="PropertyContext{TEntity, TProperty}"/> class.
/// </summary>
/// <param name="context">The validation context for the parent entity.</param>
/// <param name="value">The property value.</param>
/// <param name="name">The property name.</param>
/// <param name="jsonName">The JSON property name.</param>
/// <param name="text">The property text.</param>
public PropertyContext(ValidationContext<TEntity> context, TProperty? value, string name, string? jsonName = null, LText? text = null)
{
Parent = context.ThrowIfNull(nameof(context));
Name = name.ThrowIfNullOrEmpty(nameof(name));
JsonName = jsonName ?? Name;
UseJsonName = context.UsedJsonNames;
Text = text ?? Name.ToSentenceCase()!;
Value = value;
FullyQualifiedPropertyName = CreateFullyQualifiedPropertyName(Name);
FullyQualifiedJsonPropertyName = CreateFullyQualifiedJsonPropertyName(JsonName);
}
/// <summary>
/// Initializes a new instance of the <see cref="PropertyContext{TEntity, TProperty}"/> class where the property is considered the value (impacts the fully-qualified names, etc.).
/// </summary>
/// <param name="text">The property text.</param>
/// <param name="context">The validation context for the parent entity.</param>
/// <param name="value">The property value.</param>
public PropertyContext(LText? text, ValidationContext<TEntity> context, TProperty? value)
{
Parent = context.ThrowIfNull(nameof(context));
FullyQualifiedPropertyName = Parent.FullyQualifiedEntityName ?? Validation.ValueNameDefault;
FullyQualifiedJsonPropertyName = Parent.FullyQualifiedJsonEntityName ?? Validation.ValueNameDefault;
Name = FullyQualifiedPropertyName.Split('.', StringSplitOptions.RemoveEmptyEntries).Last();
JsonName = FullyQualifiedJsonPropertyName.Split('.', StringSplitOptions.RemoveEmptyEntries).Last();
Text = text ?? Name.ToSentenceCase()!;
Value = value;
_doNotAppendName = true;
}
/// <summary>
/// Gets the <see cref="ExecutionContext.Current"/> instance.
/// </summary>
public CoreEx.ExecutionContext ExecutionContext => ExecutionContext.Current;
/// <summary>
/// Gets the <see cref="ValidationContext{TEntity}"/> for the parent entity.
/// </summary>
public ValidationContext<TEntity> Parent { get; }
/// <summary>
/// Gets the <see cref="IValidationContext"/> for the parent entity.
/// </summary>
IValidationContext IPropertyContext.Parent => Parent;
/// <summary>
/// Gets the property name.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the JSON property name.
/// </summary>
public string JsonName { get; }
/// <summary>
/// Gets the fully qualified property name.
/// </summary>
public string FullyQualifiedPropertyName { get; }
/// <summary>
/// Gets the fully qualified Json property name.
/// </summary>
public string FullyQualifiedJsonPropertyName { get; }
/// <summary>
/// Indicates whether to use the JSON property name for the <see cref="MessageItem"/> <see cref="MessageItem.Property"/>; by default (<c>false</c>) uses the .NET property name.
/// </summary>
public bool UseJsonName { get; }
/// <summary>
/// Gets the property text.
/// </summary>
public string Text { get; }
/// <summary>
/// Gets the property value.
/// </summary>
object? IPropertyContext.Value => Value;
/// <summary>
/// Gets the property value.
/// </summary>
public TProperty? Value { get; private set; }
/// <summary>
/// Indicates whether there has been a validation error.
/// </summary>
public bool HasError { get; internal set; }
/// <summary>
/// Enables the underlying value to be overridden (updated).
/// </summary>
/// <param name="value">The override value.</param>
public void OverrideValue(TProperty? value)
{
if (Comparer<TProperty?>.Default.Compare(Value, value) == 0)
return;
// Get the property info.
if (Parent.Value is ValidationValue<TProperty> vv)
{
if (vv.Entity != null)
{
var pr = TypeReflector.GetReflector(TypeReflectorArgs.Default, vv.Entity.GetType()).GetProperty(Name) ?? throw new InvalidOperationException($"Property '{Name}' does not exist for Type {Parent.EntityType.Name}.");
try
{
pr.PropertyExpression.SetValue(vv.Entity, value);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Type '{Parent.EntityType.Name}' Property '{Name}' value cannot be overridden: {ex.Message}", ex);
}
}
else
throw new InvalidOperationException("A value without a parent object owning the property value cannot be overridden.");
}
else
{
var pr = TypeReflector.GetReflector<TEntity>(TypeReflectorArgs.Default).GetProperty(Name) ?? throw new InvalidOperationException($"Property '{Name}' does not exist for Type {typeof(TEntity).Name}.");
try
{
pr.PropertyExpression.SetValue(Parent.Value, value);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Type '{typeof(TEntity).Name}' Property '{Name}' value cannot be overridden: {ex.Message}", ex);
}
}
Value = value;
}
/// <summary>
/// Creates a new <see cref="MessageType.Error"/> <see cref="MessageItem"/> with the specified format and <b>adds</b> to the underlying <see cref="IValidationContext"/>.
/// The friendly <see cref="Text"/> and <see cref="Value"/> are automatically passed as the first two arguments to the string formatter.
/// </summary>
/// <param name="format">The composite format string.</param>
/// <returns>A <see cref="MessageItem"/>.</returns>
public MessageItem CreateErrorMessage(LText format) => CreateErrorMessage(format, []);
/// <summary>
/// Creates a new <see cref="MessageType.Error"/> <see cref="MessageItem"/> with the specified format and additional values to be included in the text and <b>adds</b> to the underlying <see cref="IValidationContext"/>.
/// The friendly <see cref="Text"/> and <see cref="Value"/> are automatically passed as the first two arguments to the string formatter.
/// </summary>
/// <param name="format">The composite format string.</param>
/// <param name="values">The values that form part of the message text (<see cref="Text"/> and <see cref="Value"/> are automatically passed as the first two arguments to the string formatter).</param>
/// <returns>A <see cref="MessageItem"/>.</returns>
public MessageItem CreateErrorMessage(LText format, params object?[] values)
{
HasError = true;
var fVals = (new string[] { Text, Value?.ToString()! }).Concat(values).ToArray();
if (_doNotAppendName)
return Parent.AddMessage(MessageType.Error, format, fVals);
else
return Parent.AddMessage(Name, JsonName, MessageType.Error, format, fVals);
}
/// <summary>
/// Creates a fully qualified property name for the name.
/// </summary>
/// <param name="name">The property name.</param>
public string CreateFullyQualifiedPropertyName(string name) => (Parent.FullyQualifiedEntityName == null) ? name : (Parent.FullyQualifiedEntityName + (name.StartsWith('[') ? "" : ".") + name);
/// <summary>
/// Creates a fully qualified JSON property name for the name.
/// </summary>
/// <param name="name">The property name.</param>
public string CreateFullyQualifiedJsonPropertyName(string name) => (Parent.FullyQualifiedJsonEntityName == null) ? name : (Parent.FullyQualifiedJsonEntityName + (name.StartsWith('[') ? "" : ".") + name);
/// <summary>
/// Creates a new <see cref="ValidationArgs"/> from the <see cref="PropertyContext{TEntity, TProperty}"/>.
/// </summary>
/// <returns>A <see cref="ValidationArgs"/>.</returns>
public ValidationArgs CreateValidationArgs()
{
var args = new ValidationArgs
{
FullyQualifiedEntityName = FullyQualifiedPropertyName,
FullyQualifiedJsonEntityName = FullyQualifiedJsonPropertyName,
SelectedPropertyName = Parent?.SelectedPropertyName,
UseJsonNames = Parent?.UsedJsonNames
};
// Copy the configuration values; do not allow the higher-level dictionaries (stack) to be extended by lower-level validators.
if (Parent?.Config != null)
{
args.Config ??= new Dictionary<string, object?>();
foreach (var cfg in Parent.Config)
{
args.Config.Add(cfg.Key, cfg.Value);
}
}
return args;
}
/// <summary>
/// Merges a validation result into this.
/// </summary>
/// <param name="context">The <see cref="IValidationContext"/> to merge.</param>
public void MergeResult(IValidationContext context)
{
if (context == null)
return;
if (context.HasErrors)
HasError = true;
Parent.MergeResult(context);
}
}
}