From de6c306be47a23ce5a4412d11f1a7af3470128e8 Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Mon, 19 Feb 2024 14:51:40 -0800 Subject: [PATCH 1/6] PostgreSQL. Minor fixes. Internal cleanup. --- CHANGELOG.md | 9 ++ Common.targets | 2 +- CoreEx.sln | 11 ++- nuget-publish.ps1 | 1 + samples/My.Hr/My.Hr.Api/Startup.cs | 4 +- samples/My.Hr/My.Hr.Functions/Startup.cs | 7 +- .../My.Hr.UnitTest/appsettings.unittest.json | 2 +- .../AutoMapperConverterWrapper.cs | 11 +-- src/CoreEx.AutoMapper/AutoMapperExtensions.cs | 16 ++-- .../AutoMapperServiceCollectionExtensions.cs | 4 +- src/CoreEx.AutoMapper/AutoMapperWrapper.cs | 13 +-- src/CoreEx.Azure/AppConfig/Extensions.cs | 2 +- src/CoreEx.Azure/CoreEx.Azure.csproj | 4 +- .../IServiceCollectionExtensions.cs | 37 +------- src/CoreEx.Azure/ServiceBus/README.md | 2 +- .../ServiceBusOrchestratedSubscriber.cs | 2 +- .../ServiceBus/ServiceBusPurger.cs | 29 ++----- .../ServiceBus/ServiceBusReceiverActions.cs | 13 +-- .../ServiceBus/ServiceBusSender.cs | 6 +- .../ServiceBus/ServiceBusSubscriberInvoker.cs | 4 +- .../Storage/BlobAttachmentStorage.cs | 11 +-- .../Storage/BlobLeaseSynchronizer.cs | 2 +- .../Storage/BlobSasAttachmentStorage.cs | 2 +- src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs | 23 +++-- src/CoreEx.Cosmos/CosmosDb.cs | 54 +++++------- src/CoreEx.Cosmos/CosmosDbContainer.cs | 15 ++-- src/CoreEx.Cosmos/CosmosDbContainerBase.cs | 21 ++--- src/CoreEx.Cosmos/CosmosDbModelContainer.cs | 6 +- src/CoreEx.Cosmos/CosmosDbModelQueryBase.cs | 2 +- src/CoreEx.Cosmos/CosmosDbQueryBase.cs | 2 +- src/CoreEx.Cosmos/CosmosDbValue.cs | 4 +- src/CoreEx.Cosmos/CosmosDbValueContainer.cs | 15 ++-- .../CosmosDbValueQueryableExtensions.cs | 5 +- src/CoreEx.Cosmos/CosmosExtensions.cs | 14 +-- src/CoreEx.Database.MySql/MySqlDatabase.cs | 14 +-- .../CoreEx.Database.Postgres.csproj | 22 +++++ .../PostgresDatabase.cs | 80 ++++++++++++++++++ .../PostgresDatabaseColumns.cs | 19 +++++ .../strong-name-key.snk | Bin 0 -> 596 bytes .../DatabaseServiceCollectionExtensions.cs | 3 +- .../Outbox/EventOutboxDequeueBase.cs | 24 ++---- .../Outbox/EventOutboxEnqueueBase.cs | 19 ++--- .../SqlServerDatabase.cs | 16 ++-- .../SqlServerExtensions.cs | 13 ++- .../TableValuedParameter.cs | 23 ++--- src/CoreEx.Database/Database.cs | 33 +++----- src/CoreEx.Database/DatabaseCommand.cs | 30 ++----- .../DatabaseParameterCollection.cs | 15 ++-- src/CoreEx.Database/DatabaseRecord.cs | 25 ++---- src/CoreEx.Database/DatabaseRecordMapper.cs | 11 +-- src/CoreEx.Database/DatabaseWildcard.cs | 2 +- src/CoreEx.Database/Extended/DatabaseArgs.cs | 8 +- .../Extended/DatabaseExtendedExtensions.cs | 21 ++--- src/CoreEx.Database/Extended/DatabaseQuery.cs | 2 +- src/CoreEx.Database/Extended/RefDataLoader.cs | 13 +-- src/CoreEx.Database/Extended/RefDataMapper.cs | 2 +- .../Extended/RefDataMultiSetCollArgs.cs | 34 +++----- .../IDatabaseParametersExtensions.cs | 23 ++--- .../Mapping/DatabaseMapperT.cs | 23 +++-- .../Mapping/PropertyColumnMapper.cs | 8 +- src/CoreEx.Database/MultiSetCollArgs.cs | 2 +- src/CoreEx.Database/MultiSetCollArgsT.cs | 29 ++----- src/CoreEx.Database/MultiSetSingleArgs.cs | 19 ++--- src/CoreEx.Database/MultiSetSingleArgsT.cs | 23 ++--- .../Mapping/DataverseMapperT.cs | 10 +-- .../Mapping/PropertyColumnMapper.cs | 8 +- src/CoreEx.EntityFrameworkCore/EfDb.cs | 36 +++----- src/CoreEx.EntityFrameworkCore/EfDbEntity.cs | 11 +-- src/CoreEx.EntityFrameworkCore/EfDbQuery.cs | 5 +- .../IFluentServiceCollectionExtensions.cs | 5 +- .../ValidationExtensions.cs | 2 +- .../ValidationResultWrapper.cs | 2 +- .../ValidatorWrapper.cs | 11 +-- .../Json/ContractResolver.cs | 19 ++--- src/CoreEx.Newtonsoft/Json/JsonFilterer.cs | 4 +- .../NewtonsoftServiceCollectionExtensions.cs | 3 +- src/CoreEx.OData/IBoundClientExtensions.cs | 12 +-- .../Mapping/PropertyColumnMapper.cs | 2 +- src/CoreEx.OData/ODataClient.cs | 2 +- src/CoreEx.OData/ODataItemCollection.cs | 6 +- src/CoreEx.OData/ODataQuery.cs | 5 +- .../EventDataToPubSubMessageConverter.cs | 19 ++--- src/CoreEx.Solace/PubSub/PubSubSender.cs | 10 +-- .../AspNetCore/AgentTester.cs | 7 +- .../AspNetCore/AgentTesterT.cs | 5 +- .../Expectations/EventExpectations.cs | 16 ++-- .../Expectations/ExpectedEventPublisher.cs | 17 +--- .../Json/ToCoreExJsonSerializerMapper.cs | 3 +- .../Json/ToUnitTestExJsonSerializerMapper.cs | 3 +- .../UnitTestExExtensions.cs | 10 +-- .../Clauses/DependsOnClause.cs | 17 +--- src/CoreEx.Validation/Clauses/WhenClause.cs | 9 +- src/CoreEx.Validation/CollectionValidator.cs | 11 ++- src/CoreEx.Validation/CommonValidatorT.cs | 7 +- src/CoreEx.Validation/DictionaryValidator.cs | 13 ++- src/CoreEx.Validation/IncludeBaseRule.cs | 7 +- src/CoreEx.Validation/PropertyContext.cs | 8 +- src/CoreEx.Validation/PropertyRule.cs | 5 +- src/CoreEx.Validation/PropertyRuleBase.cs | 9 +- src/CoreEx.Validation/RuleSet.cs | 2 +- src/CoreEx.Validation/Rules/BetweenRule.cs | 8 +- src/CoreEx.Validation/Rules/CollectionRule.cs | 8 +- .../Rules/CollectionRuleItem.cs | 2 +- src/CoreEx.Validation/Rules/CommonRule.cs | 11 +-- .../Rules/CompareRuleBase.cs | 3 +- .../Rules/CompareValueRule.cs | 7 +- .../Rules/CompareValuesRule.cs | 7 +- src/CoreEx.Validation/Rules/CustomRule.cs | 4 +- src/CoreEx.Validation/Rules/DictionaryRule.cs | 8 +- src/CoreEx.Validation/Rules/DuplicateRule.cs | 4 +- src/CoreEx.Validation/Rules/EntityRule.cs | 19 +---- src/CoreEx.Validation/Rules/EntityRuleWith.cs | 11 +-- .../Rules/EnumValueRuleAs.cs | 11 +-- src/CoreEx.Validation/Rules/ExistsRule.cs | 8 +- src/CoreEx.Validation/Rules/ImmutableRule.cs | 6 +- src/CoreEx.Validation/Rules/InteropRule.cs | 10 +-- src/CoreEx.Validation/Rules/MustRule.cs | 6 +- src/CoreEx.Validation/Rules/OverrideRule.cs | 4 +- .../Rules/ReferenceDataCodeRuleAs.cs | 19 ++--- src/CoreEx.Validation/ValidationContext.cs | 15 ++-- src/CoreEx.Validation/ValidationExtensions.cs | 26 ++---- .../ValidationServiceCollectionExtensions.cs | 7 +- src/CoreEx.Validation/ValidatorT.cs | 21 ++--- src/CoreEx.Validation/ValueValidatorResult.cs | 11 +-- .../Hosting/Work/WorkStateOrchestrator.cs | 2 +- src/CoreEx/Invokers/InvokeArgs.cs | 20 ++++- src/CoreEx/Invokers/InvokerBaseT.cs | 14 ++- src/CoreEx/Invokers/InvokerBaseT2.cs | 14 ++- .../EncodedStringToDateTimeConverter.cs | 3 +- .../EncodedStringToUInt32Converter.cs | 35 ++++++++ .../RefData/ReferenceDataOrchestrator.cs | 7 +- .../EncodedStringToUInt32ConverterTest.cs | 24 ++++++ .../TestFunctionIso/HttpFunctionTest.cs | 2 +- .../CoreEx.TestFunctionIso.csproj | 2 + tests/CoreEx.TestFunctionIso/Program.cs | 17 ++-- 135 files changed, 716 insertions(+), 892 deletions(-) create mode 100644 src/CoreEx.Database.Postgres/CoreEx.Database.Postgres.csproj create mode 100644 src/CoreEx.Database.Postgres/PostgresDatabase.cs create mode 100644 src/CoreEx.Database.Postgres/PostgresDatabaseColumns.cs create mode 100644 src/CoreEx.Database.Postgres/strong-name-key.snk create mode 100644 src/CoreEx/Mapping/Converters/EncodedStringToUInt32Converter.cs create mode 100644 tests/CoreEx.Test/Framework/Mapping/Converters/EncodedStringToUInt32ConverterTest.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5632ac06..438e16e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Represents the **NuGet** versions. +## v3.12.0 +- *Enhancement*: Added new `CoreEx.Database.Postgres` project/package to support [PostgreSQL](https://www.postgresql.org/) database capabilities. Primarily encapsulates the open-source [`Npqsql`](https://www.npgsql.org/) .NET ADO database provider for PostgreSQL. + - Added `EncodedStringToUInt32Converter` to support PostgreSQL `xmin` column encoding as the row version/etag. +- *Fixed:* The `IServiceCollection.AddAzureServiceBusClient` extension method as been removed; the `ServiceBusClient` will need to be instantiated prior to usage. Standard approach is for consumers to create client instances independently. +- *Fixed*: The `WorkOrchestrator.GetAsync()` and `WorkOrchestrator.GetAsync(string type, ..)` methods were not automatically cancelling where expired. +- *Fixed*: The `InvokerArgs` activity tracing is correctly capturing the `Exception.Message` where an `Exception` has been thrown. +- *Internal*: + - All `throw new ArgumentNullException` migrated to the `xxx.ThrowIfNull` extension method equivalent. + ## v3.11.0 - *Enhancement*: The `ITypedToResult` updated to correctly implement `IToResult` as the simple `ToResult` where required. - *Enhancement*: Added `Result.AsTask()` and `Result.AsTask` to simplify the conversion to a completed `Task` or `Task>` where applicable. diff --git a/Common.targets b/Common.targets index e66fa092..c90a67c7 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@  - 3.11.0 + 3.12.0 preview Avanade Avanade diff --git a/CoreEx.sln b/CoreEx.sln index 824a5a6d..9eecad99 100644 --- a/CoreEx.sln +++ b/CoreEx.sln @@ -83,9 +83,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreEx.UnitTesting", "src\C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreEx.UnitTesting.NUnit", "src\CoreEx.UnitTesting.NUnit\CoreEx.UnitTesting.NUnit.csproj", "{91910971-4B1A-4791-9BB4-65FAB3ED3C76}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreEx.TestFunctionIso", "tests\CoreEx.TestFunctionIso\CoreEx.TestFunctionIso.csproj", "{6F7B4F1E-3C3A-4CD7-A9BF-973A5053C1C8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreEx.TestFunctionIso", "tests\CoreEx.TestFunctionIso\CoreEx.TestFunctionIso.csproj", "{6F7B4F1E-3C3A-4CD7-A9BF-973A5053C1C8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreEx.Test2", "tests\CoreEx.Test2\CoreEx.Test2.csproj", "{910B5894-46BC-4427-95D6-2804F06458E3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreEx.Test2", "tests\CoreEx.Test2\CoreEx.Test2.csproj", "{910B5894-46BC-4427-95D6-2804F06458E3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreEx.Database.Postgres", "src\CoreEx.Database.Postgres\CoreEx.Database.Postgres.csproj", "{C042AC2A-415D-432E-83FA-B911FD9ED378}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -209,6 +211,10 @@ Global {910B5894-46BC-4427-95D6-2804F06458E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {910B5894-46BC-4427-95D6-2804F06458E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {910B5894-46BC-4427-95D6-2804F06458E3}.Release|Any CPU.Build.0 = Release|Any CPU + {C042AC2A-415D-432E-83FA-B911FD9ED378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C042AC2A-415D-432E-83FA-B911FD9ED378}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C042AC2A-415D-432E-83FA-B911FD9ED378}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C042AC2A-415D-432E-83FA-B911FD9ED378}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -245,6 +251,7 @@ Global {91910971-4B1A-4791-9BB4-65FAB3ED3C76} = {D2C61D4A-2A6D-4284-BF9D-09F51BA735B8} {6F7B4F1E-3C3A-4CD7-A9BF-973A5053C1C8} = {3145DCB9-98FB-4519-BCC0-75A22A252EDC} {910B5894-46BC-4427-95D6-2804F06458E3} = {3145DCB9-98FB-4519-BCC0-75A22A252EDC} + {C042AC2A-415D-432E-83FA-B911FD9ED378} = {4B6BC31E-93B1-42B0-AE09-AD85AC4DB657} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B4566D2-9B22-4E27-9654-402BDBA6C744} diff --git a/nuget-publish.ps1 b/nuget-publish.ps1 index 361a28e8..1313a463 100644 --- a/nuget-publish.ps1 +++ b/nuget-publish.ps1 @@ -56,6 +56,7 @@ param( "src\CoreEx.Database", "src\CoreEx.Database.SqlServer", "src\CoreEx.Database.MySql", + "src\CoreEx.Database.Postgres", "src\CoreEx.EntityFrameworkCore", "src\CoreEx.Cosmos", "src\CoreEx.OData", diff --git a/samples/My.Hr/My.Hr.Api/Startup.cs b/samples/My.Hr/My.Hr.Api/Startup.cs index 353017bb..48e4d3cb 100644 --- a/samples/My.Hr/My.Hr.Api/Startup.cs +++ b/samples/My.Hr/My.Hr.Api/Startup.cs @@ -4,8 +4,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Diagnostics.HealthChecks; using Azure.Monitor.OpenTelemetry.AspNetCore; -using OpenTelemetry.Instrumentation.AspNetCore; using OpenTelemetry.Trace; +using Az = Azure.Messaging.ServiceBus; namespace My.Hr.Api; @@ -28,9 +28,9 @@ public void ConfigureServices(IServiceCollection services) .AddEventDataSerializer() .AddEventDataFormatter() .AddEventPublisher() + .AddSingleton(sp => new Az.ServiceBusClient(sp.GetRequiredService().ServiceBusConnection__fullyQualifiedNamespace)) .AddAzureServiceBusSender() .AddAzureServiceBusPurger() - .AddAzureServiceBusClient(connectionName: nameof(HrSettings.ServiceBusConnection), configure: (o, sp) => o.RetryOptions.MaxRetries = 3) .AddJsonMergePatch() .AddWebApi((_, c) => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null)) .AddReferenceDataContentWebApi() diff --git a/samples/My.Hr/My.Hr.Functions/Startup.cs b/samples/My.Hr/My.Hr.Functions/Startup.cs index fc67f850..ba97fbf1 100644 --- a/samples/My.Hr/My.Hr.Functions/Startup.cs +++ b/samples/My.Hr/My.Hr.Functions/Startup.cs @@ -6,16 +6,15 @@ using CoreEx.Database; using CoreEx.DataBase.HealthChecks; using CoreEx.HealthChecks; -using CoreEx.RefData; using Microsoft.Azure.Functions.Extensions.DependencyInjection; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using My.Hr.Business; using My.Hr.Business.Data; using My.Hr.Business.External; using My.Hr.Business.Services; +using Az = Azure.Messaging.ServiceBus; [assembly: FunctionsStartup(typeof(My.Hr.Functions.Startup))] @@ -40,12 +39,12 @@ public override void Configure(IFunctionsHostBuilder builder) .AddEventDataSerializer() .AddEventDataFormatter() .AddEventPublisher() + .AddSingleton(sp => new Az.ServiceBusClient(sp.GetRequiredService().ServiceBusConnection__fullyQualifiedNamespace)) .AddAzureServiceBusSender() .AddWebApi((_, c) => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null)) .AddJsonMergePatch() .AddWebApiPublisher() - .AddAzureServiceBusSubscriber() - .AddAzureServiceBusClient(connectionName: nameof(HrSettings.ServiceBusConnection)); + .AddAzureServiceBusSubscriber(); // Register the health checks. builder.Services diff --git a/samples/My.Hr/My.Hr.UnitTest/appsettings.unittest.json b/samples/My.Hr/My.Hr.UnitTest/appsettings.unittest.json index cf829b3f..79ed8e38 100644 --- a/samples/My.Hr/My.Hr.UnitTest/appsettings.unittest.json +++ b/samples/My.Hr/My.Hr.UnitTest/appsettings.unittest.json @@ -2,7 +2,7 @@ "DOTNET_ENVIRONMENT": "Development", "VerificationResultsQueueName": "verificationResults", "VerificationQueueName": "pendingVerifications", - "ServiceBusConnection__fullyQualifiedNamespace": "topsecret", + "ServiceBusConnection__fullyQualifiedNamespace": "Endpoint=sb://top-secret.servicebus.windows.net/;SharedAccessKeyName=top-secret;SharedAccessKey=top-encrypted-secret;", "AgifyApiEndpointUri": "https://api.agify.mock.io", "NationalizeApiClientApiEndpointUri": "https://api.nationalize.mock.io", "GenderizeApiClientApiEndpointUri": "https://api.genderize.mock.io", diff --git a/src/CoreEx.AutoMapper/AutoMapperConverterWrapper.cs b/src/CoreEx.AutoMapper/AutoMapperConverterWrapper.cs index 39216fa1..25060832 100644 --- a/src/CoreEx.AutoMapper/AutoMapperConverterWrapper.cs +++ b/src/CoreEx.AutoMapper/AutoMapperConverterWrapper.cs @@ -11,23 +11,18 @@ namespace CoreEx.Mapping.Converters /// The destination . /// The . /// The declaring itself to enable . - public abstract class AutoMapperConverterWrapper + /// The optional function to create the instance. + public abstract class AutoMapperConverterWrapper(Func? create = null) where TConverter : IConverter, new() where TSelf : AutoMapperConverterWrapper, new() { - private readonly Func _create; + private readonly Func _create = create ?? (() => new TConverter()); /// /// Gets or sets the default (singleton) instance. /// public static TSelf Default { get; set; } = new(); - /// - /// Initializes a new instance of the class. - /// - /// The optional function to create the instance. - public AutoMapperConverterWrapper(Func? create = null) => _create = create ?? (() => new TConverter()); - /// /// Gets the source to destination . /// diff --git a/src/CoreEx.AutoMapper/AutoMapperExtensions.cs b/src/CoreEx.AutoMapper/AutoMapperExtensions.cs index c87882f8..7cb5766c 100644 --- a/src/CoreEx.AutoMapper/AutoMapperExtensions.cs +++ b/src/CoreEx.AutoMapper/AutoMapperExtensions.cs @@ -27,10 +27,7 @@ public static class AutoMapperExtensions /// Uses the . public static IMemberConfigurationExpression OperationTypes(this IMemberConfigurationExpression mce, OperationTypes operationTypes) { - if (mce == null) - throw new ArgumentNullException(nameof(mce)); - - mce.PreCondition((ResolutionContext rc) => !rc.Items.TryGetValue(OperationTypesName, out var ot) || operationTypes.HasFlag((OperationTypes)ot)); + mce.ThrowIfNull(nameof(mce)).PreCondition((ResolutionContext rc) => !rc.Items.TryGetValue(OperationTypesName, out var ot) || operationTypes.HasFlag((OperationTypes)ot)); return mce; } @@ -45,10 +42,7 @@ public static IMemberConfigurationExpressionUses the . public static IPathConfigurationExpression OperationTypes(this IPathConfigurationExpression pce, OperationTypes operationTypes) { - if (pce == null) - throw new ArgumentNullException(nameof(pce)); - - pce.Condition(cp => !cp.Context.Items.TryGetValue(OperationTypesName, out var ot) || operationTypes.HasFlag((OperationTypes)ot)); + pce.ThrowIfNull(nameof(pce)).Condition(cp => !cp.Context.Items.TryGetValue(OperationTypesName, out var ot) || operationTypes.HasFlag((OperationTypes)ot)); return pce; } @@ -61,7 +55,7 @@ public static IPathConfigurationExpression Opera /// The singluar CRUD value being performed. /// The destination value. public static TDestination Map(this AutoMapper.IMapper mapper, object source, OperationTypes operationType) - => (mapper ?? throw new ArgumentNullException(nameof(mapper))).Map(source, o => o.Items.Add(OperationTypesName, operationType)); + => mapper.ThrowIfNull(nameof(mapper)).Map(source, o => o.Items.Add(OperationTypesName, operationType)); /// /// Maps the value to a new value. @@ -73,7 +67,7 @@ public static TDestination Map(this AutoMapper.IMapper mapper, obj /// The singluar CRUD value being performed. /// The destination value. public static TDestination Map(this AutoMapper.IMapper mapper, TSource source, OperationTypes operationType) - => (mapper ?? throw new ArgumentNullException(nameof(mapper))).Map(source, o => o.Items.Add(OperationTypesName, operationType)); + => mapper.ThrowIfNull(nameof(mapper)).Map(source, o => o.Items.Add(OperationTypesName, operationType)); /// /// Maps the value into the existing value. @@ -86,6 +80,6 @@ public static TDestination Map(this AutoMapper.IMapper ma /// The singluar CRUD value being performed. /// The value. public static TDestination Map(this AutoMapper.IMapper mapper, TSource source, TDestination destination, OperationTypes operationType) - => (mapper ?? throw new ArgumentNullException(nameof(mapper))).Map(source, destination, o => o.Items.Add(OperationTypesName, operationType)); + => mapper.ThrowIfNull(nameof(mapper)).Map(source, destination, o => o.Items.Add(OperationTypesName, operationType)); } } \ No newline at end of file diff --git a/src/CoreEx.AutoMapper/AutoMapperServiceCollectionExtensions.cs b/src/CoreEx.AutoMapper/AutoMapperServiceCollectionExtensions.cs index 2955bb6d..5a7b9508 100644 --- a/src/CoreEx.AutoMapper/AutoMapperServiceCollectionExtensions.cs +++ b/src/CoreEx.AutoMapper/AutoMapperServiceCollectionExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using CoreEx.Mapping; -using System; namespace Microsoft.Extensions.DependencyInjection { @@ -16,6 +16,6 @@ public static class AutoMapperServiceCollectionExtensions /// The . /// The for fluent-style method-chaining. public static IServiceCollection AddAutoMapperWrapper(this IServiceCollection services) - => (services ?? throw new ArgumentNullException(nameof(services))).AddSingleton(sp => new AutoMapperWrapper(sp.GetRequiredService())); + => services.ThrowIfNull(nameof(services)).AddSingleton(sp => new AutoMapperWrapper(sp.GetRequiredService())); } } \ No newline at end of file diff --git a/src/CoreEx.AutoMapper/AutoMapperWrapper.cs b/src/CoreEx.AutoMapper/AutoMapperWrapper.cs index f663d53b..575274ac 100644 --- a/src/CoreEx.AutoMapper/AutoMapperWrapper.cs +++ b/src/CoreEx.AutoMapper/AutoMapperWrapper.cs @@ -1,24 +1,17 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx -using System; - namespace CoreEx.Mapping { /// /// Represents an wrapper to enable CoreEx . /// - public class AutoMapperWrapper : IMapper + /// The being wrapped. + public class AutoMapperWrapper(AutoMapper.IMapper autoMapper) : IMapper { - /// - /// Initializes a new instance of the class. - /// - /// The being wrapped. - public AutoMapperWrapper(AutoMapper.IMapper autoMapper) => Mapper = autoMapper ?? throw new ArgumentNullException(nameof(autoMapper)); - /// /// Gets the wrapped /// - public AutoMapper.IMapper Mapper { get; } + public AutoMapper.IMapper Mapper { get; } = autoMapper.ThrowIfNull(nameof(autoMapper)); /// public TDestination? Map(object? source, OperationTypes operationType = OperationTypes.Unspecified) => Mapper.Map(source!, operationType); diff --git a/src/CoreEx.Azure/AppConfig/Extensions.cs b/src/CoreEx.Azure/AppConfig/Extensions.cs index b69ba389..8a68c154 100644 --- a/src/CoreEx.Azure/AppConfig/Extensions.cs +++ b/src/CoreEx.Azure/AppConfig/Extensions.cs @@ -23,7 +23,7 @@ public static class Extensions public static IConfigurationBuilder AddAzureAppConfiguration(this IConfigurationBuilder builder, string connectionName = "AppConfigConnectionString", string? labelFilter = null, params string[] keyPrefixes) { // build configuration and get connection string to Azure App Configuration (this most likely will be environment variable set in app service) - var config = (builder ?? throw new ArgumentNullException(nameof(builder))).Build(); + var config = builder.ThrowIfNull(nameof(builder)).Build(); var accs = config.GetValue(connectionName); if (string.IsNullOrEmpty(accs)) diff --git a/src/CoreEx.Azure/CoreEx.Azure.csproj b/src/CoreEx.Azure/CoreEx.Azure.csproj index 7da3376f..7d1d1879 100644 --- a/src/CoreEx.Azure/CoreEx.Azure.csproj +++ b/src/CoreEx.Azure/CoreEx.Azure.csproj @@ -4,8 +4,8 @@ net6.0;net7.0;net8.0 CoreEx.Azure CoreEx - CoreEx .NET Standard Azure Extensions. - CoreEx .NET Standard (v2.1) Azure Extensions. + CoreEx .NET Azure Extensions. + CoreEx .NET Azure Extensions. coreex api function aspnet azure servicebus diff --git a/src/CoreEx.Azure/ServiceBus/IServiceCollectionExtensions.cs b/src/CoreEx.Azure/ServiceBus/IServiceCollectionExtensions.cs index 6cb63911..88db64cd 100644 --- a/src/CoreEx.Azure/ServiceBus/IServiceCollectionExtensions.cs +++ b/src/CoreEx.Azure/ServiceBus/IServiceCollectionExtensions.cs @@ -1,16 +1,13 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx -using System; -using Azure.Identity; using CoreEx; using CoreEx.Azure.ServiceBus; using CoreEx.Configuration; using CoreEx.Events; -using Microsoft.Extensions.Azure; +using CoreEx.Events.Subscribing; using Microsoft.Extensions.Logging; +using System; using Asb = Azure.Messaging.ServiceBus; -using Ace = Azure.Core.Extensions; -using CoreEx.Events.Subscribing; namespace Microsoft.Extensions.DependencyInjection { @@ -19,36 +16,6 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class ServiceCollectionExtensions { - /// - /// Adds the Azure . - /// - /// The . - /// The connection configuration setting name. - /// Adds a delegate to configure the client options. - /// The . - public static IServiceCollection AddAzureServiceBusClient(this IServiceCollection services, string connectionName = "ServiceBusConnection", Action? configure = null) - { - services.AddAzureClients(cb => - { - var config = services.BuildServiceProvider().GetRequiredService(); - var sbcs = config.GetValue(connectionName); - - if (string.IsNullOrEmpty(sbcs)) - throw new InvalidOperationException(@$"The Azure Service Bus connection string is not configured; the configuration setting '{connectionName}' is either not specified or does not have a value."); - - Ace.IAzureClientBuilder acb; - if (sbcs.Contains("SharedAccessKey=", StringComparison.OrdinalIgnoreCase)) - acb = cb.AddServiceBusClient(sbcs); // Connect to Azure Service Bus with secret. - else - acb = cb.AddServiceBusClientWithNamespace(sbcs).WithCredential(new DefaultAzureCredential()); // Connect to Azure Service Bus with managed identity. - - if (configure != null) - acb.ConfigureOptions(configure); - }); - - return services; - } - /// /// Adds the Azure as a scoped service. /// diff --git a/src/CoreEx.Azure/ServiceBus/README.md b/src/CoreEx.Azure/ServiceBus/README.md index f8a0bf0c..f14177dd 100644 --- a/src/CoreEx.Azure/ServiceBus/README.md +++ b/src/CoreEx.Azure/ServiceBus/README.md @@ -60,7 +60,7 @@ public class AppInsightInstrumentation : EventSubscriberInstrumentationBase private readonly TelemetryClient _telemetryClient; public AppInsightInstrumentation(TelemetryClient telemetryClient) - => _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); + => _telemetryClient = telemetryClient.ThrowIfNull(nameof(telemetryClient)); public override void Instrument(ErrorHandling? errorHandling = null, Exception? exception = null) => _telemetryClient.TrackEvent(GetInstrumentName("Subscriber", errorHandling, exception)); diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusOrchestratedSubscriber.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusOrchestratedSubscriber.cs index 9a046a7c..208e6610 100644 --- a/src/CoreEx.Azure/ServiceBus/ServiceBusOrchestratedSubscriber.cs +++ b/src/CoreEx.Azure/ServiceBus/ServiceBusOrchestratedSubscriber.cs @@ -37,7 +37,7 @@ public class ServiceBusOrchestratedSubscriber : EventSubscriberBase, IServiceBus public ServiceBusOrchestratedSubscriber(EventSubscriberOrchestrator orchestrator, ExecutionContext executionContext, SettingsBase settings, ILogger logger, EventSubscriberInvoker? eventSubscriberInvoker = null, ServiceBusSubscriberInvoker? serviceBusSubscriberInvoker = null, IEventDataConverter? eventDataConverter = null, IEventSerializer? eventSerializer = null) : base(eventDataConverter ?? new ServiceBusReceivedMessageEventDataConverter(eventSerializer ?? new CoreEx.Text.Json.EventDataSerializer()), executionContext, settings, logger, eventSubscriberInvoker) { - Orchestrator = orchestrator ?? throw new ArgumentNullException(nameof(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)}"); diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusPurger.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusPurger.cs index 2935800c..65146f98 100644 --- a/src/CoreEx.Azure/ServiceBus/ServiceBusPurger.cs +++ b/src/CoreEx.Azure/ServiceBus/ServiceBusPurger.cs @@ -12,26 +12,14 @@ namespace CoreEx.Azure.ServiceBus /// /// Provides the Azure ServiceBus purging capability. /// - public class ServiceBusPurger : IEventPurger + /// The underlying . + /// The . + /// The . + public class ServiceBusPurger(ServiceBusClient client, SettingsBase settings, ILogger logger) : IEventPurger { - private readonly ServiceBusClient _client; - private readonly SettingsBase Settings; - private readonly ILogger Logger; - - /// - /// Initializes a new instance of the class. - /// - /// The underlying . - /// The . - /// The . - public ServiceBusPurger(ServiceBusClient client, SettingsBase settings, ILogger logger) - { - _client = client ?? throw new ArgumentNullException(nameof(client), "Verify dependency injection configuration and if service bus connection string for publisher was correctly defined."); - _client.ConfigureAwait(false); - - Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } + private readonly ServiceBusClient _client = client.ThrowIfNull(nameof(client)); + private readonly SettingsBase Settings = settings.ThrowIfNull(nameof(settings)); + private readonly ILogger Logger = logger.ThrowIfNull(nameof(logger)); /// public Task PurgeDeadLetterAsync(string queueName, Action? messageAction = null, CancellationToken cancellationToken = default) @@ -54,8 +42,7 @@ public Task PurgeAsync(string topicName, string subscriptionName, Action private async Task PurgeAsync(string queueOrTopicName, string? subscriptionName, SubQueue subQueue, Action? messageAction, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(queueOrTopicName)) - throw new ArgumentNullException(nameof(queueOrTopicName)); + queueOrTopicName.ThrowIfNullOrEmpty(nameof(queueOrTopicName)); // Get queue name and subscription name by checking settings override. var qn = Settings.GetValue($"Publisher_ServiceBusQueueName_{queueOrTopicName}", defaultValue: queueOrTopicName); diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusReceiverActions.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusReceiverActions.cs index b4510dad..e2940d04 100644 --- a/src/CoreEx.Azure/ServiceBus/ServiceBusReceiverActions.cs +++ b/src/CoreEx.Azure/ServiceBus/ServiceBusReceiverActions.cs @@ -13,16 +13,11 @@ namespace CoreEx.Azure.ServiceBus /// Represents the set of message actions that can be performed on a and related . /// /// This is required as the base contains internal constructor for therefore this is needed to override methods and implement same. - public class ServiceBusReceiverActions : ServiceBusMessageActions + /// The . + /// + public class ServiceBusReceiverActions(ServiceBusReceiver serviceBusReceiver) : ServiceBusMessageActions { - private readonly ServiceBusReceiver _serviceBusReceiver; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// - public ServiceBusReceiverActions(ServiceBusReceiver serviceBusReceiver) => _serviceBusReceiver = serviceBusReceiver ?? throw new ArgumentNullException(nameof(serviceBusReceiver)); + private readonly ServiceBusReceiver _serviceBusReceiver = serviceBusReceiver.ThrowIfNull(nameof(serviceBusReceiver)); /// public override Task AbandonMessageAsync(ServiceBusReceivedMessage message, IDictionary propertiesToModify = default!, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusSender.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusSender.cs index 82d9d365..d2792ff3 100644 --- a/src/CoreEx.Azure/ServiceBus/ServiceBusSender.cs +++ b/src/CoreEx.Azure/ServiceBus/ServiceBusSender.cs @@ -27,17 +27,17 @@ public class ServiceBusSender(ServiceBusClient client, SettingsBase settings, IL { private const string _unspecifiedQueueOrTopicName = "$default"; private static ServiceBusSenderInvoker? _invoker; - private readonly ServiceBusClient _client = client ?? throw new ArgumentNullException(nameof(client), "Verify dependency injection configuration and if service bus connection string for publisher was correctly defined."); + private readonly ServiceBusClient _client = client.ThrowIfNull(nameof(client)); /// /// Gets the . /// - protected SettingsBase Settings { get; } = settings ?? throw new ArgumentNullException(nameof(settings)); + protected SettingsBase Settings { get; } = settings.ThrowIfNull(nameof(settings)); /// /// Gets the . /// - protected ILogger Logger { get; } = logger ?? throw new ArgumentNullException(nameof(logger)); + protected ILogger Logger { get; } = logger.ThrowIfNull(nameof(logger)); /// /// Gets the . diff --git a/src/CoreEx.Azure/ServiceBus/ServiceBusSubscriberInvoker.cs b/src/CoreEx.Azure/ServiceBus/ServiceBusSubscriberInvoker.cs index 8732ca1e..1ee89ca2 100644 --- a/src/CoreEx.Azure/ServiceBus/ServiceBusSubscriberInvoker.cs +++ b/src/CoreEx.Azure/ServiceBus/ServiceBusSubscriberInvoker.cs @@ -29,10 +29,10 @@ public class ServiceBusSubscriberInvoker : InvokerBase OnInvokeAsync(InvokeArgs invokeArgs, EventSubscriberBase invoker, Func> func, (ServiceBusReceivedMessage Message, ServiceBusMessageActions MessageActions) args, CancellationToken cancellationToken) { if (args.Message == null) - throw new ArgumentNullException(nameof(args), $"The {nameof(ServiceBusReceivedMessage)} value is required."); + throw new ArgumentException($"The {nameof(ServiceBusReceivedMessage)} value is required.", nameof(args)); if (args.MessageActions == null) - throw new ArgumentNullException(nameof(args), $"The {nameof(ServiceBusMessageActions)} value is required."); + throw new ArgumentException($"The {nameof(ServiceBusMessageActions)} value is required.", nameof(args)); if (!string.IsNullOrEmpty(args.Message.CorrelationId)) invoker.ExecutionContext.CorrelationId = args.Message.CorrelationId; diff --git a/src/CoreEx.Azure/Storage/BlobAttachmentStorage.cs b/src/CoreEx.Azure/Storage/BlobAttachmentStorage.cs index 25aeaf35..71d2dae7 100644 --- a/src/CoreEx.Azure/Storage/BlobAttachmentStorage.cs +++ b/src/CoreEx.Azure/Storage/BlobAttachmentStorage.cs @@ -14,15 +14,10 @@ namespace CoreEx.Azure.Storage /// Provides the reading and writing of a attachment that exceeds the as identified by a corresponding within /// Azure Blob Storage. /// - public class BlobAttachmentStorage : IAttachmentStorage + /// The . + public class BlobAttachmentStorage(BlobContainerClient blobContainerClient) : IAttachmentStorage { - private readonly BlobContainerClient _blobContainerClient; - - /// - /// Initializes a new instance of class. - /// - /// The . - public BlobAttachmentStorage(BlobContainerClient blobContainerClient) => _blobContainerClient = blobContainerClient ?? throw new ArgumentNullException(nameof(blobContainerClient)); + private readonly BlobContainerClient _blobContainerClient = blobContainerClient.ThrowIfNull(nameof(blobContainerClient)); /// public int MaxDataSize { get; set; } diff --git a/src/CoreEx.Azure/Storage/BlobLeaseSynchronizer.cs b/src/CoreEx.Azure/Storage/BlobLeaseSynchronizer.cs index 35350efe..8ccc7063 100644 --- a/src/CoreEx.Azure/Storage/BlobLeaseSynchronizer.cs +++ b/src/CoreEx.Azure/Storage/BlobLeaseSynchronizer.cs @@ -36,7 +36,7 @@ public class BlobLeaseSynchronizer : IServiceSynchronizer /// Performs a to ensure the container exists. public BlobLeaseSynchronizer(BlobContainerClient client) { - _client = client ?? throw new ArgumentNullException(nameof(client)); + _client = client.ThrowIfNull(nameof(client)); _client.CreateIfNotExists(); _timer = new Lazy(() => new Timer(_ => diff --git a/src/CoreEx.Azure/Storage/BlobSasAttachmentStorage.cs b/src/CoreEx.Azure/Storage/BlobSasAttachmentStorage.cs index a01770bd..6caf4edc 100644 --- a/src/CoreEx.Azure/Storage/BlobSasAttachmentStorage.cs +++ b/src/CoreEx.Azure/Storage/BlobSasAttachmentStorage.cs @@ -23,7 +23,7 @@ public class BlobSasAttachmentStorage : IAttachmentStorage /// Initializes a new instance of class with a /// /// The . - public BlobSasAttachmentStorage(BlobContainerClient blobContainerClient) => _blobContainerClient = blobContainerClient ?? throw new ArgumentNullException(nameof(blobContainerClient)); + public BlobSasAttachmentStorage(BlobContainerClient blobContainerClient) => _blobContainerClient = blobContainerClient.ThrowIfNull(nameof(blobContainerClient)); /// /// Initializes a new instance of class. diff --git a/src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs b/src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs index 150dcd62..9cc3a4b7 100644 --- a/src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs +++ b/src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs @@ -37,12 +37,12 @@ public static class CosmosDbBatch /// Each item is added individually and is not transactional. public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, string containerId, IEnumerable items, Func? modelUpdater = null, ItemRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) where TModel : class, IIdentifier { - var container = (cosmosDb ?? throw new ArgumentNullException(nameof(cosmosDb))).Database.GetContainer(containerId ?? throw new ArgumentNullException(nameof(containerId))); + var container = cosmosDb.ThrowIfNull(nameof(cosmosDb)).Database.GetContainer(containerId.ThrowIfNull(nameof(containerId))); if (items == null) return; - List tasks = new(); + List tasks = []; foreach (var item in items) { if (SequentialExecution) @@ -83,8 +83,8 @@ public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, strin /// Each item is added individually and is not transactional. public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, string containerId, JsonDataReader jsonDataReader, string? name = null, Func? modelUpdater = null, ItemRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) where TModel : class, IIdentifier, new() { - var container = (cosmosDb ?? throw new ArgumentNullException(nameof(cosmosDb))).Database.GetContainer(containerId ?? throw new ArgumentNullException(nameof(containerId))); - if (!(jsonDataReader ?? throw new ArgumentNullException(nameof(jsonDataReader))).TryDeserialize(name, out var items)) + var container = cosmosDb.ThrowIfNull(nameof(cosmosDb)).Database.GetContainer(containerId.ThrowIfNull(nameof(containerId))); + if (!jsonDataReader.ThrowIfNull(nameof(jsonDataReader)).TryDeserialize(name, out var items)) return false; await ImportBatchAsync(cosmosDb, containerId, items, modelUpdater, requestOptions, cancellationToken).ConfigureAwait(false); @@ -120,12 +120,12 @@ public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, strin /// Each item is added individually and is not transactional. public static async Task ImportValueBatchAsync(this ICosmosDb cosmosDb, string containerId, IEnumerable items, Func, CosmosDbValue>? modelUpdater = null, ItemRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) where TModel : class, IIdentifier, new() { - var container = (cosmosDb ?? throw new ArgumentNullException(nameof(cosmosDb))).Database.GetContainer(containerId ?? throw new ArgumentNullException(nameof(containerId))); + var container = cosmosDb.ThrowIfNull(nameof(cosmosDb)).Database.GetContainer(containerId.ThrowIfNull(nameof(containerId))); if (items == null) return; - List tasks = new(); + List tasks = []; foreach (var item in items) { var cdv = new CosmosDbValue(item); @@ -169,8 +169,8 @@ public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, strin /// Each item is added individually and is not transactional. public static async Task ImportValueBatchAsync(this ICosmosDb cosmosDb, string containerId, JsonDataReader jsonDataReader, string? name = null, Func, CosmosDbValue>? modelUpdater = null, ItemRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) where TModel : class, IIdentifier, new() { - var container = (cosmosDb ?? throw new ArgumentNullException(nameof(cosmosDb))).Database.GetContainer(containerId ?? throw new ArgumentNullException(nameof(containerId))); - if (!(jsonDataReader ?? throw new ArgumentNullException(nameof(jsonDataReader))).TryDeserialize(name, out var items)) + var container = cosmosDb.ThrowIfNull(nameof(cosmosDb)).Database.GetContainer(containerId.ThrowIfNull(nameof(containerId))); + if (!jsonDataReader.ThrowIfNull(nameof(jsonDataReader)).TryDeserialize(name, out var items)) return false; await ImportValueBatchAsync(cosmosDb, containerId, items, modelUpdater, requestOptions, cancellationToken).ConfigureAwait(false); @@ -207,11 +207,10 @@ public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, strin /// Each item is added individually and is not transactional. public static async Task ImportValueBatchAsync(this ICosmosDb cosmosDb, string containerId, JsonDataReader jsonDataReader, IEnumerable types, Func? modelUpdater = null, ItemRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) { - var container = (cosmosDb ?? throw new ArgumentNullException(nameof(cosmosDb))).Database.GetContainer(containerId ?? throw new ArgumentNullException(nameof(containerId))); - var tasks = new List(); + var container = cosmosDb.ThrowIfNull(nameof(cosmosDb)).Database.GetContainer(containerId.ThrowIfNull(nameof(containerId))); + jsonDataReader.ThrowIfNull(nameof(jsonDataReader)); - if (jsonDataReader == null) - throw new ArgumentNullException(nameof(jsonDataReader)); + var tasks = new List(); foreach (var type in types) { diff --git a/src/CoreEx.Cosmos/CosmosDb.cs b/src/CoreEx.Cosmos/CosmosDb.cs index 00f4f525..fb691ef1 100644 --- a/src/CoreEx.Cosmos/CosmosDb.cs +++ b/src/CoreEx.Cosmos/CosmosDb.cs @@ -13,7 +13,10 @@ namespace CoreEx.Cosmos /// /// Provides extended CosmosDb data access. /// - public class CosmosDb : ICosmosDb + /// The . + /// The . + /// Enables the to be overridden; defaults to . + public class CosmosDb(Database database, IMapper mapper, CosmosDbInvoker? invoker = null) : ICosmosDb { private static CosmosDbInvoker? _invoker; private Action? _updateRequestOptionsAction; @@ -24,40 +27,21 @@ public class CosmosDb : ICosmosDb /// /// Provides key as combination of model type and container identifier. /// - private readonly struct Key + private readonly struct Key(Type modelType, string containerId) { - public Key(Type modelType, string containerId) - { - ModelType = modelType; - ContainerId = containerId; - } + public Type ModelType { get; } = modelType; - public Type ModelType { get; } - - public string ContainerId { get; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// Enables the to be overridden; defaults to . - public CosmosDb(Database database, IMapper mapper, CosmosDbInvoker? invoker = null) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - Mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - Invoker = invoker ?? (_invoker ??= new CosmosDbInvoker()); + public string ContainerId { get; } = containerId; } /// - public Database Database { get; } + public Database Database { get; } = database.ThrowIfNull(nameof(database)); /// - public IMapper Mapper { get; } + public IMapper Mapper { get; } = mapper.ThrowIfNull(nameof(mapper)); /// - public CosmosDbInvoker Invoker { get; } + public CosmosDbInvoker Invoker { get; } = invoker ?? (_invoker ??= new CosmosDbInvoker()); /// public virtual PartitionKey? PartitionKey => _partitionKey; @@ -104,14 +88,14 @@ public CosmosDb UsePartitionKey(PartitionKey? partitionKey) /// The instance to support fluent-style method-chaining. public CosmosDb UseAuthorizeFilter(string containerId, Func filter) { - if (!_filters.TryAdd(new Key(typeof(TModel), containerId ?? throw new ArgumentNullException(nameof(containerId))), filter ?? throw new ArgumentNullException(nameof(filter)))) + if (!_filters.TryAdd(new Key(typeof(TModel), containerId.ThrowIfNull(nameof(containerId))), filter.ThrowIfNull(nameof(filter)))) throw new InvalidOperationException("A filter cannot be overridden."); return this; } /// - public Func? GetAuthorizeFilter(string containerId) => _filters.TryGetValue(new Key(typeof(TModel), containerId ?? throw new ArgumentNullException(nameof(containerId))), out var filter) ? filter : null; + public Func? GetAuthorizeFilter(string containerId) => _filters.TryGetValue(new Key(typeof(TModel), containerId.ThrowIfNull(nameof(containerId))), out var filter) ? filter : null; /// /// Sets the to update the for the selected operation. @@ -120,7 +104,7 @@ public CosmosDb UseAuthorizeFilter(string containerId, FuncThis instance to support fluent-style method-chaining. public CosmosDb ItemRequestOptions(Action updateItemRequestOptionsAction) { - _updateRequestOptionsAction = updateItemRequestOptionsAction ?? throw new ArgumentNullException(nameof(updateItemRequestOptionsAction)); + _updateRequestOptionsAction = updateItemRequestOptionsAction.ThrowIfNull(nameof(updateItemRequestOptionsAction)); return this; } @@ -128,7 +112,7 @@ public CosmosDb ItemRequestOptions(Action updateItemRequestOptio /// Updates the using the set with . /// /// The . - public void UpdateItemRequestOptions(RequestOptions itemRequestOptions) => _updateRequestOptionsAction?.Invoke(itemRequestOptions ?? throw new ArgumentNullException(nameof(itemRequestOptions))); + public void UpdateItemRequestOptions(RequestOptions itemRequestOptions) => _updateRequestOptionsAction?.Invoke(itemRequestOptions.ThrowIfNull(nameof(itemRequestOptions))); /// ItemRequestOptions ICosmosDb.GetItemRequestOptions(CosmosDbArgs dbArgs) where T : class where TModel : class @@ -145,7 +129,7 @@ ItemRequestOptions ICosmosDb.GetItemRequestOptions(CosmosDbArgs dbArg /// This instance to support fluent-style method-chaining. public CosmosDb QueryRequestOptions(Action updateQueryRequestOptionsAction) { - _updateQueryRequestOptionsAction = updateQueryRequestOptionsAction ?? throw new ArgumentNullException(nameof(updateQueryRequestOptionsAction)); + _updateQueryRequestOptionsAction = updateQueryRequestOptionsAction.ThrowIfNull(nameof(updateQueryRequestOptionsAction)); return this; } @@ -153,7 +137,7 @@ public CosmosDb QueryRequestOptions(Action updateQueryReque /// Updates the using the set with . /// /// The . - public void UpdateQueryRequestOptions(QueryRequestOptions queryRequestOptions) => _updateQueryRequestOptionsAction?.Invoke(queryRequestOptions ?? throw new ArgumentNullException(nameof(queryRequestOptions))); + public void UpdateQueryRequestOptions(QueryRequestOptions queryRequestOptions) => _updateQueryRequestOptionsAction?.Invoke(queryRequestOptions.ThrowIfNull(nameof(queryRequestOptions))); /// QueryRequestOptions ICosmosDb.GetQueryRequestOptions(CosmosDbArgs dbArgs) where T : class where TModel : class @@ -184,7 +168,7 @@ QueryRequestOptions ICosmosDb.GetQueryRequestOptions(CosmosDbArgs dbArgs /// The . /// The containing the appropriate . /// Where overridding and the is not specifically handled then invoke the base to ensure any standard handling is executed. - protected virtual Result? OnCosmosException(CosmosException cex) => cex == null ? throw new ArgumentNullException(nameof(cex)) : cex.StatusCode switch + protected virtual Result? OnCosmosException(CosmosException cex) => cex.ThrowIfNull(nameof(cex)).StatusCode switch { System.Net.HttpStatusCode.NotFound => Result.Fail(new NotFoundException(null, cex)), System.Net.HttpStatusCode.Conflict => Result.Fail(new DuplicateException(null, cex)), @@ -193,7 +177,7 @@ QueryRequestOptions ICosmosDb.GetQueryRequestOptions(CosmosDbArgs dbArgs }; /// - public virtual string FormatIdentifier(object? id) => id == null ? throw new ArgumentNullException(nameof(id)) : id switch + public virtual string FormatIdentifier(object? id) => id.ThrowIfNull(nameof(id)) switch { string si => si, int ii => ii.ToString(System.Globalization.CultureInfo.InvariantCulture), @@ -203,7 +187,7 @@ QueryRequestOptions ICosmosDb.GetQueryRequestOptions(CosmosDbArgs dbArgs }; /// - public virtual object? ParseIdentifier(Type type, string? id) => (type ?? throw new ArgumentNullException(nameof(type))) switch + public virtual object? ParseIdentifier(Type type, string? id) => type.ThrowIfNull(nameof(type)) switch { Type t when t == typeof(string) => id, Type t when t == typeof(int) => id == null ? 0 : int.Parse(id, System.Globalization.CultureInfo.InvariantCulture), diff --git a/src/CoreEx.Cosmos/CosmosDbContainer.cs b/src/CoreEx.Cosmos/CosmosDbContainer.cs index 48cc6858..c3543b41 100644 --- a/src/CoreEx.Cosmos/CosmosDbContainer.cs +++ b/src/CoreEx.Cosmos/CosmosDbContainer.cs @@ -17,15 +17,10 @@ namespace CoreEx.Cosmos /// /// The entity . /// The cosmos model . - public class CosmosDbContainer : CosmosDbContainerBase> where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() + /// The . + /// The identifier. + public class CosmosDbContainer(ICosmosDb cosmosDb, string containerId) : CosmosDbContainerBase>(cosmosDb, containerId) where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() { - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The identifier. - public CosmosDbContainer(ICosmosDb cosmosDb, string containerId) : base(cosmosDb, containerId) { } - /// /// Gets the value from the response updating any special properties as required. /// @@ -126,7 +121,7 @@ private Result CheckAuthorized(TModel model) }, cancellationToken, nameof(GetWithResultAsync)); /// - public override Task> CreateWithResultAsync(T value, CosmosDbArgs dbArgs, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, value ?? throw new ArgumentNullException(nameof(value)), dbArgs, async (_, v, args, ct) => + public override Task> CreateWithResultAsync(T value, CosmosDbArgs dbArgs, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, value.ThrowIfNull(nameof(value)), dbArgs, async (_, v, args, ct) => { var pk = GetPartitionKey(v); ChangeLog.PrepareCreated(v); @@ -141,7 +136,7 @@ private Result CheckAuthorized(TModel model) }, cancellationToken, nameof(CreateWithResultAsync)); /// - public override Task> UpdateWithResultAsync(T value, CosmosDbArgs dbArgs, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, value ?? throw new ArgumentNullException(nameof(value)), dbArgs, async (_,v, args, ct) => + public override Task> UpdateWithResultAsync(T value, CosmosDbArgs dbArgs, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, value.ThrowIfNull(nameof(value)), dbArgs, async (_,v, args, ct) => { var key = GetCosmosId(v); var pk = GetPartitionKey(v); diff --git a/src/CoreEx.Cosmos/CosmosDbContainerBase.cs b/src/CoreEx.Cosmos/CosmosDbContainerBase.cs index d3351fa5..cf2d362b 100644 --- a/src/CoreEx.Cosmos/CosmosDbContainerBase.cs +++ b/src/CoreEx.Cosmos/CosmosDbContainerBase.cs @@ -15,26 +15,17 @@ namespace CoreEx.Cosmos /// The entity . /// The cosmos model . /// The itself. - public abstract class CosmosDbContainerBase : ICosmosDbContainer where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() where TSelf : CosmosDbContainerBase + /// The . + /// The identifier. + public abstract class CosmosDbContainerBase(ICosmosDb cosmosDb, string containerId) : ICosmosDbContainer where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() where TSelf : CosmosDbContainerBase { private Func? _partitionKey; - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The identifier. - public CosmosDbContainerBase(ICosmosDb cosmosDb, string containerId) - { - CosmosDb = cosmosDb ?? throw new ArgumentNullException(nameof(cosmosDb)); - Container = cosmosDb.GetCosmosContainer(containerId); - } - /// - public ICosmosDb CosmosDb { get; } + public ICosmosDb CosmosDb { get; } = cosmosDb.ThrowIfNull(nameof(cosmosDb)); /// - public Container Container { get; } + public Container Container { get; } = cosmosDb.GetCosmosContainer(containerId); /// /// Gets the from the . @@ -84,7 +75,7 @@ public TSelf UsePartitionKey(Func partitionKey) /// /// The entity value. /// The CosmosDb identifier. - public string GetCosmosId(T value) => GetCosmosId((value ?? throw new ArgumentNullException(nameof(value))).EntityKey); + public string GetCosmosId(T value) => GetCosmosId(value.ThrowIfNull(nameof(value)).EntityKey); /// /// Gets the entity for the specified . diff --git a/src/CoreEx.Cosmos/CosmosDbModelContainer.cs b/src/CoreEx.Cosmos/CosmosDbModelContainer.cs index 1c7305f3..22be3e3d 100644 --- a/src/CoreEx.Cosmos/CosmosDbModelContainer.cs +++ b/src/CoreEx.Cosmos/CosmosDbModelContainer.cs @@ -1,7 +1,5 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx -using System; - namespace CoreEx.Cosmos { /// @@ -16,8 +14,8 @@ public class CosmosDbModelContainer : ICosmosDbContainer /// The . internal CosmosDbModelContainer(ICosmosDb cosmosDb, Microsoft.Azure.Cosmos.Container container) { - CosmosDb = cosmosDb ?? throw new ArgumentNullException(nameof(cosmosDb)); - Container = container ?? throw new ArgumentNullException(nameof(container)); + CosmosDb = cosmosDb.ThrowIfNull(nameof(cosmosDb)); + Container = container.ThrowIfNull(nameof(container)); } /// diff --git a/src/CoreEx.Cosmos/CosmosDbModelQueryBase.cs b/src/CoreEx.Cosmos/CosmosDbModelQueryBase.cs index 6bfc2c68..1d299a22 100644 --- a/src/CoreEx.Cosmos/CosmosDbModelQueryBase.cs +++ b/src/CoreEx.Cosmos/CosmosDbModelQueryBase.cs @@ -22,7 +22,7 @@ namespace CoreEx.Cosmos /// protected CosmosDbModelQueryBase(ICosmosDbContainer container, CosmosDbArgs dbArgs) { - Container = container ?? throw new ArgumentNullException(nameof(container)); + Container = container.ThrowIfNull(nameof(container)); QueryArgs = dbArgs; } diff --git a/src/CoreEx.Cosmos/CosmosDbQueryBase.cs b/src/CoreEx.Cosmos/CosmosDbQueryBase.cs index f2e2d30a..03f6d77c 100644 --- a/src/CoreEx.Cosmos/CosmosDbQueryBase.cs +++ b/src/CoreEx.Cosmos/CosmosDbQueryBase.cs @@ -23,7 +23,7 @@ namespace CoreEx.Cosmos /// protected CosmosDbQueryBase(ICosmosDbContainer container, CosmosDbArgs dbArgs) { - Container = container ?? throw new ArgumentNullException(nameof(container)); + Container = container.ThrowIfNull(nameof(container)); QueryArgs = dbArgs; } diff --git a/src/CoreEx.Cosmos/CosmosDbValue.cs b/src/CoreEx.Cosmos/CosmosDbValue.cs index 02ec7738..4bc2b636 100644 --- a/src/CoreEx.Cosmos/CosmosDbValue.cs +++ b/src/CoreEx.Cosmos/CosmosDbValue.cs @@ -32,7 +32,7 @@ public CosmosDbValue() public CosmosDbValue(TModel value) { Type = typeof(TModel).Name; - _value = value ?? throw new ArgumentNullException(nameof(value)); + _value = value.ThrowIfNull(nameof(value)); } /// @@ -45,7 +45,7 @@ public CosmosDbValue(TModel value) /// Gets or sets the value. /// [JsonProperty("value")] - public TModel Value { get => _value; set => _value = value ?? throw new ArgumentNullException(nameof(Value)); } + public TModel Value { get => _value; set => _value = value.ThrowIfNull(nameof(Value)); } /// /// Gets the value. diff --git a/src/CoreEx.Cosmos/CosmosDbValueContainer.cs b/src/CoreEx.Cosmos/CosmosDbValueContainer.cs index 62e4011a..1adcf8d8 100644 --- a/src/CoreEx.Cosmos/CosmosDbValueContainer.cs +++ b/src/CoreEx.Cosmos/CosmosDbValueContainer.cs @@ -18,17 +18,12 @@ namespace CoreEx.Cosmos /// The entity . /// The cosmos model . /// Represents a special-purpose CosmosDb that houses an underlying , including name, and flexible , for persistence. - public class CosmosDbValueContainer : CosmosDbContainerBase> where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() + /// The . + /// The identifier. + public class CosmosDbValueContainer(ICosmosDb cosmosDb, string containerId) : CosmosDbContainerBase>(cosmosDb, containerId) where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() { private readonly string _typeName = typeof(TModel).Name; - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The identifier. - public CosmosDbValueContainer(ICosmosDb cosmosDb, string containerId) : base(cosmosDb, containerId) { } - /// /// Gets the value from the response updating any special properties as required. /// @@ -134,7 +129,7 @@ private Result CheckAuthorized(CosmosDbValue model) }, cancellationToken, nameof(GetWithResultAsync)); /// - public override Task> CreateWithResultAsync(T value, CosmosDbArgs dbArgs, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, value ?? throw new ArgumentNullException(nameof(value)), dbArgs, async (_, v, args, ct) => + public override Task> CreateWithResultAsync(T value, CosmosDbArgs dbArgs, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, value.ThrowIfNull(nameof(value)), dbArgs, async (_, v, args, ct) => { var pk = GetPartitionKey(v); ChangeLog.PrepareCreated(v); @@ -153,7 +148,7 @@ private Result CheckAuthorized(CosmosDbValue model) }, cancellationToken, nameof(CreateWithResultAsync)); /// - public override Task> UpdateWithResultAsync(T value, CosmosDbArgs dbArgs, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, value ?? throw new ArgumentNullException(nameof(value)), dbArgs, async (_, v, args, ct) => + public override Task> UpdateWithResultAsync(T value, CosmosDbArgs dbArgs, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, value.ThrowIfNull(nameof(value)), dbArgs, async (_, v, args, ct) => { var key = GetCosmosId(v); var pk = GetPartitionKey(v); diff --git a/src/CoreEx.Cosmos/CosmosDbValueQueryableExtensions.cs b/src/CoreEx.Cosmos/CosmosDbValueQueryableExtensions.cs index 133577d4..060ad0f1 100644 --- a/src/CoreEx.Cosmos/CosmosDbValueQueryableExtensions.cs +++ b/src/CoreEx.Cosmos/CosmosDbValueQueryableExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using CoreEx.Cosmos; using CoreEx.Entities; using System.Linq; @@ -19,7 +20,7 @@ public static class CosmosDbValueQueryableExtensions /// The query. /// The name. /// The query. - public static IQueryable> WhereType(this IQueryable> query, string typeName) where T : class, IIdentifier, new() => query.Where("type = @0", typeName ?? throw new ArgumentNullException(nameof(typeName))); + public static IQueryable> WhereType(this IQueryable> query, string typeName) where T : class, IIdentifier, new() => query.Where("type = @0", typeName.ThrowIfNull(nameof(typeName))); /// /// Filters a sequence of values based on the equalling the . @@ -28,6 +29,6 @@ public static class CosmosDbValueQueryableExtensions /// The query. /// The . /// The query. - public static IQueryable> WhereType(this IQueryable> query, Type type) where T : class, IIdentifier, new() => query.WhereType((type ?? throw new ArgumentNullException(nameof(type))).Name); + public static IQueryable> WhereType(this IQueryable> query, Type type) where T : class, IIdentifier, new() => query.WhereType(type.ThrowIfNull(nameof(type)).Name); } } \ No newline at end of file diff --git a/src/CoreEx.Cosmos/CosmosExtensions.cs b/src/CoreEx.Cosmos/CosmosExtensions.cs index 0021d98c..57625b5a 100644 --- a/src/CoreEx.Cosmos/CosmosExtensions.cs +++ b/src/CoreEx.Cosmos/CosmosExtensions.cs @@ -22,11 +22,8 @@ public static class CosmosExtensions /// The . public static Task DeleteContainerAsync(this Database database, string containerId, ContainerRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) { - if (database == null) - throw new ArgumentNullException(nameof(database)); - - if (containerId == null) - throw new ArgumentNullException(nameof(containerId)); + database.ThrowIfNull(nameof(database)); + containerId.ThrowIfNull(nameof(containerId)); var container = database.GetContainer(containerId); return container.DeleteContainerAsync(requestOptions, cancellationToken); @@ -43,11 +40,8 @@ public static Task DeleteContainerAsync(this Database databas /// The . public static async Task ReplaceOrCreateContainerAsync(this Database database, ContainerProperties containerProperties, int? throughput = null, RequestOptions? requestOptions = null, CancellationToken cancellationToken = default) { - if (database == null) - throw new ArgumentNullException(nameof(database)); - - if (containerProperties == null) - throw new ArgumentNullException(nameof(containerProperties)); + database.ThrowIfNull(nameof(database)); + containerProperties.ThrowIfNull(nameof(containerProperties)); try { diff --git a/src/CoreEx.Database.MySql/MySqlDatabase.cs b/src/CoreEx.Database.MySql/MySqlDatabase.cs index e636474c..1a62de28 100644 --- a/src/CoreEx.Database.MySql/MySqlDatabase.cs +++ b/src/CoreEx.Database.MySql/MySqlDatabase.cs @@ -13,17 +13,11 @@ namespace CoreEx.Database.MySql /// /// Provides MySQL database access functionality. /// - public class MySqlDatabase : Database + /// The function to create the . + /// The optional . + /// The optional . + public class MySqlDatabase(Func create, ILogger? logger = null, DatabaseInvoker? invoker = null) : Database(create, MySqlClientFactory.Instance, logger, invoker) { - /// - /// Initializes a new instance of the class. - /// - /// The function to create the . - /// The optional . - /// The optional . - public MySqlDatabase(Func create, ILogger? logger = null, DatabaseInvoker? invoker = null) - : base(create, MySqlClientFactory.Instance, logger, invoker) { } - /// public override IConverter RowVersionConverter => EncodedStringToDateTimeConverter.Default; diff --git a/src/CoreEx.Database.Postgres/CoreEx.Database.Postgres.csproj b/src/CoreEx.Database.Postgres/CoreEx.Database.Postgres.csproj new file mode 100644 index 00000000..ffcece23 --- /dev/null +++ b/src/CoreEx.Database.Postgres/CoreEx.Database.Postgres.csproj @@ -0,0 +1,22 @@ + + + + net6.0;net7.0;net8.0;netstandard2.1 + CoreEx.Database.Postgres + CoreEx + CoreEx .NET Postgres Database extras. + CoreEx .NET Postgres Database extras. + coreex db database sql postgres ado.net relational + + + + + + + + + + + + + diff --git a/src/CoreEx.Database.Postgres/PostgresDatabase.cs b/src/CoreEx.Database.Postgres/PostgresDatabase.cs new file mode 100644 index 00000000..58afb084 --- /dev/null +++ b/src/CoreEx.Database.Postgres/PostgresDatabase.cs @@ -0,0 +1,80 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx + +using CoreEx.Mapping.Converters; +using CoreEx.Results; +using Microsoft.Extensions.Logging; +using Npgsql; +using System; +using System.Collections.Generic; +using System.Data.Common; + +namespace CoreEx.Database.Postgres +{ + /// + /// Provides Npgsql (PostgreSQL) database access functionality. + /// + /// The function to create the . + /// The optional . + /// The optional . + public class PostgresDatabase(Func create, ILogger? logger = null, DatabaseInvoker? invoker = null) : Database(create, NpgsqlFactory.Instance, logger, invoker) + { + private static readonly PostgresDatabaseColumns _defaultColumns = new(); + + /// + /// Gets or sets the names of the pre-configured . + /// + /// Do not update the default properties directly as a shared static instance is used (unless this is the desired behaviour); create a new instance for overridding. + public new PostgresDatabaseColumns DatabaseColumns { get; set; } = _defaultColumns; + + /// + public override IConverter RowVersionConverter => EncodedStringToUInt32Converter.Default; + + /// + /// Indicates whether to transform the into an equivalent based on the . + /// + /// Transforms and throws the equivalent from the known list. + public bool ThrowTransformedException { get; set; } = true; + + /// + /// Indicates whether to check the when catching the . + /// + public bool CheckSqlDuplicateErrorNumbers { get; set; } = true; + + /// + /// Gets or sets the list of known values that are considered a duplicate error. + /// + /// See . + public List SqlDuplicateErrorNumbers { get; set; } = new List(new string[] { "23505" }); + + /// + protected override Result? OnDbException(DbException dbex) + { + if (ThrowTransformedException && dbex is PostgresException pex) + { + var msg = pex.Message?.TrimEnd(); + if (string.IsNullOrEmpty(msg)) + msg = null; + + switch (pex.SqlState) + { + case "56001": return Result.Fail(new ValidationException(msg, pex)); + case "56002": return Result.Fail(new BusinessException(msg, pex)); + case "56003": return Result.Fail(new AuthorizationException(msg, pex)); + case "56004": return Result.Fail(new ConcurrencyException(msg, pex)); + case "56005": return Result.Fail(new NotFoundException(msg, pex)); + case "56006": return Result.Fail(new ConflictException(msg, pex)); + case "56007": return Result.Fail(new DuplicateException(msg, pex)); + case "56010": return Result.Fail(new DataConsistencyException(msg, pex)); + + default: + if (CheckSqlDuplicateErrorNumbers && SqlDuplicateErrorNumbers.Contains(pex.SqlState)) + return Result.Fail(new DuplicateException(null, pex)); + + break; + } + } + + return base.OnDbException(dbex); + } + } +} \ No newline at end of file diff --git a/src/CoreEx.Database.Postgres/PostgresDatabaseColumns.cs b/src/CoreEx.Database.Postgres/PostgresDatabaseColumns.cs new file mode 100644 index 00000000..4014f2e1 --- /dev/null +++ b/src/CoreEx.Database.Postgres/PostgresDatabaseColumns.cs @@ -0,0 +1,19 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx + +using CoreEx.Database.Extended; + +namespace CoreEx.Database.Postgres +{ + /// + /// Extends the adding additional SQL Server specific. + /// + /// Overrides the to 'xmin'. This is a PostgreSQL system column (hidden); see + /// and for more information. + public class PostgresDatabaseColumns : DatabaseColumns + { + /// + /// Initializes a new instance of the class. + /// + public PostgresDatabaseColumns() => RowVersionName = "xmin"; + } +} \ No newline at end of file diff --git a/src/CoreEx.Database.Postgres/strong-name-key.snk b/src/CoreEx.Database.Postgres/strong-name-key.snk new file mode 100644 index 0000000000000000000000000000000000000000..5bced3906b9de4a27f90758305b30ed16b1c5f35 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097f?o$f(*a46&Kxkf^(4*Ly9ozLqzCQL8 ze4lx`&2(hhFTIrh<4%ZNH{;**>J_B`(L zg2w#VAib4t`Xlh$Ungys?-x~y+Wvh;xRN67N}>kKsDKiPB*9Gb7j$PhcmQ=c&vKYs zYL8fK??~H;uqL2sxUhx*XOZqD()r`xgYyBHFhtcmx zuFz5&;wc29C`~$WpcEFk`IDWAVMFO_rZ|ClrE@jhq)yI?FC^;2`{-+TQ=dqDZn<_f zpR07zgt%W`u586(DhW&RiBZp%9klaKr>nIxgi$GNxEzgifyIE5#vcv12~t4f%y?2C zyPgfZL#XP7%!Awfy`;)A6cHg>7KRRs>#5 z2I-~)zZO-3_H1R-IV4e+D>_DCrav32(SMsi(9l&PtnFm8Hf7X>cyo?*41FrD5lbJf ipiUG-DI^tR9Mcea^C%Jh+%U*&_-v6R3hAfG$wP6upd;x3 literal 0 HcmV?d00001 diff --git a/src/CoreEx.Database.SqlServer/DatabaseServiceCollectionExtensions.cs b/src/CoreEx.Database.SqlServer/DatabaseServiceCollectionExtensions.cs index 49d8d642..27d4828d 100644 --- a/src/CoreEx.Database.SqlServer/DatabaseServiceCollectionExtensions.cs +++ b/src/CoreEx.Database.SqlServer/DatabaseServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using CoreEx.Configuration; using CoreEx.Database.SqlServer.Outbox; using CoreEx.Hosting; @@ -29,7 +30,7 @@ public static IServiceCollection AddSqlServerEventOutboxHostedService(this IServ { services.AddHostedService(sp => new EventOutboxHostedService(sp, sp.GetRequiredService>(), sp.GetRequiredService(), sp.GetRequiredService(), partitionKey, destination) { - EventOutboxDequeueFactory = eventOutboxDequeueFactory ?? throw new ArgumentNullException(nameof(eventOutboxDequeueFactory)) + EventOutboxDequeueFactory = eventOutboxDequeueFactory.ThrowIfNull(nameof(eventOutboxDequeueFactory)) }); } diff --git a/src/CoreEx.Database.SqlServer/Outbox/EventOutboxDequeueBase.cs b/src/CoreEx.Database.SqlServer/Outbox/EventOutboxDequeueBase.cs index b47ba748..32535fa9 100644 --- a/src/CoreEx.Database.SqlServer/Outbox/EventOutboxDequeueBase.cs +++ b/src/CoreEx.Database.SqlServer/Outbox/EventOutboxDequeueBase.cs @@ -20,35 +20,25 @@ namespace CoreEx.Database.SqlServer.Outbox /// The (being the unique event identifier) can be leveraged by the underlying messaging platform to perform duplicate checking. There is no guarantee that a dequeued event is on published more /// than once, the guarantee is at best at-least once semantics based on the implementation of the final . /// - public abstract class EventOutboxDequeueBase : IDatabaseMapper + /// The . + /// The . + /// The . + public abstract class EventOutboxDequeueBase(IDatabase database, IEventSender eventSender, ILogger logger) : IDatabaseMapper { - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// The . - public EventOutboxDequeueBase(IDatabase database, IEventSender eventSender, ILogger logger) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - EventSender = eventSender ?? throw new ArgumentNullException(nameof(eventSender)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - /// /// Gets the . /// - protected IDatabase Database { get; } + protected IDatabase Database { get; } = database.ThrowIfNull(nameof(database)); /// /// Gets the . /// - protected IEventSender EventSender { get; } + protected IEventSender EventSender { get; } = eventSender.ThrowIfNull(nameof(eventSender)); /// /// Gets the . /// - protected ILogger Logger { get; } + protected ILogger Logger { get; } = logger.ThrowIfNull(nameof(logger)); /// /// Gets the event outbox dequeue stored procedure name. diff --git a/src/CoreEx.Database.SqlServer/Outbox/EventOutboxEnqueueBase.cs b/src/CoreEx.Database.SqlServer/Outbox/EventOutboxEnqueueBase.cs index 47c2d336..5547b6b5 100644 --- a/src/CoreEx.Database.SqlServer/Outbox/EventOutboxEnqueueBase.cs +++ b/src/CoreEx.Database.SqlServer/Outbox/EventOutboxEnqueueBase.cs @@ -21,30 +21,21 @@ namespace CoreEx.Database.SqlServer.Outbox /// can be specified using . This will then be used to send the events immediately, and where successful, they will be audited in the database as dequeued /// event(s); versus on error (as a backup), where they will be enqueued for the out-of-process dequeue and send (as per default). Note: the challenge this primary sender introduces is in-order publishing; there is no means to guarantee order for the /// events that are processed on error. - public abstract class EventOutboxEnqueueBase : IEventSender + /// The . + /// The . + public abstract class EventOutboxEnqueueBase(IDatabase database, ILogger logger) : IEventSender { private IEventSender? _eventSender; - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - public EventOutboxEnqueueBase(IDatabase database, ILogger logger) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - /// /// Gets the . /// - protected IDatabase Database { get; } + protected IDatabase Database { get; } = database.ThrowIfNull(nameof(database)); /// /// Gets the . /// - protected ILogger Logger { get; } + protected ILogger Logger { get; } = logger.ThrowIfNull(nameof(logger)); /// /// Gets the database type name for the . diff --git a/src/CoreEx.Database.SqlServer/SqlServerDatabase.cs b/src/CoreEx.Database.SqlServer/SqlServerDatabase.cs index c0c3b32e..0cc4a164 100644 --- a/src/CoreEx.Database.SqlServer/SqlServerDatabase.cs +++ b/src/CoreEx.Database.SqlServer/SqlServerDatabase.cs @@ -15,23 +15,17 @@ namespace CoreEx.Database.SqlServer /// /// Provides SQL Server database access functionality. /// - public class SqlServerDatabase : Database + /// The function to create the . + /// The optional . + /// The optional . + public class SqlServerDatabase(Func create, ILogger? logger = null, DatabaseInvoker? invoker = null) : Database(create, SqlClientFactory.Instance, logger, invoker) { private static readonly SqlServerDatabaseColumns _defaultColumns = new(); - /// - /// Initializes a new instance of the class. - /// - /// The function to create the . - /// The optional . - /// The optional . - public SqlServerDatabase(Func create, ILogger? logger = null, DatabaseInvoker? invoker = null) - : base(create, SqlClientFactory.Instance, logger, invoker) { } - /// /// Gets or sets the names of the pre-configured . /// - /// Do not update the default properties directly as a shared static instance is used (unless this is the desired behaviour); create a new instance for overridding. + /// Do not update the default properties directly as a shared static instance is used (unless this is the desired behaviour); create a new instance for overridding. public new SqlServerDatabaseColumns DatabaseColumns { get; set; } = _defaultColumns; /// diff --git a/src/CoreEx.Database.SqlServer/SqlServerExtensions.cs b/src/CoreEx.Database.SqlServer/SqlServerExtensions.cs index c58dca56..7c6861e6 100644 --- a/src/CoreEx.Database.SqlServer/SqlServerExtensions.cs +++ b/src/CoreEx.Database.SqlServer/SqlServerExtensions.cs @@ -25,7 +25,7 @@ public static class SqlServerExtensions /// A . public static SqlParameter AddParameter(this DatabaseParameterCollection dpc, string name, object? value, SqlDbType? sqlDbType = null, ParameterDirection direction = ParameterDirection.Input) { - var p = (SqlParameter)((dpc ?? throw new ArgumentNullException(nameof(dpc))).Database.Provider.CreateParameter() ?? throw new InvalidOperationException($"The {nameof(DbProviderFactory)}.{nameof(DbProviderFactory.CreateParameter)} returned a null.")); + var p = (SqlParameter)(dpc.ThrowIfNull(nameof(dpc)).Database.Provider.CreateParameter() ?? throw new InvalidOperationException($"The {nameof(DbProviderFactory)}.{nameof(DbProviderFactory.CreateParameter)} returned a null.")); p.ParameterName = DatabaseParameterCollection.ParameterizeName(name); if (sqlDbType.HasValue) p.SqlDbType = sqlDbType.Value; @@ -47,10 +47,10 @@ public static SqlParameter AddParameter(this DatabaseParameterCollection dpc, st /// This specifically implies that the is being used; if not then an exception will be thrown. public static SqlParameter AddTableValuedParameter(this DatabaseParameterCollection dpc, string name, TableValuedParameter tvp) { - var p = (SqlParameter)((dpc ?? throw new ArgumentNullException(nameof(dpc))).Database.Provider.CreateParameter() ?? throw new InvalidOperationException($"The {nameof(DbProviderFactory)}.{nameof(DbProviderFactory.CreateParameter)} returned a null.")); + var p = (SqlParameter)(dpc.ThrowIfNull(nameof(dpc)).Database.Provider.CreateParameter() ?? throw new InvalidOperationException($"The {nameof(DbProviderFactory)}.{nameof(DbProviderFactory.CreateParameter)} returned a null.")); p.ParameterName = DatabaseParameterCollection.ParameterizeName(name); p.SqlDbType = SqlDbType.Structured; - p.TypeName = (tvp ?? throw new ArgumentNullException(nameof(tvp))).TypeName; + p.TypeName = tvp.ThrowIfNull(nameof(tvp)).TypeName; p.Value = tvp.Value; p.Direction = ParameterDirection.Input; @@ -72,11 +72,8 @@ public static SqlParameter AddTableValuedParameter(this DatabaseParameterCollect /// The to support fluent-style method-chaining. public static TSelf ParamWhen(this IDatabaseParameters parameters, bool? when, string name, Func value, SqlDbType sqlDbType, ParameterDirection direction = ParameterDirection.Input) { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - if (value == null) - throw new ArgumentNullException(nameof(value)); + parameters.ThrowIfNull(nameof(parameters)); + value.ThrowIfNull(nameof(value)); if (when == true) parameters.Parameters.AddParameter(name, value(), sqlDbType, direction); diff --git a/src/CoreEx.Database.SqlServer/TableValuedParameter.cs b/src/CoreEx.Database.SqlServer/TableValuedParameter.cs index 09e9331f..848cdd19 100644 --- a/src/CoreEx.Database.SqlServer/TableValuedParameter.cs +++ b/src/CoreEx.Database.SqlServer/TableValuedParameter.cs @@ -10,28 +10,19 @@ namespace CoreEx.Database.SqlServer /// /// Represents a SQL-Server table-valued parameter (see ). /// - public class TableValuedParameter + /// The SQL type name of the table-valued parameter. + /// The value. + public class TableValuedParameter(string typeName, DataTable value) { - /// - /// Initializes a new instance of the class. - /// - /// The SQL type name of the table-valued parameter. - /// The value. - public TableValuedParameter(string typeName, DataTable value) - { - TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); - Value = value ?? throw new ArgumentNullException(nameof(value)); - } - /// /// Gets or sets the SQL type name of the table-valued parameter. /// - public string TypeName { get; } + public string TypeName { get; } = typeName.ThrowIfNull(nameof(typeName)); /// /// Gets or sets the value. /// - public DataTable Value { get; } + public DataTable Value { get; } = value.ThrowIfNull(nameof(value)); /// /// Adds a new to the using the specified . @@ -57,8 +48,8 @@ public void AddRow(params object?[] columnValues) /// Zero or more items to add. public void AddRows(IDatabase database, IDatabaseMapper mapper, IEnumerable? items) { - if (database == null) throw new ArgumentNullException(nameof(database)); - if (mapper == null) throw new ArgumentNullException(nameof(mapper)); + database.ThrowIfNull(nameof(database)); + mapper.ThrowIfNull(nameof(mapper)); if (items == null) return; diff --git a/src/CoreEx.Database/Database.cs b/src/CoreEx.Database/Database.cs index 6f79050f..9f415454 100644 --- a/src/CoreEx.Database/Database.cs +++ b/src/CoreEx.Database/Database.cs @@ -18,41 +18,30 @@ namespace CoreEx.Database /// Provides the common/base database access functionality. /// /// The . - public class Database : IDatabase where TConnection : DbConnection + /// The function to create the . + /// The underlying . + /// The optional . + /// The optional . + public class Database(Func create, DbProviderFactory provider, ILogger>? logger = null, DatabaseInvoker? invoker = null) : IDatabase where TConnection : DbConnection { private static readonly DatabaseColumns _defaultColumns = new(); private static readonly DatabaseWildcard _defaultWildcard = new(); private static DatabaseInvoker? _invoker; - private readonly Func _dbConnCreate; + private readonly Func _dbConnCreate = create.ThrowIfNull(nameof(create)); private TConnection? _dbConn; - /// - /// Initializes a new instance of the class. - /// - /// The function to create the . - /// The underlying . - /// The optional . - /// The optional . - public Database(Func create, DbProviderFactory provider, ILogger>? logger = null, DatabaseInvoker? invoker = null) - { - _dbConnCreate = create ?? throw new ArgumentNullException(nameof(create)); - Provider = provider ?? throw new ArgumentNullException(nameof(provider)); - Logger = logger ?? ExecutionContext.GetService>>(); - Invoker = invoker ?? (_invoker ??= new DatabaseInvoker()); - } - /// - public DbProviderFactory Provider { get; } + public DbProviderFactory Provider { get; } = provider.ThrowIfNull(nameof(provider)); /// public Guid DatabaseId { get; } = Guid.NewGuid(); /// - public ILogger? Logger { get; } + public ILogger? Logger { get; } = logger ?? ExecutionContext.GetService>>(); /// - public DatabaseInvoker Invoker { get; } + public DatabaseInvoker Invoker { get; } = invoker ?? (_invoker ??= new DatabaseInvoker()); /// public DatabaseArgs DbArgs { get; set; } = new DatabaseArgs(); @@ -119,11 +108,11 @@ public async Task GetConnectionAsync(CancellationToken cancellation /// public DatabaseCommand StoredProcedure(string storedProcedure) - => new(this, CommandType.StoredProcedure, storedProcedure ?? throw new ArgumentNullException(nameof(storedProcedure))); + => new(this, CommandType.StoredProcedure, storedProcedure.ThrowIfNull(nameof(storedProcedure))); /// public DatabaseCommand SqlStatement(string sqlStatement) - => new(this, CommandType.Text, sqlStatement ?? throw new ArgumentNullException(nameof(sqlStatement))); + => new(this, CommandType.Text, sqlStatement.ThrowIfNull(nameof(sqlStatement))); /// public DatabaseCommand SqlFromResource(string resourceName, Assembly? assembly = null) diff --git a/src/CoreEx.Database/DatabaseCommand.cs b/src/CoreEx.Database/DatabaseCommand.cs index 56a78c0d..31c3a0c2 100644 --- a/src/CoreEx.Database/DatabaseCommand.cs +++ b/src/CoreEx.Database/DatabaseCommand.cs @@ -16,39 +16,28 @@ namespace CoreEx.Database /// Provides extended database command capabilities. /// /// As the underlying implements this is only created (and automatically disposed) where executing the command proper. - public sealed class DatabaseCommand : IDatabaseParameters + /// The . + /// The . + /// The command text. + public sealed class DatabaseCommand(IDatabase db, CommandType commandType, string commandText) : IDatabaseParameters { - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// The command text. - public DatabaseCommand(IDatabase db, CommandType commandType, string commandText) - { - Database = db ?? throw new ArgumentNullException(nameof(db)); - Parameters = new DatabaseParameterCollection(db); - CommandType = commandType; - CommandText = commandText ?? throw new ArgumentNullException(nameof(commandText)); - } - /// /// Gets the underlying . /// - public IDatabase Database { get; } + public IDatabase Database { get; } = db.ThrowIfNull(nameof(db)); /// - public DatabaseParameterCollection Parameters { get; } + public DatabaseParameterCollection Parameters { get; } = new DatabaseParameterCollection(db); /// /// Gets the . /// - public CommandType CommandType { get; } + public CommandType CommandType { get; } = commandType; /// /// Gets the command text. /// - public string CommandText { get; } + public string CommandText { get; } = commandText.ThrowIfNull(nameof(commandText)); /// /// Creates the corresponding . @@ -572,8 +561,7 @@ private async Task> SelectSingleFirstWithResultInternalAsync(IDatab /// private async Task SelectInternalAsync(TColl coll, IDatabaseMapper mapper, bool throwWhereMulti, bool stopAfterOneRow, string memberName, CancellationToken cancellationToken) where TColl : ICollection { - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); + mapper.ThrowIfNull(nameof(mapper)); return await Database.Invoker.InvokeAsync(Database, mapper, throwWhereMulti, stopAfterOneRow, async (_, mapper, throwWhereMulti, stopAfterOneRow, ct) => { diff --git a/src/CoreEx.Database/DatabaseParameterCollection.cs b/src/CoreEx.Database/DatabaseParameterCollection.cs index dba34f04..76954db9 100644 --- a/src/CoreEx.Database/DatabaseParameterCollection.cs +++ b/src/CoreEx.Database/DatabaseParameterCollection.cs @@ -13,20 +13,15 @@ namespace CoreEx.Database /// /// Provides a collection used for a . /// - public sealed class DatabaseParameterCollection : ICollection, IDatabaseParameters + /// The . + public sealed class DatabaseParameterCollection(IDatabase database) : ICollection, IDatabaseParameters { - private readonly List _parameters = new(); - - /// - /// Initializes a new instance of the . - /// - /// The . - public DatabaseParameterCollection(IDatabase database) => Database = database ?? throw new ArgumentNullException(nameof(database)); + private readonly List _parameters = []; /// /// Gets the underlying . /// - public IDatabase Database { get; } + public IDatabase Database { get; } = database.ThrowIfNull(nameof(database)); /// DatabaseParameterCollection IDatabaseParameters.Parameters => this; @@ -129,7 +124,7 @@ public DbParameter AddReturnValueParameter() /// /// The parameter name. /// The parameterized name. - public static string ParameterizeName(string name) => (name ?? throw new ArgumentNullException(nameof(name))).StartsWith('@') ? name : $"@{name}"; + public static string ParameterizeName(string name) => name.ThrowIfNull(nameof(name)).StartsWith('@') ? name : $"@{name}"; /// /// Gets or sets the at the specified . diff --git a/src/CoreEx.Database/DatabaseRecord.cs b/src/CoreEx.Database/DatabaseRecord.cs index 098d17b2..40664a00 100644 --- a/src/CoreEx.Database/DatabaseRecord.cs +++ b/src/CoreEx.Database/DatabaseRecord.cs @@ -9,28 +9,19 @@ namespace CoreEx.Database /// /// Encapsulates the to provide requisite column value capabilities. /// - public class DatabaseRecord + /// The owning . + /// The underlying . + public class DatabaseRecord(IDatabase database, DbDataReader dataReader) { - /// - /// Initializes a new instance of the class. - /// - /// The owning . - /// The underlying . - public DatabaseRecord(IDatabase database, DbDataReader dataReader) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - DataReader = dataReader ?? throw new ArgumentNullException(nameof(dataReader)); - } - /// /// Gets the underlying . /// - public IDatabase Database { get; } + public IDatabase Database { get; } = database.ThrowIfNull(nameof(database)); /// /// Gets the underlying . /// - public DbDataReader DataReader { get; } + public DbDataReader DataReader { get; } = dataReader.ThrowIfNull(nameof(dataReader)); /// /// Gets the named column value. @@ -38,7 +29,7 @@ public DatabaseRecord(IDatabase database, DbDataReader dataReader) /// The value . /// The column name. /// The value. - public T GetValue(string columnName) => GetValue(DataReader.GetOrdinal(columnName ?? throw new ArgumentNullException(nameof(columnName)))); + public T GetValue(string columnName) => GetValue(DataReader.GetOrdinal(columnName.ThrowIfNull(nameof(columnName)))); /// /// Gets the specified column value. @@ -63,7 +54,7 @@ public T GetValue(int ordinal) /// true indicates that the column value has a value; otherwise, false. public bool IsDBNull(string columnName, out int ordinal) { - ordinal = DataReader.GetOrdinal(columnName ?? throw new ArgumentNullException(nameof(columnName))); + ordinal = DataReader.GetOrdinal(columnName.ThrowIfNull(nameof(columnName))); return DataReader.IsDBNull(ordinal); } @@ -75,7 +66,7 @@ public bool IsDBNull(string columnName, out int ordinal) /// The RowVersion column will be converted to a using the . public string GetRowVersion(string columnName) { - var i = DataReader.GetOrdinal(columnName ?? throw new ArgumentNullException(nameof(columnName))); + var i = DataReader.GetOrdinal(columnName.ThrowIfNull(nameof(columnName))); return (string)(Database.RowVersionConverter.ConvertToSource(DataReader.GetFieldValue(i)) ?? string.Empty); } } diff --git a/src/CoreEx.Database/DatabaseRecordMapper.cs b/src/CoreEx.Database/DatabaseRecordMapper.cs index aa88022c..3d586b85 100644 --- a/src/CoreEx.Database/DatabaseRecordMapper.cs +++ b/src/CoreEx.Database/DatabaseRecordMapper.cs @@ -8,15 +8,10 @@ namespace CoreEx.Database /// /// Provides a per mapper that simulates a by invoking the function passed into the constructor. /// - internal class DatabaseRecordMapper : IDatabaseMapper + /// The mapping function. + internal class DatabaseRecordMapper(Func func) : IDatabaseMapper { - private readonly Func _func; - - /// - /// Initializes a new instance of the class. - /// - /// The mapping function. - public DatabaseRecordMapper(Func func) => _func = func ?? throw new ArgumentNullException(nameof(func)); + private readonly Func _func = func.ThrowIfNull(nameof(func)); /// T? IDatabaseMapper.MapFromDb(DatabaseRecord record, OperationTypes operationType) => _func(record); diff --git a/src/CoreEx.Database/DatabaseWildcard.cs b/src/CoreEx.Database/DatabaseWildcard.cs index ab3d8c53..157b3578 100644 --- a/src/CoreEx.Database/DatabaseWildcard.cs +++ b/src/CoreEx.Database/DatabaseWildcard.cs @@ -27,7 +27,7 @@ public class DatabaseWildcard /// Gets the default list of characters that are to be escaped. /// /// See for more detail on escape characters. - public static readonly char[] DefaultCharactersToEscape = { '%', '_', '[' }; + public static readonly char[] DefaultCharactersToEscape = ['%', '_', '[']; /// /// Gets the default escaping format string when one of the is found. diff --git a/src/CoreEx.Database/Extended/DatabaseArgs.cs b/src/CoreEx.Database/Extended/DatabaseArgs.cs index f82d2ec4..4491d2d2 100644 --- a/src/CoreEx.Database/Extended/DatabaseArgs.cs +++ b/src/CoreEx.Database/Extended/DatabaseArgs.cs @@ -23,7 +23,7 @@ public DatabaseArgs() { } /// The . public DatabaseArgs(DatabaseArgs template, IDatabaseMapper mapper) { - _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + _mapper = mapper.ThrowIfNull(nameof(mapper)); Refresh = template.Refresh; } @@ -31,17 +31,17 @@ public DatabaseArgs(DatabaseArgs template, IDatabaseMapper mapper) /// Initializes a new instance of the struct. /// /// The . - public DatabaseArgs(IDatabaseMapper mapper) => _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + public DatabaseArgs(IDatabaseMapper mapper) => _mapper = mapper.ThrowIfNull(nameof(mapper)); /// /// Gets the . /// - public IDatabaseMapper Mapper => _mapper ?? throw new InvalidOperationException("Mapper must have been specified for it to be referenced."); + public readonly IDatabaseMapper Mapper => _mapper ?? throw new InvalidOperationException("Mapper must have been specified for it to be referenced."); /// /// Indicates whether the has been specified. /// - public bool HasMapper => _mapper != null; + public readonly bool HasMapper => _mapper != null; /// /// Indicates whether the data should be refreshed (reselected where applicable) after a save operation (defaults to true). diff --git a/src/CoreEx.Database/Extended/DatabaseExtendedExtensions.cs b/src/CoreEx.Database/Extended/DatabaseExtendedExtensions.cs index ef4c9937..0a836bf1 100644 --- a/src/CoreEx.Database/Extended/DatabaseExtendedExtensions.cs +++ b/src/CoreEx.Database/Extended/DatabaseExtendedExtensions.cs @@ -43,7 +43,7 @@ public static RefDataLoader ReferenceData( where TColl : class, IReferenceDataCollection, new() where TItem : class, IReferenceData, new() where TId : IComparable, IEquatable - => ReferenceData((database ?? throw new ArgumentNullException(nameof(database))).StoredProcedure(storedProcedure)); + => ReferenceData(database.ThrowIfNull(nameof(database)).StoredProcedure(storedProcedure)); /// /// Creates a (for loading) using a 'SELECT * FROM [].[]' SQL statement. @@ -66,11 +66,11 @@ public static RefDataLoader ReferenceData( var cb = database.Provider.CreateCommandBuilder() ?? throw new InvalidOperationException($"The {nameof(DbProviderFactory)}.{nameof(DbProviderFactory.CreateCommandBuilder)} returned a null."); if (string.IsNullOrEmpty(schemaName)) - return ReferenceData((database ?? throw new ArgumentNullException(nameof(database))) - .SqlStatement($"SELECT * FROM {cb.QuoteIdentifier(tableName ?? throw new ArgumentNullException(nameof(tableName)))}")); + return ReferenceData(database.ThrowIfNull(nameof(database)) + .SqlStatement($"SELECT * FROM {cb.QuoteIdentifier(tableName.ThrowIfNull(nameof(tableName)))}")); else - return ReferenceData((database ?? throw new ArgumentNullException(nameof(database))) - .SqlStatement($"SELECT * FROM {cb.QuoteIdentifier(schemaName ?? throw new ArgumentNullException(nameof(schemaName)))}.{cb.QuoteIdentifier(tableName ?? throw new ArgumentNullException(nameof(tableName)))}")); + return ReferenceData(database.ThrowIfNull(nameof(database)) + .SqlStatement($"SELECT * FROM {cb.QuoteIdentifier(schemaName)}.{cb.QuoteIdentifier(tableName.ThrowIfNull(nameof(tableName)))}")); } /// @@ -98,11 +98,8 @@ public static RefDataLoader ReferenceData( /// private static async Task> SaveWithResultAsync(this DatabaseCommand command, DatabaseArgs args, T value, OperationTypes operationType, CancellationToken cancellationToken = default) where T : class, new() { - if (command == null) - throw new ArgumentNullException(nameof(command)); - - if (value == null) - throw new ArgumentNullException(nameof(value)); + command.ThrowIfNull(nameof(command)); + value.ThrowIfNull(nameof(value)); // Set ChangeLog properties where appropriate. if (operationType == OperationTypes.Create) @@ -301,7 +298,7 @@ public static Task DeleteAsync(this DatabaseCommand command, IDatabaseMapper map /// The . /// The value where found; otherwise, null. public static Task> GetWithResultAsync(this DatabaseCommand command, DatabaseArgs args, CompositeKey key, CancellationToken cancellationToken = default) where T : class, new() - => (command ?? throw new ArgumentNullException(nameof(command))) + => command.ThrowIfNull(nameof(command)) .Params(p => args.Mapper.MapPrimaryKeyParameters(p, OperationTypes.Get, key)) .SelectFirstOrDefaultWithResultAsync((IDatabaseMapper)args.Mapper, cancellationToken); @@ -394,7 +391,7 @@ public static Task DeleteWithResultAsync(this DatabaseCommand command, I /// The . public static async Task DeleteWithResultAsync(this DatabaseCommand command, DatabaseArgs args, CompositeKey key, CancellationToken cancellationToken = default) { - var rowsAffectedResult = await (command ?? throw new ArgumentNullException(nameof(command))) + var rowsAffectedResult = await command.ThrowIfNull(nameof(command)) .Params(p => args.Mapper.MapPrimaryKeyParameters(p, OperationTypes.Get, key)) .ScalarWithResultAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/CoreEx.Database/Extended/DatabaseQuery.cs b/src/CoreEx.Database/Extended/DatabaseQuery.cs index 64ef75b4..9a32fb85 100644 --- a/src/CoreEx.Database/Extended/DatabaseQuery.cs +++ b/src/CoreEx.Database/Extended/DatabaseQuery.cs @@ -23,7 +23,7 @@ namespace CoreEx.Database.Extended /// The query action to enable additional filtering. internal DatabaseQuery(DatabaseCommand command, DatabaseArgs args, Action? queryParams) { - Command = command ?? throw new ArgumentNullException(nameof(command)); + Command = command.ThrowIfNull(nameof(command)); Parameters = new DatabaseParameterCollection(Database); QueryArgs = args; Mapper = (IDatabaseMapper)args.Mapper; diff --git a/src/CoreEx.Database/Extended/RefDataLoader.cs b/src/CoreEx.Database/Extended/RefDataLoader.cs index 6e16de64..4a79ca15 100644 --- a/src/CoreEx.Database/Extended/RefDataLoader.cs +++ b/src/CoreEx.Database/Extended/RefDataLoader.cs @@ -16,21 +16,17 @@ namespace CoreEx.Database.Extended /// The . /// The item . /// The . - public class RefDataLoader + /// The . + public class RefDataLoader(DatabaseCommand command) where TColl : class, IReferenceDataCollection, new() where TItem : class, IReferenceData, new() where TId : IComparable, IEquatable { - /// - /// Initializes a new instance of the class. - /// - /// The . - public RefDataLoader(DatabaseCommand command) => Command = command ?? throw new ArgumentNullException(nameof(command)); /// /// Gets the . /// - public DatabaseCommand Command { get; } + public DatabaseCommand Command { get; } = command.ThrowIfNull(nameof(command)); /// /// Executes a dynamic query updating the . @@ -57,8 +53,7 @@ public async Task LoadAsync(TColl coll, string? idColumnName = null, Action LoadWithResultAsync(TColl coll, string? idColumnName = null, Action? additionalProperties = null, IEnumerable? multiSetArgs = null, Func? confirmItemIsToBeAdded = null, CancellationToken cancellationToken = default) { - if (coll == null) - throw new ArgumentNullException(nameof(coll)); + coll.ThrowIfNull(nameof(coll)); var list = new List { new RefDataMultiSetCollArgs(Command.Database, coll.Add, idColumnName, additionalProperties, confirmItemIsToBeAdded) }; if (multiSetArgs != null) diff --git a/src/CoreEx.Database/Extended/RefDataMapper.cs b/src/CoreEx.Database/Extended/RefDataMapper.cs index 75861c5e..557e7615 100644 --- a/src/CoreEx.Database/Extended/RefDataMapper.cs +++ b/src/CoreEx.Database/Extended/RefDataMapper.cs @@ -38,7 +38,7 @@ public override TItem MapFromDb(DatabaseRecord dr, OperationTypes operationType { if (_fields == null) { - _fields = new(); + _fields = []; for (var i = 0; i < dr.DataReader.FieldCount; i++) { _fields.Add(dr.DataReader.GetName(i), i); diff --git a/src/CoreEx.Database/Extended/RefDataMultiSetCollArgs.cs b/src/CoreEx.Database/Extended/RefDataMultiSetCollArgs.cs index e706e714..fbc4591a 100644 --- a/src/CoreEx.Database/Extended/RefDataMultiSetCollArgs.cs +++ b/src/CoreEx.Database/Extended/RefDataMultiSetCollArgs.cs @@ -12,38 +12,24 @@ namespace CoreEx.Database.Extended /// The . /// The item . /// The . - internal class RefDataMultiSetCollArgs : MultiSetCollArgs + /// The . + /// The action that will be invoked with the result of each item. + /// The column name override; defaults to . + /// The additional properties action that enables non-standard properties to be updated from the . + /// The action to confirm whether the item is to be added (defaults to true). + internal class RefDataMultiSetCollArgs(IDatabase database, Action item, string? idColumnName = null, Action? additionalProperties = null, Func? confirmItemIsToBeAdded = null) : MultiSetCollArgs(stopOnNull: true) where TColl : class, IReferenceDataCollection where TItem : class, IReferenceData, new() where TId : IComparable, IEquatable { - private readonly Action _item; - private readonly RefDataMapper _refDataMapper; - private readonly Func? _confirmItemIsToBeAdded; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The action that will be invoked with the result of each item. - /// The column name override; defaults to . - /// The additional properties action that enables non-standard properties to be updated from the . - /// The action to confirm whether the item is to be added (defaults to true). - public RefDataMultiSetCollArgs(IDatabase database, Action item, string? idColumnName = null, Action? additionalProperties = null, Func? confirmItemIsToBeAdded = null) - : base(stopOnNull: true) - { - _item = item; - _refDataMapper = new RefDataMapper(database, idColumnName, additionalProperties); - _confirmItemIsToBeAdded = confirmItemIsToBeAdded; - } + private readonly Action _item = item; + private readonly RefDataMapper _refDataMapper = new(database, idColumnName, additionalProperties); + private readonly Func? _confirmItemIsToBeAdded = confirmItemIsToBeAdded; /// public override void DatasetRecord(DatabaseRecord dr) { - if (dr == null) - throw new ArgumentNullException(nameof(dr)); - - var rdi = _refDataMapper.MapFromDb(dr); + var rdi = _refDataMapper.MapFromDb(dr.ThrowIfNull(nameof(dr))); if (_confirmItemIsToBeAdded == null || _confirmItemIsToBeAdded(dr, rdi)) _item(rdi); } diff --git a/src/CoreEx.Database/IDatabaseParametersExtensions.cs b/src/CoreEx.Database/IDatabaseParametersExtensions.cs index e1cc6a69..14ef4754 100644 --- a/src/CoreEx.Database/IDatabaseParametersExtensions.cs +++ b/src/CoreEx.Database/IDatabaseParametersExtensions.cs @@ -24,10 +24,7 @@ public static class IDatabaseParametersExtensions /// The current instance to support chaining (fluent interface). public static TSelf Params(this IDatabaseParameters parameters, Action action) { - if (action == null) - throw new ArgumentNullException(nameof(action)); - - action.Invoke((parameters ?? throw new ArgumentNullException(nameof(parameters))).Parameters); + action.ThrowIfNull(nameof(action))(parameters.ThrowIfNull(nameof(parameters)).Parameters); return (TSelf)parameters; } @@ -59,7 +56,7 @@ public static TSelf Params(this IDatabaseParameters parameters, IE /// The to support fluent-style method-chaining. public static TSelf Param(this IDatabaseParameters parameters, string name, object? value, ParameterDirection direction = ParameterDirection.Input) { - (parameters ?? throw new ArgumentNullException(nameof(parameters))).Parameters.AddParameter(name, value, direction); + parameters.ThrowIfNull(nameof(parameters)).Parameters.AddParameter(name, value, direction); return (TSelf)parameters; } @@ -75,7 +72,7 @@ public static TSelf Param(this IDatabaseParameters parameters, str /// The to support fluent-style method-chaining. public static TSelf Param(this IDatabaseParameters parameters, string name, object? value, DbType dbType, ParameterDirection direction = ParameterDirection.Input) { - (parameters ?? throw new ArgumentNullException(nameof(parameters))).Parameters.AddParameter(name, value, dbType, direction); + parameters.ThrowIfNull(nameof(parameters)).Parameters.AddParameter(name, value, dbType, direction); return (TSelf)parameters; } @@ -91,7 +88,7 @@ public static TSelf Param(this IDatabaseParameters parameters, str /// The to support fluent-style method-chaining. public static TSelf Param(this IDatabaseParameters parameters, string name, DbType dbType, int size, ParameterDirection direction = ParameterDirection.Input) { - (parameters ?? throw new ArgumentNullException(nameof(parameters))).Parameters.AddParameter(name, dbType, size, direction); + parameters.ThrowIfNull(nameof(parameters)).Parameters.AddParameter(name, dbType, size, direction); return (TSelf)parameters; } @@ -150,11 +147,10 @@ public static TSelf Param(this IDatabaseParameters parameters, IPr /// The to support fluent-style method-chaining. public static TSelf ParamWhen(this IDatabaseParameters parameters, bool? when, string name, Func value, ParameterDirection direction = ParameterDirection.Input) { - if (value == null) - throw new ArgumentNullException(nameof(value)); + value.ThrowIfNull(nameof(value)); if (when == true) - (parameters ?? throw new ArgumentNullException(nameof(parameters))).Parameters.AddParameter(name, value(), direction); + parameters.ThrowIfNull(nameof(parameters)).Parameters.AddParameter(name, value(), direction); return (TSelf)parameters; } @@ -173,11 +169,10 @@ public static TSelf ParamWhen(this IDatabaseParameters paramete /// The to support fluent-style method-chaining. public static TSelf ParamWhen(this IDatabaseParameters parameters, bool? when, string name, Func value, DbType dbType, ParameterDirection direction = ParameterDirection.Input) { - if (value == null) - throw new ArgumentNullException(nameof(value)); + value.ThrowIfNull(nameof(value)); if (when == true) - (parameters ?? throw new ArgumentNullException(nameof(parameters))).Parameters.AddParameter(name, value(), dbType, direction); + parameters.ThrowIfNull(nameof(parameters)).Parameters.AddParameter(name, value(), dbType, direction); return (TSelf)parameters; } @@ -411,7 +406,7 @@ public static TSelf RowVersionParam(this IDatabaseParameters param /// The current instance to support chaining (fluent interface). public static TSelf ReselectRecordParam(this IDatabaseParameters parameters, bool reselect = true) { - (parameters ?? throw new ArgumentNullException(nameof(parameters))).Parameters.AddReselectRecordParam(reselect); + parameters.ThrowIfNull(nameof(parameters)).Parameters.AddReselectRecordParam(reselect); return (TSelf)parameters; } diff --git a/src/CoreEx.Database/Mapping/DatabaseMapperT.cs b/src/CoreEx.Database/Mapping/DatabaseMapperT.cs index acbf7b96..8e7e32a0 100644 --- a/src/CoreEx.Database/Mapping/DatabaseMapperT.cs +++ b/src/CoreEx.Database/Mapping/DatabaseMapperT.cs @@ -19,7 +19,7 @@ namespace CoreEx.Database.Mapping /// The source . public class DatabaseMapper : IDatabaseMapper, IDatabaseMapperMappings where TSource : class, new() { - private readonly List _mappings = new(); + private readonly List _mappings = []; /// /// Initializes a new instance of the class. @@ -50,8 +50,7 @@ public DatabaseMapper(bool autoMap = false, params string[] ignoreSrceProperties { get { - if (propertyExpression == null) - throw new ArgumentNullException(nameof(propertyExpression)); + propertyExpression.ThrowIfNull(nameof(propertyExpression)); MemberExpression? me = null; if (propertyExpression.Body.NodeType == ExpressionType.MemberAccess) @@ -98,8 +97,8 @@ private void AutomagicallyMap(string[] ignoreSrceProperties) var sex = Expression.Lambda(Expression.Property(spe, sp), spe); typeof(DatabaseMapper) .GetMethod("Property", BindingFlags.Public | BindingFlags.Instance)! - .MakeGenericMethod(new Type[] { sp.PropertyType }) - .Invoke(this, new object?[] { sex, null, null, OperationTypes.Any }); + .MakeGenericMethod([sp.PropertyType]) + .Invoke(this, [sex, null, null, OperationTypes.Any]); } } @@ -187,7 +186,7 @@ public DatabaseMapper HasProperty(ExpressionThe to inherit from. Must also implement . public void InheritPropertiesFrom(IDatabaseMapper inheritMapper) where T : class, new() { - if (inheritMapper == null) throw new ArgumentNullException(nameof(inheritMapper)); + inheritMapper.ThrowIfNull(nameof(inheritMapper)); if (!typeof(TSource).IsSubclassOf(typeof(T))) throw new ArgumentException($"Type {typeof(TSource).Name} must inherit from {typeof(T).Name}.", nameof(inheritMapper)); if (inheritMapper is not IDatabaseMapperMappings inheritMappings) throw new ArgumentException($"Type {typeof(T).Name} must implement {typeof(IDatabaseMapperMappings).Name} to copy the mappings.", nameof(inheritMapper)); @@ -200,7 +199,7 @@ public DatabaseMapper HasProperty(Expression HasProperty(Expression public void MapToDb(TSource? value, DatabaseParameterCollection parameters, OperationTypes operationType = OperationTypes.Unspecified) { - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); + parameters.ThrowIfNull(nameof(parameters)); if (value == null) return; foreach (var p in _mappings) @@ -241,8 +240,7 @@ protected virtual void OnMapToDb(TSource value, DatabaseParameterCollection para /// public TSource? MapFromDb(DatabaseRecord record, OperationTypes operationType = OperationTypes.Unspecified) { - if (record == null) throw new ArgumentNullException(nameof(record)); - + record.ThrowIfNull(nameof(record)); var value = new TSource(); foreach (var p in _mappings) @@ -266,7 +264,7 @@ protected virtual void OnMapToDb(TSource value, DatabaseParameterCollection para /// void IDatabaseMapper.MapPrimaryKeyParameters(DatabaseParameterCollection parameters, OperationTypes operationType, TSource? value) { - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); + parameters.ThrowIfNull(nameof(parameters)); if (value == null) return; foreach (var p in _mappings.Where(x => x.IsPrimaryKey)) @@ -289,8 +287,7 @@ void IDatabaseMapper.MapPrimaryKeyParameters(DatabaseParameterCollectio /// void IDatabaseMapper.MapPrimaryKeyParameters(DatabaseParameterCollection parameters, OperationTypes operationType, CompositeKey key) { - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); - + parameters.ThrowIfNull(nameof(parameters)); var pk = _mappings.Where(x => x.IsPrimaryKey).ToArray(); if (key.Args == null || key.Args.Length != pk.Length) throw new ArgumentException($"The number of keys supplied must equal the number of properties identified as {nameof(IPropertyColumnMapper.IsPrimaryKey)}.", nameof(key)); diff --git a/src/CoreEx.Database/Mapping/PropertyColumnMapper.cs b/src/CoreEx.Database/Mapping/PropertyColumnMapper.cs index 56e0a0ee..9719f725 100644 --- a/src/CoreEx.Database/Mapping/PropertyColumnMapper.cs +++ b/src/CoreEx.Database/Mapping/PropertyColumnMapper.cs @@ -86,8 +86,7 @@ public PropertyColumnMapper SetDbType(DbType dbType) /// void IPropertyColumnMapper.SetConverter(IConverter converter) { - if (converter == null) - throw new ArgumentNullException(nameof(converter)); + converter.ThrowIfNull(nameof(converter)); if (Mapper != null) throw new InvalidOperationException("The Mapper and Converter cannot be both set; only one is permissible."); @@ -113,8 +112,7 @@ public PropertyColumnMapper SetConverter void IPropertyColumnMapper.SetMapper(IDatabaseMapper mapper) { - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); + mapper.ThrowIfNull(nameof(mapper)); if (Converter != null) throw new InvalidOperationException("The Mapper and Converter cannot be both set; only one is permissible."); @@ -123,7 +121,7 @@ void IPropertyColumnMapper.SetMapper(IDatabaseMapper mapper) throw new InvalidOperationException($"The PropertyType '{PropertyType.Name}' must be a class to set a Mapper."); if (mapper.SourceType != typeof(TSourceProperty)) - throw new ArgumentNullException($"The PropertyType '{PropertyType.Name}' and IDatabaseMapper.SourceType '{mapper.SourceType.Name}' must match."); + throw new ArgumentException($"The PropertyType '{PropertyType.Name}' and IDatabaseMapper.SourceType '{mapper.SourceType.Name}' must match.", nameof(mapper)); if (IsPrimaryKey) throw new InvalidOperationException("A Mapper can not be set for a primary key."); diff --git a/src/CoreEx.Database/MultiSetCollArgs.cs b/src/CoreEx.Database/MultiSetCollArgs.cs index 26cac5ad..c936ee57 100644 --- a/src/CoreEx.Database/MultiSetCollArgs.cs +++ b/src/CoreEx.Database/MultiSetCollArgs.cs @@ -15,7 +15,7 @@ public abstract class MultiSetCollArgs : IMultiSetArgs /// The minimum number of rows allowed. /// The maximum number of rows allowed. /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - protected MultiSetCollArgs(int minRows = 0, int? maxRows = null, bool stopOnNull = false) + public MultiSetCollArgs(int minRows = 0, int? maxRows = null, bool stopOnNull = false) { if (maxRows.HasValue && minRows <= maxRows.Value) throw new ArgumentException("Max Rows is less than Min Rows.", nameof(maxRows)); diff --git a/src/CoreEx.Database/MultiSetCollArgsT.cs b/src/CoreEx.Database/MultiSetCollArgsT.cs index c45846e3..0d3dec8b 100644 --- a/src/CoreEx.Database/MultiSetCollArgsT.cs +++ b/src/CoreEx.Database/MultiSetCollArgsT.cs @@ -10,31 +10,22 @@ namespace CoreEx.Database /// /// The collection . /// The item . - public class MultiSetCollArgs : MultiSetCollArgs, IMultiSetArgs + /// The for the . + /// The action that will be invoked with the result of the set. + /// The minimum number of rows allowed. + /// The maximum number of rows allowed. + /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). + public class MultiSetCollArgs(IDatabaseMapper mapper, Action result, int minRows = 0, int? maxRows = null, bool stopOnNull = false) : MultiSetCollArgs(minRows, maxRows, stopOnNull), IMultiSetArgs where TItem : class, new() where TColl : class, ICollection, new() { private TColl? _coll; - private readonly Action _result; - - /// - /// Initializes a new instance of the class. - /// - /// The for the . - /// The action that will be invoked with the result of the set. - /// The minimum number of rows allowed. - /// The maximum number of rows allowed. - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - public MultiSetCollArgs(IDatabaseMapper mapper, Action result, int minRows = 0, int? maxRows = null, bool stopOnNull = false) : base(minRows, maxRows, stopOnNull) - { - Mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - _result = result ?? throw new ArgumentNullException(nameof(result)); - } + private readonly Action _result = result.ThrowIfNull(nameof(result)); /// /// Gets the for the . /// - public IDatabaseMapper Mapper { get; private set; } + public IDatabaseMapper Mapper { get; private set; } = mapper.ThrowIfNull(nameof(mapper)); /// /// The method invoked for each record for its respective dataset. @@ -42,9 +33,7 @@ public MultiSetCollArgs(IDatabaseMapper mapper, Action result, int /// The . public override void DatasetRecord(DatabaseRecord dr) { - if (dr == null) - throw new ArgumentNullException(nameof(dr)); - + dr.ThrowIfNull(nameof(dr)); _coll ??= new TColl(); var item = Mapper.MapFromDb(dr); diff --git a/src/CoreEx.Database/MultiSetSingleArgs.cs b/src/CoreEx.Database/MultiSetSingleArgs.cs index 9ac731c0..7b5dd666 100644 --- a/src/CoreEx.Database/MultiSetSingleArgs.cs +++ b/src/CoreEx.Database/MultiSetSingleArgs.cs @@ -5,23 +5,14 @@ namespace CoreEx.Database /// /// Provides the base Database multi-set arguments when expecting a single item/record only. /// - public abstract class MultiSetSingleArgs : IMultiSetArgs + /// Indicates whether the value is mandatory; defaults to true. + /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). + public abstract class MultiSetSingleArgs(bool isMandatory = true, bool stopOnNull = false) : IMultiSetArgs { - /// - /// Initializes a new instance of the class. - /// - /// Indicates whether the value is mandatory; defaults to true. - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - protected MultiSetSingleArgs(bool isMandatory = true, bool stopOnNull = false) - { - IsMandatory = isMandatory; - StopOnNull = stopOnNull; - } - /// /// Indicates whether the value is mandatory; i.e. a corresponding record must be read. /// - public bool IsMandatory { get; set; } + public bool IsMandatory { get; set; } = isMandatory; /// /// Gets or sets the minimum number of rows allowed. @@ -36,7 +27,7 @@ protected MultiSetSingleArgs(bool isMandatory = true, bool stopOnNull = false) /// /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). /// - public bool StopOnNull { get; set; } + public bool StopOnNull { get; set; } = stopOnNull; /// /// The method invoked for each record for its respective dataset. diff --git a/src/CoreEx.Database/MultiSetSingleArgsT.cs b/src/CoreEx.Database/MultiSetSingleArgsT.cs index 0e9c455d..609c3bfa 100644 --- a/src/CoreEx.Database/MultiSetSingleArgsT.cs +++ b/src/CoreEx.Database/MultiSetSingleArgsT.cs @@ -8,29 +8,20 @@ namespace CoreEx.Database /// Provides the Database multi-set arguments when expecting a single item/record only. /// /// The item . - public class MultiSetSingleArgs : MultiSetSingleArgs, IMultiSetArgs + /// The for the . + /// The action that will be invoked with the result of the set. + /// Indicates whether the value is mandatory; defaults to true. + /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). + public class MultiSetSingleArgs(IDatabaseMapper mapper, Action result, bool isMandatory = true, bool stopOnNull = false) : MultiSetSingleArgs(isMandatory, stopOnNull), IMultiSetArgs where T : class, new() { private T? _value; - private readonly Action _result; - - /// - /// Initializes a new instance of the class. - /// - /// The for the . - /// The action that will be invoked with the result of the set. - /// Indicates whether the value is mandatory; defaults to true. - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - public MultiSetSingleArgs(IDatabaseMapper mapper, Action result, bool isMandatory = true, bool stopOnNull = false) : base(isMandatory, stopOnNull) - { - Mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - _result = result ?? throw new ArgumentNullException(nameof(result)); - } + private readonly Action _result = result.ThrowIfNull(nameof(result)); /// /// Gets the for the . /// - public IDatabaseMapper Mapper { get; private set; } + public IDatabaseMapper Mapper { get; private set; } = mapper.ThrowIfNull(nameof(mapper)); /// /// The method invoked for each record for its respective dataset. diff --git a/src/CoreEx.Dataverse/Mapping/DataverseMapperT.cs b/src/CoreEx.Dataverse/Mapping/DataverseMapperT.cs index b3421266..1bec0b92 100644 --- a/src/CoreEx.Dataverse/Mapping/DataverseMapperT.cs +++ b/src/CoreEx.Dataverse/Mapping/DataverseMapperT.cs @@ -52,8 +52,7 @@ public DataverseMapper(bool autoMap = false, params string[] ignoreSrcePropertie { get { - if (propertyExpression == null) - throw new ArgumentNullException(nameof(propertyExpression)); + propertyExpression.ThrowIfNull(nameof(propertyExpression)); MemberExpression? me = null; if (propertyExpression.Body.NodeType == ExpressionType.MemberAccess) @@ -187,7 +186,7 @@ public DataverseMapper HasProperty(ExpressionThe to inherit from. Must also implement . public void InheritPropertiesFrom(IDataverseMapper inheritMapper) where T : class, new() { - if (inheritMapper == null) throw new ArgumentNullException(nameof(inheritMapper)); + inheritMapper.ThrowIfNull(nameof(inheritMapper)); if (!typeof(TSource).IsSubclassOf(typeof(T))) throw new ArgumentException($"Type {typeof(TSource).Name} must inherit from {typeof(T).Name}.", nameof(inheritMapper)); if (inheritMapper is not IDataverseMapperMappings inheritMappings) throw new ArgumentException($"Type {typeof(T).Name} must implement {typeof(IDataverseMapperMappings).Name} to copy the mappings.", nameof(inheritMapper)); @@ -216,7 +215,7 @@ public DataverseMapper HasProperty(Expression public void MapToDataverse(TSource? value, Entity entity, OperationTypes operationType = OperationTypes.Unspecified) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); + entity.ThrowIfNull(nameof(entity)); if (value == null) return; foreach (var p in _mappings) @@ -238,8 +237,7 @@ protected virtual void OnMapToDataverse(TSource value, Entity entity, OperationT /// public TSource? MapFromDataverse(Entity entity, OperationTypes operationType = OperationTypes.Unspecified) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); - + entity.ThrowIfNull(nameof(entity)); var value = new TSource(); foreach (var p in _mappings) diff --git a/src/CoreEx.Dataverse/Mapping/PropertyColumnMapper.cs b/src/CoreEx.Dataverse/Mapping/PropertyColumnMapper.cs index e08e42d4..05d9fb47 100644 --- a/src/CoreEx.Dataverse/Mapping/PropertyColumnMapper.cs +++ b/src/CoreEx.Dataverse/Mapping/PropertyColumnMapper.cs @@ -65,8 +65,7 @@ internal PropertyColumnMapper(Expression> propert /// void IPropertyColumnMapper.SetConverter(IConverter converter) { - if (converter == null) - throw new ArgumentNullException(nameof(converter)); + converter.ThrowIfNull(nameof(converter)); if (Mapper != null) throw new InvalidOperationException("The Mapper and Converter cannot be both set; only one is permissible."); @@ -92,8 +91,7 @@ public PropertyColumnMapper SetConverter void IPropertyColumnMapper.SetMapper(IDataverseMapper mapper) { - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); + mapper.ThrowIfNull(nameof(mapper)); if (Converter != null) throw new InvalidOperationException("The Mapper and Converter cannot be both set; only one is permissible."); @@ -102,7 +100,7 @@ void IPropertyColumnMapper.SetMapper(IDataverseMapper mapper) throw new InvalidOperationException($"The PropertyType '{PropertyType.Name}' must be a class to set a Mapper."); if (mapper.SourceType != typeof(TSourceProperty)) - throw new ArgumentNullException($"The PropertyType '{PropertyType.Name}' and IDataverseMapper.SourceType '{mapper.SourceType.Name}' must match."); + throw new ArgumentException($"The PropertyType '{PropertyType.Name}' and IDataverseMapper.SourceType '{mapper.SourceType.Name}' must match.", nameof(mapper)); if (IsPrimaryKey) throw new InvalidOperationException("A Mapper can not be set for a primary key."); diff --git a/src/CoreEx.EntityFrameworkCore/EfDb.cs b/src/CoreEx.EntityFrameworkCore/EfDb.cs index a5e7aa3e..96f1c015 100644 --- a/src/CoreEx.EntityFrameworkCore/EfDb.cs +++ b/src/CoreEx.EntityFrameworkCore/EfDb.cs @@ -30,34 +30,24 @@ namespace CoreEx.EntityFrameworkCore /// /// /// - public class EfDb : IEfDb where TDbContext : DbContext, IEfDbContext + /// The . + /// The . + /// Enables the to be overridden; defaults to . + public class EfDb(TDbContext dbContext, IMapper mapper, EfDbInvoker? invoker = null) : IEfDb where TDbContext : DbContext, IEfDbContext { - private readonly TDbContext _dbContext; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// Enables the to be overridden; defaults to . - public EfDb(TDbContext dbContext, IMapper mapper, EfDbInvoker? invoker = null) - { - _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); - Mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - Invoker = invoker ?? new EfDbInvoker(); - } + private readonly TDbContext _dbContext = dbContext.ThrowIfNull(nameof(dbContext)); /// public DbContext DbContext => _dbContext; /// - public EfDbInvoker Invoker { get; } + public EfDbInvoker Invoker { get; } = invoker ?? new EfDbInvoker(); /// public IDatabase Database => _dbContext.BaseDatabase; /// - public IMapper Mapper { get; } + public IMapper Mapper { get; } = mapper.ThrowIfNull(nameof(mapper)); /// public EfDbArgs DbArgs { get; set; } = new EfDbArgs(); @@ -75,7 +65,7 @@ public EfDb(TDbContext dbContext, IMapper mapper, EfDbInvoker? invoker = null) private async Task> GetWithResultInternalAsync(EfDbArgs args, CompositeKey key, string memberName, CancellationToken cancellationToken) where T : class, IEntityKey, new() where TModel : class, new() => await Invoker.InvokeAsync(this, key, async (_, key, ct) => { - var model = await DbContext.FindAsync(key.Args.ToArray(), cancellationToken).ConfigureAwait(false); + var model = await DbContext.FindAsync([.. key.Args], cancellationToken).ConfigureAwait(false); if (args.ClearChangeTrackerAfterGet) DbContext.ChangeTracker.Clear(); @@ -100,10 +90,8 @@ public EfDb(TDbContext dbContext, IMapper mapper, EfDbInvoker? invoker = null) private async Task> CreateWithResultInternalAsync(EfDbArgs args, T value, string memberName, CancellationToken cancellationToken) where T : class, IEntityKey, new() where TModel : class, new() { CheckSaveArgs(args); - if (value == null) - throw new ArgumentNullException(nameof(value)); - ChangeLog.PrepareCreated(value); + ChangeLog.PrepareCreated(value.ThrowIfNull(nameof(value))); Cleaner.ResetTenantId(value); return await Invoker.InvokeAsync(this, args, value, async (_, args, value, ct) => @@ -137,10 +125,8 @@ public EfDb(TDbContext dbContext, IMapper mapper, EfDbInvoker? invoker = null) private async Task> UpdateWithResultInternalAsync(EfDbArgs args, T value, string memberName, CancellationToken cancellationToken) where T : class, IEntityKey, new() where TModel : class, new() { CheckSaveArgs(args); - if (value == null) - throw new ArgumentNullException(nameof(value)); - ChangeLog.PrepareUpdated(value); + ChangeLog.PrepareUpdated(value.ThrowIfNull(nameof(value))); Cleaner.ResetTenantId(value); return await Invoker.InvokeAsync(this, args, value, async (_, args, value, ct) => @@ -185,7 +171,7 @@ public EfDb(TDbContext dbContext, IMapper mapper, EfDbInvoker? invoker = null) return await Invoker.InvokeAsync(this, args, key, async (_, args, key, ct) => { // A pre-read is required to get the row version for concurrency. - var model = await DbContext.FindAsync(key.Args.ToArray(), ct).ConfigureAwait(false); + var model = await DbContext.FindAsync([.. key.Args], ct).ConfigureAwait(false); if (model == null) return Result.NotFoundError(); diff --git a/src/CoreEx.EntityFrameworkCore/EfDbEntity.cs b/src/CoreEx.EntityFrameworkCore/EfDbEntity.cs index a6f51e0f..c0019ac9 100644 --- a/src/CoreEx.EntityFrameworkCore/EfDbEntity.cs +++ b/src/CoreEx.EntityFrameworkCore/EfDbEntity.cs @@ -10,16 +10,11 @@ namespace CoreEx.EntityFrameworkCore /// /// The resultant . /// The entity framework model . - public readonly struct EfDbEntity : IEfDbEntity where T : class, IEntityKey, new() where TModel : class, new() + /// The . + public readonly struct EfDbEntity(IEfDb efDb) : IEfDbEntity where T : class, IEntityKey, new() where TModel : class, new() { - /// - /// Initializes a new instance of the class. - /// - /// The . - public EfDbEntity(IEfDb efDb) => EfDb = efDb ?? throw new ArgumentNullException(nameof(efDb)); - /// - public IEfDb EfDb { get; } + public IEfDb EfDb { get; } = efDb.ThrowIfNull(nameof(efDb)); /// /// Creates an to enable select-like capabilities. diff --git a/src/CoreEx.EntityFrameworkCore/EfDbQuery.cs b/src/CoreEx.EntityFrameworkCore/EfDbQuery.cs index 1912338e..45169f77 100644 --- a/src/CoreEx.EntityFrameworkCore/EfDbQuery.cs +++ b/src/CoreEx.EntityFrameworkCore/EfDbQuery.cs @@ -26,7 +26,7 @@ namespace CoreEx.EntityFrameworkCore /// A function to modify the underlying . internal EfDbQuery(IEfDb efdb, EfDbArgs args, Func, IQueryable>? query = null) { - EfDb = efdb ?? throw new ArgumentNullException(nameof(efdb)); + EfDb = efdb.ThrowIfNull(nameof(efdb)); Args = args; _query = query; Paging = null; @@ -248,8 +248,7 @@ public readonly Task> SelectQueryWithResultAsync(TColl coll /// private async readonly Task> SelectQueryWithResultInternalAsync(TColl collection, string memberName, CancellationToken cancellationToken) where TColl : ICollection { - if (collection == null) - throw new ArgumentNullException(nameof(collection)); + collection.ThrowIfNull(nameof(collection)); var paging = Paging; var mapper = Mapper; diff --git a/src/CoreEx.FluentValidation/IFluentServiceCollectionExtensions.cs b/src/CoreEx.FluentValidation/IFluentServiceCollectionExtensions.cs index 30f766c4..293b433d 100644 --- a/src/CoreEx.FluentValidation/IFluentServiceCollectionExtensions.cs +++ b/src/CoreEx.FluentValidation/IFluentServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using System; using System.Linq; @@ -24,7 +25,7 @@ public static IServiceCollection AddFluentValidator(this IService /// Adds the , the corresponding and as scoped services. /// private static IServiceCollection AddFluentValidatorWithInterfacesInternal(this IServiceCollection services) where TValidator : class, FluentValidation.IValidator - => (services ?? throw new ArgumentNullException(nameof(services))) + => services.ThrowIfNull(nameof(services)) .AddScoped, TValidator>() .AddScoped>(sp => new CoreEx.FluentValidation.ValidatorWrapper(sp.GetRequiredService>())) .AddScoped(sp => (TValidator)sp.GetRequiredService>()); @@ -43,7 +44,7 @@ public static IServiceCollection AddFluentValidator(this IServiceCol /// Adds the as a scoped service only. /// private static IServiceCollection AddFluentValidatorInternal(this IServiceCollection services) where TValidator : class, FluentValidation.IValidator - => (services ?? throw new ArgumentNullException(nameof(services))).AddScoped(); + => services.ThrowIfNull(nameof(services)).AddScoped(); /// /// Adds all the (s) and corresponding (s) from the specified as scoped services. diff --git a/src/CoreEx.FluentValidation/ValidationExtensions.cs b/src/CoreEx.FluentValidation/ValidationExtensions.cs index 2437fca6..57da729d 100644 --- a/src/CoreEx.FluentValidation/ValidationExtensions.cs +++ b/src/CoreEx.FluentValidation/ValidationExtensions.cs @@ -31,7 +31,7 @@ public static void ThrowValidationException(this ValidationResult validationResu /// The where the has errors; otherwise, null. public static ValidationException? ToValidationException(this ValidationResult validationResult) { - if ((validationResult ?? throw new ArgumentNullException(nameof(validationResult))).IsValid) + if (validationResult.ThrowIfNull(nameof(validationResult)).IsValid) return null; var mic = new MessageItemCollection(); diff --git a/src/CoreEx.FluentValidation/ValidationResultWrapper.cs b/src/CoreEx.FluentValidation/ValidationResultWrapper.cs index 1abfdb26..6e28dece 100644 --- a/src/CoreEx.FluentValidation/ValidationResultWrapper.cs +++ b/src/CoreEx.FluentValidation/ValidationResultWrapper.cs @@ -24,7 +24,7 @@ public class ValidationResultWrapper : IValidationResult /// public ValidationResultWrapper(ValidationResult result, T? value) { - Result = result ?? throw new ArgumentNullException(nameof(result)); + Result = result.ThrowIfNull(nameof(result)); Value = value; } diff --git a/src/CoreEx.FluentValidation/ValidatorWrapper.cs b/src/CoreEx.FluentValidation/ValidatorWrapper.cs index 8f53dcfb..ffdda4d3 100644 --- a/src/CoreEx.FluentValidation/ValidatorWrapper.cs +++ b/src/CoreEx.FluentValidation/ValidatorWrapper.cs @@ -12,18 +12,13 @@ namespace CoreEx.FluentValidation /// Represents an wrapper to enable CoreEx interoperability. /// /// The value . - public sealed class ValidatorWrapper : IValidator + /// The to wrap. + public sealed class ValidatorWrapper(FV.IValidator fluentValidator) : IValidator { - /// - /// Initializes a new instance of the class. - /// - /// The to wrap. - public ValidatorWrapper(FV.IValidator fluentValidator) => Validator = fluentValidator ?? throw new ArgumentNullException(nameof(fluentValidator)); - /// /// Gets the underlying that is being wrapped. /// - public FV.IValidator Validator { get; } + public FV.IValidator Validator { get; } = fluentValidator.ThrowIfNull(nameof(fluentValidator)); /// public async Task> ValidateAsync(T value, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Newtonsoft/Json/ContractResolver.cs b/src/CoreEx.Newtonsoft/Json/ContractResolver.cs index 98c01621..e11ca535 100644 --- a/src/CoreEx.Newtonsoft/Json/ContractResolver.cs +++ b/src/CoreEx.Newtonsoft/Json/ContractResolver.cs @@ -66,7 +66,7 @@ static ContractResolver() /// The instance to support fluent-style method-chaining. public ContractResolver AddType(Type type) { - (_typeDict ??= new HashSet()).Add(type); + (_typeDict ??= []).Add(type); return this; } @@ -78,8 +78,7 @@ public ContractResolver AddType(Type type) /// The instance to support fluent-style method-chaining. public ContractResolver AddRename(Type type, IDictionary pairs) { - if (type == null) - throw new ArgumentNullException(nameof(type)); + type.ThrowIfNull(nameof(type)); if (pairs != null) { @@ -101,14 +100,9 @@ public ContractResolver AddRename(Type type, IDictionary pairs) /// The instance to support fluent-style method-chaining. public ContractResolver AddRename(Type type, string propertyName, string jsonName) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - if (string.IsNullOrEmpty(propertyName)) - throw new ArgumentNullException(nameof(propertyName)); - - if (string.IsNullOrEmpty(jsonName)) - throw new ArgumentNullException(nameof(jsonName)); + type.ThrowIfNull(nameof(type)); + propertyName.ThrowIfNullOrEmpty(nameof(propertyName)); + jsonName.ThrowIfNullOrEmpty(nameof(jsonName)); if (propertyName == jsonName) return this; @@ -126,8 +120,7 @@ public ContractResolver AddRename(Type type, string propertyName, string jsonNam /// The instance to support fluent-style method-chaining. public ContractResolver AddIgnore(Type type, params string[] propertyNames) { - if (type == null) - throw new ArgumentNullException(nameof(type)); + type.ThrowIfNull(nameof(type)); if (propertyNames == null || propertyNames.Length == 0) return this; diff --git a/src/CoreEx.Newtonsoft/Json/JsonFilterer.cs b/src/CoreEx.Newtonsoft/Json/JsonFilterer.cs index 76e2f401..ba127621 100644 --- a/src/CoreEx.Newtonsoft/Json/JsonFilterer.cs +++ b/src/CoreEx.Newtonsoft/Json/JsonFilterer.cs @@ -48,9 +48,7 @@ public static bool TryApply(T value, IEnumerable? paths, out string j /// true indicates that at least one JSON node was filtered (removed); otherwise, false for no changes. public static bool TryApply(T value, IEnumerable? paths, out JToken json, JsonPropertyFilter filter = JsonPropertyFilter.Include, JsonSerializerSettings? settings = null, StringComparison comparison = StringComparison.OrdinalIgnoreCase, Action? preFilterInspector = null) { - if (value == null) - throw new ArgumentNullException(nameof(value)); - + value.ThrowIfNull(nameof(value)); json = JToken.FromObject(value, Nsj.JsonSerializer.Create(settings)); preFilterInspector?.Invoke(new JsonPreFilterInspector(json)); return Apply(json, paths, filter, comparison); diff --git a/src/CoreEx.Newtonsoft/Json/NewtonsoftServiceCollectionExtensions.cs b/src/CoreEx.Newtonsoft/Json/NewtonsoftServiceCollectionExtensions.cs index aea2ee8b..01dbcf6e 100644 --- a/src/CoreEx.Newtonsoft/Json/NewtonsoftServiceCollectionExtensions.cs +++ b/src/CoreEx.Newtonsoft/Json/NewtonsoftServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using CoreEx.Events; using CoreEx.Json; using System; @@ -14,7 +15,7 @@ public static class NewtonsoftServiceCollectionExtensions /// /// Checks that the is not null. /// - private static IServiceCollection CheckServices(IServiceCollection services) => services ?? throw new ArgumentNullException(nameof(services)); + private static IServiceCollection CheckServices(IServiceCollection services) => services.ThrowIfNull(nameof(services)); /// /// Adds the as the and as the singleton services. diff --git a/src/CoreEx.OData/IBoundClientExtensions.cs b/src/CoreEx.OData/IBoundClientExtensions.cs index a59cb2cd..76d751b1 100644 --- a/src/CoreEx.OData/IBoundClientExtensions.cs +++ b/src/CoreEx.OData/IBoundClientExtensions.cs @@ -24,9 +24,9 @@ public static class IBoundClientExtensions /// The resulting query. public static IBoundClient FilterWhen(this IBoundClient query, bool when, Expression> predicate) where TElement : class { - var q = query ?? throw new ArgumentNullException(nameof(query)); + var q = query.ThrowIfNull(nameof(query)); if (when) - return q.Filter(predicate ?? throw new ArgumentNullException(nameof(predicate))); + return q.Filter(predicate.ThrowIfNull(nameof(predicate))); else return q; } @@ -43,13 +43,13 @@ public static IBoundClient FilterWhen(this IBoundClientThe resulting query. public static IBoundClient FilterWith(this IBoundClient query, T with, Expression> predicate) where TElement : class { - var q = query ?? throw new ArgumentNullException(nameof(query)); + var q = query.ThrowIfNull(nameof(query)); if (Comparer.Default.Compare(with, default!) != 0 && Comparer.Default.Compare(with, default!) != 0) { if (with is not string && with is System.Collections.IEnumerable ie && !ie.GetEnumerator().MoveNext()) return q; - return q.Filter(predicate ?? throw new ArgumentNullException(nameof(predicate))); + return q.Filter(predicate.ThrowIfNull(nameof(predicate))); } else return q; @@ -67,8 +67,8 @@ public static IBoundClient FilterWith(this IBoundClientThe resulting (updated) query. public static IBoundClient FilterWildcard(this IBoundClient query, Expression> property, string? text, bool ignoreCase = true, bool checkForNull = true) where TElement : class { - var q = query ?? throw new ArgumentNullException(nameof(query)); - var p = property ?? throw new ArgumentNullException(nameof(property)); + var q = query.ThrowIfNull(nameof(query)); + var p = property.ThrowIfNull(nameof(property)); // Check the expression. if (p.Body is not MemberExpression me) diff --git a/src/CoreEx.OData/Mapping/PropertyColumnMapper.cs b/src/CoreEx.OData/Mapping/PropertyColumnMapper.cs index e8316e1a..40bcd205 100644 --- a/src/CoreEx.OData/Mapping/PropertyColumnMapper.cs +++ b/src/CoreEx.OData/Mapping/PropertyColumnMapper.cs @@ -112,7 +112,7 @@ void IPropertyColumnMapper.SetMapper(IODataMapper mapper) throw new InvalidOperationException($"The PropertyType '{PropertyType.Name}' must be a class to set a Mapper."); if (mapper.SourceType != typeof(TSourceProperty)) - throw new ArgumentNullException($"The PropertyType '{PropertyType.Name}' and IDictionaryMapper.SourceType '{mapper.SourceType.Name}' must match."); + throw new ArgumentException($"The PropertyType '{PropertyType.Name}' and IDictionaryMapper.SourceType '{mapper.SourceType.Name}' must match.", nameof(mapper)); Mapper = mapper; } diff --git a/src/CoreEx.OData/ODataClient.cs b/src/CoreEx.OData/ODataClient.cs index c1cbd2e8..400c4ebe 100644 --- a/src/CoreEx.OData/ODataClient.cs +++ b/src/CoreEx.OData/ODataClient.cs @@ -133,7 +133,7 @@ public class ODataClient(Soc.ODataClient client, IMapper? mapper = null, ODataIn /// The . /// The containing the appropriate . /// Where overridding and the is not specifically handled then invoke the base to ensure any standard handling is executed. - protected virtual Result? OnCosmosException(Soc.WebRequestException odex) => odex == null ? throw new ArgumentNullException(nameof(odex)) : odex.Code switch + protected virtual Result? OnCosmosException(Soc.WebRequestException odex) => odex.ThrowIfNull(nameof(odex)).Code switch { HttpStatusCode.NotFound => Result.Fail(new NotFoundException(null, odex)), HttpStatusCode.Conflict => Result.Fail(new DuplicateException(null, odex)), diff --git a/src/CoreEx.OData/ODataItemCollection.cs b/src/CoreEx.OData/ODataItemCollection.cs index b17b612f..455b92b9 100644 --- a/src/CoreEx.OData/ODataItemCollection.cs +++ b/src/CoreEx.OData/ODataItemCollection.cs @@ -34,7 +34,7 @@ namespace CoreEx.OData /// /// Gets the owning . /// - public ODataClient Owner { get; } = client ?? throw new ArgumentNullException(nameof(client)); + public ODataClient Owner { get; } = client.ThrowIfNull(nameof(client)); /// /// Gets the . @@ -44,12 +44,12 @@ namespace CoreEx.OData /// /// Gets the collection name. /// - public string CollectionName { get; } = collectionName ?? throw new ArgumentNullException(nameof(collectionName)); + public string CollectionName { get; } = collectionName.ThrowIfNull(nameof(collectionName)); /// /// Gets the . /// - public IODataMapper Mapper { get; } = mapper ?? throw new ArgumentNullException(nameof(mapper)); + public IODataMapper Mapper { get; } = mapper.ThrowIfNull(nameof(mapper)); /// /// Gets the entity for the specified mapping from the to . diff --git a/src/CoreEx.OData/ODataQuery.cs b/src/CoreEx.OData/ODataQuery.cs index 4c4882ba..43669b85 100644 --- a/src/CoreEx.OData/ODataQuery.cs +++ b/src/CoreEx.OData/ODataQuery.cs @@ -31,7 +31,7 @@ namespace CoreEx.OData /// internal ODataQuery(IOData odata, ODataArgs args, string? collectionName, Func, Soc.IBoundClient>? query) { - ODataClient = odata ?? throw new ArgumentNullException(nameof(odata)); + ODataClient = odata.ThrowIfNull(nameof(odata)); Args = args; CollectionName = collectionName; _query = query; @@ -241,8 +241,7 @@ public readonly Task> SelectQueryWithResultAsync(TColl coll /// private async readonly Task> SelectQueryWithResultInternalAsync(TColl collection, string memberName, CancellationToken cancellationToken) where TColl : ICollection { - if (collection == null) - throw new ArgumentNullException(nameof(collection)); + collection.ThrowIfNull(nameof(collection)); var paging = Paging; var mapper = Mapper; diff --git a/src/CoreEx.Solace/PubSub/EventDataToPubSubMessageConverter.cs b/src/CoreEx.Solace/PubSub/EventDataToPubSubMessageConverter.cs index 2ce57714..09d5a7ff 100644 --- a/src/CoreEx.Solace/PubSub/EventDataToPubSubMessageConverter.cs +++ b/src/CoreEx.Solace/PubSub/EventDataToPubSubMessageConverter.cs @@ -13,28 +13,19 @@ namespace CoreEx.Solace.PubSub /// Converts an to a . /// /// Internally converts an to a corresponding using the , then converts to the using the . - public class EventDataToPubSubMessageConverter : IValueConverter + /// The to serialize the into a corresponding . + /// The to convert an to a corresponding . + public class EventDataToPubSubMessageConverter(IEventSerializer? eventSerializer = null, IValueConverter? valueConverter = null) : IValueConverter { - /// - /// Initializes a new instance of the class. - /// - /// The to serialize the into a corresponding . - /// The to convert an to a corresponding . - public EventDataToPubSubMessageConverter(IEventSerializer? eventSerializer = null, IValueConverter? valueConverter = null) - { - EventSerializer = eventSerializer ?? ExecutionContext.GetService() ?? new EventDataSerializer(); - EventSendDataConverter = valueConverter ?? ExecutionContext.GetService>() ?? new EventSendDataToPubSubConverter(); - } - /// /// Gets the to serialize the into for the . /// - protected IEventSerializer EventSerializer { get; } + protected IEventSerializer EventSerializer { get; } = eventSerializer ?? ExecutionContext.GetService() ?? new EventDataSerializer(); /// /// Gets the to convert an to a corresponding . /// - protected IValueConverter EventSendDataConverter { get; } + protected IValueConverter EventSendDataConverter { get; } = valueConverter ?? ExecutionContext.GetService>() ?? new EventSendDataToPubSubConverter(); /// public IMessage Convert(EventData @event) diff --git a/src/CoreEx.Solace/PubSub/PubSubSender.cs b/src/CoreEx.Solace/PubSub/PubSubSender.cs index 367a70a8..47b15bf0 100644 --- a/src/CoreEx.Solace/PubSub/PubSubSender.cs +++ b/src/CoreEx.Solace/PubSub/PubSubSender.cs @@ -33,10 +33,10 @@ public class PubSubSender : IPubSubSender /// The optional to convert an to a corresponding . public PubSubSender(IContext solaceContext, SessionProperties sessionProperties, SettingsBase settings, ILogger logger, PubSubSenderInvoker? invoker = null, IValueConverter? converter = null) { - SolaceContext = solaceContext ?? throw new ArgumentNullException(nameof(solaceContext), "Verify PubSub connection properties have been correctly defined."); - SessionProperties = sessionProperties ?? throw new ArgumentNullException(nameof(sessionProperties), "Verify PubSub connection properties have been correctly defined."); - Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + SolaceContext = solaceContext.ThrowIfNull(nameof(solaceContext)); + SessionProperties = sessionProperties.ThrowIfNull(nameof(sessionProperties)); + Settings = settings.ThrowIfNull(nameof(settings)); + Logger = logger.ThrowIfNull(nameof(logger)); Invoker = invoker ?? (_invoker ??= new PubSubSenderInvoker()); Converter = converter ?? new EventSendDataToPubSubConverter(); DefaultQueueOrTopicName = Settings.GetValue($"{GetType().Name}:QueueOrTopicName", defaultValue: _unspecifiedQueueOrTopicName); @@ -160,7 +160,7 @@ public Task SendAsync(IEnumerable events, CancellationToken cance try { Logger.LogInformation("Sending {Count} message(s) to PubSub Broker.", messageBatch.Count); - var returnCode = session.Send(messageBatch.ToArray(), 0, messageBatch.Count, out int sentCount); + var returnCode = session.Send([.. messageBatch], 0, messageBatch.Count, out int sentCount); if (returnCode != ReturnCode.SOLCLIENT_OK) { diff --git a/src/CoreEx.UnitTesting/AspNetCore/AgentTester.cs b/src/CoreEx.UnitTesting/AspNetCore/AgentTester.cs index c1215748..795fe983 100644 --- a/src/CoreEx.UnitTesting/AspNetCore/AgentTester.cs +++ b/src/CoreEx.UnitTesting/AspNetCore/AgentTester.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using System; @@ -46,7 +47,7 @@ public class AgentTester(TesterBase owner, TestServer testServer) : Http /// An . public async Task RunAsync(Func> func) { - if (func == null) throw new ArgumentNullException(nameof(func)); + func.ThrowIfNull(nameof(func)); using var scope = this.CreateClientScope(); var agent = scope.ServiceProvider.GetRequiredService(); @@ -67,7 +68,7 @@ public async Task RunAsync(FuncAn . public async Task> RunAsync(Func>> func) { - if (func == null) throw new ArgumentNullException(nameof(func)); + func.ThrowIfNull(nameof(func)); using var scope = this.CreateClientScope(); var agent = scope.ServiceProvider.GetRequiredService(); @@ -91,7 +92,7 @@ public async Task> RunAsync(FuncAn . public async Task RunAsync(Func> func) { - if (func == null) throw new ArgumentNullException(nameof(func)); + func.ThrowIfNull(nameof(func)); using var scope = this.CreateClientScope(); var agent = scope.ServiceProvider.GetRequiredService(); diff --git a/src/CoreEx.UnitTesting/AspNetCore/AgentTesterT.cs b/src/CoreEx.UnitTesting/AspNetCore/AgentTesterT.cs index 12e20a2f..d9ebaeb4 100644 --- a/src/CoreEx.UnitTesting/AspNetCore/AgentTesterT.cs +++ b/src/CoreEx.UnitTesting/AspNetCore/AgentTesterT.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using CoreEx.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; @@ -41,7 +42,7 @@ public class AgentTester(TesterBase owner, TestServer testServer /// An . public async Task> RunAsync(Func>> func) { - if (func == null) throw new ArgumentNullException(nameof(func)); + func.ThrowIfNull(nameof(func)); using var scope = this.CreateClientScope(); var agent = scope.ServiceProvider.GetRequiredService(); @@ -65,7 +66,7 @@ public async Task> RunAsync(FuncAn . public async Task> RunAsync(Func> func) { - if (func == null) throw new ArgumentNullException(nameof(func)); + func.ThrowIfNull(nameof(func)); using var scope = this.CreateClientScope(); var agent = scope.ServiceProvider.GetRequiredService(); diff --git a/src/CoreEx.UnitTesting/Expectations/EventExpectations.cs b/src/CoreEx.UnitTesting/Expectations/EventExpectations.cs index 76d5c9c2..853e3640 100644 --- a/src/CoreEx.UnitTesting/Expectations/EventExpectations.cs +++ b/src/CoreEx.UnitTesting/Expectations/EventExpectations.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using CoreEx.Events; using System; using System.Buffers; @@ -75,7 +76,7 @@ private void Add(string? destination, (string? Source, string? Subject, string? /// The named destination (e.g. queue or topic). /// The expected subject (may contain wildcards). /// The expected action (may contain wildcards). - public void Expect(string? destination, string subject, string? action = "*") => Add(destination, ("*", subject ?? throw new ArgumentNullException(nameof(subject)), action, null, [])); + public void Expect(string? destination, string subject, string? action = "*") => Add(destination, ("*", subject.ThrowIfNull(nameof(subject)), action, null, [])); /// /// Expects that the corresponding event has been published (in order specified). The expected event , and can use wildcards. All other @@ -85,7 +86,7 @@ private void Add(string? destination, (string? Source, string? Subject, string? /// The expected source formatted as a (may contain wildcards). /// The expected subject (may contain wildcards). /// The expected action (may contain wildcards). - public void Expect(string? destination, string source, string subject, string? action = "*") => Add(destination, (source ?? throw new ArgumentNullException(nameof(source)), subject ?? throw new ArgumentNullException(nameof(subject)), action, null, [])); + public void Expect(string? destination, string source, string subject, string? action = "*") => Add(destination, (source.ThrowIfNull(nameof(source)), subject.ThrowIfNull(nameof(subject)), action, null, [])); /// /// Expects that the corresponding has been published (in order specified). All properties for expected event will be compared again the actual. @@ -94,7 +95,7 @@ private void Add(string? destination, (string? Source, string? Subject, string? /// The expected . Wildcards are supported for and . /// The JSON paths to ignore from the comparison. Defaults to . /// Wildcards are supported for , and . - public void Expect(string? destination, EventData @event, params string[] pathsToIgnore) => Add(destination, (null, @event?.Subject, @event?.Action, @event ?? throw new ArgumentNullException(nameof(@event)), pathsToIgnore)); + public void Expect(string? destination, EventData @event, params string[] pathsToIgnore) => Add(destination, (null, @event?.Subject, @event?.Action, @event.ThrowIfNull(nameof(@event)), pathsToIgnore)); /// /// Expects that the corresponding has been published (in order specified). All properties for expected event will be compared again the actual. @@ -104,7 +105,7 @@ private void Add(string? destination, (string? Source, string? Subject, string? /// The expected . Wildcards are supported for and . /// The JSON paths to ignore from the comparison. Defaults to . /// Wildcards are supported for , and . - public void Expect(string? destination, string source, EventData @event, params string[] pathsToIgnore) => Add(destination, (source, @event?.Subject, @event?.Action, @event ?? throw new ArgumentNullException(nameof(@event)), pathsToIgnore)); + public void Expect(string? destination, string source, EventData @event, params string[] pathsToIgnore) => Add(destination, (source, @event?.Subject, @event?.Action, @event.ThrowIfNull(nameof(@event)), pathsToIgnore)); /// protected override Task OnAssertAsync(AssertArgs args) @@ -166,10 +167,7 @@ protected override Task OnAssertAsync(AssertArgs args) /// private void AssertDestination(AssertArgs args, string? destination, List<(string? Source, string? Subject, string? Action, EventData? Event, string[] PathsToIgnore)> expectedEvents, List actualEvents) { - if (actualEvents == null) - throw new ArgumentNullException(nameof(actualEvents)); - - if (actualEvents.Count != expectedEvents.Count) + if (actualEvents.ThrowIfNull(nameof(actualEvents)).Count != expectedEvents.Count) args.Tester.Implementor.AssertFail($"Destination {destination}: Expected {_expectedEvents.Count} event(s); there were {actualEvents.Count} actual."); for (int i = 0; i < actualEvents.Count; i++) @@ -216,7 +214,7 @@ private void AssertDestination(AssertArgs args, string? destination, List<(strin /// true where there is a wildcard match; otherwise, false. public bool WildcardMatch(AssertArgs args, string expected, string? actual, char separatorCharacter) { - if ((expected ?? throw new ArgumentNullException(nameof(expected))) == "*") + if (expected.ThrowIfNull(nameof(expected)) == "*") return true; var eparts = expected.Split(separatorCharacter); diff --git a/src/CoreEx.UnitTesting/Expectations/ExpectedEventPublisher.cs b/src/CoreEx.UnitTesting/Expectations/ExpectedEventPublisher.cs index ef6661c1..53e63be8 100644 --- a/src/CoreEx.UnitTesting/Expectations/ExpectedEventPublisher.cs +++ b/src/CoreEx.UnitTesting/Expectations/ExpectedEventPublisher.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using CoreEx.Events; using CoreEx.Json; using Microsoft.Extensions.Logging; @@ -33,12 +34,7 @@ public sealed class ExpectedEventPublisher : EventPublisher /// The . /// The where found; otherwise, null. public static ExpectedEventPublisher? GetFromSharedState(TestSharedState sharedState) - { - if (sharedState == null) - throw new ArgumentNullException(nameof(sharedState)); - - return sharedState.StateData.TryGetValue(nameof(ExpectedEventPublisher), out var eep) ? eep as ExpectedEventPublisher : null; - } + => sharedState.ThrowIfNull(nameof(sharedState)).StateData.TryGetValue(nameof(ExpectedEventPublisher), out var eep) ? eep as ExpectedEventPublisher : null; /// /// Sets the into the . @@ -46,12 +42,7 @@ public sealed class ExpectedEventPublisher : EventPublisher /// The . /// The . public static void SetToSharedState(TestSharedState sharedState, ExpectedEventPublisher? expectedEventPublisher) - { - if (sharedState == null) - throw new ArgumentNullException(nameof(sharedState)); - - sharedState.StateData[nameof(ExpectedEventPublisher)] = expectedEventPublisher ?? throw new ArgumentNullException(nameof(expectedEventPublisher)); - } + => sharedState.ThrowIfNull(nameof(sharedState)).StateData[nameof(ExpectedEventPublisher)] = expectedEventPublisher.ThrowIfNull(nameof(expectedEventPublisher)); /// /// Initializes a new instance of the class. @@ -63,7 +54,7 @@ public static void SetToSharedState(TestSharedState sharedState, ExpectedEventPu public ExpectedEventPublisher(TestSharedState sharedState, ILogger? logger = null, IJsonSerializer? jsonSerializer = null, EventDataFormatter? eventDataFormatter = null) : base(eventDataFormatter, new CoreEx.Text.Json.EventDataSerializer(), new NullEventSender()) { - _sharedState = sharedState ?? throw new ArgumentNullException(nameof(sharedState)); + _sharedState = sharedState.ThrowIfNull(nameof(sharedState)); SetToSharedState(_sharedState, this); _logger = logger; _jsonSerializer = jsonSerializer ?? JsonSerializer.Default; diff --git a/src/CoreEx.UnitTesting/Json/ToCoreExJsonSerializerMapper.cs b/src/CoreEx.UnitTesting/Json/ToCoreExJsonSerializerMapper.cs index a7149fe2..a6b77a7b 100644 --- a/src/CoreEx.UnitTesting/Json/ToCoreExJsonSerializerMapper.cs +++ b/src/CoreEx.UnitTesting/Json/ToCoreExJsonSerializerMapper.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -14,7 +15,7 @@ namespace UnitTestEx.Json /// The . public class ToCoreExJsonSerializerMapper(IJsonSerializer testJsonSerializer) : CoreEx.Json.IJsonSerializer { - private readonly IJsonSerializer _testJsonSerializer = testJsonSerializer ?? throw new ArgumentNullException(nameof(testJsonSerializer)); + private readonly IJsonSerializer _testJsonSerializer = testJsonSerializer.ThrowIfNull(nameof(testJsonSerializer)); /// public object Options => _testJsonSerializer.Options; diff --git a/src/CoreEx.UnitTesting/Json/ToUnitTestExJsonSerializerMapper.cs b/src/CoreEx.UnitTesting/Json/ToUnitTestExJsonSerializerMapper.cs index 9d968861..75bac02f 100644 --- a/src/CoreEx.UnitTesting/Json/ToUnitTestExJsonSerializerMapper.cs +++ b/src/CoreEx.UnitTesting/Json/ToUnitTestExJsonSerializerMapper.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -14,7 +15,7 @@ namespace UnitTestEx.Json /// The . public class ToUnitTestExJsonSerializerMapper(CoreEx.Json.IJsonSerializer coreJsonSerializer) : IJsonSerializer, CoreEx.Json.IJsonSerializer { - private readonly CoreEx.Json.IJsonSerializer _coreJsonSerializer = coreJsonSerializer ?? throw new ArgumentNullException(nameof(coreJsonSerializer)); + private readonly CoreEx.Json.IJsonSerializer _coreJsonSerializer = coreJsonSerializer.ThrowIfNull(nameof(coreJsonSerializer)); /// public object Options => _coreJsonSerializer.Options; diff --git a/src/CoreEx.UnitTesting/UnitTestExExtensions.cs b/src/CoreEx.UnitTesting/UnitTestExExtensions.cs index 592cf5d8..80218939 100644 --- a/src/CoreEx.UnitTesting/UnitTestExExtensions.cs +++ b/src/CoreEx.UnitTesting/UnitTestExExtensions.cs @@ -59,7 +59,7 @@ public static class UnitTestExExtensions /// The . /// The to support fluent-style method-chaining. public static TSelf UseJsonSerializer(this TesterBase tester, CoreEx.Json.IJsonSerializer jsonSerializer) where TSelf : TesterBase - => tester.UseJsonSerializer((jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer))).ToUnitTestEx()); + => tester.UseJsonSerializer((jsonSerializer.ThrowIfNull(nameof(jsonSerializer))).ToUnitTestEx()); #endregion @@ -285,7 +285,7 @@ public static ActionResultAssertor AssertLocationHeader(this ActionResul /// internal GenericTesterBaseWith(GenericTesterBase tester, OperationType operationType) { - _tester = tester ?? throw new ArgumentNullException(nameof(tester)); + _tester = tester.ThrowIfNull(nameof(tester)); _operationType = operationType; } @@ -417,7 +417,7 @@ public class AgentTesterWith where TEntryPoint : class where /// /// Initializes a new instance of the . /// - internal AgentTesterWith(ApiTesterBase tester) => _tester = tester ?? throw new ArgumentNullException(nameof(tester)); + internal AgentTesterWith(ApiTesterBase tester) => _tester = tester.ThrowIfNull(nameof(tester)); /// /// Enables a test to be sent to the underlying leveraging the specified agent. @@ -528,7 +528,7 @@ void rm(HttpRequestMessage hr) /// The . public static ServiceBusReceivedMessage CreateServiceBusMessage(this TesterBase tester, EventData @event) where TSelf : TesterBase { - if (@event == null) throw new ArgumentNullException(nameof(@event)); + @event.ThrowIfNull(nameof(@event)); var message = (tester.Services.GetService() ?? new EventDataToServiceBusConverter(tester.Services.GetService(), tester.Services.GetService>())).Convert(@event).GetRawAmqpMessage(); tester.ResetHost(false); return tester.CreateServiceBusMessage(message); @@ -544,7 +544,7 @@ public static ServiceBusReceivedMessage CreateServiceBusMessage(this Test /// The . public static ServiceBusReceivedMessage CreateServiceBusMessage(this TesterBase tester, EventData @event, Action? messageModify) where TSelf : TesterBase { - if (@event == null) throw new ArgumentNullException(nameof(@event)); + @event.ThrowIfNull(nameof(@event)); var message = (tester.Services.GetService() ?? new EventDataToServiceBusConverter(tester.Services.GetService(), tester.Services.GetService>())).Convert(@event).GetRawAmqpMessage(); tester.ResetHost(false); return tester.CreateServiceBusMessage(message, messageModify); diff --git a/src/CoreEx.Validation/Clauses/DependsOnClause.cs b/src/CoreEx.Validation/Clauses/DependsOnClause.cs index 2f24ac8c..e40a8c1f 100644 --- a/src/CoreEx.Validation/Clauses/DependsOnClause.cs +++ b/src/CoreEx.Validation/Clauses/DependsOnClause.cs @@ -11,16 +11,10 @@ namespace CoreEx.Validation.Clauses /// /// The entity . /// The property . - public class DependsOnClause : IPropertyRuleClause where TEntity : class + /// The to reference the depends on entity property. + public class DependsOnClause(Expression> dependsOnExpression) : IPropertyRuleClause where TEntity : class { - private readonly PropertyExpression _dependsOn; - - /// - /// Initializes a new instance of the class. - /// - /// The to reference the depends on entity property. - public DependsOnClause(Expression> dependsOnExpression) - => _dependsOn = PropertyExpression.Create(dependsOnExpression ?? throw new ArgumentNullException(nameof(dependsOnExpression))); + private readonly PropertyExpression _dependsOn = PropertyExpression.Create(dependsOnExpression.ThrowIfNull(nameof(dependsOnExpression))); /// /// Checks the clause. @@ -29,11 +23,8 @@ public DependsOnClause(Expression> dependsOnExpression) /// true where validation is to continue; otherwise, false to stop. public bool Check(IPropertyContext context) { - if (context == null) - throw new ArgumentNullException(nameof(context)); - // Do not continue where the depends on property is in error. - if (context.Parent.HasError(context.CreateFullyQualifiedPropertyName(_dependsOn.Name))) + if (context.ThrowIfNull(nameof(context)).Parent.HasError(context.CreateFullyQualifiedPropertyName(_dependsOn.Name))) return false; // Check depends on value to continue. diff --git a/src/CoreEx.Validation/Clauses/WhenClause.cs b/src/CoreEx.Validation/Clauses/WhenClause.cs index 86773398..5f8ebc03 100644 --- a/src/CoreEx.Validation/Clauses/WhenClause.cs +++ b/src/CoreEx.Validation/Clauses/WhenClause.cs @@ -19,19 +19,19 @@ public class WhenClause : IPropertyRuleClause class with a being passed the . /// /// The when predicate. - public WhenClause(Predicate predicate) => _entityPredicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + public WhenClause(Predicate predicate) => _entityPredicate = predicate.ThrowIfNull(nameof(predicate)); /// /// Initializes a new instance of the class with a function. /// /// The when function. - public WhenClause(Func when) => _when = when ?? throw new ArgumentNullException(nameof(when)); + public WhenClause(Func when) => _when = when.ThrowIfNull(nameof(when)); /// /// Initializes a new instance of the class with a being passed the . /// /// The when predicate. - public WhenClause(Predicate predicate) => _propertyPredicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + public WhenClause(Predicate predicate) => _propertyPredicate = predicate.ThrowIfNull(nameof(predicate)); /// /// Checks the clause. @@ -40,8 +40,7 @@ public class WhenClause : IPropertyRuleClausetrue where validation is to continue; otherwise, false to stop. public bool Check(IPropertyContext context) { - if (context == null) - throw new ArgumentNullException(nameof(context)); + context.ThrowIfNull(nameof(context)); return _when != null ? _when.Invoke() : _entityPredicate != null ? _entityPredicate.Invoke((TEntity)context.Parent.Value!) diff --git a/src/CoreEx.Validation/CollectionValidator.cs b/src/CoreEx.Validation/CollectionValidator.cs index cc0abc3b..aa3a23eb 100644 --- a/src/CoreEx.Validation/CollectionValidator.cs +++ b/src/CoreEx.Validation/CollectionValidator.cs @@ -67,8 +67,7 @@ public ICollectionRuleItem? Item /// public override Task> ValidateAsync(TColl? value, ValidationArgs? args = null, CancellationToken cancellationToken = default) { - if (value == null) - throw new ArgumentNullException(nameof(value)); + value.ThrowIfNull(nameof(value)); return ValidationInvoker.Current.InvokeAsync(this, async (_, cancellationToken) => { @@ -109,13 +108,13 @@ public override Task> ValidateAsync(TColl? value, Valid var text = new Lazy(() => Text ?? PropertyExpression.ConvertToSentenceCase(args?.FullyQualifiedEntityName) ?? PropertyExpression.ConvertToSentenceCase(Validation.ValueNameDefault)!); if (hasNullItem) - context.AddMessage(Entities.MessageType.Error, ValidatorStrings.CollectionNullItemFormat, new object?[] { text.Value, null }); + context.AddMessage(Entities.MessageType.Error, ValidatorStrings.CollectionNullItemFormat, [text.Value, null]); // Check the length/count. if (i < MinCount) - context.AddMessage(Entities.MessageType.Error, ValidatorStrings.MinCountFormat, new object?[] { text.Value, null, MinCount }); + context.AddMessage(Entities.MessageType.Error, ValidatorStrings.MinCountFormat, [text.Value, null, MinCount]); else if (MaxCount.HasValue && i > MaxCount.Value) - context.AddMessage(Entities.MessageType.Error, ValidatorStrings.MaxCountFormat, new object?[] { text.Value, null, MaxCount }); + context.AddMessage(Entities.MessageType.Error, ValidatorStrings.MaxCountFormat, [text.Value, null, MaxCount]); // Check for duplicates. if (!hasItemErrors && Item != null) @@ -153,7 +152,7 @@ public CollectionValidator AdditionalAsync(Func, T>> ValidateAsync(T? /// The . internal async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken) where TEntity : class { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - var vv = new ValidationValue(context.Parent.Value, context.Value); + var vv = new ValidationValue(context.ThrowIfNull(nameof(context)).Parent.Value, context.Value); var vc = new ValidationContext>(vv, new ValidationArgs { Config = context.Parent.Config, @@ -127,7 +124,7 @@ public CommonValidator AdditionalAsync(Func public override Task> ValidateAsync(TDict? value, ValidationArgs? args = null, CancellationToken cancellationToken = default) { - if (value == null) - throw new ArgumentNullException(nameof(value)); + value.ThrowIfNull(nameof(value)); return ValidationInvoker.Current.InvokeAsync(this, async (_, cancellationToken) => { @@ -133,16 +132,16 @@ public override Task> ValidateAsync(TDict? value, Valid var text = new Lazy(() => Text ?? PropertyExpression.ConvertToSentenceCase(args?.FullyQualifiedEntityName) ?? Validation.ValueNameDefault); if (hasNullKey) - context.AddMessage(Entities.MessageType.Error, ValidatorStrings.DictionaryNullKeyFormat, new object?[] { text.Value, null }); + context.AddMessage(Entities.MessageType.Error, ValidatorStrings.DictionaryNullKeyFormat, [text.Value, null]); if (hasNullValue) - context.AddMessage(Entities.MessageType.Error, ValidatorStrings.DictionaryNullValueFormat, new object?[] { text.Value, null }); + context.AddMessage(Entities.MessageType.Error, ValidatorStrings.DictionaryNullValueFormat, [text.Value, null]); // Check the length/count. if (i < MinCount) - context.AddMessage(Entities.MessageType.Error, ValidatorStrings.MinCountFormat, new object?[] { text.Value, null, MinCount }); + context.AddMessage(Entities.MessageType.Error, ValidatorStrings.MinCountFormat, [text.Value, null, MinCount]); else if (MaxCount.HasValue && i > MaxCount.Value) - context.AddMessage(Entities.MessageType.Error, ValidatorStrings.MaxCountFormat, new object?[] { text.Value, null, MaxCount }); + context.AddMessage(Entities.MessageType.Error, ValidatorStrings.MaxCountFormat, [text.Value, null, MaxCount]); if (context.FailureResult is not null) return context; @@ -174,7 +173,7 @@ public DictionaryValidator AdditionalAsync(Func : ValidatorBase, IPrope /// Initializes a new instance of the class. /// /// The base . - internal IncludeBaseRule(IValidatorEx include) => _include = include ?? throw new ArgumentNullException(nameof(include)); + internal IncludeBaseRule(IValidatorEx include) => _include = include.ThrowIfNull(nameof(include)); /// public async Task ValidateAsync(ValidationContext context, CancellationToken cancellationToken = default) { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - if (context.Value is not TInclude val) + if (context.ThrowIfNull(nameof(context)).Value is not TInclude val) throw new InvalidOperationException($"Type {typeof(TEntity).Name} must inherit from {typeof(TInclude).Name}."); var ctx = new ValidationContext(val, new ValidationArgs diff --git a/src/CoreEx.Validation/PropertyContext.cs b/src/CoreEx.Validation/PropertyContext.cs index 203ab7bd..24a9362b 100644 --- a/src/CoreEx.Validation/PropertyContext.cs +++ b/src/CoreEx.Validation/PropertyContext.cs @@ -28,8 +28,8 @@ public class PropertyContext : IPropertyContextThe property text. public PropertyContext(ValidationContext context, TProperty? value, string name, string? jsonName = null, LText? text = null) { - Parent = context ?? throw new ArgumentNullException(nameof(context)); - Name = string.IsNullOrEmpty(name) ? throw new ArgumentNullException(nameof(name)) : name; + Parent = context.ThrowIfNull(nameof(context)); + Name = name.ThrowIfNullOrEmpty(nameof(name)); JsonName = jsonName ?? Name; UseJsonName = context.UsedJsonNames; Text = text ?? Name.ToSentenceCase()!; @@ -46,7 +46,7 @@ public PropertyContext(ValidationContext context, TProperty? value, str /// The property value. public PropertyContext(LText? text, ValidationContext context, TProperty? value) { - Parent = context ?? throw new ArgumentNullException(nameof(context)); + Parent = context.ThrowIfNull(nameof(context)); FullyQualifiedPropertyName = Parent.FullyQualifiedEntityName ?? Validation.ValueNameDefault; FullyQualifiedJsonPropertyName = Parent.FullyQualifiedJsonEntityName ?? Validation.ValueNameDefault; Name = FullyQualifiedPropertyName.Split('.', StringSplitOptions.RemoveEmptyEntries).Last(); @@ -167,7 +167,7 @@ public void OverrideValue(TProperty? value) /// /// The composite format string. /// A . - public MessageItem CreateErrorMessage(LText format) => CreateErrorMessage(format, Array.Empty()); + public MessageItem CreateErrorMessage(LText format) => CreateErrorMessage(format, []); /// /// Creates a new with the specified format and additional values to be included in the text and adds to the underlying . diff --git a/src/CoreEx.Validation/PropertyRule.cs b/src/CoreEx.Validation/PropertyRule.cs index 6ad86464..1be4e0ac 100644 --- a/src/CoreEx.Validation/PropertyRule.cs +++ b/src/CoreEx.Validation/PropertyRule.cs @@ -33,10 +33,7 @@ public PropertyRule(Expression> propertyExpression) : t /// public async Task ValidateAsync(ValidationContext context, CancellationToken cancellationToken = default) { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - if (context.Value == null) + if (context.ThrowIfNull(nameof(context)).Value == null) return; // Where validating a specific property then make sure the names match. diff --git a/src/CoreEx.Validation/PropertyRuleBase.cs b/src/CoreEx.Validation/PropertyRuleBase.cs index db7468c2..17952432 100644 --- a/src/CoreEx.Validation/PropertyRuleBase.cs +++ b/src/CoreEx.Validation/PropertyRuleBase.cs @@ -18,8 +18,8 @@ namespace CoreEx.Validation /// The property . public abstract class PropertyRuleBase : IPropertyRule where TEntity : class { - private readonly List> _rules = new(); - private readonly List> _clauses = new(); + private readonly List> _rules = []; + private readonly List> _clauses = []; /// /// Initializes a new instance of the class. @@ -29,7 +29,7 @@ public abstract class PropertyRuleBase : IPropertyRuleThe JSON property name (defaults to ). protected PropertyRuleBase(string name, LText? text = null, string? jsonName = null) { - Name = string.IsNullOrEmpty(name) ? throw new ArgumentNullException(nameof(name)) : name; + Name = name.ThrowIfNullOrEmpty(nameof(name)); Text = text ?? Name.ToSentenceCase()!; JsonName = string.IsNullOrEmpty(jsonName) ? Name : jsonName; } @@ -79,8 +79,7 @@ public void AddClause(IPropertyRuleClause clause) /// The . protected async Task InvokeAsync(PropertyContext context, CancellationToken cancellationToken) { - if (context == null) - throw new ArgumentNullException(nameof(context)); + context.ThrowIfNull(nameof(context)); // Check all "this" clauses. foreach (var clause in _clauses) diff --git a/src/CoreEx.Validation/RuleSet.cs b/src/CoreEx.Validation/RuleSet.cs index dee6c233..3621f730 100644 --- a/src/CoreEx.Validation/RuleSet.cs +++ b/src/CoreEx.Validation/RuleSet.cs @@ -16,7 +16,7 @@ public class RuleSet : ValidatorBase, IPropertyRule w /// Initializes a new instance of the class to be invoked where the predicate is true. /// /// A function to determine whether the is to be validated. - internal RuleSet(Predicate> predicate) => Predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + internal RuleSet(Predicate> predicate) => Predicate = predicate.ThrowIfNull(nameof(predicate)); /// /// Gets the function to determine whether the is to be validated. diff --git a/src/CoreEx.Validation/Rules/BetweenRule.cs b/src/CoreEx.Validation/Rules/BetweenRule.cs index 4b35d058..eed7fbee 100644 --- a/src/CoreEx.Validation/Rules/BetweenRule.cs +++ b/src/CoreEx.Validation/Rules/BetweenRule.cs @@ -54,10 +54,10 @@ public BetweenRule(TProperty compareFromValue, TProperty compareToValue, LText? /// Indicates whether the between comparison is exclusive or inclusive (default). public BetweenRule(Func compareFromValueFunction, Func compareToValueFunction, Func? compareFromTextFunction = null, Func? compareToTextFunction = null, bool exclusiveBetween = false) { - _compareFromValueFunction = compareFromValueFunction ?? throw new ArgumentNullException(nameof(compareFromValueFunction)); + _compareFromValueFunction = compareFromValueFunction.ThrowIfNull(nameof(compareFromValueFunction)); _compareFromTextFunction = compareFromTextFunction; _compareFromValue = default!; - _compareToValueFunction = compareToValueFunction ?? throw new ArgumentNullException(nameof(compareToValueFunction)); + _compareToValueFunction = compareToValueFunction.ThrowIfNull(nameof(compareToValueFunction)); _compareToTextFunction = compareToTextFunction; _compareToValue = default!; _exclusiveBetween = exclusiveBetween; @@ -73,10 +73,10 @@ public BetweenRule(Func compareFromValueFunction, FuncIndicates whether the between comparison is exclusive or inclusive (default). public BetweenRule(Func> compareFromValueFunctionAsync, Func> compareToValueFunctionAsync, Func? compareFromTextFunction = null, Func? compareToTextFunction = null, bool exclusiveBetween = false) { - _compareFromValueFunctionAsync = compareFromValueFunctionAsync ?? throw new ArgumentNullException(nameof(compareFromValueFunctionAsync)); + _compareFromValueFunctionAsync = compareFromValueFunctionAsync.ThrowIfNull(nameof(compareFromValueFunctionAsync)); _compareFromTextFunction = compareFromTextFunction; _compareFromValue = default!; - _compareToValueFunctionAsync = compareToValueFunctionAsync ?? throw new ArgumentNullException(nameof(compareToValueFunctionAsync)); + _compareToValueFunctionAsync = compareToValueFunctionAsync.ThrowIfNull(nameof(compareToValueFunctionAsync)); _compareToTextFunction = compareToTextFunction; _compareToValue = default!; _exclusiveBetween = exclusiveBetween; diff --git a/src/CoreEx.Validation/Rules/CollectionRule.cs b/src/CoreEx.Validation/Rules/CollectionRule.cs index 2bc1cd0c..7f4d799f 100644 --- a/src/CoreEx.Validation/Rules/CollectionRule.cs +++ b/src/CoreEx.Validation/Rules/CollectionRule.cs @@ -65,13 +65,7 @@ public ICollectionRuleItem? Item /// /// The . /// true where validation is to continue; otherwise, false to stop. - protected override bool Check(PropertyContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - return !context.Parent.ShallowValidation && base.Check(context); - } + protected override bool Check(PropertyContext context) => !context.ThrowIfNull(nameof(context)).Parent.ShallowValidation && base.Check(context); /// protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/CollectionRuleItem.cs b/src/CoreEx.Validation/Rules/CollectionRuleItem.cs index 79734ebf..32703533 100644 --- a/src/CoreEx.Validation/Rules/CollectionRuleItem.cs +++ b/src/CoreEx.Validation/Rules/CollectionRuleItem.cs @@ -22,7 +22,7 @@ public static class CollectionRuleItem /// The item . /// The corresponding item . /// The . - public static CollectionRuleItem Create(IValidatorEx validator) => new(validator ?? throw new ArgumentNullException(nameof(validator))); + public static CollectionRuleItem Create(IValidatorEx validator) => new(validator.ThrowIfNull(nameof(validator))); /// /// Create an instance of the class leveraging the to get the instance. diff --git a/src/CoreEx.Validation/Rules/CommonRule.cs b/src/CoreEx.Validation/Rules/CommonRule.cs index f9bc25ea..df094595 100644 --- a/src/CoreEx.Validation/Rules/CommonRule.cs +++ b/src/CoreEx.Validation/Rules/CommonRule.cs @@ -11,15 +11,10 @@ namespace CoreEx.Validation.Rules /// /// The entity . /// The property . - internal class CommonRule : ValueRuleBase where TEntity : class + /// The . + internal class CommonRule(CommonValidator commonValidator) : ValueRuleBase where TEntity : class { - private readonly CommonValidator _commonValidator; - - /// - /// Initializes a new instance of the class specifying the corresponding . - /// - /// The . - public CommonRule(CommonValidator commonValidator) => _commonValidator = commonValidator ?? throw new ArgumentNullException(nameof(commonValidator)); + private readonly CommonValidator _commonValidator = commonValidator.ThrowIfNull(nameof(commonValidator)); /// protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/CompareRuleBase.cs b/src/CoreEx.Validation/Rules/CompareRuleBase.cs index 363ab32a..e41a3c22 100644 --- a/src/CoreEx.Validation/Rules/CompareRuleBase.cs +++ b/src/CoreEx.Validation/Rules/CompareRuleBase.cs @@ -53,8 +53,7 @@ public abstract class CompareRuleBase : ValueRuleBaseThe compare text to be passed for the error message. protected void CreateErrorMessage(PropertyContext context, LText compareToText) { - if (context == null) - throw new ArgumentNullException(nameof(context)); + context.ThrowIfNull(nameof(context)); switch (Operator) { diff --git a/src/CoreEx.Validation/Rules/CompareValueRule.cs b/src/CoreEx.Validation/Rules/CompareValueRule.cs index 950c2968..63e43fa5 100644 --- a/src/CoreEx.Validation/Rules/CompareValueRule.cs +++ b/src/CoreEx.Validation/Rules/CompareValueRule.cs @@ -40,7 +40,7 @@ public CompareValueRule(CompareOperator compareOperator, TProperty compareToValu /// The compare to text function (default is to use the result of the ). public CompareValueRule(CompareOperator compareOperator, Func compareToValueFunction, Func? compareToTextFunction = null) : base(compareOperator) { - _compareToValueFunction = compareToValueFunction ?? throw new ArgumentNullException(nameof(compareToValueFunction)); + _compareToValueFunction = compareToValueFunction.ThrowIfNull(nameof(compareToValueFunction)); _compareToTextFunction = compareToTextFunction; _compareToValue = default!; } @@ -53,7 +53,7 @@ public CompareValueRule(CompareOperator compareOperator, FuncThe compare to text function (default is to use the result of the ). public CompareValueRule(CompareOperator compareOperator, Func> compareToValueFunctionAsync, Func? compareToTextFunction = null) : base(compareOperator) { - _compareToValueFunctionAsync = compareToValueFunctionAsync ?? throw new ArgumentNullException(nameof(compareToValueFunctionAsync)); + _compareToValueFunctionAsync = compareToValueFunctionAsync.ThrowIfNull(nameof(compareToValueFunctionAsync)); _compareToTextFunction = compareToTextFunction; _compareToValue = default!; } @@ -61,8 +61,7 @@ public CompareValueRule(CompareOperator compareOperator, Func protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) { - if (context == null) - throw new ArgumentNullException(nameof(context)); + context.ThrowIfNull(nameof(context)); var compareToValue = _compareToValueFunction != null ? _compareToValueFunction(context.Parent.Value!) diff --git a/src/CoreEx.Validation/Rules/CompareValuesRule.cs b/src/CoreEx.Validation/Rules/CompareValuesRule.cs index 9c052d09..74b716e5 100644 --- a/src/CoreEx.Validation/Rules/CompareValuesRule.cs +++ b/src/CoreEx.Validation/Rules/CompareValuesRule.cs @@ -28,14 +28,14 @@ public class CompareValuesRule : ValueRuleBase /// The compare to values. public CompareValuesRule(IEnumerable compareToValues) : this() - => _compareToValues = compareToValues ?? throw new ArgumentNullException(nameof(compareToValues)); + => _compareToValues = compareToValues.ThrowIfNull(nameof(compareToValues)); /// /// Initializes a new instance of the class specifying the compare to values async function (as an ). /// /// The compare to values function. public CompareValuesRule(Func>> compareToValuesFunctionAsync) : this() - => _compareToValuesFunctionAsync = compareToValuesFunctionAsync ?? throw new ArgumentNullException(nameof(compareToValuesFunctionAsync)); + => _compareToValuesFunctionAsync = compareToValuesFunctionAsync.ThrowIfNull(nameof(compareToValuesFunctionAsync)); /// /// Gets or sets the . @@ -50,8 +50,7 @@ public CompareValuesRule(Func protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) { - if (context == null) - throw new ArgumentNullException(nameof(context)); + context.ThrowIfNull(nameof(context)); // Perform the comparison. var values = _compareToValues != null ? _compareToValues! : await _compareToValuesFunctionAsync!(context.Parent.Value!, cancellationToken).ConfigureAwait(false); diff --git a/src/CoreEx.Validation/Rules/CustomRule.cs b/src/CoreEx.Validation/Rules/CustomRule.cs index 80cfffb4..bf1ebeee 100644 --- a/src/CoreEx.Validation/Rules/CustomRule.cs +++ b/src/CoreEx.Validation/Rules/CustomRule.cs @@ -21,13 +21,13 @@ public class CustomRule : ValueRuleBase /// Initializes a new instance of the class specifying the corresponding . /// /// The function to invoke to perform the custom validation. - public CustomRule(Func, Result> custom) => _custom = custom ?? throw new ArgumentNullException(nameof(custom)); + public CustomRule(Func, Result> custom) => _custom = custom.ThrowIfNull(nameof(custom)); /// /// Initializes a new instance of the class specifying the corresponding . /// /// The function to invoke to perform the custom validation. - public CustomRule(Func, CancellationToken, Task> customAsync) => _customAsync = customAsync ?? throw new ArgumentNullException(nameof(customAsync)); + public CustomRule(Func, CancellationToken, Task> customAsync) => _customAsync = customAsync.ThrowIfNull(nameof(customAsync)); /// /// Validate the property value. diff --git a/src/CoreEx.Validation/Rules/DictionaryRule.cs b/src/CoreEx.Validation/Rules/DictionaryRule.cs index cde342b5..0012a65a 100644 --- a/src/CoreEx.Validation/Rules/DictionaryRule.cs +++ b/src/CoreEx.Validation/Rules/DictionaryRule.cs @@ -79,13 +79,7 @@ public IDictionaryRuleItem? Item /// /// The . /// true where validation is to continue; otherwise, false to stop. - protected override bool Check(PropertyContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - return !context.Parent.ShallowValidation && base.Check(context); - } + protected override bool Check(PropertyContext context) => !context.ThrowIfNull(nameof(context)).Parent.ShallowValidation && base.Check(context); /// protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/DuplicateRule.cs b/src/CoreEx.Validation/Rules/DuplicateRule.cs index e08dd038..ca787b63 100644 --- a/src/CoreEx.Validation/Rules/DuplicateRule.cs +++ b/src/CoreEx.Validation/Rules/DuplicateRule.cs @@ -20,13 +20,13 @@ public class DuplicateRule : ValueRuleBase class with a . /// /// The must predicate. - public DuplicateRule(Predicate predicate) => _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + public DuplicateRule(Predicate predicate) => _predicate = predicate.ThrowIfNull(nameof(predicate)); /// /// Initializes a new instance of the class with a function. /// /// The duplicate function. - public DuplicateRule(Func duplicate) => _duplicate = duplicate ?? throw new ArgumentNullException(nameof(duplicate)); + public DuplicateRule(Func duplicate) => _duplicate = duplicate.ThrowIfNull(nameof(duplicate)); /// protected override Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/EntityRule.cs b/src/CoreEx.Validation/Rules/EntityRule.cs index d6e54ab1..b70fa71d 100644 --- a/src/CoreEx.Validation/Rules/EntityRule.cs +++ b/src/CoreEx.Validation/Rules/EntityRule.cs @@ -12,31 +12,20 @@ namespace CoreEx.Validation.Rules /// The entity . /// The property . /// The property validator . - public class EntityRule : ValueRuleBase where TEntity : class where TProperty : class? where TValidator : IValidatorEx + /// The . + public class EntityRule(TValidator validator) : ValueRuleBase where TEntity : class where TProperty : class? where TValidator : IValidatorEx { - /// - /// Initializes a new instance of the class. - /// - /// The . - public EntityRule(TValidator validator) => Validator = validator ?? throw new ArgumentNullException(nameof(validator)); - /// /// Gets the . /// - public TValidator Validator { get; private set; } + public TValidator Validator { get; private set; } = validator.ThrowIfNull(nameof(validator)); /// /// Overrides the Check method and will not validate where performing a shallow validation. /// /// The . /// true where validation is to continue; otherwise, false to stop. - protected override bool Check(PropertyContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - return !context.Parent.ShallowValidation && base.Check(context); - } + protected override bool Check(PropertyContext context) => !context.ThrowIfNull(nameof(context)).Parent.ShallowValidation && base.Check(context); /// protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/EntityRuleWith.cs b/src/CoreEx.Validation/Rules/EntityRuleWith.cs index c7730393..02f1e36d 100644 --- a/src/CoreEx.Validation/Rules/EntityRuleWith.cs +++ b/src/CoreEx.Validation/Rules/EntityRuleWith.cs @@ -9,15 +9,10 @@ namespace CoreEx.Validation.Rules /// /// The entity . /// The property . - public class EntityRuleWith where TEntity : class where TProperty : class? + /// The parent . + public class EntityRuleWith(IPropertyRule parent) where TEntity : class where TProperty : class? { - private readonly IPropertyRule _parent; - - /// - /// Initializes a new instance of the class. - /// - /// The parent . - public EntityRuleWith(IPropertyRule parent) => _parent = parent ?? throw new ArgumentNullException(nameof(parent)); + private readonly IPropertyRule _parent = parent.ThrowIfNull(nameof(parent)); /// /// Adds an using a validator a specified . diff --git a/src/CoreEx.Validation/Rules/EnumValueRuleAs.cs b/src/CoreEx.Validation/Rules/EnumValueRuleAs.cs index bb5d0c9a..c85052d6 100644 --- a/src/CoreEx.Validation/Rules/EnumValueRuleAs.cs +++ b/src/CoreEx.Validation/Rules/EnumValueRuleAs.cs @@ -9,15 +9,10 @@ namespace CoreEx.Validation.Rules /// Provides a means to add an a specified . /// /// The entity . - public class EnumValueRuleAs where TEntity : class + /// The parent . + public class EnumValueRuleAs(IPropertyRule parent) where TEntity : class { - private readonly IPropertyRule _parent; - - /// - /// Initializes a new instance of the class. - /// - /// The parent . - public EnumValueRuleAs(IPropertyRule parent) => _parent = parent ?? throw new ArgumentNullException(nameof(parent)); + private readonly IPropertyRule _parent = parent.ThrowIfNull(nameof(parent)); /// /// Adds an using a validator a specified . diff --git a/src/CoreEx.Validation/Rules/ExistsRule.cs b/src/CoreEx.Validation/Rules/ExistsRule.cs index 69f46ff4..44093cb9 100644 --- a/src/CoreEx.Validation/Rules/ExistsRule.cs +++ b/src/CoreEx.Validation/Rules/ExistsRule.cs @@ -25,19 +25,19 @@ public class ExistsRule : ValueRuleBase /// Initializes a new instance of the class with a . /// /// The must predicate. - public ExistsRule(Predicate predicate) => _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + public ExistsRule(Predicate predicate) => _predicate = predicate.ThrowIfNull(nameof(predicate)); /// /// Initializes a new instance of the class with an function that must return true. /// /// The exists function. - public ExistsRule(Func> exists) => _exists = exists ?? throw new ArgumentNullException(nameof(exists)); + public ExistsRule(Func> exists) => _exists = exists.ThrowIfNull(nameof(exists)); /// /// Initializes a new instance of the class with an function that must return a value. /// /// The exists function. - public ExistsRule(Func> exists) => _existsNotNull = exists ?? throw new ArgumentNullException(nameof(exists)); + public ExistsRule(Func> exists) => _existsNotNull = exists.ThrowIfNull(nameof(exists)); /// /// Initializes a new instance of the class with an function that must return a successful . @@ -46,7 +46,7 @@ public class ExistsRule : ValueRuleBase /// A result of implies exists, whilst a of does not. /// Any other status code will result in the underlying being invoked resulting in an /// appropriate exception being thrown. - public ExistsRule(Func> httpResult) => _httpResult = httpResult ?? throw new ArgumentNullException(nameof(httpResult)); + public ExistsRule(Func> httpResult) => _httpResult = httpResult.ThrowIfNull(nameof(httpResult)); /// protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/ImmutableRule.cs b/src/CoreEx.Validation/Rules/ImmutableRule.cs index a114cee8..66656e13 100644 --- a/src/CoreEx.Validation/Rules/ImmutableRule.cs +++ b/src/CoreEx.Validation/Rules/ImmutableRule.cs @@ -21,19 +21,19 @@ public class ImmutableRule : ValueRuleBase class with a . /// /// The must predicate. - public ImmutableRule(Predicate predicate) => _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + public ImmutableRule(Predicate predicate) => _predicate = predicate.ThrowIfNull(nameof(predicate)); /// /// Initializes a new instance of the class with an function. /// /// The immutable function. - public ImmutableRule(Func immutable) => _immutable = immutable ?? throw new ArgumentNullException(nameof(immutable)); + public ImmutableRule(Func immutable) => _immutable = immutable.ThrowIfNull(nameof(immutable)); /// /// Initializes a new instance of the class with an function. /// /// The immutable function. - public ImmutableRule(Func> immutableAsync) => _immutableAsync = immutableAsync ?? throw new ArgumentNullException(nameof(immutableAsync)); + public ImmutableRule(Func> immutableAsync) => _immutableAsync = immutableAsync.ThrowIfNull(nameof(immutableAsync)); /// protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/InteropRule.cs b/src/CoreEx.Validation/Rules/InteropRule.cs index 75039527..306f750f 100644 --- a/src/CoreEx.Validation/Rules/InteropRule.cs +++ b/src/CoreEx.Validation/Rules/InteropRule.cs @@ -20,7 +20,7 @@ public class InteropRule : ValueRuleBaseThe function to return the . public InteropRule(Func validatorFunc) { - ValidatorFunc = validatorFunc ?? throw new ArgumentNullException(nameof(validatorFunc)); + ValidatorFunc = validatorFunc.ThrowIfNull(nameof(validatorFunc)); if (validatorFunc is IValidatorEx) throw new ArgumentException($"{ValidatorFunc.GetType().Name} implements {typeof(IValidatorEx).Name} and as such must use {typeof(EntityRule<,,>).Name}.", nameof(validatorFunc)); } @@ -35,13 +35,7 @@ public InteropRule(Func validatorFunc) /// /// The . /// true where validation is to continue; otherwise, false to stop. - protected override bool Check(PropertyContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - return !context.Parent.ShallowValidation && base.Check(context); - } + protected override bool Check(PropertyContext context) => !context.ThrowIfNull(nameof(context)).Parent.ShallowValidation && base.Check(context); /// protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/MustRule.cs b/src/CoreEx.Validation/Rules/MustRule.cs index 177bdf3c..7ef54559 100644 --- a/src/CoreEx.Validation/Rules/MustRule.cs +++ b/src/CoreEx.Validation/Rules/MustRule.cs @@ -21,19 +21,19 @@ public class MustRule : ValueRuleBase wh /// Initializes a new instance of the class with a . /// /// The must predicate. - public MustRule(Predicate predicate) => _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + public MustRule(Predicate predicate) => _predicate = predicate.ThrowIfNull(nameof(predicate)); /// /// Initializes a new instance of the class with a function. /// /// The must function. - public MustRule(Func must) => _must = must ?? throw new ArgumentNullException(nameof(must)); + public MustRule(Func must) => _must = must.ThrowIfNull(nameof(must)); /// /// Initializes a new instance of the class with a function. /// /// The must function. - public MustRule(Func> mustAsync) => _mustAsync = mustAsync ?? throw new ArgumentNullException(nameof(mustAsync)); + public MustRule(Func> mustAsync) => _mustAsync = mustAsync.ThrowIfNull(nameof(mustAsync)); /// protected override async Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/OverrideRule.cs b/src/CoreEx.Validation/Rules/OverrideRule.cs index 80faeffd..0b2b9ebe 100644 --- a/src/CoreEx.Validation/Rules/OverrideRule.cs +++ b/src/CoreEx.Validation/Rules/OverrideRule.cs @@ -22,13 +22,13 @@ public class OverrideRule : ValueRuleBase class with a . /// /// The override function. - public OverrideRule(Func func) => _func = func ?? throw new ArgumentNullException(nameof(func)); + public OverrideRule(Func func) => _func = func.ThrowIfNull(nameof(func)); /// /// Initializes a new instance of the class with a . /// /// The override function. - public OverrideRule(Func> funcAsync) => _funcAsync = funcAsync ?? throw new ArgumentNullException(nameof(funcAsync)); + public OverrideRule(Func> funcAsync) => _funcAsync = funcAsync.ThrowIfNull(nameof(funcAsync)); /// /// Initializes a new instance of the class with a . diff --git a/src/CoreEx.Validation/Rules/ReferenceDataCodeRuleAs.cs b/src/CoreEx.Validation/Rules/ReferenceDataCodeRuleAs.cs index a513b636..1ee5abc6 100644 --- a/src/CoreEx.Validation/Rules/ReferenceDataCodeRuleAs.cs +++ b/src/CoreEx.Validation/Rules/ReferenceDataCodeRuleAs.cs @@ -10,21 +10,12 @@ namespace CoreEx.Validation.Rules /// Provides a means to add an using a validator a specified . /// /// The entity . - public class ReferenceDataCodeRuleAs where TEntity : class + /// The parent . + /// The error message format text (overrides the default). + public class ReferenceDataCodeRuleAs(IPropertyRule parent, LText? errorText = null) where TEntity : class { - private readonly IPropertyRule _parent; - private readonly LText? _errorText; - - /// - /// Initializes a new instance of the class. - /// - /// The parent . - /// The error message format text (overrides the default). - public ReferenceDataCodeRuleAs(IPropertyRule parent, LText? errorText = null) - { - _parent = parent ?? throw new ArgumentNullException(nameof(parent)); - _errorText = errorText; - } + private readonly IPropertyRule _parent = parent.ThrowIfNull(nameof(parent)); + private readonly LText? _errorText = errorText; /// /// Adds an using a validator a specified . diff --git a/src/CoreEx.Validation/ValidationContext.cs b/src/CoreEx.Validation/ValidationContext.cs index 30390926..e46ca72f 100644 --- a/src/CoreEx.Validation/ValidationContext.cs +++ b/src/CoreEx.Validation/ValidationContext.cs @@ -28,8 +28,7 @@ public class ValidationContext : IValidationContext, IValidationResult< /// Optional override. public ValidationContext(TEntity value, ValidationArgs args, string? fullyQualifiedEntityNameOverride = null, string? fullyQualifiedJsonEntityNameOverride = null) { - if (args == null) - throw new ArgumentNullException(nameof(args)); + args.ThrowIfNull(nameof(args)); Value = value; FullyQualifiedEntityName = fullyQualifiedEntityNameOverride ?? args.FullyQualifiedEntityName; @@ -411,8 +410,7 @@ public MessageItem AddMessage(MessageType type, LText format, params object?[] v public bool Check(Expression> propertyExpression, Func predicate, LText text) { var pe = CreatePropertyExpression(propertyExpression); - if (predicate == null) - throw new ArgumentNullException(nameof(predicate)); + predicate.ThrowIfNull(nameof(predicate)); if (HasError(pe)) return true; @@ -461,8 +459,7 @@ public bool Check(Expression> propertyExpres public bool Check(Expression> propertyExpression, Func predicate, LText format, params object[] values) { var pe = CreatePropertyExpression(propertyExpression); - if (predicate == null) - throw new ArgumentNullException(nameof(predicate)); + predicate.ThrowIfNull(nameof(predicate)); if (HasError(pe)) return true; @@ -519,16 +516,14 @@ private string CreateFullyQualifiedName(Expression private string CreateFullyQualifiedName(PropertyExpression propertyExpression) - => CreateFullyQualifiedName((propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression))).Name, propertyExpression.JsonName); + => CreateFullyQualifiedName(propertyExpression.ThrowIfNull(nameof(propertyExpression)).Name, propertyExpression.JsonName); /// /// Creates the fully qualified name using the property and json property names. /// private string CreateFullyQualifiedName(string propertyName, string? jsonPropertyName) { - if (propertyName == null) - throw new ArgumentNullException(nameof(propertyName)); - + propertyName.ThrowIfNullOrEmpty(nameof(propertyName)); return UsedJsonNames ? CreateFullyQualifiedJsonPropertyName(jsonPropertyName ?? propertyName) : CreateFullyQualifiedPropertyName(propertyName); } diff --git a/src/CoreEx.Validation/ValidationExtensions.cs b/src/CoreEx.Validation/ValidationExtensions.cs index b8941063..7c6bbaf6 100644 --- a/src/CoreEx.Validation/ValidationExtensions.cs +++ b/src/CoreEx.Validation/ValidationExtensions.cs @@ -876,7 +876,7 @@ public static IPropertyRule IsValid(this /// The error message format text (overrides the default). /// A . public static ReferenceDataCodeRuleAs RefDataCode(this IPropertyRule rule, LText? errorText = null) where TEntity : class - => new(rule ?? throw new ArgumentNullException(nameof(rule)), errorText); + => new(rule.ThrowIfNull(nameof(rule)), errorText); #endregion @@ -962,7 +962,7 @@ public static EntityRuleWith Entity(this /// A . /// This is only intended to be leveraged for the root entity value being validated as no are passed meaning advanced capabilities will be ignored. public static IPropertyRule Interop(this IPropertyRule rule, TValidator validator) where TEntity : class where TProperty : class? where TValidator : IValidator - => rule.ThrowIfNull(nameof(rule)).AddRule(new InteropRule(() => validator ?? throw new ArgumentNullException(nameof(validator)))); + => rule.ThrowIfNull(nameof(rule)).AddRule(new InteropRule(() => validator.ThrowIfNull(nameof(validator)))); /// /// Adds an interop validation (see ) (intended for non-CoreEx.Validation). @@ -1127,13 +1127,8 @@ public static IPropertyRule Default(this /// The (this) . public static MultiValidator Add(this MultiValidator multiValidator, ValueValidator validator) { - if (multiValidator == null) - throw new ArgumentNullException(nameof(multiValidator)); - - if (validator == null) - throw new ArgumentNullException(nameof(validator)); - - (multiValidator ?? throw new ArgumentNullException(nameof(multiValidator))).Validators.Add(async ct => await validator.ValidateAsync(ct).ConfigureAwait(false)); + validator.ThrowIfNull(nameof(validator)); + multiValidator.ThrowIfNull(nameof(multiValidator)).Validators.Add(async ct => await validator.ValidateAsync(ct).ConfigureAwait(false)); return multiValidator; } @@ -1146,13 +1141,8 @@ public static MultiValidator Add(this MultiValidator multiValidator, ValueVal /// The (this) . public static MultiValidator Add(this MultiValidator multiValidator, IPropertyRule, T> validator) { - if (multiValidator == null) - throw new ArgumentNullException(nameof(multiValidator)); - - if (validator == null) - throw new ArgumentNullException(nameof(validator)); - - (multiValidator ?? throw new ArgumentNullException(nameof(multiValidator))).Validators.Add(validator.ValidateAsync); + validator.ThrowIfNull(nameof(validator)); + multiValidator.ThrowIfNull(nameof(multiValidator)).Validators.Add(validator.ValidateAsync); return multiValidator; } @@ -1173,7 +1163,7 @@ public static MultiValidator Add(this MultiValidator multiValidator, IPropert /// Where the corresponding will be updated with the . public static async Task> ValidateAsync(this Result result, Func, TEntity?>, IPropertyRule, TEntity?>> validator, string? name = default, LText? text = default, CancellationToken cancellationToken = default) { - if (validator == null) throw new ArgumentNullException(nameof(validator)); + validator.ThrowIfNull(nameof(validator)); return await result.ThenAsync(async v => { @@ -1196,7 +1186,7 @@ public static async Task> ValidateAsync(this ResultWhere the corresponding will be updated with the . public static async Task> ValidateAsync(this Task> result, Func, TEntity?>, IPropertyRule, TEntity?>> validator, string? name = default, LText? text = default, CancellationToken cancellationToken = default) { - if (validator == null) throw new ArgumentNullException(nameof(validator)); + validator.ThrowIfNull(nameof(validator)); return await result.ThenAsync(async v => { diff --git a/src/CoreEx.Validation/ValidationServiceCollectionExtensions.cs b/src/CoreEx.Validation/ValidationServiceCollectionExtensions.cs index b62cead0..56564a39 100644 --- a/src/CoreEx.Validation/ValidationServiceCollectionExtensions.cs +++ b/src/CoreEx.Validation/ValidationServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx +using CoreEx; using CoreEx.Localization; using CoreEx.Validation; using System; @@ -26,7 +27,7 @@ public static IServiceCollection AddValidator(this IServiceCollec /// Adds the , the , and as scoped services. /// private static IServiceCollection AddValidatorWithInterfacesInternal(this IServiceCollection services) where TValidator : class, IValidatorEx - => (services ?? throw new ArgumentNullException(nameof(services))) + => services.ThrowIfNull(nameof(services)) .AddScoped, TValidator>() .AddScoped>(sp => sp.GetRequiredService>()) .AddScoped(sp => (TValidator)sp.GetRequiredService>()); @@ -45,7 +46,7 @@ public static IServiceCollection AddValidator(this IServiceCollectio /// Adds the as a scoped service only. /// private static IServiceCollection AddValidatorInternal(this IServiceCollection services) where TValidator : class, IValidatorEx - => (services ?? throw new ArgumentNullException(nameof(services))).AddScoped(); + => services.ThrowIfNull(nameof(services)).AddScoped(); /// /// Adds all the validators from the specified as scoped services. @@ -85,6 +86,6 @@ public static IServiceCollection AddValidators(this IServiceCollectio /// The . /// The for fluent-style method-chaining. public static IServiceCollection AddValidationTextProvider(this IServiceCollection services) - => (services ?? throw new ArgumentNullException(nameof(services))).AddSingleton(); + => services.ThrowIfNull(nameof(services)).AddSingleton(); } } \ No newline at end of file diff --git a/src/CoreEx.Validation/ValidatorT.cs b/src/CoreEx.Validation/ValidatorT.cs index 2c681e30..99f9346c 100644 --- a/src/CoreEx.Validation/ValidatorT.cs +++ b/src/CoreEx.Validation/ValidatorT.cs @@ -100,11 +100,10 @@ public Validator HasProperty(ExpressionThe . public Validator IncludeBase(IValidatorEx include) where TInclude : class { - if (include == null) - throw new ArgumentNullException(nameof(include)); + include.ThrowIfNull(nameof(include)); if (!typeof(TEntity).GetTypeInfo().IsSubclassOf(typeof(TInclude))) - throw new ArgumentException($"Type {typeof(TEntity).Name} must inherit from {typeof(TInclude).Name}."); + throw new ArgumentException($"Type {typeof(TEntity).Name} must inherit from {typeof(TInclude).Name}.", nameof(include)); if (_currentRuleSet == null) base.Rules.Add(new IncludeBaseRule(include)); @@ -131,7 +130,7 @@ public Validator AdditionalAsync(Func, Cance if (_additionalAsync != null) throw new InvalidOperationException("Additional can only be defined once for a Validator."); - _additionalAsync = additionalAsync ?? throw new ArgumentNullException(nameof(additionalAsync)); + _additionalAsync = additionalAsync.ThrowIfNull(nameof(additionalAsync)); return this; } @@ -143,11 +142,8 @@ public Validator AdditionalAsync(Func, Cance /// The . public RuleSet RuleSet(Predicate> predicate, Action action) { - if (predicate == null) - throw new ArgumentNullException(nameof(predicate)); - - if (action == null) - throw new ArgumentNullException(nameof(action)); + predicate.ThrowIfNull(nameof(predicate)); + action.ThrowIfNull(nameof(action)); return SetRuleSet(new RuleSet(predicate), (v) => action()); } @@ -160,11 +156,8 @@ public RuleSet RuleSet(Predicate> predicate, /// The . public Validator HasRuleSet(Predicate> predicate, Action> action) { - if (predicate == null) - throw new ArgumentNullException(nameof(predicate)); - - if (action == null) - throw new ArgumentNullException(nameof(action)); + predicate.ThrowIfNull(nameof(predicate)); + action.ThrowIfNull(nameof(action)); SetRuleSet(new RuleSet(predicate), action); return this; diff --git a/src/CoreEx.Validation/ValueValidatorResult.cs b/src/CoreEx.Validation/ValueValidatorResult.cs index 9e760ab4..750ae869 100644 --- a/src/CoreEx.Validation/ValueValidatorResult.cs +++ b/src/CoreEx.Validation/ValueValidatorResult.cs @@ -18,15 +18,10 @@ public interface IValueValidatorResult : IValidationResu /// /// The entity . /// The property . - public sealed class ValueValidatorResult : IValueValidatorResult where TEntity : class + /// The . + public sealed class ValueValidatorResult(PropertyContext context) : IValueValidatorResult where TEntity : class { - private readonly PropertyContext _context; - - /// - /// Initializes a new instance of the class. - /// - /// The . - public ValueValidatorResult(PropertyContext context) => _context = context ?? throw new ArgumentNullException(nameof(context)); + private readonly PropertyContext _context = context.ThrowIfNull(nameof(context)); /// public TProperty? Value => _context.Value; diff --git a/src/CoreEx/Hosting/Work/WorkStateOrchestrator.cs b/src/CoreEx/Hosting/Work/WorkStateOrchestrator.cs index 2776fda2..54323205 100644 --- a/src/CoreEx/Hosting/Work/WorkStateOrchestrator.cs +++ b/src/CoreEx/Hosting/Work/WorkStateOrchestrator.cs @@ -346,7 +346,7 @@ public async Task SetDataAsync(string id, BinaryData data, CancellationToken can /// Will automatically set the to when the work is not and has expired (see ). public async Task GetAsync(string type, string id, CancellationToken cancellationToken = default) { - var ws = await Persistence.GetAsync(id.ThrowIfNullOrEmpty(nameof(id)), cancellationToken).ConfigureAwait(false); + var ws = await GetAsync(id, cancellationToken).ConfigureAwait(false); return ws is null || ws.TypeName != type ? null : ws; } diff --git a/src/CoreEx/Invokers/InvokeArgs.cs b/src/CoreEx/Invokers/InvokeArgs.cs index a050d6f0..a96c9259 100644 --- a/src/CoreEx/Invokers/InvokeArgs.cs +++ b/src/CoreEx/Invokers/InvokeArgs.cs @@ -89,6 +89,7 @@ private static bool IsTracingEnabled(Type invokerType) /// The result . /// The result value. /// The . + /// Where the is a then the underlying or will be recorded accordingly. public TResult TraceResult(TResult result) { if (Activity is not null) @@ -105,9 +106,24 @@ public TResult TraceResult(TResult result) } /// - /// Completes the (where started). + /// Completes the tracing (where started) recording the with the and capturing the corresponding . /// - public readonly void Complete() + /// The . + public void TraceException(Exception ex) + { + if (Activity is not null && ex is not null) + { + Activity.SetTag(InvokerResult, ExceptionState); + Activity.SetTag(InvokerFailure, $"{ex.Message} [{ex.GetType().Name}]"); + _isComplete = true; + } + } + + /// + /// Completes (stops) the tracing (where started). + /// + /// Where not previously recorded as complete will set the to . + public readonly void TraceComplete() { if (Activity is not null) { diff --git a/src/CoreEx/Invokers/InvokerBaseT.cs b/src/CoreEx/Invokers/InvokerBaseT.cs index 9b518f38..1417a060 100644 --- a/src/CoreEx/Invokers/InvokerBaseT.cs +++ b/src/CoreEx/Invokers/InvokerBaseT.cs @@ -46,9 +46,14 @@ private TResult TraceOnInvoke(TInvoker invoker, Func TraceOnInvokeAsync(TInvoker invoker, Func(TInvoker invoker, Func TraceOnInvokeAsync(TInvoker invoker, Func - /// Represents an encoded to converter (uses and for encoding; - /// and and for underlying value.). + /// Represents an encoded to converter. /// public readonly struct EncodedStringToDateTimeConverter : IConverter { diff --git a/src/CoreEx/Mapping/Converters/EncodedStringToUInt32Converter.cs b/src/CoreEx/Mapping/Converters/EncodedStringToUInt32Converter.cs new file mode 100644 index 00000000..d4bf2877 --- /dev/null +++ b/src/CoreEx/Mapping/Converters/EncodedStringToUInt32Converter.cs @@ -0,0 +1,35 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx + +using System; + +namespace CoreEx.Mapping.Converters +{ + /// + /// Represents an encoded to converter. + /// + public readonly struct EncodedStringToUInt32Converter : IConverter + { + private static readonly ValueConverter _convertToDestination = new(s => s == null ? 0 : BitConverter.ToUInt32(Convert.FromBase64String(s))); + private static readonly ValueConverter _convertToSource = new(d => d == 0 ? null : Convert.ToBase64String(BitConverter.GetBytes(d))); + + /// + /// Gets or sets the default (singleton) instance. + /// + public static EncodedStringToUInt32Converter Default { get; set; } = new(); + + /// + /// Initializes a new instance of the struct. + /// + public EncodedStringToUInt32Converter() { } + + /// + /// Gets the source to destination . + /// + public IValueConverter ToDestination => _convertToDestination; + + /// + /// Gets the destination to source . + /// + public IValueConverter ToSource => _convertToSource; + } +} \ No newline at end of file diff --git a/src/CoreEx/RefData/ReferenceDataOrchestrator.cs b/src/CoreEx/RefData/ReferenceDataOrchestrator.cs index 69d08ff0..9990a68d 100644 --- a/src/CoreEx/RefData/ReferenceDataOrchestrator.cs +++ b/src/CoreEx/RefData/ReferenceDataOrchestrator.cs @@ -320,9 +320,14 @@ private async Task GetByTypeInternalAsync(ReferenceDat return ria.TraceResult(coll); } + catch (Exception ex) + { + ria.TraceException(ex); + throw; + } finally { - ria.Complete(); + ria.TraceComplete(); } } diff --git a/tests/CoreEx.Test/Framework/Mapping/Converters/EncodedStringToUInt32ConverterTest.cs b/tests/CoreEx.Test/Framework/Mapping/Converters/EncodedStringToUInt32ConverterTest.cs new file mode 100644 index 00000000..072f9b9b --- /dev/null +++ b/tests/CoreEx.Test/Framework/Mapping/Converters/EncodedStringToUInt32ConverterTest.cs @@ -0,0 +1,24 @@ +using CoreEx.Mapping.Converters; +using NUnit.Framework; + +namespace CoreEx.Test.Framework.Mapping.Converters +{ + [TestFixture] + public class EncodedStringToUInt32ConverterTest + { + [Test] + public void ConvertToSource() + { + uint i = 4020; + var val = EncodedStringToUInt32Converter.Default.ToSource.Convert(i); + Assert.That(val, Is.EqualTo("tA8AAA==")); + } + + [Test] + public void ConvertToDestination() + { + var i = EncodedStringToUInt32Converter.Default.ToDestination.Convert("tA8AAA=="); + Assert.That(i, Is.EqualTo(4020)); + } + } +} \ No newline at end of file diff --git a/tests/CoreEx.Test2/TestFunctionIso/HttpFunctionTest.cs b/tests/CoreEx.Test2/TestFunctionIso/HttpFunctionTest.cs index 23b25789..fbf48b78 100644 --- a/tests/CoreEx.Test2/TestFunctionIso/HttpFunctionTest.cs +++ b/tests/CoreEx.Test2/TestFunctionIso/HttpFunctionTest.cs @@ -1,7 +1,7 @@ using CoreEx.TestFunctionIso; using UnitTestEx.NUnit; -namespace CoreEx.Test.TestFunctionIso +namespace CoreEx.Test2.TestFunctionIso { [TestFixture] public class HttpFunctionTest diff --git a/tests/CoreEx.TestFunctionIso/CoreEx.TestFunctionIso.csproj b/tests/CoreEx.TestFunctionIso/CoreEx.TestFunctionIso.csproj index ce59c542..c8ac548b 100644 --- a/tests/CoreEx.TestFunctionIso/CoreEx.TestFunctionIso.csproj +++ b/tests/CoreEx.TestFunctionIso/CoreEx.TestFunctionIso.csproj @@ -8,6 +8,7 @@ + @@ -15,6 +16,7 @@ + diff --git a/tests/CoreEx.TestFunctionIso/Program.cs b/tests/CoreEx.TestFunctionIso/Program.cs index 0ad7e14d..613cb31d 100644 --- a/tests/CoreEx.TestFunctionIso/Program.cs +++ b/tests/CoreEx.TestFunctionIso/Program.cs @@ -1,17 +1,22 @@ +//using Azure.Monitor.OpenTelemetry.AspNetCore; using CoreEx.Hosting; using CoreEx.TestFunctionIso; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using OpenTelemetry.Trace; +using Azure.Monitor.OpenTelemetry.Exporter; -var host = new HostBuilder() - .ConfigureHostStartup() +new HostBuilder() .ConfigureFunctionsWebApplication() - .ConfigureServices(services => + .ConfigureServices(services => { services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); + services.AddOpenTelemetry().WithTracing(b => b.AddSource("CoreEx.*").AddAzureMonitorTraceExporter()); + //services.AddOpenTelemetry().UseAzureMonitor(); + //services.ConfigureOpenTelemetryTracerProvider(tpb => tpb.AddSource("CoreEx.*")); }) - .Build(); - -host.Run(); + .ConfigureHostStartup() + .Build() + .Run(); \ No newline at end of file From 121b536a52c2b48938822ac39512ba40848d934a Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Mon, 19 Feb 2024 15:41:41 -0800 Subject: [PATCH 2/6] Run Code Analysis --- CHANGELOG.md | 3 +- .../ReferenceDataOrchestratorExtensions.cs | 2 +- .../HealthChecks/HealthService.cs | 2 +- .../WebApis/ValueContentResult.cs | 5 ++- src/CoreEx.AspNetCore/WebApis/WebApi.cs | 6 ++-- src/CoreEx.AspNetCore/WebApis/WebApiBase.cs | 2 +- .../AzureServiceBusQueueHealthCheck.cs | 20 +++--------- .../AzureServiceBusTopicHealthCheck.cs | 28 ++++------------ .../AzureServiceHealthCheckBase.cs | 23 +++---------- .../EventSendDataToServiceBusConverter.cs | 2 +- src/CoreEx.Cosmos/CosmosDbModelQuery.cs | 15 +++------ src/CoreEx.Cosmos/CosmosDbQuery.cs | 15 +++------ src/CoreEx.Cosmos/CosmosDbValueModelQuery.cs | 15 +++------ src/CoreEx.Cosmos/CosmosDbValueQuery.cs | 15 +++------ src/CoreEx.Database.MySql/MySqlDatabase.cs | 18 +++++++---- .../PostgresDatabase.cs | 18 +++++++---- .../Outbox/EventOutboxService.cs | 26 +++++---------- .../SqlServerDatabase.cs | 20 ++++++++---- .../Json/JsonPreFilterInspector.cs | 11 ++----- .../ReferenceDataContentJsonSerializer.cs | 9 ++---- .../PubSub/EventSendDataToPubSubConverter.cs | 4 +-- src/CoreEx.Validation/PropertyContext.cs | 4 +-- .../Rules/ComparePropertyRule.cs | 21 ++++-------- src/CoreEx.Validation/Rules/ValueRuleBase.cs | 2 +- src/CoreEx.Validation/ValidationContext.cs | 4 +-- src/CoreEx.Validation/ValidatorBase.cs | 2 +- src/CoreEx.Validation/ValueValidator.cs | 15 +++------ .../Reflection/PropertyExpression.cs | 4 +-- src/CoreEx/Entities/CompositeKey.cs | 12 +++++++ src/CoreEx/Http/HttpRequestLogger.cs | 4 --- src/CoreEx/Http/HttpResultBase.cs | 2 +- src/CoreEx/Http/TypedHttpClientBase.cs | 22 +++---------- src/CoreEx/Http/TypedHttpClientCore.cs | 18 ++++------- src/CoreEx/Invokers/ResultInvokerWith.cs | 32 +++++++------------ .../Json/Merge/JsonMergePatchOptions.cs | 11 ++----- src/CoreEx/Mapping/MapperOptions.cs | 19 +++-------- .../Text/Json/JsonPreFilterInspector.cs | 11 ++----- src/CoreEx/Validation/MultiValidator.cs | 2 +- src/CoreEx/Validation/MultiValidatorResult.cs | 2 +- src/CoreEx/Wildcards/Wildcard.cs | 4 +-- 40 files changed, 167 insertions(+), 283 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 438e16e6..b0c7f15c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ Represents the **NuGet** versions. - *Fixed*: The `WorkOrchestrator.GetAsync()` and `WorkOrchestrator.GetAsync(string type, ..)` methods were not automatically cancelling where expired. - *Fixed*: The `InvokerArgs` activity tracing is correctly capturing the `Exception.Message` where an `Exception` has been thrown. - *Internal*: - - All `throw new ArgumentNullException` migrated to the `xxx.ThrowIfNull` extension method equivalent. + - All `throw new ArgumentNullException` migrated to the `xxx.ThrowIfNull` extension method equivalent. + - All _Run Code Analysis_ issues resolved. ## v3.11.0 - *Enhancement*: The `ITypedToResult` updated to correctly implement `IToResult` as the simple `ToResult` where required. diff --git a/src/CoreEx.AspNetCore/Abstractions/ReferenceDataOrchestratorExtensions.cs b/src/CoreEx.AspNetCore/Abstractions/ReferenceDataOrchestratorExtensions.cs index cb9081e3..c7177b9e 100644 --- a/src/CoreEx.AspNetCore/Abstractions/ReferenceDataOrchestratorExtensions.cs +++ b/src/CoreEx.AspNetCore/Abstractions/ReferenceDataOrchestratorExtensions.cs @@ -56,7 +56,7 @@ public static Task GetNamedAsync(this ReferenceDat /// /// Perform a further split of the string values. /// - private static IEnumerable SplitStringValues(IEnumerable values) + private static List SplitStringValues(IEnumerable values) { var list = new List(); foreach (var value in values) diff --git a/src/CoreEx.AspNetCore/HealthChecks/HealthService.cs b/src/CoreEx.AspNetCore/HealthChecks/HealthService.cs index 535fb3de..2072c1c0 100644 --- a/src/CoreEx.AspNetCore/HealthChecks/HealthService.cs +++ b/src/CoreEx.AspNetCore/HealthChecks/HealthService.cs @@ -42,7 +42,7 @@ public async Task RunAsync() /// /// Builds the health report response. /// - private IActionResult BuildResponse(HealthReport healthReport) + private ContentResult BuildResponse(HealthReport healthReport) { var code = healthReport.Status == HealthStatus.Healthy ? HttpStatusCode.OK : HttpStatusCode.ServiceUnavailable; diff --git a/src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs b/src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs index c902135b..2a2f9592 100644 --- a/src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs +++ b/src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs @@ -4,7 +4,6 @@ using CoreEx.AspNetCore.Http; using CoreEx.Entities; using CoreEx.Json; -using CoreEx.Results; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; @@ -159,12 +158,12 @@ public static bool TryCreateValueContentResult(T value, HttpStatusCode status bool hasETag = TryGetETag(val, out var etag); Action? inspector; - if (requestOptions.IncludeFields != null && requestOptions.IncludeFields.Any()) + if (requestOptions.IncludeFields != null && requestOptions.IncludeFields.Length > 0) { inspector = hasETag ? null : fi => etag = GenerateETag(requestOptions, val, fi.ToJsonString(), jsonSerializer); jsonSerializer.TryApplyFilter(val, requestOptions.IncludeFields, out json, JsonPropertyFilter.Include, preFilterInspector: inspector); } - else if (requestOptions.ExcludeFields != null && requestOptions.ExcludeFields.Any()) + else if (requestOptions.ExcludeFields != null && requestOptions.ExcludeFields.Length > 0) { inspector = hasETag ? null : fi => etag = GenerateETag(requestOptions, val, fi.ToJsonString(), jsonSerializer); jsonSerializer.TryApplyFilter(val, requestOptions.ExcludeFields, out json, JsonPropertyFilter.Exclude, preFilterInspector: inspector); diff --git a/src/CoreEx.AspNetCore/WebApis/WebApi.cs b/src/CoreEx.AspNetCore/WebApis/WebApi.cs index ed37f674..e149656d 100644 --- a/src/CoreEx.AspNetCore/WebApis/WebApi.cs +++ b/src/CoreEx.AspNetCore/WebApis/WebApi.cs @@ -695,7 +695,7 @@ private async Task PutInternalAsync(HttpRequest request, // Get the current value before we perform the update; also performing a concurrency match. var cvalue = await get(wap, ct).ConfigureAwait(false); - var ex = cvalue == null ? new NotFoundException() : ConcurrencyETagMatching(wap, cvalue, wapv!.Value, simulatedConcurrency); + var ex = cvalue == null ? (Exception)new NotFoundException() : ConcurrencyETagMatching(wap, cvalue, wapv!.Value, simulatedConcurrency); if (ex is not null) return await CreateActionResultFromExceptionAsync(this, request.HttpContext, ex, Settings, Logger, OnUnhandledExceptionAsync, cancellationToken).ConfigureAwait(false); @@ -708,7 +708,7 @@ private async Task PutInternalAsync(HttpRequest request, /// /// Where etags are supported or automatic concurrency then we need to make sure one was provided up-front and match. /// - private Exception? ConcurrencyETagMatching(WebApiParam wap, TValue getValue, TValue putValue, bool autoConcurrency) + private ConcurrencyException? ConcurrencyETagMatching(WebApiParam wap, TValue getValue, TValue putValue, bool autoConcurrency) { var et = putValue as IETag; if (et != null || autoConcurrency) @@ -844,7 +844,7 @@ public async Task PatchAsync(HttpRequest request, Func /// Creates an from an unexpected . /// - private static IActionResult CreateActionResultForUnexpectedResult(Exception exception, bool includeExceptionInResult) => includeExceptionInResult + private static ContentResult CreateActionResultForUnexpectedResult(Exception exception, bool includeExceptionInResult) => includeExceptionInResult ? new ContentResult { StatusCode = (int)HttpStatusCode.InternalServerError, ContentType = MediaTypeNames.Text.Plain, Content = $"An unexpected internal server error has occurred. CorrelationId={ExecutionContext.Current.CorrelationId} Exception={exception}" } : new ContentResult { StatusCode = (int)HttpStatusCode.InternalServerError, ContentType = MediaTypeNames.Text.Plain, Content = $"An unexpected internal server error has occurred. CorrelationId={ExecutionContext.Current.CorrelationId}" }; } diff --git a/src/CoreEx.Azure/HealthChecks/AzureServiceBusQueueHealthCheck.cs b/src/CoreEx.Azure/HealthChecks/AzureServiceBusQueueHealthCheck.cs index bbbb6203..13cd341c 100644 --- a/src/CoreEx.Azure/HealthChecks/AzureServiceBusQueueHealthCheck.cs +++ b/src/CoreEx.Azure/HealthChecks/AzureServiceBusQueueHealthCheck.cs @@ -6,7 +6,6 @@ namespace CoreEx.Azure.HealthChecks { - /// Health check for Azure Service Bus Queue. /// Check doesn't verify permissions to Send/Receive.
/// To use this health check, add the following to the HealthCheckBuilder. @@ -22,29 +21,18 @@ namespace CoreEx.Azure.HealthChecks /// ); /// ///
- public class AzureServiceBusQueueHealthCheck : AzureServiceHealthCheckBase + public class AzureServiceBusQueueHealthCheck(SettingsBase settings, string connectionName, string queueSettingName) : AzureServiceHealthCheckBase(settings, connectionName) { - private readonly string _queueName; - private readonly string _queueSettingName; - - /// constructor. - /// Note that constructor takes setting NAMES not values, values are looked up from . - public AzureServiceBusQueueHealthCheck(SettingsBase settings, string connectionName, string queueSettingName) - : base(settings, connectionName) - { - _queueName = settings.GetValue(queueSettingName); - _queueSettingName = queueSettingName; - } + private readonly string _queueName = settings.GetValue(queueSettingName); + private readonly string _queueSettingName = queueSettingName; /// protected override async Task CheckServiceBusHealthAsync(ServiceBusAdministrationClient managementClient, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(_queueName)) - { return HealthCheckResult.Unhealthy($"Queue name is not configured under '{_queueSettingName}' in settings"); - } - _ = await managementClient.GetQueueRuntimePropertiesAsync(_queueName, cancellationToken); + _ = await managementClient.GetQueueRuntimePropertiesAsync(_queueName, cancellationToken).ConfigureAwait(false); return HealthCheckResult.Healthy(); } diff --git a/src/CoreEx.Azure/HealthChecks/AzureServiceBusTopicHealthCheck.cs b/src/CoreEx.Azure/HealthChecks/AzureServiceBusTopicHealthCheck.cs index c094cbb1..51c104e9 100644 --- a/src/CoreEx.Azure/HealthChecks/AzureServiceBusTopicHealthCheck.cs +++ b/src/CoreEx.Azure/HealthChecks/AzureServiceBusTopicHealthCheck.cs @@ -6,7 +6,6 @@ namespace CoreEx.Azure.HealthChecks { - /// Health check for Azure Service Bus Topic. /// Check doesn't verify permissions to Send/Receive.
/// To use this health check, add the following to the HealthCheckBuilder. @@ -23,38 +22,23 @@ namespace CoreEx.Azure.HealthChecks /// ); /// ///
- public class AzureServiceBusTopicHealthCheck : AzureServiceHealthCheckBase + public class AzureServiceBusTopicHealthCheck(SettingsBase settings, string connectionName, string topicSettingName, string subscriptionSettingName) : AzureServiceHealthCheckBase(settings, connectionName) { - private readonly string _topicName; - private readonly string _subscriptionName; - private readonly string _topicSettingName; - private readonly string _subscriptionSettingName; - - /// constructor. - /// Note that constructor takes setting NAMES not values, values are looked up from . - public AzureServiceBusTopicHealthCheck(SettingsBase settings, string connectionName, string topicSettingName, string subscriptionSettingName) - : base(settings, connectionName) - { - _topicName = settings.GetValue(topicSettingName); - _subscriptionName = settings.GetValue(subscriptionSettingName); - _topicSettingName = topicSettingName; - _subscriptionSettingName = subscriptionSettingName; - } + private readonly string _topicName = settings.GetValue(topicSettingName); + private readonly string _subscriptionName = settings.GetValue(subscriptionSettingName); + private readonly string _topicSettingName = topicSettingName; + private readonly string _subscriptionSettingName = subscriptionSettingName; /// protected override async Task CheckServiceBusHealthAsync(ServiceBusAdministrationClient managementClient, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(_topicName)) - { return HealthCheckResult.Unhealthy($"Topic name is not configured under '{_topicSettingName}' in settings"); - } if (string.IsNullOrEmpty(_subscriptionName)) - { return HealthCheckResult.Unhealthy($"Subscription name is not configured under '{_subscriptionSettingName}' in settings"); - } - _ = await managementClient.GetSubscriptionRuntimePropertiesAsync(_topicName, _subscriptionName, cancellationToken); + _ = await managementClient.GetSubscriptionRuntimePropertiesAsync(_topicName, _subscriptionName, cancellationToken).ConfigureAwait(false); return HealthCheckResult.Healthy(); } diff --git a/src/CoreEx.Azure/HealthChecks/AzureServiceHealthCheckBase.cs b/src/CoreEx.Azure/HealthChecks/AzureServiceHealthCheckBase.cs index 8a591d9b..7a8846c2 100644 --- a/src/CoreEx.Azure/HealthChecks/AzureServiceHealthCheckBase.cs +++ b/src/CoreEx.Azure/HealthChecks/AzureServiceHealthCheckBase.cs @@ -10,39 +10,28 @@ namespace CoreEx.Azure.HealthChecks { /// Base Health check class for Azure Service Bus health checks. - public abstract class AzureServiceHealthCheckBase : IHealthCheck + /// Note that constructor takes setting NAMES not values, values are looked up from . + public abstract class AzureServiceHealthCheckBase(SettingsBase settings, string connectionName) : IHealthCheck { /// Management connections used by health checks. public static readonly ConcurrentDictionary ManagementClientConnections = new(); - private readonly string _endPoint; - private readonly string _connectionName; + private readonly string _endPoint = settings.GetValue(connectionName); + private readonly string _connectionName = connectionName; private string ConnectionKey => $"{_endPoint}"; - /// constructor. - /// Note that constructor takes setting NAMES not values, values are looked up from . - public AzureServiceHealthCheckBase(SettingsBase settings, string connectionName) - { - _endPoint = settings.GetValue(connectionName); - _connectionName = connectionName; - } - /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(_endPoint)) - { return HealthCheckResult.Unhealthy($"Service bus connection is not configured under '{_connectionName}' in settings"); - } try { var managementClient = ManagementClientConnections.GetOrAdd(ConnectionKey, key => CreateManagementClient()); if (managementClient == null) - { return new HealthCheckResult(context.Registration.FailureStatus, description: "No service bus administration client connection can't be added into dictionary."); - } return await CheckServiceBusHealthAsync(managementClient, cancellationToken); @@ -60,13 +49,9 @@ private ServiceBusAdministrationClient CreateManagementClient() { ServiceBusAdministrationClient managementClient; if (_endPoint.Contains("SharedAccessKey=", StringComparison.OrdinalIgnoreCase)) - { managementClient = new ServiceBusAdministrationClient(_endPoint); - } else - { managementClient = new ServiceBusAdministrationClient(_endPoint, new DefaultAzureCredential()); - } return managementClient; } diff --git a/src/CoreEx.Azure/ServiceBus/EventSendDataToServiceBusConverter.cs b/src/CoreEx.Azure/ServiceBus/EventSendDataToServiceBusConverter.cs index 8820a58c..87c7ecfa 100644 --- a/src/CoreEx.Azure/ServiceBus/EventSendDataToServiceBusConverter.cs +++ b/src/CoreEx.Azure/ServiceBus/EventSendDataToServiceBusConverter.cs @@ -75,7 +75,7 @@ public ServiceBusMessage Convert(EventSendData @event) if (@event.Attributes != null && @event.Attributes.Count > 0 && PropertySelection.HasFlag(EventDataProperty.Attributes)) { // Attrtibutes that start with an underscore are considered internal and will not be sent automatically; i.e. _SessionId and _TimeToLive. - foreach (var attribute in @event.Attributes.Where(x => !string.IsNullOrEmpty(x.Key) && !x.Key.StartsWith("_"))) + foreach (var attribute in @event.Attributes.Where(x => !string.IsNullOrEmpty(x.Key) && !x.Key.StartsWith('_'))) { message.ApplicationProperties.Add(attribute.Key, attribute.Value); } diff --git a/src/CoreEx.Cosmos/CosmosDbModelQuery.cs b/src/CoreEx.Cosmos/CosmosDbModelQuery.cs index 38711d25..a79fd8e4 100644 --- a/src/CoreEx.Cosmos/CosmosDbModelQuery.cs +++ b/src/CoreEx.Cosmos/CosmosDbModelQuery.cs @@ -14,17 +14,12 @@ namespace CoreEx.Cosmos /// Encapsulates a CosmosDb model-only query enabling all select-like capabilities. /// /// The cosmos model . - public class CosmosDbModelQuery : CosmosDbModelQueryBase> where TModel : class, IIdentifier, new() + /// The . + /// The . + /// A function to modify the underlying . + public class CosmosDbModelQuery(ICosmosDbContainer container, CosmosDbArgs dbArgs, Func, IQueryable>? query) : CosmosDbModelQueryBase>(container, dbArgs) where TModel : class, IIdentifier, new() { - private readonly Func, IQueryable>? _query; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// A function to modify the underlying . - public CosmosDbModelQuery(ICosmosDbContainer container, CosmosDbArgs dbArgs, Func, IQueryable>? query) : base(container, dbArgs) => _query = query; + private readonly Func, IQueryable>? _query = query; /// /// Instantiates the . diff --git a/src/CoreEx.Cosmos/CosmosDbQuery.cs b/src/CoreEx.Cosmos/CosmosDbQuery.cs index e84925bf..84393532 100644 --- a/src/CoreEx.Cosmos/CosmosDbQuery.cs +++ b/src/CoreEx.Cosmos/CosmosDbQuery.cs @@ -15,17 +15,12 @@ namespace CoreEx.Cosmos /// /// The resultant . /// The cosmos model . - public class CosmosDbQuery : CosmosDbQueryBase> where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() + /// The . + /// The . + /// A function to modify the underlying . + public class CosmosDbQuery(CosmosDbContainer container, CosmosDbArgs dbArgs, Func, IQueryable>? query) : CosmosDbQueryBase>(container, dbArgs) where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() { - private readonly Func, IQueryable>? _query; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// A function to modify the underlying . - public CosmosDbQuery(CosmosDbContainer container, CosmosDbArgs dbArgs, Func, IQueryable>? query) : base(container, dbArgs) => _query = query; + private readonly Func, IQueryable>? _query = query; /// /// Gets the . diff --git a/src/CoreEx.Cosmos/CosmosDbValueModelQuery.cs b/src/CoreEx.Cosmos/CosmosDbValueModelQuery.cs index 10fb0f77..c17d27a9 100644 --- a/src/CoreEx.Cosmos/CosmosDbValueModelQuery.cs +++ b/src/CoreEx.Cosmos/CosmosDbValueModelQuery.cs @@ -14,17 +14,12 @@ namespace CoreEx.Cosmos /// Encapsulates a CosmosDb model-only query enabling all select-like capabilities. /// /// The cosmos model . - public class CosmosDbValueModelQuery : CosmosDbModelQueryBase, CosmosDbValueModelQuery> where TModel : class, IIdentifier, new() + /// The . + /// The . + /// A function to modify the underlying . + public class CosmosDbValueModelQuery(ICosmosDbContainer container, CosmosDbArgs dbArgs, Func>, IQueryable>>? query) : CosmosDbModelQueryBase, CosmosDbValueModelQuery>(container, dbArgs) where TModel : class, IIdentifier, new() { - private readonly Func>, IQueryable>>? _query; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// A function to modify the underlying . - public CosmosDbValueModelQuery(ICosmosDbContainer container, CosmosDbArgs dbArgs, Func>, IQueryable>>? query) : base(container, dbArgs) => _query = query; + private readonly Func>, IQueryable>>? _query = query; /// /// Instantiates the . diff --git a/src/CoreEx.Cosmos/CosmosDbValueQuery.cs b/src/CoreEx.Cosmos/CosmosDbValueQuery.cs index c9d59cac..51474221 100644 --- a/src/CoreEx.Cosmos/CosmosDbValueQuery.cs +++ b/src/CoreEx.Cosmos/CosmosDbValueQuery.cs @@ -15,17 +15,12 @@ namespace CoreEx.Cosmos /// /// The resultant . /// The cosmos model . - public class CosmosDbValueQuery : CosmosDbQueryBase> where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() + /// The . + /// The . + /// A function to modify the underlying . + public class CosmosDbValueQuery(CosmosDbValueContainer container, CosmosDbArgs dbArgs, Func>, IQueryable>>? query) : CosmosDbQueryBase>(container, dbArgs) where T : class, IEntityKey, new() where TModel : class, IIdentifier, new() { - private readonly Func>, IQueryable>>? _query; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// A function to modify the underlying . - public CosmosDbValueQuery(CosmosDbValueContainer container, CosmosDbArgs dbArgs, Func>, IQueryable>>? query) : base(container, dbArgs) => _query = query; + private readonly Func>, IQueryable>>? _query = query; /// /// Gets the . diff --git a/src/CoreEx.Database.MySql/MySqlDatabase.cs b/src/CoreEx.Database.MySql/MySqlDatabase.cs index 1a62de28..335a94e6 100644 --- a/src/CoreEx.Database.MySql/MySqlDatabase.cs +++ b/src/CoreEx.Database.MySql/MySqlDatabase.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using MySql.Data.MySqlClient; using System; -using System.Collections.Generic; +using System.Linq; using System.Data.Common; namespace CoreEx.Database.MySql @@ -18,6 +18,12 @@ namespace CoreEx.Database.MySql /// The optional . public class MySqlDatabase(Func create, ILogger? logger = null, DatabaseInvoker? invoker = null) : Database(create, MySqlClientFactory.Instance, logger, invoker) { + /// + /// Gets the default . + /// + /// See . + public static int[] DefaultDuplicateErrorNumbers { get; } = [1022, 1062, 1088, 1291, 1586, 1859]; + /// public override IConverter RowVersionConverter => EncodedStringToDateTimeConverter.Default; @@ -28,15 +34,15 @@ public class MySqlDatabase(Func create, ILogger? public bool ThrowTransformedException { get; set; } = true; /// - /// Indicates whether to check the when catching the . + /// Indicates whether to check the when catching the . /// - public bool CheckSqlDuplicateErrorNumbers { get; set; } = true; + public bool CheckDuplicateErrorNumbers { get; set; } = true; /// /// Gets or sets the list of known values that are considered a duplicate error. /// - /// See . - public List SqlDuplicateErrorNumbers { get; set; } = new List(new int[] { 1022, 1062, 1088, 1291, 1586, 1859 }); + /// Overrides the . + public int[]? DuplicateErrorNumbers { get; set; } /// protected override Result? OnDbException(DbException dbex) @@ -59,7 +65,7 @@ public class MySqlDatabase(Func create, ILogger? case 56010: return Result.Fail(new DataConsistencyException(msg, sex)); default: - if (CheckSqlDuplicateErrorNumbers && SqlDuplicateErrorNumbers.Contains(sex.Number)) + if (CheckDuplicateErrorNumbers && (DuplicateErrorNumbers ?? DefaultDuplicateErrorNumbers).Contains(sex.Number)) return Result.Fail(new DuplicateException(null, sex)); break; diff --git a/src/CoreEx.Database.Postgres/PostgresDatabase.cs b/src/CoreEx.Database.Postgres/PostgresDatabase.cs index 58afb084..48563c4a 100644 --- a/src/CoreEx.Database.Postgres/PostgresDatabase.cs +++ b/src/CoreEx.Database.Postgres/PostgresDatabase.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using Npgsql; using System; -using System.Collections.Generic; +using System.Linq; using System.Data.Common; namespace CoreEx.Database.Postgres @@ -20,6 +20,12 @@ public class PostgresDatabase(Func create, ILogger + /// Gets the default . + /// + /// See . + public static string[] DefaultDuplicateErrorNumbers { get; } = ["23505"]; + /// /// Gets or sets the names of the pre-configured . /// @@ -36,15 +42,15 @@ public class PostgresDatabase(Func create, ILogger - /// Indicates whether to check the when catching the . + /// Indicates whether to check the when catching the . /// - public bool CheckSqlDuplicateErrorNumbers { get; set; } = true; + public bool CheckDuplicateErrorNumbers { get; set; } = true; /// /// Gets or sets the list of known values that are considered a duplicate error. /// - /// See . - public List SqlDuplicateErrorNumbers { get; set; } = new List(new string[] { "23505" }); + /// Overrides the . + public string[]? DuplicateErrorNumbers { get; set; } /// protected override Result? OnDbException(DbException dbex) @@ -67,7 +73,7 @@ public class PostgresDatabase(Func create, ILogger dequeue and publish self-service capabilities. /// /// This will instantiate an using the underlying and invoke . - public class EventOutboxService : ServiceBase + /// The . + /// The . + /// The . + /// The optional partition key. + /// The optional destination name (i.e. queue or topic). + public class EventOutboxService(IServiceProvider serviceProvider, ILogger logger, SettingsBase? settings = null, string? partitionKey = null, string? destination = null) : ServiceBase(serviceProvider, logger, settings) { private string? _name; private int? _maxIterations; @@ -25,30 +30,15 @@ public class EventOutboxService : ServiceBase /// public string MaxDequeueSizeName { get; set; } = "MaxDequeueSize"; - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// The . - /// The optional partition key. - /// The optional destination name (i.e. queue or topic). - public EventOutboxService(IServiceProvider serviceProvider, ILogger logger, SettingsBase? settings = null, string? partitionKey = null, string? destination = null) - : base(serviceProvider, logger, settings) - { - PartitionKey = partitionKey; - Destination = destination; - } - /// /// Gets the optional partition key. /// - public string? PartitionKey { get; } + public string? PartitionKey { get; } = partitionKey; /// /// Gets the optional destination name (i.e. queue or topic). /// - public string? Destination { get; } + public string? Destination { get; } = destination; /// /// Gets the service name (used for the likes of configuration and logging). diff --git a/src/CoreEx.Database.SqlServer/SqlServerDatabase.cs b/src/CoreEx.Database.SqlServer/SqlServerDatabase.cs index 0cc4a164..31533d6f 100644 --- a/src/CoreEx.Database.SqlServer/SqlServerDatabase.cs +++ b/src/CoreEx.Database.SqlServer/SqlServerDatabase.cs @@ -5,7 +5,7 @@ using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; +using System.Linq; using System.Data.Common; using System.Threading; using System.Threading.Tasks; @@ -22,6 +22,13 @@ public class SqlServerDatabase(Func create, ILogger + /// Gets the default . + /// + /// See + /// and . + public static int[] DefaultDuplicateErrorNumbers { get; } = [2601, 2627]; + /// /// Gets or sets the names of the pre-configured . /// @@ -44,16 +51,15 @@ public class SqlServerDatabase(Func create, ILogger - /// Indicates whether to check the when catching the . + /// Indicates whether to check the when catching the . /// - public bool CheckSqlDuplicateErrorNumbers { get; set; } = true; + public bool CheckDuplicateErrorNumbers { get; set; } = true; /// /// Gets or sets the list of known values that are considered a duplicate error. /// - /// See - /// and . - public List SqlDuplicateErrorNumbers { get; set; } = new List(new int[] { 2601, 2627 }); + /// Overrides the . + public int[]? DuplicateErrorNumbers { get; set; } /// /// Sets the SQL session context using the specified values by invoking the using parameters named , @@ -116,7 +122,7 @@ public Task SetSqlSessionContextAsync(ExecutionContext? executionContext = null, case 56010: return Result.Fail(new DataConsistencyException(msg, sex)); default: - if (CheckSqlDuplicateErrorNumbers && SqlDuplicateErrorNumbers.Contains(sex.Number)) + if (CheckDuplicateErrorNumbers && (DuplicateErrorNumbers ?? DefaultDuplicateErrorNumbers).Contains(sex.Number)) return Result.Fail(new DuplicateException(null, sex)); break; diff --git a/src/CoreEx.Newtonsoft/Json/JsonPreFilterInspector.cs b/src/CoreEx.Newtonsoft/Json/JsonPreFilterInspector.cs index 15c1408e..9e6838b8 100644 --- a/src/CoreEx.Newtonsoft/Json/JsonPreFilterInspector.cs +++ b/src/CoreEx.Newtonsoft/Json/JsonPreFilterInspector.cs @@ -9,21 +9,16 @@ namespace CoreEx.Newtonsoft.Json /// /// Provides pre (prior) to filtering JSON inspection. /// - public readonly struct JsonPreFilterInspector : IJsonPreFilterInspector + /// The . + public readonly struct JsonPreFilterInspector(JToken json) : IJsonPreFilterInspector { - /// - /// Initializes a new instance of the struct. - /// - /// The . - public JsonPreFilterInspector(JToken json) => Json = json; - /// object IJsonPreFilterInspector.Json => Json; /// /// Gets the before any filtering has been applied. /// - public JToken Json { get; } + public JToken Json { get; } = json; /// public string? ToJsonString() => Json.ToString(Nsj.Formatting.None); diff --git a/src/CoreEx.Newtonsoft/Json/ReferenceDataContentJsonSerializer.cs b/src/CoreEx.Newtonsoft/Json/ReferenceDataContentJsonSerializer.cs index 3a5847b3..876e300e 100644 --- a/src/CoreEx.Newtonsoft/Json/ReferenceDataContentJsonSerializer.cs +++ b/src/CoreEx.Newtonsoft/Json/ReferenceDataContentJsonSerializer.cs @@ -11,7 +11,8 @@ namespace CoreEx.Newtonsoft.Json /// Provides the JSON Serialize and Deserialize implementation to allow types to serialize contents. /// /// Generally, types will serialize the as the value; this allows for full contents to be serialized. - public class ReferenceDataContentJsonSerializer : JsonSerializer, IReferenceDataContentJsonSerializer + /// The . Defaults to . + public class ReferenceDataContentJsonSerializer(JsonSerializerSettings? settings = null) : JsonSerializer(settings ?? DefaultSettings), IReferenceDataContentJsonSerializer { /// /// Gets or sets the default without to allow types to serialize contents. @@ -33,11 +34,5 @@ public class ReferenceDataContentJsonSerializer : JsonSerializer, IReferenceData ContractResolver = ContractResolver.Default, Converters = { new Nsj.Converters.StringEnumConverter(), new CollectionResultJsonConverter() } }; - - /// - /// Initializes a new instance of the class. - /// - /// The . Defaults to . - public ReferenceDataContentJsonSerializer(JsonSerializerSettings? settings = null) : base(settings ?? DefaultSettings) { } } } \ No newline at end of file diff --git a/src/CoreEx.Solace/PubSub/EventSendDataToPubSubConverter.cs b/src/CoreEx.Solace/PubSub/EventSendDataToPubSubConverter.cs index c805d958..d94abbca 100644 --- a/src/CoreEx.Solace/PubSub/EventSendDataToPubSubConverter.cs +++ b/src/CoreEx.Solace/PubSub/EventSendDataToPubSubConverter.cs @@ -45,7 +45,7 @@ public IMessage Convert(EventSendData @event) { var message = ContextFactory.Instance.CreateMessage(); - message.BinaryAttachment = @event.Data?.ToArray() ?? Array.Empty(); + message.BinaryAttachment = @event.Data?.ToArray() ?? []; message.ApplicationMessageId = @event.Id; message.HttpContentType = MediaTypeNames.Application.Json; message.CorrelationId = @event.CorrelationId ?? (ExecutionContext.HasCurrent ? ExecutionContext.Current.CorrelationId : null); @@ -77,7 +77,7 @@ public IMessage Convert(EventSendData @event) if (@event.Attributes != null && @event.Attributes.Count > 0 && PropertySelection.HasFlag(EventDataProperty.Attributes)) { // Attrtibutes that start with an underscore are considered internal and will not be sent automatically; i.e. _SessionId and _TimeToLive. - foreach (var attribute in @event.Attributes.Where(x => !string.IsNullOrEmpty(x.Key) && !x.Key.StartsWith("_"))) + foreach (var attribute in @event.Attributes.Where(x => !string.IsNullOrEmpty(x.Key) && !x.Key.StartsWith('_'))) { message.UserPropertyMap.AddString(attribute.Key, attribute.Value); } diff --git a/src/CoreEx.Validation/PropertyContext.cs b/src/CoreEx.Validation/PropertyContext.cs index 24a9362b..4443f3aa 100644 --- a/src/CoreEx.Validation/PropertyContext.cs +++ b/src/CoreEx.Validation/PropertyContext.cs @@ -191,13 +191,13 @@ public MessageItem CreateErrorMessage(LText format, params object?[] values) /// Creates a fully qualified property name for the name. /// /// The property name. - public string CreateFullyQualifiedPropertyName(string name) => (Parent.FullyQualifiedEntityName == null) ? name : (Parent.FullyQualifiedEntityName + (name.StartsWith("[") ? "" : ".") + name); + public string CreateFullyQualifiedPropertyName(string name) => (Parent.FullyQualifiedEntityName == null) ? name : (Parent.FullyQualifiedEntityName + (name.StartsWith('[') ? "" : ".") + name); /// /// Creates a fully qualified JSON property name for the name. /// /// The property name. - public string CreateFullyQualifiedJsonPropertyName(string name) => (Parent.FullyQualifiedJsonEntityName == null) ? name : (Parent.FullyQualifiedJsonEntityName + (name.StartsWith("[") ? "" : ".") + name); + public string CreateFullyQualifiedJsonPropertyName(string name) => (Parent.FullyQualifiedJsonEntityName == null) ? name : (Parent.FullyQualifiedJsonEntityName + (name.StartsWith('[') ? "" : ".") + name); /// /// Creates a new from the . diff --git a/src/CoreEx.Validation/Rules/ComparePropertyRule.cs b/src/CoreEx.Validation/Rules/ComparePropertyRule.cs index 2de380e0..109eeed4 100644 --- a/src/CoreEx.Validation/Rules/ComparePropertyRule.cs +++ b/src/CoreEx.Validation/Rules/ComparePropertyRule.cs @@ -15,22 +15,13 @@ namespace CoreEx.Validation.Rules /// The entity . /// The property . /// The compare to property . - public class ComparePropertyRule : CompareRuleBase where TEntity : class + /// The . + /// The to reference the compare to entity property. + /// The compare to text to be passed for the error message (default is to derive the text from the property itself). + public class ComparePropertyRule(CompareOperator compareOperator, Expression> compareToPropertyExpression, LText? compareToText = null) : CompareRuleBase(compareOperator) where TEntity : class { - private readonly PropertyExpression _compareTo; - private readonly LText? _compareToText; - - /// - /// Initializes a new instance of the class specifying the compare to property. - /// - /// The . - /// The to reference the compare to entity property. - /// The compare to text to be passed for the error message (default is to derive the text from the property itself). - public ComparePropertyRule(CompareOperator compareOperator, Expression> compareToPropertyExpression, LText? compareToText = null) : base(compareOperator) - { - _compareTo = PropertyExpression.Create(compareToPropertyExpression); - _compareToText = compareToText; - } + private readonly PropertyExpression _compareTo = PropertyExpression.Create(compareToPropertyExpression); + private readonly LText? _compareToText = compareToText; /// protected override Task ValidateAsync(PropertyContext context, CancellationToken cancellationToken = default) diff --git a/src/CoreEx.Validation/Rules/ValueRuleBase.cs b/src/CoreEx.Validation/Rules/ValueRuleBase.cs index f1e5ea5e..07f41520 100644 --- a/src/CoreEx.Validation/Rules/ValueRuleBase.cs +++ b/src/CoreEx.Validation/Rules/ValueRuleBase.cs @@ -15,7 +15,7 @@ namespace CoreEx.Validation.Rules /// The property . public abstract class ValueRuleBase : IValueRule where TEntity : class { - private readonly List> _clauses = new(); + private readonly List> _clauses = []; /// /// Gets or sets the error message format text (overrides the default). diff --git a/src/CoreEx.Validation/ValidationContext.cs b/src/CoreEx.Validation/ValidationContext.cs index e46ca72f..a9c04384 100644 --- a/src/CoreEx.Validation/ValidationContext.cs +++ b/src/CoreEx.Validation/ValidationContext.cs @@ -532,13 +532,13 @@ private string CreateFullyQualifiedName(string propertyName, string? jsonPropert /// /// The property name. /// The fully qualified property name. - internal string CreateFullyQualifiedPropertyName(string name) => FullyQualifiedEntityName == null ? name : name.StartsWith("[") ? FullyQualifiedEntityName + name : FullyQualifiedEntityName + "." + name; + internal string CreateFullyQualifiedPropertyName(string name) => FullyQualifiedEntityName == null ? name : name.StartsWith('[') ? FullyQualifiedEntityName + name : FullyQualifiedEntityName + "." + name; /// /// Creates a fully qualified JSON property name for the specified name. /// /// The property name. /// The fully qualified property name. - internal string CreateFullyQualifiedJsonPropertyName(string name) => FullyQualifiedJsonEntityName == null ? name : name.StartsWith("[") ? FullyQualifiedJsonEntityName + name : FullyQualifiedJsonEntityName + "." + name; + internal string CreateFullyQualifiedJsonPropertyName(string name) => FullyQualifiedJsonEntityName == null ? name : name.StartsWith('[') ? FullyQualifiedJsonEntityName + name : FullyQualifiedJsonEntityName + "." + name; } } \ No newline at end of file diff --git a/src/CoreEx.Validation/ValidatorBase.cs b/src/CoreEx.Validation/ValidatorBase.cs index ab3123d6..7e8a35c3 100644 --- a/src/CoreEx.Validation/ValidatorBase.cs +++ b/src/CoreEx.Validation/ValidatorBase.cs @@ -17,7 +17,7 @@ public abstract class ValidatorBase : IValidatorEx where TEnti /// /// Gets the underlying rules collection. /// - internal protected List> Rules { get; } = new List>(); + internal protected List> Rules { get; } = []; /// /// Gets the instance. diff --git a/src/CoreEx.Validation/ValueValidator.cs b/src/CoreEx.Validation/ValueValidator.cs index 1c443dbc..32255c47 100644 --- a/src/CoreEx.Validation/ValueValidator.cs +++ b/src/CoreEx.Validation/ValueValidator.cs @@ -11,20 +11,15 @@ namespace CoreEx.Validation /// Enables validation for a value. /// /// The value . - public class ValueValidator : PropertyRuleBase, T> + /// The value to validate. + /// The value name (defaults to ). + /// The friendly text name used in validation messages (defaults to as sentence case where not specified). + public class ValueValidator(T value, string? name = null, LText? text = null) : PropertyRuleBase, T>(string.IsNullOrEmpty(name) ? Validation.ValueNameDefault : name, text) { - /// - /// Initializes a new instance of the class. - /// - /// The value to validate. - /// The value name (defaults to ). - /// The friendly text name used in validation messages (defaults to as sentence case where not specified). - public ValueValidator(T value, string? name = null, LText? text = null) : base(string.IsNullOrEmpty(name) ? Validation.ValueNameDefault : name, text) => ValidationValue = new ValidationValue(null, value); - /// /// Gets the . /// - public ValidationValue ValidationValue { get; } + public ValidationValue ValidationValue { get; } = new ValidationValue(null, value); /// /// Gets the value. diff --git a/src/CoreEx/Abstractions/Reflection/PropertyExpression.cs b/src/CoreEx/Abstractions/Reflection/PropertyExpression.cs index 4d21b17c..5ce46b42 100644 --- a/src/CoreEx/Abstractions/Reflection/PropertyExpression.cs +++ b/src/CoreEx/Abstractions/Reflection/PropertyExpression.cs @@ -23,7 +23,7 @@ public static partial class PropertyExpression /// public const string SentenceCaseWordSplitPattern = "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))"; -#if NET8_0_OR_GREATER +#if NET7_0_OR_GREATER private readonly static Lazy _regex = new(SentenceRegex); #else private readonly static Lazy _regex = new(() => new Regex(SentenceCaseWordSplitPattern, RegexOptions.CultureInvariant | RegexOptions.Compiled)); @@ -138,7 +138,7 @@ public static PropertyExpression Create( /// For example a value of 'EmployeeId' would return just 'Employee'. public static List SentenceCaseLastWordRemovals { get; set; } = ["Id"]; -#if NET8_0_OR_GREATER +#if NET7_0_OR_GREATER /// /// Provides the compiled for splitting strings into a sentence of words. /// diff --git a/src/CoreEx/Entities/CompositeKey.cs b/src/CoreEx/Entities/CompositeKey.cs index f6059685..315a3af1 100644 --- a/src/CoreEx/Entities/CompositeKey.cs +++ b/src/CoreEx/Entities/CompositeKey.cs @@ -49,7 +49,11 @@ namespace CoreEx.Entities /// /// Initializes a new structure. /// +#if NET8_0_OR_GREATER + public CompositeKey() => _args = []; +#else public CompositeKey() => _args = ImmutableArray.Empty; +#endif /// /// Initializes a new structure with one or more values that represent the composite key. @@ -59,7 +63,11 @@ public CompositeKey(params object?[] args) { if (args == null) { +#if NET8_0_OR_GREATER + _args = [null]; +#else _args = new object?[] { null }.ToImmutableArray(); +#endif return; } @@ -84,7 +92,11 @@ public CompositeKey(params object?[] args) }; } +#if NET8_0_OR_GREATER + _args = [.. args]; +#else _args = args.ToImmutableArray(); +#endif } /// diff --git a/src/CoreEx/Http/HttpRequestLogger.cs b/src/CoreEx/Http/HttpRequestLogger.cs index 3d139f3e..b491b754 100644 --- a/src/CoreEx/Http/HttpRequestLogger.cs +++ b/src/CoreEx/Http/HttpRequestLogger.cs @@ -40,9 +40,6 @@ private HttpRequestLogger(SettingsBase settings, ILogger logger) /// /// The . /// The . -#if NETSTANDARD2_1 - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Future proofing.")] -#endif public async Task LogRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { try @@ -73,7 +70,6 @@ public async Task LogRequestAsync(HttpRequestMessage request, CancellationToken /// The . /// Time in which response was received by the client /// The . - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Future proofing.")] public async Task LogResponseAsync(HttpRequestMessage request, HttpResponseMessage response, TimeSpan operationTime, CancellationToken cancellationToken = default) { // Logging should never throw an exception. diff --git a/src/CoreEx/Http/HttpResultBase.cs b/src/CoreEx/Http/HttpResultBase.cs index 23319a3f..4f7a85ec 100644 --- a/src/CoreEx/Http/HttpResultBase.cs +++ b/src/CoreEx/Http/HttpResultBase.cs @@ -188,7 +188,7 @@ protected HttpResultBase(HttpResponseMessage response, BinaryData? content) { foreach (var error in kvp.Value.Where(x => !string.IsNullOrEmpty(x))) { - (mic ??= new MessageItemCollection()).AddPropertyError(kvp.Key, error); + (mic ??= []).AddPropertyError(kvp.Key, error); } } } diff --git a/src/CoreEx/Http/TypedHttpClientBase.cs b/src/CoreEx/Http/TypedHttpClientBase.cs index 10575062..fc3e508d 100644 --- a/src/CoreEx/Http/TypedHttpClientBase.cs +++ b/src/CoreEx/Http/TypedHttpClientBase.cs @@ -18,23 +18,14 @@ namespace CoreEx.Http /// /// Represents a typed foundation wrapper. /// - public abstract class TypedHttpClientBase + /// The underlying . + /// The . Defaults to .. + public abstract class TypedHttpClientBase(HttpClient client, IJsonSerializer? jsonSerializer = null) { - /// - /// Initializes a new instance of the . - /// - /// The underlying . - /// The . Defaults to .. - public TypedHttpClientBase(HttpClient client, IJsonSerializer? jsonSerializer = null) - { - Client = client.ThrowIfNull(nameof(client)); - JsonSerializer = jsonSerializer ?? CoreEx.Json.JsonSerializer.Default; - } - /// /// Gets the underlying . /// - protected HttpClient Client { get; } + protected HttpClient Client { get; } = client.ThrowIfNull(nameof(client)); /// /// Gets the Base Address of the client @@ -44,7 +35,7 @@ public TypedHttpClientBase(HttpClient client, IJsonSerializer? jsonSerializer = /// /// Gets the . /// - protected IJsonSerializer JsonSerializer { get; } + protected IJsonSerializer JsonSerializer { get; } = jsonSerializer ?? CoreEx.Json.JsonSerializer.Default; /// /// Create an with no specified content. @@ -239,9 +230,6 @@ private static int FindBraceIndex(string format, char brace, int startIndex, int /// The . /// The . /// The deserialized response value. -#if NETSTANDARD2_1 - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Future proofing.")] -#endif protected async Task ReadAsJsonAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) { response.EnsureSuccessStatusCode(); diff --git a/src/CoreEx/Http/TypedHttpClientCore.cs b/src/CoreEx/Http/TypedHttpClientCore.cs index 33c16d09..b9da144b 100644 --- a/src/CoreEx/Http/TypedHttpClientCore.cs +++ b/src/CoreEx/Http/TypedHttpClientCore.cs @@ -16,19 +16,13 @@ namespace CoreEx.Http /// Represents a typed base wrapper that supports , , , , and . /// /// The self for support fluent-style method-chaining. - public abstract class TypedHttpClientCore : TypedHttpClientBase where TSelf : TypedHttpClientCore + /// The underlying . + /// The optional . Defaults to . + /// The optional . Defaults to a new instance. + /// The optional . Defaults to . + /// The optional . Defaults to . + public abstract class TypedHttpClientCore(HttpClient client, IJsonSerializer? jsonSerializer = null, ExecutionContext? executionContext = null, SettingsBase? settings = null, ILogger>? logger = null) : TypedHttpClientBase(client, jsonSerializer, executionContext, settings, logger) where TSelf : TypedHttpClientCore { - /// - /// Initializes a new instance of the . - /// - /// The underlying . - /// The optional . Defaults to . - /// The optional . Defaults to a new instance. - /// The optional . Defaults to . - /// The optional . Defaults to . - public TypedHttpClientCore(HttpClient client, IJsonSerializer? jsonSerializer = null, ExecutionContext? executionContext = null, SettingsBase? settings = null, ILogger>? logger = null) - : base(client, jsonSerializer, executionContext, settings, logger) { } - #region HeadAsync /// diff --git a/src/CoreEx/Invokers/ResultInvokerWith.cs b/src/CoreEx/Invokers/ResultInvokerWith.cs index 9a5ec6bd..8311f529 100644 --- a/src/CoreEx/Invokers/ResultInvokerWith.cs +++ b/src/CoreEx/Invokers/ResultInvokerWith.cs @@ -10,42 +10,34 @@ namespace CoreEx.Invokers /// Represents the with capability. /// /// The . - public readonly struct ResultInvokerWith where T : IResult + /// + /// Initializes a new instance of the . + /// + /// The originating . + /// The . + /// The owner/invoker. + /// The . + public readonly struct ResultInvokerWith(T result, InvokerBase invoker, object owner, InvokerArgs? args = null) where T : IResult { - /// - /// Initializes a new instance of the . - /// - /// The originating . - /// The . - /// The owner/invoker. - /// The . - public ResultInvokerWith(T result, InvokerBase invoker, object owner, InvokerArgs? args = null) - { - Result = result ?? throw new System.ArgumentNullException(nameof(result)); - Invoker = invoker ?? throw new System.ArgumentNullException(nameof(invoker)); - Owner = owner ?? throw new System.ArgumentNullException(nameof(owner)); - Args = args ?? InvokerArgs.Default; - } - /// /// Gets the originating result. /// - public T Result { get; } + public T Result { get; } = result ?? throw new System.ArgumentNullException(nameof(result)); /// /// Gets the . /// - public InvokerBase Invoker { get; } + public InvokerBase Invoker { get; } = invoker ?? throw new System.ArgumentNullException(nameof(invoker)); /// /// Gets the owner/invoker. /// - public object Owner { get; } + public object Owner { get; } = owner ?? throw new System.ArgumentNullException(nameof(owner)); /// /// Gets the . /// - public InvokerArgs Args { get; } + public InvokerArgs Args { get; } = args ?? InvokerArgs.Default; /// /// Executes the where the is . diff --git a/src/CoreEx/Json/Merge/JsonMergePatchOptions.cs b/src/CoreEx/Json/Merge/JsonMergePatchOptions.cs index e56c1f2b..5c550f28 100644 --- a/src/CoreEx/Json/Merge/JsonMergePatchOptions.cs +++ b/src/CoreEx/Json/Merge/JsonMergePatchOptions.cs @@ -7,18 +7,13 @@ namespace CoreEx.Json.Merge /// /// The options. /// - public class JsonMergePatchOptions + /// The . Defaults to . + public class JsonMergePatchOptions(IJsonSerializer? jsonSerializer = null) { - /// - /// Initializes a new instance of the class. - /// - /// The . Defaults to . - public JsonMergePatchOptions(IJsonSerializer? jsonSerializer = null) => JsonSerializer = jsonSerializer ?? Json.JsonSerializer.Default; - /// /// Gets the . /// - public IJsonSerializer JsonSerializer { get; } + public IJsonSerializer JsonSerializer { get; } = jsonSerializer ?? Json.JsonSerializer.Default; /// /// Gets or sets the for matching the JSON name (defaults to ). diff --git a/src/CoreEx/Mapping/MapperOptions.cs b/src/CoreEx/Mapping/MapperOptions.cs index 941717d1..c0885844 100644 --- a/src/CoreEx/Mapping/MapperOptions.cs +++ b/src/CoreEx/Mapping/MapperOptions.cs @@ -7,28 +7,19 @@ namespace CoreEx.Mapping /// /// Represents the options. /// - public class MapperOptions + /// The owning . + /// The singluar CRUD value being performed. + public class MapperOptions(Mapper mapper, OperationTypes operationType) { - /// - /// Initialize a new instance of the class. - /// - /// The owning . - /// The singluar CRUD value being performed. - public MapperOptions(Mapper mapper, OperationTypes operationType) - { - Owner = mapper.ThrowIfNull(nameof(mapper)); - OperationType = operationType; - } - /// /// Gets the owning . /// - public Mapper Owner { get; } + public Mapper Owner { get; } = mapper.ThrowIfNull(nameof(mapper)); /// /// Gets the singluar CRUD value being performed. /// - public OperationTypes OperationType { get; } + public OperationTypes OperationType { get; } = operationType; /// /// Maps the (inferring ) value to a new value. diff --git a/src/CoreEx/Text/Json/JsonPreFilterInspector.cs b/src/CoreEx/Text/Json/JsonPreFilterInspector.cs index ab48bc1e..36dfd1ec 100644 --- a/src/CoreEx/Text/Json/JsonPreFilterInspector.cs +++ b/src/CoreEx/Text/Json/JsonPreFilterInspector.cs @@ -8,21 +8,16 @@ namespace CoreEx.Text.Json /// /// Provides pre (prior) to filtering JSON inspection. /// - public readonly struct JsonPreFilterInspector : IJsonPreFilterInspector + /// The . + public readonly struct JsonPreFilterInspector(JsonNode json) : IJsonPreFilterInspector { - /// - /// Initializes a new instance of the struct. - /// - /// The . - public JsonPreFilterInspector(JsonNode json) => Json = json; - /// object IJsonPreFilterInspector.Json => Json; /// /// Gets the before any filtering has been applied. /// - public JsonNode Json { get; } + public JsonNode Json { get; } = json; /// public string? ToJsonString() => Json.ToJsonString(); diff --git a/src/CoreEx/Validation/MultiValidator.cs b/src/CoreEx/Validation/MultiValidator.cs index 0bd8936f..32235242 100644 --- a/src/CoreEx/Validation/MultiValidator.cs +++ b/src/CoreEx/Validation/MultiValidator.cs @@ -21,7 +21,7 @@ public class MultiValidator /// /// Gets the list of validators. /// - public List>> Validators { get; } = new(); + public List>> Validators { get; } = []; /// /// Adds an . diff --git a/src/CoreEx/Validation/MultiValidatorResult.cs b/src/CoreEx/Validation/MultiValidatorResult.cs index 268af017..75373fe6 100644 --- a/src/CoreEx/Validation/MultiValidatorResult.cs +++ b/src/CoreEx/Validation/MultiValidatorResult.cs @@ -29,7 +29,7 @@ public MessageItemCollection Messages if (_messages != null) return _messages; - _messages = new(); + _messages = []; _messages.CollectionChanged += Messages_CollectionChanged; return _messages; } diff --git a/src/CoreEx/Wildcards/Wildcard.cs b/src/CoreEx/Wildcards/Wildcard.cs index b4997fae..3236c6f9 100644 --- a/src/CoreEx/Wildcards/Wildcard.cs +++ b/src/CoreEx/Wildcards/Wildcard.cs @@ -84,7 +84,7 @@ public Wildcard(WildcardSelection supported, char multiWildcard = MultiWildcardC Supported = supported; MultiWildcard = multiWildcard; SingleWildcard = singleWildcard; - CharactersNotAllowed = new ReadOnlyCollection(charactersNotAllowed != null ? (char[])charactersNotAllowed.Clone() : Array.Empty()); + CharactersNotAllowed = new ReadOnlyCollection(charactersNotAllowed != null ? (char[])charactersNotAllowed.Clone() : []); SpaceTreatment = spaceTreatment; Transform = transform; } @@ -166,7 +166,7 @@ public WildcardResult Parse(string? text) var sb = new StringBuilder(); var wr = new WildcardResult(this) { Selection = WildcardSelection.Undetermined }; - if (CharactersNotAllowed != null && CharactersNotAllowed.Count > 0 && text.IndexOfAny(CharactersNotAllowed.ToArray()) >= 0) + if (CharactersNotAllowed != null && CharactersNotAllowed.Count > 0 && text.IndexOfAny([.. CharactersNotAllowed]) >= 0) wr.Selection |= WildcardSelection.InvalidCharacter; var hasMulti = SpaceTreatment == WildcardSpaceTreatment.MultiWildcardWhenOthers && Supported.HasFlag(WildcardSelection.MultiWildcard) && text.Contains(MultiWildcardCharacter, StringComparison.InvariantCulture); From b7b34750994c1f8db5477b6d69ce54495c6ce8b9 Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Mon, 19 Feb 2024 17:10:50 -0800 Subject: [PATCH 3/6] Text.SentenceCase --- CHANGELOG.md | 5 +- src/CoreEx.Validation/CollectionValidator.cs | 2 +- src/CoreEx.Validation/CommonValidatorT.cs | 2 +- src/CoreEx.Validation/DictionaryValidator.cs | 2 +- src/CoreEx.Validation/PropertyRuleBase.cs | 2 +- src/CoreEx/Abstractions/ObjectExtensions.cs | 6 +- .../Reflection/PropertyExpression.cs | 97 --------------- src/CoreEx/Text/SentenceCase.cs | 110 ++++++++++++++++++ .../Reflection/PropertyExpressionTest.cs | 14 +-- 9 files changed, 127 insertions(+), 113 deletions(-) create mode 100644 src/CoreEx/Text/SentenceCase.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index b0c7f15c..8defbcaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,12 @@ Represents the **NuGet** versions. ## v3.12.0 - *Enhancement*: Added new `CoreEx.Database.Postgres` project/package to support [PostgreSQL](https://www.postgresql.org/) database capabilities. Primarily encapsulates the open-source [`Npqsql`](https://www.npgsql.org/) .NET ADO database provider for PostgreSQL. - Added `EncodedStringToUInt32Converter` to support PostgreSQL `xmin` column encoding as the row version/etag. +- *Enhancement*: Migrated sentence case logic from inside `PropertyExpression` into `CoreEx.Text.SentenceCase` to improve discoverablity and reuse opportunities. - *Fixed:* The `IServiceCollection.AddAzureServiceBusClient` extension method as been removed; the `ServiceBusClient` will need to be instantiated prior to usage. Standard approach is for consumers to create client instances independently. - *Fixed*: The `WorkOrchestrator.GetAsync()` and `WorkOrchestrator.GetAsync(string type, ..)` methods were not automatically cancelling where expired. -- *Fixed*: The `InvokerArgs` activity tracing is correctly capturing the `Exception.Message` where an `Exception` has been thrown. +- *Fixed*: The `InvokerArgs` activity tracing updated to correctly capture the `Exception.Message` where an `Exception` has been thrown. - *Internal*: - - All `throw new ArgumentNullException` migrated to the `xxx.ThrowIfNull` extension method equivalent. + - All `throw new ArgumentNullException` checking migrated to the `xxx.ThrowIfNull` extension method equivalent. - All _Run Code Analysis_ issues resolved. ## v3.11.0 diff --git a/src/CoreEx.Validation/CollectionValidator.cs b/src/CoreEx.Validation/CollectionValidator.cs index aa3a23eb..bef31a60 100644 --- a/src/CoreEx.Validation/CollectionValidator.cs +++ b/src/CoreEx.Validation/CollectionValidator.cs @@ -106,7 +106,7 @@ public override Task> ValidateAsync(TColl? value, Valid i++; } - var text = new Lazy(() => Text ?? PropertyExpression.ConvertToSentenceCase(args?.FullyQualifiedEntityName) ?? PropertyExpression.ConvertToSentenceCase(Validation.ValueNameDefault)!); + var text = new Lazy(() => Text ?? args?.FullyQualifiedEntityName.ToSentenceCase() ?? Validation.ValueTextDefault!); if (hasNullItem) context.AddMessage(Entities.MessageType.Error, ValidatorStrings.CollectionNullItemFormat, [text.Value, null]); diff --git a/src/CoreEx.Validation/CommonValidatorT.cs b/src/CoreEx.Validation/CommonValidatorT.cs index 1d36bc53..468e55cf 100644 --- a/src/CoreEx.Validation/CommonValidatorT.cs +++ b/src/CoreEx.Validation/CommonValidatorT.cs @@ -54,7 +54,7 @@ public async Task, T>> ValidateAsync(T? { var vv = new ValidationValue(null, value); var ctx = new PropertyContext, T>(new ValidationContext>(vv, - new ValidationArgs()), value, name ?? Name, jsonName ?? JsonName, text ?? PropertyExpression.ConvertToSentenceCase(name) ?? Text); + new ValidationArgs()), value, name ?? Name, jsonName ?? JsonName, text ?? name.ToSentenceCase() ?? Text); await InvokeAsync(ctx, cancellationToken).ConfigureAwait(false); var res = new ValueValidatorResult, T>(ctx); diff --git a/src/CoreEx.Validation/DictionaryValidator.cs b/src/CoreEx.Validation/DictionaryValidator.cs index 15fd2768..37d5e1d9 100644 --- a/src/CoreEx.Validation/DictionaryValidator.cs +++ b/src/CoreEx.Validation/DictionaryValidator.cs @@ -130,7 +130,7 @@ public override Task> ValidateAsync(TDict? value, Valid } } - var text = new Lazy(() => Text ?? PropertyExpression.ConvertToSentenceCase(args?.FullyQualifiedEntityName) ?? Validation.ValueNameDefault); + var text = new Lazy(() => Text ?? args?.FullyQualifiedEntityName.ToSentenceCase() ?? Validation.ValueTextDefault); if (hasNullKey) context.AddMessage(Entities.MessageType.Error, ValidatorStrings.DictionaryNullKeyFormat, [text.Value, null]); diff --git a/src/CoreEx.Validation/PropertyRuleBase.cs b/src/CoreEx.Validation/PropertyRuleBase.cs index 17952432..d0cf876d 100644 --- a/src/CoreEx.Validation/PropertyRuleBase.cs +++ b/src/CoreEx.Validation/PropertyRuleBase.cs @@ -25,7 +25,7 @@ public abstract class PropertyRuleBase : IPropertyRule class. /// /// The property name. - /// The friendly text name used in validation messages (defaults to as ). + /// The friendly text name used in validation messages (defaults to as ). /// The JSON property name (defaults to ). protected PropertyRuleBase(string name, LText? text = null, string? jsonName = null) { diff --git a/src/CoreEx/Abstractions/ObjectExtensions.cs b/src/CoreEx/Abstractions/ObjectExtensions.cs index 5a627976..3f03de19 100644 --- a/src/CoreEx/Abstractions/ObjectExtensions.cs +++ b/src/CoreEx/Abstractions/ObjectExtensions.cs @@ -1,6 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx -using CoreEx.Abstractions.Reflection; +using CoreEx.Text; using System; using System.Diagnostics.CodeAnalysis; #if NET6_0_OR_GREATER @@ -37,9 +37,9 @@ public static class ObjectExtensions /// The text to convert. /// The as sentence case. /// For example a value of 'VarNameDB' would return 'Var Name DB'. - /// Uses the function to perform the conversion. + /// Uses the function to perform the conversion. [return: NotNullIfNotNull(nameof(text))] - public static string? ToSentenceCase(this string? text) => PropertyExpression.ToSentenceCase(text); + public static string? ToSentenceCase(this string? text) => SentenceCase.ToSentenceCase(text); #if NET6_0_OR_GREATER /// diff --git a/src/CoreEx/Abstractions/Reflection/PropertyExpression.cs b/src/CoreEx/Abstractions/Reflection/PropertyExpression.cs index 5ce46b42..fd6f3973 100644 --- a/src/CoreEx/Abstractions/Reflection/PropertyExpression.cs +++ b/src/CoreEx/Abstractions/Reflection/PropertyExpression.cs @@ -3,11 +3,7 @@ using CoreEx.Json; using Microsoft.Extensions.Caching.Memory; using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; using System.Linq.Expressions; -using System.Text.RegularExpressions; namespace CoreEx.Abstractions.Reflection { @@ -18,17 +14,6 @@ public static partial class PropertyExpression { private static IMemoryCache? _fallbackCache; - /// - /// The pattern for splitting strings into a sentence of words. - /// - public const string SentenceCaseWordSplitPattern = "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))"; - -#if NET7_0_OR_GREATER - private readonly static Lazy _regex = new(SentenceRegex); -#else - private readonly static Lazy _regex = new(() => new Regex(SentenceCaseWordSplitPattern, RegexOptions.CultureInvariant | RegexOptions.Compiled)); -#endif - /// /// Gets the . /// @@ -63,87 +48,5 @@ public static PropertyExpression Create( /// This does scream Service Locator, which is considered an anti-pattern by some, but this avoids the added complexity of passing the where most implementations will default to the /// implementation - this just avoids unnecessary awkwardness for sake of purity. Finally, this class is intended for largely internal use only. private static IJsonSerializer DetermineJsonSerializer(IJsonSerializer? jsonSerializer) => jsonSerializer ?? ExecutionContext.GetService() ?? JsonSerializer.Default; - - /// - /// Converts a into sentence case. - /// - /// The text to convert. - /// The as sentence case. - /// For example a value of 'VarNameDB' would return 'Var Name DB'. - /// Uses the function to perform the conversion. - public static string? ToSentenceCase(this string? text) => SentenceCaseConverter == null ? text : SentenceCaseConverter(text); - - /// - /// Converts a into sentence case. - /// - /// The text to convert. - /// The as sentence case. - /// For example a value of 'VarNameDB' would return 'Var Name DB'. - /// Uses the function to perform the conversion. - public static string? ConvertToSentenceCase(string? text) => string.IsNullOrEmpty(text) ? text : text.ToSentenceCase(); - - /// - /// Gets or sets the underlying logic to perform the sentence case conversion. - /// - /// Defaults to . - public static Func? SentenceCaseConverter { get; set; } = SentenceCaseConversion; - - /// - /// Performs the out-of-the-box sentence case conversion. - /// - /// The text. - /// Defaults to the following: Initial word splitting is performed using the . First letter is always capitalized, initial full text is tested (and replaced where matched) - /// against , then each word is tested (and replaced where matched) against . Finally, the last word in the initial text is tested against - /// and where matched the final word will be removed. - public static string? SentenceCaseConversion(string? text) - { - if (string.IsNullOrEmpty(text)) - return text; - - // Make sure the first character is always upper case. - if (char.IsLower(text[0])) - text = char.ToUpper(text[0], CultureInfo.InvariantCulture) + text[1..]; - - // Check if there is a one-to-one substitution. - if (SentenceCaseSubstitutions.TryGetValue(text, out var scs)) - return scs; - - // Determine whether last word should be removed, then go through each word and substitute. - var s = _regex.Value.Replace(text, "$1 "); // Split the string into words. - var parts = s.Split(' ', StringSplitOptions.RemoveEmptyEntries); - - var removeLastWord = SentenceCaseLastWordRemovals.Contains(parts.Last()); - - for (int i = 0; i < parts.Length; i++) - { - if (SentenceCaseSubstitutions.TryGetValue(text, out var iscs)) - parts[i] = iscs; - } - - // Rejoin the words back into the final sentence. - return string.Join(" ", parts, 0, parts.Length - (removeLastWord ? 1 : 0)); - } - - /// - /// Gets or sets the sentence case substitutions where the key is the originating (input) text and the value the corresponding substitution sentence case text. - /// - /// Defaults with the following entry: key 'Id' and value 'Identifier'. - /// This subtitution applies to all words in the text with the exception of the last where it matches the . - public static Dictionary SentenceCaseSubstitutions { get; set; } = new() { { "Id", "Identifier" } }; - - /// - /// Gets or sets the sentence case last word removal list; i.e. where there is more than one word, and there is a match, the word will be removed. - /// - /// Defaults with the following entry: 'Id'. - /// For example a value of 'EmployeeId' would return just 'Employee'. - public static List SentenceCaseLastWordRemovals { get; set; } = ["Id"]; - -#if NET7_0_OR_GREATER - /// - /// Provides the compiled for splitting strings into a sentence of words. - /// - [GeneratedRegex(SentenceCaseWordSplitPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant)] - private static partial Regex SentenceRegex(); -#endif } } \ No newline at end of file diff --git a/src/CoreEx/Text/SentenceCase.cs b/src/CoreEx/Text/SentenceCase.cs new file mode 100644 index 00000000..0f29cad5 --- /dev/null +++ b/src/CoreEx/Text/SentenceCase.cs @@ -0,0 +1,110 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx + +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System; +using System.Text.RegularExpressions; +using System.Diagnostics.CodeAnalysis; + +namespace CoreEx.Text +{ + /// + /// Provides common sentence case capabilities. + /// + public static partial class SentenceCase + { + /// + /// The pattern for splitting strings into a sentence of words. + /// + public const string WordSplitPattern = "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))"; + + /// + /// Gets the compiled for splitting strings into a sentence of words (see ). + /// +#if NET7_0_OR_GREATER + public static Regex WordSplitRegex { get; } = _wordSplitRegex(); + + /// + /// Provides the generated for splitting strings into a sentence of words. + /// + [GeneratedRegex(WordSplitPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant)] + private static partial Regex _wordSplitRegex(); +#else + public static Regex WordSplitRegex { get; } = new Regex(WordSplitPattern, RegexOptions.CultureInvariant | RegexOptions.Compiled); +#endif + + /// + /// Performs a sentence case word split on the specified . + /// + /// The text to sentence case word split. + /// + public static string[] WordSplit(string text) => string.IsNullOrEmpty(text) ? [] : WordSplitRegex.Split(text); + + /// + /// Converts a into sentence case. + /// + /// The text to convert. + /// The as sentence case. + /// For example a value of 'VarNameDB' would return 'Var Name DB'. + /// Uses the function to perform the conversion. + [return: NotNullIfNotNull(nameof(text))] + public static string? ToSentenceCase(string? text) => SentenceCaseConverter == null ? text : SentenceCaseConverter(text); + + /// + /// Gets or sets the underlying logic to perform the sentence case conversion. + /// + /// Defaults to the internal logic. + public static Func? SentenceCaseConverter { get; set; } = SentenceCaseConversion; + + /// + /// Performs the out-of-the-box sentence case conversion. + /// + /// The text. + /// Defaults to the following: Initial word splitting is performed using the . First letter is always capitalized, initial full text is tested (and replaced where matched) + /// against , then each word is tested (and replaced where matched) against . Finally, the last word in the initial text is tested against + /// and where matched the final word will be removed. + private static string? SentenceCaseConversion(string? text) + { + if (string.IsNullOrEmpty(text)) + return text; + + // Make sure the first character is always upper case. + if (char.IsLower(text[0])) + text = char.ToUpper(text[0], CultureInfo.InvariantCulture) + text[1..]; + + // Check if there is a one-to-one substitution. + if (Substitutions.TryGetValue(text, out var scs)) + return scs; + + // Determine whether last word should be removed, then go through each word and substitute. + var s = WordSplitRegex.Replace(text, "$1 "); // Split the string into words. + var parts = s.Split(' ', StringSplitOptions.RemoveEmptyEntries); + + var removeLastWord = LastWordRemovals.Contains(parts.Last()); + + for (int i = 0; i < parts.Length; i++) + { + if (Substitutions.TryGetValue(text, out var iscs)) + parts[i] = iscs; + } + + // Rejoin the words back into the final sentence. + return string.Join(" ", parts, 0, parts.Length - (removeLastWord ? 1 : 0)); + } + + /// + /// Gets or sets the sentence case substitutions where the key is the originating (input) text and the value the corresponding substitution sentence case text. + /// + /// Defaults with the following entry: key 'Id' and value 'Identifier'. + /// This subtitution applies to all words in the text with the exception of the last where it matches the . + public static Dictionary Substitutions { get; set; } = new() { { "Id", "Identifier" } }; + + /// + /// Gets or sets the sentence case last word removal list; i.e. where there is more than one word, and there is a match, the word will be removed. + /// + /// Defaults with the following entry: 'Id'. + /// For example a value of 'EmployeeId' would return just 'Employee'. + public static List LastWordRemovals { get; set; } = ["Id"]; + } +} \ No newline at end of file diff --git a/tests/CoreEx.Test/Framework/Abstractions/Reflection/PropertyExpressionTest.cs b/tests/CoreEx.Test/Framework/Abstractions/Reflection/PropertyExpressionTest.cs index 7c2feef7..006ef155 100644 --- a/tests/CoreEx.Test/Framework/Abstractions/Reflection/PropertyExpressionTest.cs +++ b/tests/CoreEx.Test/Framework/Abstractions/Reflection/PropertyExpressionTest.cs @@ -61,13 +61,13 @@ public void ToSentenceCase() { Assert.Multiple(() => { - Assert.That(PropertyExpression.ToSentenceCase(null), Is.Null); - Assert.That(PropertyExpression.ToSentenceCase(string.Empty), Is.EqualTo(string.Empty)); - Assert.That(PropertyExpression.ToSentenceCase("Id"), Is.EqualTo("Identifier")); - Assert.That(PropertyExpression.ToSentenceCase("id"), Is.EqualTo("Identifier")); - Assert.That(PropertyExpression.ToSentenceCase("FirstName"), Is.EqualTo("First Name")); - Assert.That(PropertyExpression.ToSentenceCase("firstName"), Is.EqualTo("First Name")); - Assert.That(PropertyExpression.ToSentenceCase("EmployeeId"), Is.EqualTo("Employee")); + Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase(null), Is.Null); + Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase(string.Empty), Is.EqualTo(string.Empty)); + Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase("Id"), Is.EqualTo("Identifier")); + Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase("id"), Is.EqualTo("Identifier")); + Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase("FirstName"), Is.EqualTo("First Name")); + Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase("firstName"), Is.EqualTo("First Name")); + Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase("EmployeeId"), Is.EqualTo("Employee")); }); } } From c58598840b1b3a0174045b6d6e1d013d3be05853 Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Tue, 20 Feb 2024 10:21:27 -0800 Subject: [PATCH 4/6] Readme doco update. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d6e43ae1..e85c335a 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,9 @@ Package | Status | Source & documentation `CoreEx.Azure` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Azure.svg)](https://badge.fury.io/nu/CoreEx.Azure) | [Link](./src/CoreEx.Azure) `CoreEx.Cosmos` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Cosmos.svg)](https://badge.fury.io/nu/CoreEx.Cosmos) | [Link](./src/CoreEx.Cosmos) `CoreEx.Database` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.svg)](https://badge.fury.io/nu/CoreEx.Database) | [Link](./src/CoreEx.Database) -`CoreEx.Database.SqlServer` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.SqlServer.svg)](https://badge.fury.io/nu/CoreEx.Database.SqlServer) | [Link](./src/CoreEx.Database.SqlServer) `CoreEx.Database.MySql` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.MySql.svg)](https://badge.fury.io/nu/CoreEx.Database.MySql) | [Link](./src/CoreEx.Database.MySql) +`CoreEx.Database.Postgres` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.Postgres.svg)](https://badge.fury.io/nu/CoreEx.Database.Postgres) | [Link](./src/CoreEx.Database.Postgres) +`CoreEx.Database.SqlServer` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.SqlServer.svg)](https://badge.fury.io/nu/CoreEx.Database.SqlServer) | [Link](./src/CoreEx.Database.SqlServer) `CoreEx.EntityFrameworkCore` | [![NuGet version](https://badge.fury.io/nu/CoreEx.EntityFrameworkCore.svg)](https://badge.fury.io/nu/CoreEx.EntityFrameworkCore) | [Link](./src/CoreEx.EntityFrameworkCore) `CoreEx.FluentValidation` | [![NuGet version](https://badge.fury.io/nu/CoreEx.FluentValidation.svg)](https://badge.fury.io/nu/CoreEx.FluentValidation) | [Link](./src/CoreEx.FluentValidation) `CoreEx.Newtonsoft` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Newtonsoft.svg)](https://badge.fury.io/nu/CoreEx.Newtonsoft) | [Link](./src/CoreEx.Newtonsoft) From f9f7f4a549068904c03f98b345d4fafd5c92317f Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Sun, 25 Feb 2024 15:47:36 -0800 Subject: [PATCH 5/6] JsonDataReader DataOnly/TimeOnly SentenceCase fix --- src/CoreEx/Json/Data/JsonDataReader.cs | 4 ++++ src/CoreEx/Text/SentenceCase.cs | 15 ++++++++++----- .../Reflection/PropertyExpressionTest.cs | 9 +++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/CoreEx/Json/Data/JsonDataReader.cs b/src/CoreEx/Json/Data/JsonDataReader.cs index 0a2abf47..02ed58c5 100644 --- a/src/CoreEx/Json/Data/JsonDataReader.cs +++ b/src/CoreEx/Json/Data/JsonDataReader.cs @@ -318,6 +318,10 @@ private void ReplaceDynamicParameter(JsonElement je, Utf8JsonWriter jw) case Guid gv: jw.WriteStringValue(gv); break; case DateTime dv: jw.WriteStringValue(dv); break; case DateTimeOffset ov: jw.WriteStringValue(ov); break; +#if NET7_0_OR_GREATER + case DateOnly dv: jw.WriteStringValue(dv.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture)); break; + case TimeOnly tv: jw.WriteStringValue(tv.ToString("HH:mm:ss.FFFFFFF", System.Globalization.CultureInfo.InvariantCulture)); break; +#endif case bool bv: jw.WriteBooleanValue(bv); break; case short nsv: jw.WriteNumberValue(nsv); break; case int niv: jw.WriteNumberValue(niv); break; diff --git a/src/CoreEx/Text/SentenceCase.cs b/src/CoreEx/Text/SentenceCase.cs index 0f29cad5..fb8a583d 100644 --- a/src/CoreEx/Text/SentenceCase.cs +++ b/src/CoreEx/Text/SentenceCase.cs @@ -38,8 +38,15 @@ public static partial class SentenceCase /// Performs a sentence case word split on the specified . /// /// The text to sentence case word split. - /// - public static string[] WordSplit(string text) => string.IsNullOrEmpty(text) ? [] : WordSplitRegex.Split(text); + /// An array of words. + public static string[] SplitIntoWords(string? text) + { + if (string.IsNullOrEmpty(text)) + return []; + + var s = WordSplitRegex.Replace(text, "$1 "); // Add a space between each word. + return s.Split(' ', StringSplitOptions.RemoveEmptyEntries); // Split the string into words. + } /// /// Converts a into sentence case. @@ -78,9 +85,7 @@ public static partial class SentenceCase return scs; // Determine whether last word should be removed, then go through each word and substitute. - var s = WordSplitRegex.Replace(text, "$1 "); // Split the string into words. - var parts = s.Split(' ', StringSplitOptions.RemoveEmptyEntries); - + var parts = SplitIntoWords(text); var removeLastWord = LastWordRemovals.Contains(parts.Last()); for (int i = 0; i < parts.Length; i++) diff --git a/tests/CoreEx.Test/Framework/Abstractions/Reflection/PropertyExpressionTest.cs b/tests/CoreEx.Test/Framework/Abstractions/Reflection/PropertyExpressionTest.cs index 006ef155..b748ef28 100644 --- a/tests/CoreEx.Test/Framework/Abstractions/Reflection/PropertyExpressionTest.cs +++ b/tests/CoreEx.Test/Framework/Abstractions/Reflection/PropertyExpressionTest.cs @@ -69,6 +69,15 @@ public void ToSentenceCase() Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase("firstName"), Is.EqualTo("First Name")); Assert.That(CoreEx.Text.SentenceCase.ToSentenceCase("EmployeeId"), Is.EqualTo("Employee")); }); + + var w = CoreEx.Text.SentenceCase.SplitIntoWords("FirstXMLCode"); + Assert.Multiple(() => + { + Assert.That(w, Has.Length.EqualTo(3)); + Assert.That(w[0], Is.EqualTo("First")); + Assert.That(w[1], Is.EqualTo("XML")); + Assert.That(w[2], Is.EqualTo("Code")); + }); } } } \ No newline at end of file From 30f087126de94861c33ecc051822b32ebaf74e58 Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Sun, 25 Feb 2024 15:54:17 -0800 Subject: [PATCH 6/6] README tweak. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e85c335a..032ec408 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,9 @@ These other _Avanade_ repositories leverage _CoreEx_: Repo | Description -|- -[Beef](https://github.com/Avanade/beef) | Code-generation capabilities to support the industrialization of API development leveraging `CoreEx` as the primary runtime framework (_Beef_ version `v5+`). -[DbEx](https://github.com/Avanade/dbex) | Provides database extensions for DbUp-inspired database migrations. -[NTangle](https://github.com/Avanade/ntangle) | Change Data Capture (CDC) code generation tool and runtime. +[*Beef*](https://github.com/Avanade/beef) | Code-generation capabilities to support the industrialization of API development leveraging `CoreEx` as the primary runtime framework (_Beef_ version `v5+`). +[*DbEx*](https://github.com/Avanade/dbex) | Provides database extensions for DbUp-inspired database migrations. +[*NTangle*](https://github.com/Avanade/ntangle) | Change Data Capture (CDC) code generation tool and runtime.