Skip to content

Commit 929adec

Browse files
authored
v3.16.0 (#96)
* FluentValidator compatibility. * StringSyntaxAttribute support. * Added EventPublisherHealthCheck. StringSyntaxAttribute conditional compile. Update Validation doco. Additional Validation tests.
1 parent d999488 commit 929adec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1515
-92
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
Represents the **NuGet** versions.
44

5+
## v3.16.0
6+
- *Enhancement*: Added basic [FluentValidator](https://docs.fluentvalidation.net/en/latest/) compatibility to the `CoreEx.Validation` by supporting _key_ (common) named capabilities:
7+
- `AbstractValidator<T>` added as a wrapper for `Validator<T>`; with both supporting `RuleFor` method (wrapper for existing `Property`).
8+
- `NotEmpty`, `NotNull`, `Empty`, `Null`, `InclusiveBetween`, `ExclusiveBetween`, `Equal`, `NotEqual`, `LessThan`, `LessThanOrEqualTo`, `GreaterThan`, `GreaterThanOrEqualTo`, `Matches`, `Length`, `MinimumLength`, `MaximumLength`, `PrecisionScale`, `EmailAddress` and `IsInEnum` extension methods added (invoking existing equivalents).
9+
- `NullRule` and `NotNullRule` added to support the `Null` and `NotNull` capabilities specifically.
10+
- `WithMessage` added to explcitly set the error message for a preceeding `IValueRule` (equivalent to specifying when invoking extension method).
11+
- `ValidatorStrings` have had their fallback texts added to ensure an appropriate text is output where `ITextProvider` is not available.
12+
- _Note:_ The above changes are to achieve a basic level of compatibility, they are not intended to implement the full capabilities of _FluentValidation_; nor, will it ever. The `CoreEx.FluentValidation` enables _FluentValidation_ to be used directly where required; also, the existing `CoreEx.Validation.InteropRule` enables interoperability between the two.
13+
- *Enhancement*: Added `StringSyntaxAttribute` support to improve intellisense for JSON and URI specification.
14+
- *Enhancement*: Added `EventPublisherHealthCheck` that will send an `EventData` message to verify that the `IEventPublisher` is functioning correctly.
15+
- _Note:_ only use where the corresponding subscriber(s)/consumer(s) are aware and can ignore/filter to avoid potential downstream challenges.
16+
517
## v3.15.0
618
- *Enhancement*: This is a clean-up version to remove all obsolete code and dependencies. This will result in a number of minor breaking changes, but will ensure that the codebase is up-to-date and maintainable.
719
- As per [`v3.14.0`](#v3.14.0) the previously obsoleted `TypedHttpClientBase` methods `WithRetry`, `WithTimeout`, `WithCustomRetryPolicy` and `WithMaxRetryDelay` are now removed; including `TypedHttpClientOptions`, `HttpRequestLogger` and related `SettingsBase` capabilities.

Common.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>3.15.0</Version>
3+
<Version>3.16.0</Version>
44
<LangVersion>preview</LangVersion>
55
<Authors>Avanade</Authors>
66
<Company>Avanade</Company>

samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</ItemGroup>
2222

2323
<ItemGroup>
24-
<PackageReference Include="DbEx.SqlServer" Version="2.5.0" />
24+
<PackageReference Include="DbEx.SqlServer" Version="2.5.1" />
2525
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
2626
</ItemGroup>
2727

src/CoreEx.AspNetCore/HealthChecks/HealthReportStatusWriter.cs

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ public static async Task WriteJson(HttpContext context, HealthReport healthRepor
7171
{
7272
var settings = ExecutionContext.GetService<SettingsBase>();
7373
if (settings is not null && settings.IncludeExceptionInResult)
74+
jsonWriter.WriteString("exception", e.Value.Exception?.ToString());
75+
else
7476
jsonWriter.WriteString("exception", e.Value.Exception?.Message);
7577
}
7678

src/CoreEx.Newtonsoft/Json/JsonSerializer.cs

+18-3
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,28 @@ public BinaryData SerializeToBinaryData<T>(T value, JsonWriteFormat? format = nu
6767
}
6868

6969
/// <inheritdoc/>
70-
public object? Deserialize(string json) => Deserialize(BinaryData.FromString(json));
70+
#if NET7_0_OR_GREATER
71+
public object? Deserialize([StringSyntax(StringSyntaxAttribute.Json)] string json)
72+
#else
73+
public object? Deserialize(string json)
74+
#endif
75+
=> Deserialize(BinaryData.FromString(json));
7176

7277
/// <inheritdoc/>
73-
public object? Deserialize(string json, Type type) => Deserialize(BinaryData.FromString(json), type);
78+
#if NET7_0_OR_GREATER
79+
public object? Deserialize([StringSyntax(StringSyntaxAttribute.Json)] string json, Type type)
80+
#else
81+
public object? Deserialize(string json, Type type)
82+
#endif
83+
=> Deserialize(BinaryData.FromString(json), type);
7484

7585
/// <inheritdoc/>
76-
public T? Deserialize<T>(string json) =>Deserialize<T>(BinaryData.FromString(json))!;
86+
#if NET7_0_OR_GREATER
87+
public T? Deserialize<T>([StringSyntax(StringSyntaxAttribute.Json)] string json)
88+
#else
89+
public T? Deserialize<T>(string json)
90+
#endif
91+
=> Deserialize<T>(BinaryData.FromString(json))!;
7792

7893
/// <inheritdoc/>
7994
public object? Deserialize(BinaryData json) => Deserialize<dynamic>(json);

src/CoreEx.UnitTesting.NUnit/CoreEx.UnitTesting.NUnit.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<Import Project="..\..\Common.targets" />
1313

1414
<ItemGroup>
15-
<PackageReference Include="UnitTestEx.NUnit" Version="4.3.0" />
15+
<PackageReference Include="UnitTestEx.NUnit" Version="4.3.1" />
1616
</ItemGroup>
1717

1818
<ItemGroup>

src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
<ItemGroup>
2121
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
22-
<PackageReference Include="UnitTestEx" Version="4.3.0" />
22+
<PackageReference Include="UnitTestEx" Version="4.3.1" />
2323
</ItemGroup>
2424

2525
</Project>

src/CoreEx.UnitTesting/UnitTestExExtensions.cs

+30-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Microsoft.Extensions.DependencyInjection;
1717
using Microsoft.Extensions.Logging;
1818
using System;
19+
using System.Diagnostics.CodeAnalysis;
1920
using System.Linq.Expressions;
2021
using System.Net.Http;
2122
using System.Net.Mime;
@@ -102,7 +103,11 @@ public static IServiceScope CreateClientScope<TAgent>(this HttpTesterBase tester
102103
/// <param name="requestUri">The requuest uri.</param>
103104
/// <param name="requestOptions">The optional <see cref="Ceh.HttpRequestOptions"/>.</param>
104105
/// <returns>The <see cref="HttpRequest"/>.</returns>
106+
#if NET7_0_OR_GREATER
107+
public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, Ceh.HttpRequestOptions? requestOptions = null)
108+
#else
105109
public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, string? requestUri, Ceh.HttpRequestOptions? requestOptions = null)
110+
#endif
106111
where TEntryPoint : class, new() where TSelf : FunctionTesterBase<TEntryPoint, TSelf>
107112
=> tester.CreateHttpRequest(httpMethod, requestUri).ApplyRequestOptions(requestOptions);
108113

@@ -115,7 +120,11 @@ public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTes
115120
/// <param name="requestOptions">The optional <see cref="Ceh.HttpRequestOptions"/>.</param>
116121
/// <param name="requestModifier">The optional <see cref="HttpRequest"/> modifier.</param>
117122
/// <returns>The <see cref="HttpRequest"/>.</returns>
123+
#if NET7_0_OR_GREATER
124+
public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, Ceh.HttpRequestOptions? requestOptions = null, Action<HttpRequest>? requestModifier = null)
125+
#else
118126
public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, string? requestUri, Ceh.HttpRequestOptions? requestOptions = null, Action<HttpRequest>? requestModifier = null)
127+
#endif
119128
where TEntryPoint : class, new() where TSelf : FunctionTesterBase<TEntryPoint, TSelf>
120129
=> tester.CreateHttpRequest(httpMethod, requestUri, requestModifier).ApplyRequestOptions(requestOptions);
121130

@@ -128,7 +137,11 @@ public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTes
128137
/// <param name="body">The optional body content.</param>
129138
/// <param name="requestOptions">The optional <see cref="Ceh.HttpRequestOptions"/>.</param>
130139
/// <returns>The <see cref="HttpRequest"/>.</returns>
140+
#if NET7_0_OR_GREATER
141+
public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, string? body, Ceh.HttpRequestOptions? requestOptions = null)
142+
#else
131143
public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, string? requestUri, string? body, Ceh.HttpRequestOptions? requestOptions = null)
144+
#endif
132145
where TEntryPoint : class, new() where TSelf : FunctionTesterBase<TEntryPoint, TSelf>
133146
=> tester.CreateHttpRequest(httpMethod, requestUri, body, null, null).ApplyRequestOptions(requestOptions);
134147

