diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99b18820..6a343378 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
Represents the **NuGet** versions.
+## v3.30.1
+- *Fixed:* Added support for `SettingsBase.DateTimeTransform`, `StringTransform`, `StringTrim` and `StringCase` to allow specification via configuration.
+- *Fixed:* Added support for `CoreEx:` hierarchy (optional) for all _CoreEx_ settings to enable a more structured and explicit configuration.
+
## v3.30.0
- *Enhancement:* Integrated `UnitTestEx` version `5.0.0` to enable the latest capabilities and improvements.
- `CoreEx.UnitTesting.NUnit` given changes is no longer required and has been deprecated, the `UnitTestEx.NUnit` (or other) must be explicitly referenced as per testing framework being used.
diff --git a/Common.targets b/Common.targets
index f3ea876f..c0f9c6aa 100644
--- a/Common.targets
+++ b/Common.targets
@@ -1,6 +1,6 @@
- 3.30.0
+ 3.30.1
preview
Avanade
Avanade
diff --git a/samples/My.Hr/My.Hr.Api/appsettings.json b/samples/My.Hr/My.Hr.Api/appsettings.json
index 8b858ae5..c8b9c000 100644
--- a/samples/My.Hr/My.Hr.Api/appsettings.json
+++ b/samples/My.Hr/My.Hr.Api/appsettings.json
@@ -5,14 +5,16 @@
"Microsoft.AspNetCore": "Warning"
}
},
- "PagingDefaultTake": 25,
- "PagingMaxTake": 500,
- "RefDataCache": {
- "AbsoluteExpirationRelativeToNow": "01:45:00",
- "SlidingExpiration": "00:15:00",
- "Gender": {
- "AbsoluteExpirationRelativeToNow": "03:00:00",
- "SlidingExpiration": "00:45:00"
+ "CoreEx": {
+ "PagingDefaultTake": 25,
+ "PagingMaxTake": 500,
+ "RefDataCache": {
+ "AbsoluteExpirationRelativeToNow": "01:45:00",
+ "SlidingExpiration": "00:15:00",
+ "Gender": {
+ "AbsoluteExpirationRelativeToNow": "03:00:00",
+ "SlidingExpiration": "00:45:00"
+ }
}
},
"ServiceBusConnection": {
diff --git a/samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj b/samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj
index aee41a46..9acbcaeb 100644
--- a/samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj
+++ b/samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/samples/My.Hr/My.Hr.UnitTest/EmployeeFunctionTest.cs b/samples/My.Hr/My.Hr.UnitTest/EmployeeFunctionTest.cs
index d76af59e..e06b5d65 100644
--- a/samples/My.Hr/My.Hr.UnitTest/EmployeeFunctionTest.cs
+++ b/samples/My.Hr/My.Hr.UnitTest/EmployeeFunctionTest.cs
@@ -86,6 +86,7 @@ public void A130_Get_IncludeFields()
using var test = FunctionTester.Create();
var e = test.HttpTrigger()
+ .WithRouteCheck(UnitTestEx.Azure.Functions.RouteCheckOption.PathAndQueryStartsWith)
.Run(f => f.GetAsync(test.CreateHttpRequest(HttpMethod.Get, $"api/employees/{1.ToGuid()}", new CoreEx.Http.HttpRequestOptions().Include("FirstName", "LastName")), 1.ToGuid()))
.AssertOK()
.AssertJson("{\"firstName\":\"Wendy\",\"lastName\":\"Jones\"}");
@@ -114,6 +115,7 @@ public void B110_GetAll_Paging()
using var test = FunctionTester.Create();
var v = test.HttpTrigger()
+ .WithRouteCheck(UnitTestEx.Azure.Functions.RouteCheckOption.PathAndQueryStartsWith)
.Run(f => f.GetAllAsync(test.CreateHttpRequest(HttpMethod.Get, "api/employees", CoreEx.Http.HttpRequestOptions.Create(PagingArgs.CreateSkipAndTake(1, 2, true)))))
.AssertOK()
.GetValue();
@@ -134,6 +136,7 @@ public void B120_GetAll_PagingAndIncludeFields()
using var test = FunctionTester.Create();
var v = test.HttpTrigger()
+ .WithRouteCheck(UnitTestEx.Azure.Functions.RouteCheckOption.PathAndQueryStartsWith)
.Run(f => f.GetAllAsync(test.CreateHttpRequest(HttpMethod.Get, "api/employees", CoreEx.Http.HttpRequestOptions.Create(PagingArgs.CreateSkipAndTake(1, 2, false)).Include("lastname"))))
.AssertOK()
.AssertJson("[ { \"lastName\": \"Jones\" }, { \"lastName\": \"Smith\" } ]")
diff --git a/samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj b/samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj
index f31defe7..ac0822df 100644
--- a/samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj
+++ b/samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj
@@ -31,7 +31,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusOrchestratedSubscriber.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusOrchestratedSubscriber.cs
index 208e6610..9a0797c5 100644
--- a/src/CoreEx.Azure/ServiceBus/ServiceBusOrchestratedSubscriber.cs
+++ b/src/CoreEx.Azure/ServiceBus/ServiceBusOrchestratedSubscriber.cs
@@ -39,10 +39,10 @@ public ServiceBusOrchestratedSubscriber(EventSubscriberOrchestrator orchestrator
{
Orchestrator = orchestrator.ThrowIfNull(nameof(orchestrator));
ServiceBusSubscriberInvoker = serviceBusSubscriberInvoker ?? (ServiceBusSubscriber._invoker ??= new ServiceBusSubscriberInvoker());
- AbandonOnTransient = settings.GetValue($"{GetType().Name}__{nameof(AbandonOnTransient)}", false);
- MaxDeliveryCount = settings.GetValue($"{GetType().Name}__{nameof(MaxDeliveryCount)}");
- RetryDelay = settings.GetValue($"{GetType().Name}__{nameof(RetryDelay)}");
- MaxRetryDelay = settings.GetValue($"{GetType().Name}__{nameof(MaxRetryDelay)}");
+ AbandonOnTransient = settings.GetCoreExValue($"{GetType().Name}:{nameof(AbandonOnTransient)}", false);
+ MaxDeliveryCount = settings.GetCoreExValue($"{GetType().Name}:{nameof(MaxDeliveryCount)}");
+ RetryDelay = settings.GetCoreExValue($"{GetType().Name}:{nameof(RetryDelay)}");
+ MaxRetryDelay = settings.GetCoreExValue($"{GetType().Name}:{nameof(MaxRetryDelay)}");
}
///
diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusPurger.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusPurger.cs
index 65146f98..d4713b05 100644
--- a/src/CoreEx.Azure/ServiceBus/ServiceBusPurger.cs
+++ b/src/CoreEx.Azure/ServiceBus/ServiceBusPurger.cs
@@ -45,8 +45,8 @@ private async Task PurgeAsync(string queueOrTopicName, string? subscriptionName,
queueOrTopicName.ThrowIfNullOrEmpty(nameof(queueOrTopicName));
// Get queue name and subscription name by checking settings override.
- var qn = Settings.GetValue($"Publisher_ServiceBusQueueName_{queueOrTopicName}", defaultValue: queueOrTopicName);
- var sn = string.IsNullOrEmpty(subscriptionName) ? null : Settings.GetValue($"Publisher_ServiceBusSubscriptionName_{subscriptionName}", defaultValue: subscriptionName);
+ var qn = Settings.GetCoreExValue($"Publisher_ServiceBusQueueName_{queueOrTopicName}", defaultValue: queueOrTopicName);
+ var sn = string.IsNullOrEmpty(subscriptionName) ? null : Settings.GetCoreExValue($"Publisher_ServiceBusSubscriptionName_{subscriptionName}", defaultValue: subscriptionName);
// Receive from Dead letter
var o = new ServiceBusReceiverOptions { SubQueue = subQueue, PrefetchCount = 500, ReceiveMode = ServiceBusReceiveMode.ReceiveAndDelete };
diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusSender.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusSender.cs
index c19752f8..db9e3ead 100644
--- a/src/CoreEx.Azure/ServiceBus/ServiceBusSender.cs
+++ b/src/CoreEx.Azure/ServiceBus/ServiceBusSender.cs
@@ -96,7 +96,7 @@ public Task SendAsync(IEnumerable events, CancellationToken cance
{
var n = qitem.Key == _unspecifiedQueueOrTopicName ? null : qitem.Key;
var key = $"{GetType().Name}_QueueOrTopicName{(n is null ? "" : $"_{n}")}";
- var qn = Settings.GetValue($"{GetType().Name}:QueueOrTopicName{(n is null ? "" : $"_{n}")}", defaultValue: n) ?? throw new EventSendException(PrependStats($"'{key}' configuration setting must have a non-null value.", totalCount, unsentEvents.Count), unsentEvents);
+ var qn = Settings.GetCoreExValue($"{GetType().Name}:QueueOrTopicName{(n is null ? "" : $"_{n}")}", defaultValue: n) ?? throw new EventSendException(PrependStats($"'{key}' configuration setting must have a non-null value.", totalCount, unsentEvents.Count), unsentEvents);
var queue = qitem.Value;
var sentIds = new List();
diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusSubscriber.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusSubscriber.cs
index c097cee5..f85c26e7 100644
--- a/src/CoreEx.Azure/ServiceBus/ServiceBusSubscriber.cs
+++ b/src/CoreEx.Azure/ServiceBus/ServiceBusSubscriber.cs
@@ -51,10 +51,10 @@ public ServiceBusSubscriber(ExecutionContext executionContext, SettingsBase sett
: base(eventDataConverter ?? new ServiceBusReceivedMessageEventDataConverter(eventSerializer ?? new CoreEx.Text.Json.EventDataSerializer()), executionContext, settings, logger, eventSubscriberInvoker)
{
ServiceBusSubscriberInvoker = serviceBusSubscriberInvoker ?? (_invoker ??= new ServiceBusSubscriberInvoker());
- AbandonOnTransient = settings.GetValue($"{GetType().Name}__{nameof(AbandonOnTransient)}", false);
- MaxDeliveryCount = settings.GetValue($"{GetType().Name}__{nameof(MaxDeliveryCount)}");
- RetryDelay = settings.GetValue($"{GetType().Name}__{nameof(RetryDelay)}");
- MaxRetryDelay = settings.GetValue($"{GetType().Name}__{nameof(MaxRetryDelay)}");
+ AbandonOnTransient = settings.GetCoreExValue($"{GetType().Name}:{nameof(AbandonOnTransient)}", false);
+ MaxDeliveryCount = settings.GetCoreExValue($"{GetType().Name}:{nameof(MaxDeliveryCount)}");
+ RetryDelay = settings.GetCoreExValue($"{GetType().Name}:{nameof(RetryDelay)}");
+ MaxRetryDelay = settings.GetCoreExValue($"{GetType().Name}:{nameof(MaxRetryDelay)}");
}
///
diff --git a/src/CoreEx.Azure/Storage/TableWorkStatePersistence.cs b/src/CoreEx.Azure/Storage/TableWorkStatePersistence.cs
index 21d0c852..9c42c138 100644
--- a/src/CoreEx.Azure/Storage/TableWorkStatePersistence.cs
+++ b/src/CoreEx.Azure/Storage/TableWorkStatePersistence.cs
@@ -3,7 +3,6 @@
using Azure;
using Azure.Data.Tables;
using CoreEx.Hosting.Work;
-using CoreEx.Json;
using System;
using System.IO;
using System.Linq;
@@ -18,11 +17,8 @@ namespace CoreEx.Azure.Storage
/// The maximum size currently supported is 960,000 bytes.
public class TableWorkStatePersistence : IWorkStatePersistence
{
- private static readonly string[] _columns = [nameof(WorkState.TypeName), nameof(WorkState.CorrelationId), nameof(WorkState.Status), nameof(WorkState.Created), nameof(WorkState.Expiry), nameof(WorkState.Started), nameof(WorkState.Indeterminate), nameof(WorkState.Finished), nameof(WorkState.Reason)];
-
private readonly TableClient _workStateTableClient;
private readonly TableClient _workDataTableClient;
- private readonly IJsonSerializer _jsonSerializer;
private readonly SemaphoreSlim _semaphore = new(1, 1);
private volatile bool _firstTime = true;
@@ -32,17 +28,15 @@ public class TableWorkStatePersistence : IWorkStatePersistence
/// The .
/// The work state table name.
/// The work data table name.
- /// The . Defaults to .
- public TableWorkStatePersistence(TableServiceClient tableServiceClient, string workStateTableName = "workstate", string workDataTableName = "workdata", IJsonSerializer? jsonSerializer = null)
- : this(tableServiceClient.ThrowIfNull(nameof(tableServiceClient)).GetTableClient(workStateTableName), tableServiceClient.GetTableClient(workDataTableName), jsonSerializer) { }
+ public TableWorkStatePersistence(TableServiceClient tableServiceClient, string workStateTableName = "workstate", string workDataTableName = "workdata")
+ : this(tableServiceClient.ThrowIfNull(nameof(tableServiceClient)).GetTableClient(workStateTableName), tableServiceClient.GetTableClient(workDataTableName)) { }
///
/// Initializes a new instance of the class.
///
/// The work state .
/// The work data .
- /// The . Defaults to .
- public TableWorkStatePersistence(TableClient workStateTableClient, TableClient workDataTableClient, IJsonSerializer? jsonSerializer = null)
+ public TableWorkStatePersistence(TableClient workStateTableClient, TableClient workDataTableClient)
{
if (workStateTableClient.ThrowIfNull(nameof(workStateTableClient)).Name == workDataTableClient.ThrowIfNull(nameof(workDataTableClient)).Name)
throw new ArgumentException("The work state and data table names must be different.", nameof(workDataTableClient));
@@ -52,8 +46,6 @@ public TableWorkStatePersistence(TableClient workStateTableClient, TableClient w
_workDataTableClient.CreateIfNotExists();
_workStateTableClient.CreateIfNotExists();
-
- _jsonSerializer = jsonSerializer ?? JsonSerializer.Default;
}
private class WorkStateEntity() : WorkState, ITableEntity
diff --git a/src/CoreEx.Data/CoreEx.Data.csproj b/src/CoreEx.Data/CoreEx.Data.csproj
index 19f8e4a7..36ab17ca 100644
--- a/src/CoreEx.Data/CoreEx.Data.csproj
+++ b/src/CoreEx.Data/CoreEx.Data.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/src/CoreEx.Data/Querying/QueryFilterFieldConfigBase.cs b/src/CoreEx.Data/Querying/QueryFilterFieldConfigBase.cs
index 98b143fc..b310d7b0 100644
--- a/src/CoreEx.Data/Querying/QueryFilterFieldConfigBase.cs
+++ b/src/CoreEx.Data/Querying/QueryFilterFieldConfigBase.cs
@@ -219,7 +219,11 @@ public virtual StringBuilder AppendToString(StringBuilder stringBuilder)
protected StringBuilder AppendOperatorsToString(StringBuilder stringBuilder)
{
var first = true;
+#if NET6_0_OR_GREATER
+ foreach (var e in Enum.GetValues())
+#else
foreach (var e in Enum.GetValues(typeof(QueryFilterOperator)))
+#endif
{
if (Operators.HasFlag((QueryFilterOperator)e))
{
diff --git a/src/CoreEx.Database.SqlServer/DatabaseServiceCollectionExtensions.cs b/src/CoreEx.Database.SqlServer/DatabaseServiceCollectionExtensions.cs
index d8e12852..15851e53 100644
--- a/src/CoreEx.Database.SqlServer/DatabaseServiceCollectionExtensions.cs
+++ b/src/CoreEx.Database.SqlServer/DatabaseServiceCollectionExtensions.cs
@@ -28,7 +28,7 @@ public static class DatabaseServiceCollectionExtensions
/// To turn off the execution of the (s) at runtime set the 'EventOutboxHostedService:Enabled' configuration setting to false.
public static IServiceCollection AddSqlServerEventOutboxHostedService(this IServiceCollection services, Func eventOutboxDequeueFactory, string? partitionKey = null, string? destination = null, bool healthCheck = true)
{
- var exe = services.BuildServiceProvider().GetRequiredService().GetValue("EventOutboxHostedService__Enabled");
+ var exe = services.BuildServiceProvider().GetRequiredService().GetCoreExValue("EventOutboxHostedService:Enabled");
if (!exe.HasValue || exe.Value)
{
// Add the health check.
diff --git a/src/CoreEx.Database.SqlServer/Outbox/EventOutboxHostedService.cs b/src/CoreEx.Database.SqlServer/Outbox/EventOutboxHostedService.cs
index 12bcd310..f78c6a72 100644
--- a/src/CoreEx.Database.SqlServer/Outbox/EventOutboxHostedService.cs
+++ b/src/CoreEx.Database.SqlServer/Outbox/EventOutboxHostedService.cs
@@ -110,7 +110,7 @@ public EventOutboxHostedService(IServiceProvider serviceProvider, ILoggerWill default to configuration, a) : , then b) , where specified; otherwise, .
public override TimeSpan Interval
{
- get => _interval ?? Settings.GetValue($"{ServiceName}:{IntervalName}".Replace(".", "_")) ?? Settings.GetValue(IntervalName.Replace(".", "_")) ?? DefaultInterval;
+ get => _interval ?? Settings.GetCoreExValue($"{ServiceName}:{IntervalName}".Replace(".", "_")) ?? Settings.GetCoreExValue(IntervalName.Replace(".", "_")) ?? DefaultInterval;
set => _interval = value;
}
@@ -120,7 +120,7 @@ public override TimeSpan Interval
/// Will default to configuration, a) : , then b) , where specified; otherwise, 10.
public int MaxDequeueSize
{
- get => _maxDequeueSize ?? Settings.GetValue($"{ServiceName}:{MaxDequeueSizeName}".Replace(".", "_")) ?? Settings.GetValue(MaxDequeueSizeName.Replace(".", "_")) ?? 10;
+ get => _maxDequeueSize ?? Settings.GetCoreExValue($"{ServiceName}:{MaxDequeueSizeName}".Replace(".", "_")) ?? Settings.GetCoreExValue(MaxDequeueSizeName.Replace(".", "_")) ?? 10;
set => _maxDequeueSize = value;
}
diff --git a/src/CoreEx.Database.SqlServer/Outbox/EventOutboxService.cs b/src/CoreEx.Database.SqlServer/Outbox/EventOutboxService.cs
index 82922dca..9cb9083a 100644
--- a/src/CoreEx.Database.SqlServer/Outbox/EventOutboxService.cs
+++ b/src/CoreEx.Database.SqlServer/Outbox/EventOutboxService.cs
@@ -49,7 +49,7 @@ public class EventOutboxService(IServiceProvider serviceProvider, ILoggerWill default to configuration, a) : , then b) , where specified; otherwise, .
public override int MaxIterations
{
- get => _maxIterations ?? Settings.GetValue($"{ServiceName}:{MaxIterationsName}".Replace(".", "_")) ?? Settings.GetValue(MaxIterationsName.Replace(".", "_")) ?? DefaultMaxIterations;
+ get => _maxIterations ?? Settings.GetCoreExValue($"{ServiceName}:{MaxIterationsName}".Replace(".", "_")) ?? Settings.GetCoreExValue(MaxIterationsName.Replace(".", "_")) ?? DefaultMaxIterations;
set => _maxIterations = value;
}
@@ -59,7 +59,7 @@ public override int MaxIterations
/// Will default to configuration, a) : , then b) , where specified; otherwise, 10.
public int MaxDequeueSize
{
- get => _maxDequeueSize ?? Settings.GetValue($"{ServiceName}:{MaxDequeueSizeName}".Replace(".", "_")) ?? Settings.GetValue(MaxDequeueSizeName.Replace(".", "_")) ?? 10;
+ get => _maxDequeueSize ?? Settings.GetCoreExValue($"{ServiceName}:{MaxDequeueSizeName}".Replace(".", "_")) ?? Settings.GetCoreExValue(MaxDequeueSizeName.Replace(".", "_")) ?? 10;
set => _maxDequeueSize = value;
}
diff --git a/src/CoreEx.Database/Mapping/DatabaseMapperT.cs b/src/CoreEx.Database/Mapping/DatabaseMapperT.cs
index d4f90692..853e192c 100644
--- a/src/CoreEx.Database/Mapping/DatabaseMapperT.cs
+++ b/src/CoreEx.Database/Mapping/DatabaseMapperT.cs
@@ -122,19 +122,18 @@ public PropertyColumnMapper Property(
///
/// Validates and adds a new IPropertyColumnMapper.
///
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "They are the arguments from the calling method.")]
- private void AddMapping(IPropertyColumnMapper pcm)
+ private void AddMapping(PropertyColumnMapper propertyColumnMapper)
{
- if (_mappings.Any(x => x.PropertyName == pcm.PropertyName))
- throw new ArgumentException($"Source property '{pcm.PropertyName}' must not be specified more than once.", "propertyExpression");
+ if (_mappings.Any(x => x.PropertyName == propertyColumnMapper.PropertyName))
+ throw new ArgumentException($"Source property '{propertyColumnMapper.PropertyName}' must not be specified more than once.", nameof(propertyColumnMapper));
- if (_mappings.Any(x => x.ColumnName == pcm.ColumnName))
- throw new ArgumentException($"Column '{pcm.ColumnName}' must not be specified more than once.", "columnName");
+ if (_mappings.Any(x => x.ColumnName == propertyColumnMapper.ColumnName))
+ throw new ArgumentException($"Column '{propertyColumnMapper.ColumnName}' must not be specified more than once.", nameof(propertyColumnMapper));
- if (_mappings.Any(x => x.ParameterName == pcm.ParameterName))
- throw new ArgumentException($"Parameter '{pcm.ParameterName}' must not be specified more than once.", "parameterName");
+ if (_mappings.Any(x => x.ParameterName == propertyColumnMapper.ParameterName))
+ throw new ArgumentException($"Parameter '{propertyColumnMapper.ParameterName}' must not be specified more than once.", nameof(propertyColumnMapper));
- _mappings.Add(pcm);
+ _mappings.Add(propertyColumnMapper);
}
///
diff --git a/src/CoreEx.Dataverse/Mapping/DataverseMapperT.cs b/src/CoreEx.Dataverse/Mapping/DataverseMapperT.cs
index 1bec0b92..3032b8e1 100644
--- a/src/CoreEx.Dataverse/Mapping/DataverseMapperT.cs
+++ b/src/CoreEx.Dataverse/Mapping/DataverseMapperT.cs
@@ -133,16 +133,15 @@ public PropertyColumnMapper Property(
///
/// Validates and adds a new IPropertyColumnMapper.
///
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "They are the arguments from the calling method.")]
- private void AddMapping(IPropertyColumnMapper pcm)
+ private void AddMapping(PropertyColumnMapper propertyColumnMapper)
{
- if (_mappings.Any(x => x.PropertyName == pcm.PropertyName))
- throw new ArgumentException($"Source property '{pcm.PropertyName}' must not be specified more than once.", "propertyExpression");
+ if (_mappings.Any(x => x.PropertyName == propertyColumnMapper.PropertyName))
+ throw new ArgumentException($"Source property '{propertyColumnMapper.PropertyName}' must not be specified more than once.", nameof(propertyColumnMapper));
- if (_mappings.Any(x => x.ColumnName == pcm.ColumnName))
- throw new ArgumentException($"Column '{pcm.ColumnName}' must not be specified more than once.", "columnName");
+ if (_mappings.Any(x => x.ColumnName == propertyColumnMapper.ColumnName))
+ throw new ArgumentException($"Column '{propertyColumnMapper.ColumnName}' must not be specified more than once.", nameof(propertyColumnMapper));
- _mappings.Add(pcm);
+ _mappings.Add(propertyColumnMapper);
}
///
diff --git a/src/CoreEx.Newtonsoft/Json/ContractResolver.cs b/src/CoreEx.Newtonsoft/Json/ContractResolver.cs
index e11ca535..202dd1ca 100644
--- a/src/CoreEx.Newtonsoft/Json/ContractResolver.cs
+++ b/src/CoreEx.Newtonsoft/Json/ContractResolver.cs
@@ -31,8 +31,8 @@ public class ContractResolver : CamelCasePropertyNamesContractResolver
///
static ContractResolver()
{
- _default.AddType(typeof(EntityCore))
- .AddType(typeof(EntityBase))
+ _default.AddType()
+ .AddType()
.AddType(typeof(ReferenceDataBaseEx<,>))
.AddType(typeof(ReferenceDataBase<>))
.AddType()
diff --git a/src/CoreEx.OData/Mapping/ODataMapperT.cs b/src/CoreEx.OData/Mapping/ODataMapperT.cs
index 80fe80c5..7338f29c 100644
--- a/src/CoreEx.OData/Mapping/ODataMapperT.cs
+++ b/src/CoreEx.OData/Mapping/ODataMapperT.cs
@@ -142,16 +142,15 @@ public PropertyColumnMapper Map(Expre
///
/// Validates and adds a new IPropertyColumnMapper.
///
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "They are the arguments from the calling method.")]
- private void AddMapping(IPropertyColumnMapper pcm)
+ private void AddMapping(PropertyColumnMapper propertyColumnMapper)
{
- if (_mappings.Any(x => x.PropertyName == pcm.PropertyName))
- throw new ArgumentException($"Source property '{pcm.PropertyName}' must not be specified more than once.", "propertyExpression");
+ if (_mappings.Any(x => x.PropertyName == propertyColumnMapper.PropertyName))
+ throw new ArgumentException($"Source property '{propertyColumnMapper.PropertyName}' must not be specified more than once.", nameof(propertyColumnMapper));
- if (_mappings.Any(x => x.ColumnName == pcm.ColumnName))
- throw new ArgumentException($"Column '{pcm.ColumnName}' must not be specified more than once.", "columnName");
+ if (_mappings.Any(x => x.ColumnName == propertyColumnMapper.ColumnName))
+ throw new ArgumentException($"Column '{propertyColumnMapper.ColumnName}' must not be specified more than once.", nameof(propertyColumnMapper));
- _mappings.Add(pcm);
+ _mappings.Add(propertyColumnMapper);
}
///
diff --git a/src/CoreEx.OData/README.md b/src/CoreEx.OData/README.md
index 471a4431..d9c3013a 100644
--- a/src/CoreEx.OData/README.md
+++ b/src/CoreEx.OData/README.md
@@ -136,7 +136,7 @@ public class DemoSettings : SettingsBase
///
/// Gets the Dataverse connection string.
///
- public string DataverseConnectionString => GetRequiredValue("ConnectionStrings__Dataverse");
+ public string DataverseConnectionString => GetRequiredValue("ConnectionStrings:Dataverse");
///
/// Gets the from the .
diff --git a/src/CoreEx.Solace/PubSub/PubSubSender.cs b/src/CoreEx.Solace/PubSub/PubSubSender.cs
index 1bd030aa..a9d78ddf 100644
--- a/src/CoreEx.Solace/PubSub/PubSubSender.cs
+++ b/src/CoreEx.Solace/PubSub/PubSubSender.cs
@@ -39,7 +39,7 @@ public PubSubSender(IContext solaceContext, SessionProperties sessionProperties,
Logger = logger.ThrowIfNull(nameof(logger));
Invoker = invoker ?? (_invoker ??= new PubSubSenderInvoker());
Converter = converter ?? new EventSendDataToPubSubConverter();
- DefaultQueueOrTopicName = Settings.GetValue($"{GetType().Name}:QueueOrTopicName", defaultValue: _unspecifiedQueueOrTopicName);
+ DefaultQueueOrTopicName = Settings.GetCoreExValue($"{GetType().Name}:QueueOrTopicName", defaultValue: _unspecifiedQueueOrTopicName);
}
///
diff --git a/src/CoreEx.UnitTesting.Azure.Functions/CoreEx.UnitTesting.Azure.Functions.csproj b/src/CoreEx.UnitTesting.Azure.Functions/CoreEx.UnitTesting.Azure.Functions.csproj
index 8d232d75..97ca2d5a 100644
--- a/src/CoreEx.UnitTesting.Azure.Functions/CoreEx.UnitTesting.Azure.Functions.csproj
+++ b/src/CoreEx.UnitTesting.Azure.Functions/CoreEx.UnitTesting.Azure.Functions.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/src/CoreEx.UnitTesting.Azure.ServiceBus/CoreEx.UnitTesting.Azure.ServiceBus.csproj b/src/CoreEx.UnitTesting.Azure.ServiceBus/CoreEx.UnitTesting.Azure.ServiceBus.csproj
index de87f408..f2033110 100644
--- a/src/CoreEx.UnitTesting.Azure.ServiceBus/CoreEx.UnitTesting.Azure.ServiceBus.csproj
+++ b/src/CoreEx.UnitTesting.Azure.ServiceBus/CoreEx.UnitTesting.Azure.ServiceBus.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj b/src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj
index 8d44ddcd..19bca45a 100644
--- a/src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj
+++ b/src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/src/CoreEx.Validation/Rules/CompareRuleBase.cs b/src/CoreEx.Validation/Rules/CompareRuleBase.cs
index e41a3c22..36ffb522 100644
--- a/src/CoreEx.Validation/Rules/CompareRuleBase.cs
+++ b/src/CoreEx.Validation/Rules/CompareRuleBase.cs
@@ -11,18 +11,13 @@ namespace CoreEx.Validation.Rules
///
/// The entity .
/// The property .
- public abstract class CompareRuleBase : ValueRuleBase where TEntity : class
+ /// The .
+ public abstract class CompareRuleBase(CompareOperator compareOperator) : ValueRuleBase where TEntity : class
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The .
- protected CompareRuleBase(CompareOperator compareOperator) => Operator = compareOperator;
-
///
/// Gets the .
///
- public CompareOperator Operator { get; private set; }
+ public CompareOperator Operator { get; private set; } = compareOperator;
///
/// Gets or sets the comparer.
diff --git a/src/CoreEx.Validation/Rules/EnumRule.cs b/src/CoreEx.Validation/Rules/EnumRule.cs
index a407ceee..44de7a81 100644
--- a/src/CoreEx.Validation/Rules/EnumRule.cs
+++ b/src/CoreEx.Validation/Rules/EnumRule.cs
@@ -22,7 +22,11 @@ public class EnumRule : ValueRuleBase wh
protected override Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default)
{
// Make sure the enum is defined.
+#if NET6_0_OR_GREATER
+ if (!Enum.IsDefined(context.Value))
+#else
if (!Enum.IsDefined(typeof(TProperty), context.Value))
+#endif
context.CreateErrorMessage(ErrorText ?? ValidatorStrings.InvalidFormat);
return Task.CompletedTask;
diff --git a/src/CoreEx.Validation/ValidationServiceCollectionExtensions.cs b/src/CoreEx.Validation/ValidationServiceCollectionExtensions.cs
index 6b77d829..4a0a8375 100644
--- a/src/CoreEx.Validation/ValidationServiceCollectionExtensions.cs
+++ b/src/CoreEx.Validation/ValidationServiceCollectionExtensions.cs
@@ -107,9 +107,9 @@ public static IServiceCollection AddValidators(this IServiceCollection services,
select new { valueType, type })
{
if (alsoRegisterInterfaces)
- av.MakeGenericMethod(match.valueType, match.type).Invoke(null, new object[] { services });
+ av.MakeGenericMethod(match.valueType, match.type).Invoke(null, [services]);
else
- av.MakeGenericMethod(match.type).Invoke(null, new object[] { services });
+ av.MakeGenericMethod(match.type).Invoke(null, [services]);
}
}
diff --git a/src/CoreEx/Abstractions/Internal.cs b/src/CoreEx/Abstractions/Internal.cs
index a01ae4aa..d4740226 100644
--- a/src/CoreEx/Abstractions/Internal.cs
+++ b/src/CoreEx/Abstractions/Internal.cs
@@ -1,6 +1,14 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
using Microsoft.Extensions.Caching.Memory;
+using System.Runtime.CompilerServices;
+
+[assembly:
+ InternalsVisibleTo("CoreEx.AspNetCore, PublicKey=00240000048000009400000006020000002400005253413100040000010001007dee530af6d801902d40685e9cd0a3d8991ddbf545be3ef6147c9f79bacd7464d92fbd94fee34e885c37e3dff4ea15a4f9978f1f614798e0f48e3a3d5bf15e8b2fba9c19b6966838f97444bc247bc101454946d70ac93207cf2c611956aed59c316f81f1bf8c8486f8f0b3f9adf93c2f07e06a86745f6dc4b819c2bc2f3fdad5"),
+ InternalsVisibleTo("CoreEx.Azure, PublicKey=00240000048000009400000006020000002400005253413100040000010001007dee530af6d801902d40685e9cd0a3d8991ddbf545be3ef6147c9f79bacd7464d92fbd94fee34e885c37e3dff4ea15a4f9978f1f614798e0f48e3a3d5bf15e8b2fba9c19b6966838f97444bc247bc101454946d70ac93207cf2c611956aed59c316f81f1bf8c8486f8f0b3f9adf93c2f07e06a86745f6dc4b819c2bc2f3fdad5"),
+ InternalsVisibleTo("CoreEx.Database.SqlServer, PublicKey=00240000048000009400000006020000002400005253413100040000010001007dee530af6d801902d40685e9cd0a3d8991ddbf545be3ef6147c9f79bacd7464d92fbd94fee34e885c37e3dff4ea15a4f9978f1f614798e0f48e3a3d5bf15e8b2fba9c19b6966838f97444bc247bc101454946d70ac93207cf2c611956aed59c316f81f1bf8c8486f8f0b3f9adf93c2f07e06a86745f6dc4b819c2bc2f3fdad5"),
+ InternalsVisibleTo("CoreEx.Solace, PublicKey=00240000048000009400000006020000002400005253413100040000010001007dee530af6d801902d40685e9cd0a3d8991ddbf545be3ef6147c9f79bacd7464d92fbd94fee34e885c37e3dff4ea15a4f9978f1f614798e0f48e3a3d5bf15e8b2fba9c19b6966838f97444bc247bc101454946d70ac93207cf2c611956aed59c316f81f1bf8c8486f8f0b3f9adf93c2f07e06a86745f6dc4b819c2bc2f3fdad5"),
+]
namespace CoreEx.Abstractions
{
@@ -15,7 +23,7 @@ public static class Internal
///
/// Gets the CoreEx fallback .
///
- public static IMemoryCache MemoryCache => ExecutionContext.GetService() ?? (_fallbackCache ??= new MemoryCache(new MemoryCacheOptions()));
+ internal static IMemoryCache MemoryCache => ExecutionContext.GetService() ?? (_fallbackCache ??= new MemoryCache(new MemoryCacheOptions()));
///
/// Represents a cache for internal capabilities.
diff --git a/src/CoreEx/Abstractions/Reflection/TypeReflector.cs b/src/CoreEx/Abstractions/Reflection/TypeReflector.cs
index 96969790..e86ecdfe 100644
--- a/src/CoreEx/Abstractions/Reflection/TypeReflector.cs
+++ b/src/CoreEx/Abstractions/Reflection/TypeReflector.cs
@@ -70,7 +70,7 @@ public static ITypeReflector GetReflector(TypeReflectorArgs? args, Type type)
=> (args ??= TypeReflectorArgs.Default).Cache.GetOrCreate(type.ThrowIfNull(nameof(args)), ce =>
{
var ec = typeof(TypeReflector<>).MakeGenericType(type).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, [typeof(TypeReflectorArgs)], null)!;
- var tr = (ITypeReflector)ec.Invoke(new object[] { args });
+ var tr = (ITypeReflector)ec.Invoke([args]);
args.TypeBuilder?.Invoke(tr);
return ConfigureCacheEntry(ce, tr);
})!;
diff --git a/src/CoreEx/Configuration/SettingsBase.cs b/src/CoreEx/Configuration/SettingsBase.cs
index 1f618f87..00a04511 100644
--- a/src/CoreEx/Configuration/SettingsBase.cs
+++ b/src/CoreEx/Configuration/SettingsBase.cs
@@ -1,5 +1,6 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
+using CoreEx.Entities;
using CoreEx.Hosting.Work;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
@@ -16,6 +17,10 @@ public abstract class SettingsBase
{
private readonly List _prefixes = [];
private bool? _validationUseJsonNames;
+ private DateTimeTransform? _dateTimeTransform;
+ private StringTransform? _stringTransform;
+ private StringTrim? _stringTrim;
+ private StringCase? _stringCase;
///
/// Initializes a new instance of the class.
@@ -76,7 +81,7 @@ public T GetValue([CallerMemberName] string key = "", T defaultValue = defaul
private bool TryGetValue(string key, out T value)
{
// Try the key as specified.
- if (Configuration!.GetSection(key)?.Value != null)
+ if (Configuration is not null && Configuration.GetSection(key)?.Value != null)
{
value = Configuration.GetValue(key)!;
return true;
@@ -116,17 +121,27 @@ public T GetRequiredValue([CallerMemberName] string key = "")
return TryGetValue(ckey, out kv) ? kv : throw new ArgumentException($"Configuration key '{key}' has not been configured and the value is required.", nameof(key));
}
+ ///
+ /// Gets the value using the specified excluding any prefix (key is inferred where not specified using ).
+ ///
+ /// The value .
+ /// The key excluding any prefix (key is inferred where not specified using ).
+ /// The default fallback value used where no non-default value is found.
+ /// The corresponding setting value.
+ /// This is considered a standard setting and will be checked within the CoreEx: nested strructure first to enable clear separation.
+ internal T GetCoreExValue([CallerMemberName] string key = "", T defaultValue = default!) => TryGetValue($"CoreEx:{key.ThrowIfNullOrEmpty(nameof(key))}", out var value) ? value : GetValue(key, defaultValue);
+
///
/// Indicates whether to the include the underlying content in the externally returned result.
///
/// Defaults to false.
- public bool IncludeExceptionInResult => GetValue(nameof(IncludeExceptionInResult), false);
+ public bool IncludeExceptionInResult => GetCoreExValue(nameof(IncludeExceptionInResult), false);
///
/// Gets the default maximum event publish collection size.
///
/// Defaults to 100.
- public int MaxPublishCollSize => GetValue(nameof(MaxPublishCollSize), 100);
+ public int MaxPublishCollSize => GetCoreExValue(nameof(MaxPublishCollSize), 100);
///
/// Gets the from the environment variables.
@@ -137,42 +152,71 @@ public T GetRequiredValue([CallerMemberName] string key = "")
/// Indicates whether to include any extra Health Check data that might be considered sensitive.
///
/// Defaults to false.
- public bool IncludeSensitiveHealthCheckData => GetValue(nameof(IncludeSensitiveHealthCheckData), false);
+ public bool IncludeSensitiveHealthCheckData => GetCoreExValue(nameof(IncludeSensitiveHealthCheckData), false);
///
/// Gets the ; i.e. page size.
///
/// Defaults to 100.
- public long PagingDefaultTake => GetValue(nameof(PagingDefaultTake), 100);
+ public long PagingDefaultTake => GetCoreExValue(nameof(PagingDefaultTake), 100);
///
/// Gets the ; i.e. absolute maximum page size.
///
/// Defaults to 1000.
- public long PagingMaxTake => GetValue(nameof(PagingMaxTake), 1000);
+ public long PagingMaxTake => GetCoreExValue(nameof(PagingMaxTake), 1000);
///
/// Gets the default .
///
/// Defaults to 2 hours.
- public TimeSpan? RefDataCacheAbsoluteExpirationRelativeToNow => GetValue($"RefDataCache__{nameof(ICacheEntry.AbsoluteExpirationRelativeToNow)}", TimeSpan.FromHours(2));
+ public TimeSpan? RefDataCacheAbsoluteExpirationRelativeToNow => GetCoreExValue($"RefDataCache:{nameof(ICacheEntry.AbsoluteExpirationRelativeToNow)}", TimeSpan.FromHours(2));
///
/// Gets the default .
///
/// Defaults to 30 minutes.
- public TimeSpan? RefDataCacheSlidingExpiration => GetValue($"RefDataCache__{nameof(ICacheEntry.SlidingExpiration)}", TimeSpan.FromMinutes(30));
+ public TimeSpan? RefDataCacheSlidingExpiration => GetCoreExValue($"RefDataCache:{nameof(ICacheEntry.SlidingExpiration)}", TimeSpan.FromMinutes(30));
///
/// Indicates whether the validation (CoreEx.Validation) should use JSON names.
///
- /// Defaults to true.
- public bool ValidationUseJsonNames => _validationUseJsonNames ??= GetValue(nameof(ValidationUseJsonNames), true);
+ /// Defaults to true.
+ /// The value is cached on first use and is then considered immutable as a change in behaviour may have unintended consequences.
+ public bool ValidationUseJsonNames => _validationUseJsonNames ??= GetCoreExValue(nameof(ValidationUseJsonNames), true);
///
- /// Gets or sets the .
+ /// Gets the .
///
/// Defaults to 1 hour.
- public TimeSpan WorkerExpiryTimeSpan => GetValue(nameof(WorkerExpiryTimeSpan), TimeSpan.FromHours(1));
+ public TimeSpan WorkerExpiryTimeSpan => GetCoreExValue(nameof(WorkerExpiryTimeSpan), TimeSpan.FromHours(1));
+
+ ///
+ /// Gets the .
+ ///
+ /// Defaults to .
+ /// The value is cached on first use and is then considered immutable as a change in behaviour may have unintended consequences.
+ public DateTimeTransform DateTimeTransform => _dateTimeTransform ??= GetCoreExValue($"{nameof(Cleaner)}:{nameof(DateTimeTransform)}", Cleaner.DefaultDateTimeTransform);
+
+ ///
+ /// Gets the .
+ ///
+ /// Defaults to .
+ /// The value is cached on first use and is then considered immutable as a change in behaviour may have unintended consequences.
+ public StringTransform StringTransform => _stringTransform ??= GetCoreExValue($"{nameof(Cleaner)}:{nameof(StringTransform)}", Cleaner.DefaultStringTransform);
+
+ ///
+ /// Gets the .
+ ///
+ /// Defaults to .
+ /// The value is cached on first use and is then considered immutable as a change in behaviour may have unintended consequences.
+ public StringCase StringCase => _stringCase ??= GetCoreExValue($"{nameof(Cleaner)}:{nameof(StringCase)}", Cleaner.DefaultStringCase);
+
+ ///
+ /// Gets the .
+ ///
+ /// Defaults to .
+ /// The value is cached on first use and is then considered immutable as a change in behaviour may have unintended consequences.
+ public StringTrim StringTrim => _stringTrim ??= GetCoreExValue($"{nameof(Cleaner)}:{nameof(StringTrim)}", Cleaner.DefaultStringTrim);
}
}
\ No newline at end of file
diff --git a/src/CoreEx/CoreEx.csproj b/src/CoreEx/CoreEx.csproj
index c64279a4..594fdbd9 100644
--- a/src/CoreEx/CoreEx.csproj
+++ b/src/CoreEx/CoreEx.csproj
@@ -67,7 +67,7 @@
-
+
diff --git a/src/CoreEx/Entities/Cleaner.cs b/src/CoreEx/Entities/Cleaner.cs
index 43054d06..1b9e7e94 100644
--- a/src/CoreEx/Entities/Cleaner.cs
+++ b/src/CoreEx/Entities/Cleaner.cs
@@ -1,5 +1,6 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
+using CoreEx.Configuration;
using CoreEx.Globalization;
using System;
using System.Collections.Generic;
@@ -72,14 +73,8 @@ public static StringCase DefaultStringCase
/// The cleaned value.
public static string? Clean(string? value, StringTrim trim = StringTrim.UseDefault, StringTransform transform = StringTransform.UseDefault, StringCase casing = StringCase.UseDefault)
{
- if (trim == StringTrim.UseDefault)
- trim = DefaultStringTrim;
-
if (transform == StringTransform.UseDefault)
- transform = DefaultStringTransform;
-
- if (casing == StringCase.UseDefault)
- casing = DefaultStringCase;
+ transform = ExecutionContext.GetService()?.StringTransform ?? DefaultStringTransform;
// Handle a null string.
if (value == null)
@@ -90,6 +85,9 @@ public static StringCase DefaultStringCase
return value;
}
+ if (trim == StringTrim.UseDefault)
+ trim = ExecutionContext.GetService()?.StringTrim ?? DefaultStringTrim;
+
// Trim the string.
var tmp = trim switch
{
@@ -111,6 +109,9 @@ public static StringCase DefaultStringCase
return tmp;
// Apply casing to the string.
+ if (casing == StringCase.UseDefault)
+ casing = ExecutionContext.GetService()?.StringCase ?? DefaultStringCase;
+
return casing switch
{
StringCase.Lower => CultureInfo.CurrentCulture.TextInfo.ToCasing(tmp, TextInfoCasing.Lower),
@@ -134,10 +135,11 @@ public static StringCase DefaultStringCase
/// The value to clean.
/// The to be applied.
/// The cleaned value.
+ /// Will attempt to use as a default where possible.
public static DateTime Clean(DateTime value, DateTimeTransform transform)
{
if (transform == DateTimeTransform.UseDefault)
- transform = DefaultDateTimeTransform;
+ transform = ExecutionContext.GetService()?.DateTimeTransform ?? DefaultDateTimeTransform;
switch (transform)
{
diff --git a/src/CoreEx/Entities/Extended/EntityCore.cs b/src/CoreEx/Entities/Extended/EntityCore.cs
index a861ea1c..e0ed66db 100644
--- a/src/CoreEx/Entities/Extended/EntityCore.cs
+++ b/src/CoreEx/Entities/Extended/EntityCore.cs
@@ -15,7 +15,11 @@ namespace CoreEx.Entities.Extended
[System.Diagnostics.DebuggerStepThrough]
public abstract class EntityCore : INotifyPropertyChanged, IChangeTracking, IReadOnly
{
+#if NET9_0_OR_GREATER
+ private readonly System.Threading.Lock _lock = new();
+#else
private readonly object _lock = new();
+#endif
private Dictionary? _propertyEventHandlers;
///
diff --git a/src/CoreEx/Events/CloudEventSerializerBase.cs b/src/CoreEx/Events/CloudEventSerializerBase.cs
index 9e085003..def781ec 100644
--- a/src/CoreEx/Events/CloudEventSerializerBase.cs
+++ b/src/CoreEx/Events/CloudEventSerializerBase.cs
@@ -15,7 +15,8 @@ namespace CoreEx.Events
///
/// Provides the base capabilities.
///
- public abstract class CloudEventSerializerBase : IEventSerializer
+ /// The .
+ public abstract class CloudEventSerializerBase(EventDataFormatter? eventDataFormatter) : IEventSerializer
{
private const string SubjectName = "subject";
private const string ActionName = "action";
@@ -32,14 +33,8 @@ public abstract class CloudEventSerializerBase : IEventSerializer
/// an attribute name must consist of lowercase letters and digits only; any that contain other characters will be ignored.
public static string[] ReservedNames { get; } = ["id", "time", "type", "source", SubjectName, ActionName, CorrelationIdName, TenantIdName, ETagName, PartitionKeyName, KeyName];
- ///
- /// Initializes a new instance of the class.
- ///
- /// The .
- protected CloudEventSerializerBase(EventDataFormatter? eventDataFormatter) => EventDataFormatter = eventDataFormatter ?? new EventDataFormatter();
-
///
- public EventDataFormatter EventDataFormatter { get; }
+ public EventDataFormatter EventDataFormatter { get; } = eventDataFormatter ?? new EventDataFormatter();
///
public IAttachmentStorage? AttachmentStorage { get; set; }
diff --git a/src/CoreEx/Events/EventDataSerializerBase.cs b/src/CoreEx/Events/EventDataSerializerBase.cs
index 21b7fbc5..88f06a46 100644
--- a/src/CoreEx/Events/EventDataSerializerBase.cs
+++ b/src/CoreEx/Events/EventDataSerializerBase.cs
@@ -12,26 +12,17 @@ namespace CoreEx.Events
/// Provides the base capabilities.
///
/// The indicates whether the is serialized only (default); or alternatively, the complete .
- public abstract class EventDataSerializerBase : IEventSerializer
+ /// The .
+ /// The .
+ public abstract class EventDataSerializerBase(IJsonSerializer jsonSerializer, EventDataFormatter? eventDataFormatter) : IEventSerializer
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The .
- /// The .
- protected EventDataSerializerBase(IJsonSerializer jsonSerializer, EventDataFormatter? eventDataFormatter)
- {
- JsonSerializer = jsonSerializer.ThrowIfNull(nameof(jsonSerializer));
- EventDataFormatter = eventDataFormatter ?? new EventDataFormatter();
- }
-
///
/// Gets the .
///
- public IJsonSerializer JsonSerializer { get; }
+ public IJsonSerializer JsonSerializer { get; } = jsonSerializer.ThrowIfNull(nameof(jsonSerializer));
///
- public EventDataFormatter EventDataFormatter { get; }
+ public EventDataFormatter EventDataFormatter { get; } = eventDataFormatter ?? new EventDataFormatter();
///
public IAttachmentStorage? AttachmentStorage { get; set; }
diff --git a/src/CoreEx/Events/EventPublisher.cs b/src/CoreEx/Events/EventPublisher.cs
index a5f1309e..6a4ac0cb 100644
--- a/src/CoreEx/Events/EventPublisher.cs
+++ b/src/CoreEx/Events/EventPublisher.cs
@@ -83,7 +83,7 @@ public Task SendAsync(CancellationToken cancellationToken = default) => EventPub
list.Add(esd);
}
- await EventSender.SendAsync(list.ToArray(), cancellationToken).ConfigureAwait(false);
+ await EventSender.SendAsync([.. list], cancellationToken).ConfigureAwait(false);
}, cancellationToken);
///
diff --git a/src/CoreEx/Events/EventSubscriberBase.cs b/src/CoreEx/Events/EventSubscriberBase.cs
index db0278a1..7ebd6e9f 100644
--- a/src/CoreEx/Events/EventSubscriberBase.cs
+++ b/src/CoreEx/Events/EventSubscriberBase.cs
@@ -15,7 +15,12 @@ namespace CoreEx.Events
///
/// Provides the base event subscriber capabilities.
///
- public abstract class EventSubscriberBase : IErrorHandling
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ public abstract class EventSubscriberBase(IEventDataConverter eventDataConverter, ExecutionContext executionContext, SettingsBase settings, ILogger logger, EventSubscriberInvoker? eventSubscriberInvoker = null) : IErrorHandling
{
private static EventSubscriberInvoker? _invoker;
private ErrorHandler? _errorHandler;
@@ -35,47 +40,30 @@ public abstract class EventSubscriberBase : IErrorHandling
///
public static readonly LText NullEventErrorText = new($"{typeof(BusinessException).FullName}.{nameof(NullEventErrorText)}", $"{MessageErrorText} Event deserialized as null.");
- ///
- /// Initializes a new instance of the class.
- ///
- /// The .
- /// The .
- /// The .
- /// The .
- /// The .
- protected EventSubscriberBase(IEventDataConverter eventDataConverter, ExecutionContext executionContext, SettingsBase settings, ILogger logger, EventSubscriberInvoker? eventSubscriberInvoker = null)
- {
- EventDataConverter = eventDataConverter.ThrowIfNull(nameof(eventDataConverter));
- ExecutionContext = executionContext.ThrowIfNull(nameof(executionContext));
- Settings = settings.ThrowIfNull(nameof(settings));
- Logger = logger.ThrowIfNull(nameof(logger));
- EventSubscriberInvoker = eventSubscriberInvoker ?? (_invoker ??= new EventSubscriberInvoker());
- }
-
///
/// Gets the .
///
- public IEventDataConverter EventDataConverter { get; }
+ public IEventDataConverter EventDataConverter { get; } = eventDataConverter.ThrowIfNull(nameof(eventDataConverter));
///
/// Gets the .
///
- public ExecutionContext ExecutionContext { get; }
+ public ExecutionContext ExecutionContext { get; } = executionContext.ThrowIfNull(nameof(executionContext));
///
/// Gets the .
///
- public SettingsBase Settings { get; }
+ public SettingsBase Settings { get; } = settings.ThrowIfNull(nameof(settings));
///
/// Gets the .
///
- public ILogger Logger { get; }
+ public ILogger Logger { get; } = logger.ThrowIfNull(nameof(logger));
///
/// Gets the .
///
- public EventSubscriberInvoker EventSubscriberInvoker { get; }
+ public EventSubscriberInvoker EventSubscriberInvoker { get; } = eventSubscriberInvoker ?? (_invoker ??= new EventSubscriberInvoker());
///
/// Gets or sets the where an occurs during / /.
diff --git a/src/CoreEx/Events/InMemoryPublisher.cs b/src/CoreEx/Events/InMemoryPublisher.cs
index 2920c11c..4813a464 100644
--- a/src/CoreEx/Events/InMemoryPublisher.cs
+++ b/src/CoreEx/Events/InMemoryPublisher.cs
@@ -53,14 +53,14 @@ protected override Task OnEventSendAsync(string? name, EventData eventData, Even
///
/// An array of names.
/// Where (with no name) is used the underlying destination name will be null.
- public string?[] GetNames() => _dict.Keys.ToArray();
+ public string?[] GetNames() => [.. _dict.Keys];
///
/// Gets the events sent (in order) to the named destination.
///
/// The destination name.
/// The corresponding events.
- public EventData[] GetEvents(string? name = null) => _dict.TryGetValue(name ?? NullName, out var queue) ? [.. queue] : Array.Empty();
+ public EventData[] GetEvents(string? name = null) => _dict.TryGetValue(name ?? NullName, out var queue) ? [.. queue] : [];
///
/// Resets (clears) the in-memory state.
diff --git a/src/CoreEx/Events/InMemorySender.cs b/src/CoreEx/Events/InMemorySender.cs
index bf395830..305e2630 100644
--- a/src/CoreEx/Events/InMemorySender.cs
+++ b/src/CoreEx/Events/InMemorySender.cs
@@ -27,7 +27,7 @@ public Task SendAsync(IEnumerable events, CancellationToken cance
///
/// Gets the events sent (in order).
///
- public EventSendData[] GetEvents() => _queue.ToArray();
+ public EventSendData[] GetEvents() => [.. _queue];
///
/// Resets (clears) the in-memory state.
diff --git a/src/CoreEx/Events/Subscribing/EventSubscriberOrchestrator.cs b/src/CoreEx/Events/Subscribing/EventSubscriberOrchestrator.cs
index 81aaa495..50e57415 100644
--- a/src/CoreEx/Events/Subscribing/EventSubscriberOrchestrator.cs
+++ b/src/CoreEx/Events/Subscribing/EventSubscriberOrchestrator.cs
@@ -181,7 +181,7 @@ private bool TryMatchSubscriberInternal(EventData @event, EventSubscriberArgs ar
if (subscriber != null)
return false;
- if (att.ExtendedMatchMethodInfo is not null && !(bool)att.ExtendedMatchMethodInfo.Invoke(null, new object[] { @event, args })!)
+ if (att.ExtendedMatchMethodInfo is not null && !(bool)att.ExtendedMatchMethodInfo.Invoke(null, [@event, args])!)
return false;
subscriber = (IEventSubscriber)(ServiceProvider?.GetService(item.SubscriberType) ?? ExecutionContext.GetRequiredService(item.SubscriberType));
diff --git a/src/CoreEx/ExecutionContext.cs b/src/CoreEx/ExecutionContext.cs
index 63f943c5..ac0a7cb0 100644
--- a/src/CoreEx/ExecutionContext.cs
+++ b/src/CoreEx/ExecutionContext.cs
@@ -30,7 +30,11 @@ public class ExecutionContext : ITenantId, IDisposable
private HashSet? _permissions;
private bool _isCopied;
private bool _disposed;
+#if NET9_0_OR_GREATER
+ private readonly System.Threading.Lock _lock = new();
+#else
private readonly object _lock = new();
+#endif
///
/// Gets or sets the function to create a default instance.
diff --git a/src/CoreEx/Hosting/FileLockSynchronizer.cs b/src/CoreEx/Hosting/FileLockSynchronizer.cs
index 6bfef6f9..05eb0129 100644
--- a/src/CoreEx/Hosting/FileLockSynchronizer.cs
+++ b/src/CoreEx/Hosting/FileLockSynchronizer.cs
@@ -21,7 +21,7 @@ public class FileLockSynchronizer(SettingsBase settings) : IServiceSynchronizer
///
public const string ConfigKey = "FileLockSynchronizerPath";
- private readonly string _path = settings.ThrowIfNull(nameof(settings)).GetValue(ConfigKey) ?? throw new ArgumentException($"Configuration setting '{ConfigKey}' either does not exist or has no value.", nameof(settings));
+ private readonly string _path = settings.ThrowIfNull(nameof(settings)).GetCoreExValue(ConfigKey) ?? throw new ArgumentException($"Configuration setting '{ConfigKey}' either does not exist or has no value.", nameof(settings));
private readonly ConcurrentDictionary _dict = new();
private bool _disposed;
diff --git a/src/CoreEx/Hosting/TimerHostedServiceBase.cs b/src/CoreEx/Hosting/TimerHostedServiceBase.cs
index 323bc809..33848147 100644
--- a/src/CoreEx/Hosting/TimerHostedServiceBase.cs
+++ b/src/CoreEx/Hosting/TimerHostedServiceBase.cs
@@ -25,8 +25,12 @@ public abstract class TimerHostedServiceBase : IHostedService, IDisposable
{
private static readonly Random _random = new();
- private readonly TimerHostedServiceHealthCheck? _healthCheck;
+#if NET9_0_OR_GREATER
+ private readonly System.Threading.Lock _lock = new();
+#else
private readonly object _lock = new();
+#endif
+ private readonly TimerHostedServiceHealthCheck? _healthCheck;
private TimerHostedServiceStatus _status = TimerHostedServiceStatus.Initialized;
private string? _name;
private CancellationTokenSource? _cts;
diff --git a/src/CoreEx/Hosting/Work/FileWorkStatePersistence.cs b/src/CoreEx/Hosting/Work/FileWorkStatePersistence.cs
index 62f35c32..fb733fca 100644
--- a/src/CoreEx/Hosting/Work/FileWorkStatePersistence.cs
+++ b/src/CoreEx/Hosting/Work/FileWorkStatePersistence.cs
@@ -32,7 +32,7 @@ public class FileWorkStatePersistence : IWorkStatePersistence
/// The . Defaults to .
public FileWorkStatePersistence(SettingsBase settings, IJsonSerializer? jsonSerializer = null)
{
- _path = settings.ThrowIfNull(nameof(settings)).GetValue(ConfigKey);
+ _path = settings.ThrowIfNull(nameof(settings)).GetCoreExValue(ConfigKey);
if (string.IsNullOrEmpty(_path))
throw new ArgumentException($"Configuration setting '{ConfigKey}' either does not exist or has no value.", nameof(settings));
diff --git a/src/CoreEx/Hosting/Work/InMemoryWorkStatePersistence.cs b/src/CoreEx/Hosting/Work/InMemoryWorkStatePersistence.cs
index 807fc26a..c8397c45 100644
--- a/src/CoreEx/Hosting/Work/InMemoryWorkStatePersistence.cs
+++ b/src/CoreEx/Hosting/Work/InMemoryWorkStatePersistence.cs
@@ -22,7 +22,7 @@ public class InMemoryWorkStatePersistence(ILogger?
///
/// Gets all the entries.
///
- public WorkState[] GetWorkStates() => _workStates.Values.ToArray();
+ public WorkState[] GetWorkStates() => [.. _workStates.Values];
///
public Task GetAsync(string id, CancellationToken cancellationToken)
diff --git a/src/CoreEx/Hosting/Work/WorkStateOrchestrator.cs b/src/CoreEx/Hosting/Work/WorkStateOrchestrator.cs
index c1f4e2f3..9eb1ee64 100644
--- a/src/CoreEx/Hosting/Work/WorkStateOrchestrator.cs
+++ b/src/CoreEx/Hosting/Work/WorkStateOrchestrator.cs
@@ -261,7 +261,7 @@ public async Task> CancelAsync(string id, string reason, Cance
if (WorkStatus.Finished.HasFlag(ws.Status))
return Result.Fail($"A cancellation can not be performed when the current status is {ws.Status}.");
- ws.Status = WorkStatus.Cancelled;
+ ws.Status = WorkStatus.Canceled;
ws.Finished = DateTimeOffset.UtcNow;
ws.Reason = reason.ThrowIfNullOrEmpty(nameof(reason));
diff --git a/src/CoreEx/Hosting/Work/WorkStatus.cs b/src/CoreEx/Hosting/Work/WorkStatus.cs
index febce1f6..6300378b 100644
--- a/src/CoreEx/Hosting/Work/WorkStatus.cs
+++ b/src/CoreEx/Hosting/Work/WorkStatus.cs
@@ -41,7 +41,7 @@ public enum WorkStatus
///
/// Indicates that the underlying work has been cancelled.
///
- Cancelled = 128,
+ Canceled = 128,
///
/// Indicates that the underlying work is in progress; either started or indeterminate.
@@ -56,11 +56,11 @@ public enum WorkStatus
///
/// Indicates the the underlying work has been terminated; either expired, failed or cancelled.
///
- Terminated = Expired | Failed | Cancelled,
+ Terminated = Expired | Failed | Canceled,
///
/// Indicates that the underlying work has been completed, expired, failed or cancelled.
///
- Finished = Completed | Expired | Failed | Cancelled
+ Finished = Completed | Expired | Failed | Canceled
}
}
\ No newline at end of file
diff --git a/src/CoreEx/Http/HttpArg.cs b/src/CoreEx/Http/HttpArg.cs
index 879daaf8..a5c377f2 100644
--- a/src/CoreEx/Http/HttpArg.cs
+++ b/src/CoreEx/Http/HttpArg.cs
@@ -56,6 +56,10 @@ public class HttpArg(string name, T value, HttpArgType argType = HttpArgType.
str = rd?.Code;
else if (Value is DateTime dt)
str = dt.ToString("o", CultureInfo.InvariantCulture);
+ else if (Value is DateTimeOffset dto)
+ str = dto.ToString("o", CultureInfo.InvariantCulture);
+ else if (Value is bool b)
+ str = b.ToString().ToLowerInvariant();
else if (Value is IFormattable fmt)
str = fmt.ToString(null, CultureInfo.InvariantCulture);
else
diff --git a/src/CoreEx/Http/TypedHttpClientBase.cs b/src/CoreEx/Http/TypedHttpClientBase.cs
index fa923a33..a0704c8f 100644
--- a/src/CoreEx/Http/TypedHttpClientBase.cs
+++ b/src/CoreEx/Http/TypedHttpClientBase.cs
@@ -123,7 +123,7 @@ private async Task CreateRequestInternalAsync(HttpMethod met
var qs = HttpUtility.ParseQueryString(ub.Query);
// Extend the query string from the IHttpArgs.
- foreach (var arg in (args ??= Array.Empty()).Where(x => x != null))
+ foreach (var arg in (args ??= []).Where(x => x != null))
{
arg.AddToQueryString(qs, JsonSerializer);
}
@@ -235,7 +235,6 @@ private static int FindBraceIndex(string format, char brace, int startIndex, int
return braceIndex;
}
-
///
/// Deserialize the JSON into of .
///
@@ -279,7 +278,7 @@ public static (bool result, string error) IsTransient(HttpResponseMessage? respo
return (true, $"Http Request Exception occurred: {exception.Message}");
if (exception is TaskCanceledException)
- return (true, "Task was cancelled.");
+ return (true, "Task was canceled.");
}
if (response == null)
diff --git a/src/CoreEx/Invokers/InvokeArgs.cs b/src/CoreEx/Invokers/InvokeArgs.cs
index 449211b4..c93a2c7f 100644
--- a/src/CoreEx/Invokers/InvokeArgs.cs
+++ b/src/CoreEx/Invokers/InvokeArgs.cs
@@ -42,7 +42,7 @@ private static bool IsTracingEnabled(Type invokerType)
if (settings.Configuration is null)
return true;
- return settings.GetValue($"Invokers:{invokerType.FullName}:TracingEnabled") ?? settings.GetValue("Invokers:Default:TracingEnabled") ?? true;
+ return settings.GetCoreExValue($"Invokers:{invokerType.FullName}:TracingEnabled") ?? settings.GetCoreExValue("Invokers:Default:TracingEnabled") ?? true;
}
///
@@ -54,7 +54,7 @@ private static bool IsLoggingEnabled(Type invokerType)
if (settings.Configuration is null)
return true;
- return settings.GetValue($"Invokers:{invokerType.FullName}:LoggingEnabled") ?? settings.GetValue("Invokers:Default:LoggingEnabled") ?? true;
+ return settings.GetCoreExValue($"Invokers:{invokerType.FullName}:LoggingEnabled") ?? settings.GetCoreExValue("Invokers:Default:LoggingEnabled") ?? true;
}
///
diff --git a/src/CoreEx/Json/Compare/JsonElementComparer.cs b/src/CoreEx/Json/Compare/JsonElementComparer.cs
index bb0decf9..cb94cb50 100644
--- a/src/CoreEx/Json/Compare/JsonElementComparer.cs
+++ b/src/CoreEx/Json/Compare/JsonElementComparer.cs
@@ -377,8 +377,8 @@ private static void ComputeHashCode(JsonElement json, ref HashCode hash)
///
private sealed class CompareState
{
- private readonly Stack _unqualifiedPaths = new(new[] { "$" });
- private readonly Stack _paths = new(new[] { "$" });
+ private readonly Stack _unqualifiedPaths = new(["$"]);
+ private readonly Stack _paths = new(["$"]);
///
/// Initializes a new instance of the class.
@@ -391,7 +391,7 @@ public CompareState(JsonElementComparerResult result, IEqualityComparer?
Result = result;
PathComparer = pathComparer ?? StringComparer.InvariantCultureIgnoreCase;
var maxDepth = 0;
- PathsToIgnore = new(Text.Json.JsonFilterer.CreateDictionary(pathsToIgnore, JsonPropertyFilter.Exclude, StringComparison.Ordinal, ref maxDepth, true).Keys);
+ PathsToIgnore = new(JsonFilterer.CreateDictionary(pathsToIgnore, JsonPropertyFilter.Exclude, StringComparison.Ordinal, ref maxDepth, true).Keys);
}
///
diff --git a/src/CoreEx/Json/Compare/JsonElementComparerResult.cs b/src/CoreEx/Json/Compare/JsonElementComparerResult.cs
index ec3d63f6..c7029a9f 100644
--- a/src/CoreEx/Json/Compare/JsonElementComparerResult.cs
+++ b/src/CoreEx/Json/Compare/JsonElementComparerResult.cs
@@ -77,7 +77,7 @@ internal JsonElementComparerResult(JsonElement left, JsonElement right, int maxD
/// Gets the array.
///
/// The differences found up to the specified.
- public JsonElementDifference[] GetDifferences() => _differences is null ? [] : _differences.ToArray();
+ public JsonElementDifference[] GetDifferences() => _differences is null ? [] : [.. _differences];
///
/// Adds a .
diff --git a/src/CoreEx/Json/Mapping/JsonObjectMapperT.cs b/src/CoreEx/Json/Mapping/JsonObjectMapperT.cs
index eb913fa6..449d2fb3 100644
--- a/src/CoreEx/Json/Mapping/JsonObjectMapperT.cs
+++ b/src/CoreEx/Json/Mapping/JsonObjectMapperT.cs
@@ -138,16 +138,15 @@ public PropertyJsonMapper Property(Ex
///
/// Validates and adds a new IPropertyJsonMapper.
///
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "They are the arguments from the calling method.")]
- private void AddMapping(IPropertyJsonMapper pcm)
+ private void AddMapping(PropertyJsonMapper propertyJsonMapper)
{
- if (_mappings.Any(x => x.PropertyName == pcm.PropertyName))
- throw new ArgumentException($"Source property '{pcm.PropertyName}' must not be specified more than once.", "propertyExpression");
+ if (_mappings.Any(x => x.PropertyName == propertyJsonMapper.PropertyName))
+ throw new ArgumentException($"Source property '{propertyJsonMapper.PropertyName}' must not be specified more than once.", nameof(propertyJsonMapper));
- if (_mappings.Any(x => x.JsonName == pcm.JsonName))
- throw new ArgumentException($"Column '{pcm.JsonName}' must not be specified more than once.", "jsonName");
+ if (_mappings.Any(x => x.JsonName == propertyJsonMapper.JsonName))
+ throw new ArgumentException($"Column '{propertyJsonMapper.JsonName}' must not be specified more than once.", nameof(propertyJsonMapper));
- _mappings.Add(pcm);
+ _mappings.Add(propertyJsonMapper);
}
///
diff --git a/src/CoreEx/RefData/Caching/SettingsBasedCacheEntry.cs b/src/CoreEx/RefData/Caching/SettingsBasedCacheEntry.cs
index e6dd2da0..339559a8 100644
--- a/src/CoreEx/RefData/Caching/SettingsBasedCacheEntry.cs
+++ b/src/CoreEx/RefData/Caching/SettingsBasedCacheEntry.cs
@@ -36,8 +36,8 @@ public class SettingsBasedCacheEntry(SettingsBase? settings) : ICacheEntryConfig
/// This should be overridden where more advanced behaviour is required.
public virtual void CreateCacheEntry(Type type, ICacheEntry entry)
{
- entry.AbsoluteExpirationRelativeToNow = Settings?.GetValue($"RefDataCache__{type.Name}__{nameof(ICacheEntry.AbsoluteExpirationRelativeToNow)}", Settings.RefDataCacheAbsoluteExpirationRelativeToNow) ?? TimeSpan.FromHours(2);
- entry.SlidingExpiration = Settings?.GetValue($"RefDataCache__{type.Name}__{nameof(ICacheEntry.SlidingExpiration)}", Settings.RefDataCacheSlidingExpiration) ?? TimeSpan.FromMinutes(30);
+ entry.AbsoluteExpirationRelativeToNow = Settings?.GetCoreExValue($"RefDataCache:{type.Name}:{nameof(ICacheEntry.AbsoluteExpirationRelativeToNow)}", Settings.RefDataCacheAbsoluteExpirationRelativeToNow) ?? TimeSpan.FromHours(2);
+ entry.SlidingExpiration = Settings?.GetCoreExValue($"RefDataCache:{type.Name}:{nameof(ICacheEntry.SlidingExpiration)}", Settings.RefDataCacheSlidingExpiration) ?? TimeSpan.FromMinutes(30);
}
}
}
\ No newline at end of file
diff --git a/src/CoreEx/RefData/ReferenceDataCodeList.cs b/src/CoreEx/RefData/ReferenceDataCodeList.cs
index 5d9a6cc4..64c8308b 100644
--- a/src/CoreEx/RefData/ReferenceDataCodeList.cs
+++ b/src/CoreEx/RefData/ReferenceDataCodeList.cs
@@ -32,7 +32,7 @@ namespace CoreEx.RefData
/// Initializes a new instance of the class with a list of items.
///
/// The list of items.
- public ReferenceDataCodeList(IEnumerable items) => _codes = new((items ?? Array.Empty()).Select(x => x.Code));
+ public ReferenceDataCodeList(IEnumerable items) => _codes = new((items ?? []).Select(x => x.Code));
///
/// Initializes a new instance of the class with a array.
@@ -53,7 +53,7 @@ namespace CoreEx.RefData
/// Creates a new list from the underlying contents.
///
/// A new list
- public List ToRefDataList() => this.ToList();
+ public List ToRefDataList() => [.. this];
///
/// Creates a new list from the underlying contents.
diff --git a/src/CoreEx/RefData/ReferenceDataCollection.cs b/src/CoreEx/RefData/ReferenceDataCollection.cs
index 2c5e5a72..471b0e2c 100644
--- a/src/CoreEx/RefData/ReferenceDataCollection.cs
+++ b/src/CoreEx/RefData/ReferenceDataCollection.cs
@@ -16,7 +16,11 @@ namespace CoreEx.RefData
/// The .
public class ReferenceDataCollection : IReferenceDataCollection, ICollection where TId : IComparable, IEquatable where TRef : class, IReferenceData
{
+#if NET9_0_OR_GREATER
+ private readonly System.Threading.Lock _lock = new();
+#else
private readonly object _lock = new();
+#endif
private readonly ConcurrentDictionary _rdcId = new();
private readonly ConcurrentDictionary _rdcCode;
private Dictionary<(string, object?), TRef>? _mappingsDict;
diff --git a/src/CoreEx/RefData/ReferenceDataOrchestrator.cs b/src/CoreEx/RefData/ReferenceDataOrchestrator.cs
index 2f2d4e3f..4fc51340 100644
--- a/src/CoreEx/RefData/ReferenceDataOrchestrator.cs
+++ b/src/CoreEx/RefData/ReferenceDataOrchestrator.cs
@@ -38,7 +38,11 @@ public class ReferenceDataOrchestrator
private static readonly AsyncLocal _asyncLocal = new();
+#if NET9_0_OR_GREATER
+ private readonly System.Threading.Lock _lock = new();
+#else
private readonly object _lock = new();
+#endif
private readonly ConcurrentDictionary _typeToProvider = new();
private readonly ConcurrentDictionary _nameToType = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary
/// The .
/// The error message.
- public ValidationException(MessageItem item, string? message = null) : this(new MessageItem[] { item }, message) { }
+ public ValidationException(MessageItem item, string? message = null) : this([item], message) { }
///
/// Gets the underlying messages.
diff --git a/tests/CoreEx.Cosmos.Test/CoreEx.Cosmos.Test.csproj b/tests/CoreEx.Cosmos.Test/CoreEx.Cosmos.Test.csproj
index 75c92137..af4619b5 100644
--- a/tests/CoreEx.Cosmos.Test/CoreEx.Cosmos.Test.csproj
+++ b/tests/CoreEx.Cosmos.Test/CoreEx.Cosmos.Test.csproj
@@ -34,7 +34,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/tests/CoreEx.Test/CoreEx.Test.csproj b/tests/CoreEx.Test/CoreEx.Test.csproj
index 1a5bb763..d93a566a 100644
--- a/tests/CoreEx.Test/CoreEx.Test.csproj
+++ b/tests/CoreEx.Test/CoreEx.Test.csproj
@@ -23,7 +23,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/tests/CoreEx.Test/Framework/Entities/CleanerTest.cs b/tests/CoreEx.Test/Framework/Entities/CleanerTest.cs
index fcec86e1..6a7f74c2 100644
--- a/tests/CoreEx.Test/Framework/Entities/CleanerTest.cs
+++ b/tests/CoreEx.Test/Framework/Entities/CleanerTest.cs
@@ -1,6 +1,10 @@
-using CoreEx.Entities;
+using CoreEx.Configuration;
+using CoreEx.Entities;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using System;
+using System.Collections.Generic;
namespace CoreEx.Test.Framework.Entities
{
@@ -41,6 +45,65 @@ public void NullableDateTimeCleaning()
Assert.That(dtc, Is.Null);
}
+ [Test]
+ public void DateTimeTransformFromSettings()
+ {
+ Cleaner.DefaultDateTimeTransform = DateTimeTransform.DateTimeUtc;
+
+ ConfigurationBuilder builder = new();
+ Dictionary testSettings = new()
+ {
+ {"CoreEx:Cleaner:DateTimeTransform", "DateTimeLocal"}
+ };
+ builder.AddInMemoryCollection(testSettings);
+
+ IServiceProvider serviceProvider = new ServiceCollection()
+ .AddSingleton(builder.Build())
+ .AddDefaultSettings()
+ .BuildServiceProvider();
+
+ using var scope = serviceProvider.CreateScope();
+ using var ec = ExecutionContext.CreateNew();
+ ec.ServiceProvider = scope.ServiceProvider;
+
+ DateTime? dt = DateTime.UtcNow;
+ DateTime? dtc = Cleaner.Clean(dt);
+ Assert.Multiple(() =>
+ {
+ Assert.That(dtc!.Value.Kind, Is.EqualTo(DateTimeKind.Local));
+ Assert.That(Cleaner.DefaultDateTimeTransform, Is.EqualTo(DateTimeTransform.DateTimeUtc));
+ });
+ }
+
+ [Test]
+ public void DateTimeTransformFromSettings_Load()
+ {
+ Cleaner.DefaultDateTimeTransform = DateTimeTransform.DateTimeUtc;
+
+ ConfigurationBuilder builder = new();
+ Dictionary testSettings = new()
+ {
+ {"Cleaner:DateTimeTransform", "DateTimeLocal"}
+ };
+ builder.AddInMemoryCollection(testSettings);
+
+ IServiceProvider serviceProvider = new ServiceCollection()
+ .AddSingleton(builder.Build())
+ .AddDefaultSettings()
+ .BuildServiceProvider();
+
+ using var scope = serviceProvider.CreateScope();
+ using var ec = ExecutionContext.CreateNew();
+ ec.ServiceProvider = scope.ServiceProvider;
+
+ DateTime? dtc = DateTime.UtcNow;
+ for (int i = 0; i < 10000; i++)
+ {
+ dtc = Cleaner.Clean(DateTime.UtcNow);
+ Assert.That(dtc!.Value.Kind, Is.EqualTo(DateTimeKind.Local), "Iteration" + i);
+ }
+ }
+
[Test]
public void StringTransformCleaning()
{
diff --git a/tests/CoreEx.Test/Framework/WebApis/WebApiPublisherTest.cs b/tests/CoreEx.Test/Framework/WebApis/WebApiPublisherTest.cs
index 635af5f1..4311b20e 100644
--- a/tests/CoreEx.Test/Framework/WebApis/WebApiPublisherTest.cs
+++ b/tests/CoreEx.Test/Framework/WebApis/WebApiPublisherTest.cs
@@ -544,7 +544,7 @@ public void CancelWorkStatus_Success()
Assert.That(ws, Is.Not.Null);
Assert.Multiple(() =>
{
- Assert.That(ws!.Status, Is.EqualTo(WorkStatus.Cancelled));
+ Assert.That(ws!.Status, Is.EqualTo(WorkStatus.Canceled));
Assert.That(ws.Reason, Is.EqualTo("No reason was specified."));
});
}
diff --git a/tests/CoreEx.Test/TestFunction/HttpTriggerPublishFunctionTest.cs b/tests/CoreEx.Test/TestFunction/HttpTriggerPublishFunctionTest.cs
index 5d51604d..c7f1cd23 100644
--- a/tests/CoreEx.Test/TestFunction/HttpTriggerPublishFunctionTest.cs
+++ b/tests/CoreEx.Test/TestFunction/HttpTriggerPublishFunctionTest.cs
@@ -97,7 +97,7 @@ public void InvalidValue()
test.ReplaceScoped(_ => imp)
.HttpTrigger()
- .Run(f => f.RunAsync(test.CreateJsonHttpRequest(HttpMethod.Post, "https://unittest/products", new { id = "A", price = 1.99m })))
+ .Run(f => f.RunAsync(test.CreateJsonHttpRequest(HttpMethod.Post, "https://unittest/products/publish", new { id = "A", price = 1.99m })))
.AssertBadRequest()
.AssertErrors(new ApiError("Name", "'Name' must not be empty."));
@@ -113,7 +113,7 @@ public void InvalidValue_Newtonsoft()
test.ReplaceScoped(_ => imp)
.ConfigureServices(sc => sc.ReplaceScoped())
.HttpTrigger()
- .Run(f => f.RunAsync(test.CreateJsonHttpRequest(HttpMethod.Post, "https://unittest/products", new { id = "A", price = 1.99m })))
+ .Run(f => f.RunAsync(test.CreateJsonHttpRequest(HttpMethod.Post, "https://unittest/products/publish", new { id = "A", price = 1.99m })))
.AssertBadRequest()
.AssertErrors(new ApiError("Name", "'Name' must not be empty."));
@@ -128,7 +128,7 @@ public void Success()
test.ReplaceScoped(_ => imp)
.HttpTrigger()
- .Run(f => f.RunAsync(test.CreateJsonHttpRequest(HttpMethod.Post, "https://unittest/products", new { id = "A", name = "B", price = 1.99m })))
+ .Run(f => f.RunAsync(test.CreateJsonHttpRequest(HttpMethod.Post, "https://unittest/products/publish", new { id = "A", name = "B", price = 1.99m })))
.AssertAccepted();
Assert.That(imp.GetNames(), Has.Length.EqualTo(1));
@@ -147,7 +147,7 @@ public void Success_Newtonsoft()
test.ReplaceScoped(_ => imp)
.ConfigureServices(sc => sc.ReplaceScoped())
.HttpTrigger()
- .Run(f => f.RunAsync(test.CreateJsonHttpRequest(HttpMethod.Post, "https://unittest/products", new { id = "A", name = "B", price = 1.99m })))
+ .Run(f => f.RunAsync(test.CreateJsonHttpRequest(HttpMethod.Post, "https://unittest/products/publish", new { id = "A", name = "B", price = 1.99m })))
.AssertAccepted();
Assert.That(imp.GetNames(), Has.Length.EqualTo(1));
diff --git a/tests/CoreEx.Test2/CoreEx.Test2.csproj b/tests/CoreEx.Test2/CoreEx.Test2.csproj
index 680df577..8f369af1 100644
--- a/tests/CoreEx.Test2/CoreEx.Test2.csproj
+++ b/tests/CoreEx.Test2/CoreEx.Test2.csproj
@@ -20,7 +20,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/tests/CoreEx.Test2/Framework/Validation/ValidationExtensionsTest.cs b/tests/CoreEx.Test2/Framework/Validation/ValidationExtensionsTest.cs
index 4598c9bf..9a5a2b38 100644
--- a/tests/CoreEx.Test2/Framework/Validation/ValidationExtensionsTest.cs
+++ b/tests/CoreEx.Test2/Framework/Validation/ValidationExtensionsTest.cs
@@ -27,7 +27,7 @@ private static async Task> ValidateAsync(string? email2)
return await Result.Go().ValidatesAsync(email2, v =>
{
var v2 = v;
- var v3 = v.Email();
+ var v3 = v2.Email();
});
}
}
diff --git a/tests/CoreEx.Test2/TestFunctionIso/ServiceBusTest.cs b/tests/CoreEx.Test2/TestFunctionIso/ServiceBusTest.cs
index 62253173..84470217 100644
--- a/tests/CoreEx.Test2/TestFunctionIso/ServiceBusTest.cs
+++ b/tests/CoreEx.Test2/TestFunctionIso/ServiceBusTest.cs
@@ -42,7 +42,7 @@ public async Task Complete_WorkStatus_Cancelled()
await wo.CancelAsync(message.MessageId, "No longer needed.");
test.ServiceBusTrigger()
- .ExpectLogContains("warn: Unable to process message as corresponding work state status is Cancelled: No longer needed.")
+ .ExpectLogContains("warn: Unable to process message as corresponding work state status is Canceled: No longer needed.")
.Run(f => f.Run(message, actions))
.AssertSuccess();
@@ -50,7 +50,7 @@ public async Task Complete_WorkStatus_Cancelled()
var ws = await wo.GetAsync(message.MessageId);
Assert.That(ws, Is.Not.Null);
- Assert.That(ws!.Status, Is.EqualTo(WorkStatus.Cancelled));
+ Assert.That(ws!.Status, Is.EqualTo(WorkStatus.Canceled));
}
[Test]
diff --git a/tests/CoreEx.TestFunction/Functions/HttpTriggerFunction.cs b/tests/CoreEx.TestFunction/Functions/HttpTriggerFunction.cs
index 8c4e8e69..ee3a2040 100644
--- a/tests/CoreEx.TestFunction/Functions/HttpTriggerFunction.cs
+++ b/tests/CoreEx.TestFunction/Functions/HttpTriggerFunction.cs
@@ -10,16 +10,10 @@
namespace CoreEx.TestFunction.Functions
{
- public class HttpTriggerFunction
+ public class HttpTriggerFunction(WebApi webApi, ProductService service)
{
- private readonly WebApi _webApi;
- private readonly ProductService _service;
-
- public HttpTriggerFunction(WebApi webApi, ProductService service)
- {
- _webApi = webApi;
- _service = service;
- }
+ private readonly WebApi _webApi = webApi;
+ private readonly ProductService _service = service;
[FunctionName("HttpTriggerProductGet")]
public Task GetAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "products/{id}")] HttpRequest request, string id)