@@ -142,7 +155,11 @@ public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTes
142155
/// <param name="contentType">The content type. Defaults to <see cref="MediaTypeNames.Text.Plain"/>.</param>
143156
/// <param name="requestOptions">The optional <see cref="Ceh.HttpRequestOptions"/>.</param>
144157
/// <returns>The <see cref="HttpRequest"/>.</returns>
158+
#if NET7_0_OR_GREATER
159+
public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, string? body, string? contentType, Ceh.HttpRequestOptions? requestOptions = null)
160+
#else
145161
public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, string? requestUri, string? body, string? contentType, Ceh.HttpRequestOptions? requestOptions = null)
162+
#endif
146163
where TEntryPoint : class, new() where TSelf : FunctionTesterBase<TEntryPoint, TSelf>
147164
=> tester.CreateHttpRequest(httpMethod, requestUri, body, contentType, null).ApplyRequestOptions(requestOptions);
148165

@@ -155,7 +172,11 @@ public static HttpRequest CreateHttpRequest<TEntryPoint, TSelf>(this FunctionTes
155172
/// <param name="value">The value to JSON serialize.</param>
156173
/// <param name="requestOptions">The optional <see cref="Ceh.HttpRequestOptions"/> modifier.</param>
157174
/// <returns>The <see cref="HttpRequest"/>.</returns>
175+
#if NET7_0_OR_GREATER
176+
public static HttpRequest CreateJsonHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, object? value, Ceh.HttpRequestOptions? requestOptions)
177+
#else
158178
public static HttpRequest CreateJsonHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, string? requestUri, object? value, Ceh.HttpRequestOptions? requestOptions)
179+
#endif
159180
where TEntryPoint : class, new() where TSelf : FunctionTesterBase<TEntryPoint, TSelf>
160181
=> tester.CreateJsonHttpRequest(httpMethod, requestUri, value).ApplyRequestOptions(requestOptions);
161182

@@ -169,7 +190,11 @@ public static HttpRequest CreateJsonHttpRequest<TEntryPoint, TSelf>(this Functio
169190
/// <param name="requestOptions">The optional <see cref="Ceh.HttpRequestOptions"/> modifier.</param>
170191
/// <param name="requestModifier">The optional <see cref="HttpRequest"/> modifier.</param>
171192
/// <returns>The <see cref="HttpRequest"/>.</returns>
193+
#if NET7_0_OR_GREATER
194+
public static HttpRequest CreateJsonHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, object? value, Ceh.HttpRequestOptions? requestOptions, Action<HttpRequest>? requestModifier = null)
195+
#else
172196
public static HttpRequest CreateJsonHttpRequest<TEntryPoint, TSelf>(this FunctionTesterBase<TEntryPoint, TSelf> tester, HttpMethod httpMethod, string? requestUri, object? value, Ceh.HttpRequestOptions? requestOptions, Action<HttpRequest>? requestModifier = null)
197+
#endif
173198
where TEntryPoint : class, new() where TSelf : FunctionTesterBase<TEntryPoint, TSelf>
174199
=> tester.CreateJsonHttpRequest(httpMethod, requestUri, value, requestModifier).ApplyRequestOptions(requestOptions);
175200

@@ -199,7 +224,11 @@ public static ActionResultAssertor AssertETagHeader(this ActionResultAssertor as
199224
/// <param name="assertor">The assertor.</param>
200225
/// <param name="expectedUri">The expected <see cref="Uri"/>.</param>
201226
/// <returns>The <see cref="ActionResultAssertor"/> to support fluent-style method-chaining.</returns>
227+
#if NET7_0_OR_GREATER
228+
public static ActionResultAssertor AssertLocationHeader(this ActionResultAssertor assertor, [StringSyntax(StringSyntaxAttribute.Uri)] Uri expectedUri)
229+
#else
202230
public static ActionResultAssertor AssertLocationHeader(this ActionResultAssertor assertor, Uri expectedUri)
231+
#endif
203232
{
204233
if (assertor.Result != null && assertor.Result is ValueContentResult vcr)
205234
assertor.Owner.Implementor.AssertAreEqual(expectedUri, vcr.Location, $"Expected and Actual {nameof(ValueContentResult.Location)} values are not equal.");
@@ -256,7 +285,7 @@ public static ActionResultAssertor AssertLocationHeaderContains(this ActionResul
256285
public static ActionResultAssertor AssertLocationHeader<TValue>(this ActionResultAssertor assertor, Func<TValue, string> expected)
257286
=> assertor.AssertLocationHeaderContains(expected.Invoke(assertor.GetValue<TValue>()!));
258287

259-
#endregion
288+
#endregion
260289

261290
#region GenericTesterBase
262291

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
2+
3+
using System;
4+
5+
namespace CoreEx.Validation
6+
{
7+
/// <summary>
8+
/// Represents the base entity validator using <see href="https://docs.fluentvalidation.net/en/latest/">FluentValidation</see> syntax.
9+
/// </summary>
10+
/// <typeparam name="TEntity">The entity <see cref="Type"/>.</typeparam>
11+
/// <remarks>This is a synonym for the <see cref="Validator{TEntity}"/>.</remarks>
12+
public abstract class AbstractValidator<TEntity> : Validator<TEntity> where TEntity : class { }
13+
}

src/CoreEx.Validation/CollectionValidator.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
22

3-
using CoreEx.Abstractions.Reflection;
43
using CoreEx.Localization;
54
using CoreEx.Results;
65
using CoreEx.Validation.Rules;

src/CoreEx.Validation/DictionaryValidator.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
22

3-
using CoreEx.Abstractions.Reflection;
43
using CoreEx.Localization;
54
using CoreEx.Results;
65
using CoreEx.Validation.Rules;

src/CoreEx.Validation/IPropertyRule.cs

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ public interface IPropertyRule
2626
/// </summary>
2727
public LText Text { get; set; }
2828

29+
/// <summary>
30+
/// Gets or sets the error message format text (overrides the default) used for all validation errors.
31+
/// </summary>
32+
public LText? ErrorText { get; set; }
33+
2934
/// <summary>
3035
/// Executes the validation for the property value.
3136
/// </summary>

src/CoreEx.Validation/IPropertyRuleT2.cs

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
22

3+
using CoreEx.Localization;
34
using CoreEx.Validation.Clauses;
45
using CoreEx.Validation.Rules;
56
using System;
@@ -40,5 +41,12 @@ public IPropertyRule<TEntity, TProperty> DependsOn<TDependsProperty>(Expression<
4041
AddClause(new DependsOnClause<TEntity, TDependsProperty>(expression));
4142
return this;
4243
}
44+
45+
/// <summary>
46+
/// Sets the <see cref="ValueRuleBase{TEntity, TProperty}.ErrorText"/> for the last <see cref="AddRule(IValueRule{TEntity, TProperty})">rule</see> added.
47+
/// </summary>
48+
/// <param name="errorText">The error message format text.</param>
49+
/// <returns>The <see cref="PropertyRule{TEntity, TProperty}"/>.</returns>
50+
IPropertyRule<TEntity, TProperty> WithMessage(LText errorText);
4351
}
4452
}

src/CoreEx.Validation/PropertyRule.cs

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
22

33
using CoreEx.Abstractions.Reflection;
4+
using CoreEx.Localization;
45
using CoreEx.Validation.Clauses;
56
using CoreEx.Validation.Rules;
67
using System;
@@ -63,6 +64,11 @@ public async Task ValidateAsync(ValidationContext<TEntity> context, Cancellation
6364
return this;
6465
}
6566

67+
/// <summary>
68+
/// Gets or sets the error message format text (overrides the default).
69+
/// </summary>
70+
LText? IValueRule<TEntity, TProperty>.ErrorText { get => throw new NotSupportedException("ErrorText should not bet set directly on a PropertyRule."); set => throw new NotSupportedException("ErrorText should not bet set directly on a PropertyRule."); }
71+
6672
/// <inheritdoc/>
6773
void IValueRule<TEntity, TProperty>.AddClause(IPropertyRuleClause<TEntity> clause) => AddClause(clause);
6874

src/CoreEx.Validation/PropertyRuleBase.cs

+16
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ protected PropertyRuleBase(string name, LText? text = null, string? jsonName = n
4343
/// <inheritdoc/>
4444
public LText Text { get; set; }
4545

46+
/// <inheritdoc/>
47+
public LText? ErrorText { get; set; }
48+
49+
/// <inheritdoc/>
50+
IPropertyRule<TEntity, TProperty> IPropertyRule<TEntity, TProperty>.WithMessage(LText errorText)
51+
{
52+
if (_rules.Count == 0)
53+
ErrorText = errorText;
54+
else
55+
_rules.Last().ErrorText = errorText;
56+
57+
return this;
58+
}
59+
4660
/// <inheritdoc/>
4761
IPropertyRule<TEntity, TProperty> IPropertyRule<TEntity, TProperty>.AddRule(IValueRule<TEntity, TProperty> rule) => AddRule(rule);
4862

@@ -53,6 +67,8 @@ protected PropertyRuleBase(string name, LText? text = null, string? jsonName = n
5367
/// <returns>The <see cref="PropertyRuleBase{TEntity, TProperty}"/>.</returns>
5468
public PropertyRuleBase<TEntity, TProperty> AddRule(IValueRule<TEntity, TProperty> rule)
5569
{
70+
rule.ThrowIfNull(nameof(rule)).ErrorText ??= ErrorText; // Override the rule's error text where not already overridden.
71+
5672
_rules.Add(rule);
5773
return this;
5874
}

0 commit comments

Comments
 (0)