From 06c1fcc2be37aeb97049a2ca688b455d9e624466 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 26 Nov 2020 18:44:12 -0600 Subject: [PATCH] #371 Introduce caching abstractions and mediator extensions. --- RapidField.SolidInstruments.sln | 3 + ...eld.SolidInstruments.Command.Extensions.md | 31 ++ .../RapidField.SolidInstruments.Command.md | 6 + ...apidField.SolidInstruments.Core.Caching.md | 31 ++ .../RapidField.SolidInstruments.Core.md | 6 + ...d.SolidInstruments.Messaging.Extensions.md | 31 ++ .../RapidField.SolidInstruments.Messaging.md | 6 + en-US_User.dic | 1 + .../ApplicationServiceExecutor.cs | 3 +- .../ApplicationServiceExecutor.cs | 3 +- .../BinaryTreeNode.cs | 4 +- .../AutofacCommandHandlerModule.cs | 4 + .../Extensions/ContainerBuilderExtensions.cs | 14 + .../DotNetNativeCommandHandlerModule.cs | 4 + .../IServiceCollectionExtensions.cs | 18 + .../CommandHandlingException.cs | 12 + .../CommandRegister.cs | 32 ++ .../GetConfigurationObjectCommand.cs | 70 +++ .../GetConfigurationObjectCommandHandler.cs | 98 ++++ .../GetConfigurationObjectCommandTarget.cs | 40 ++ .../GetConfigurationSectionCommand.cs | 69 +++ .../GetConfigurationSectionCommandHandler.cs | 53 ++ .../GetConfigurationValueCommand.cs | 350 +++++++++++++ .../GetConfigurationValueCommandHandler.cs | 53 ++ .../GetConnectionStringCommand.cs | 64 +++ .../GetConnectionStringCommandHandler.cs | 53 ++ .../IGetConfigurationObjectCommand.cs | 38 ++ .../Extensions/ICommandMediatorExtensions.cs | 200 ++++++++ .../Extensions/ICommandRegisterExtensions.cs | 114 +++++ .../ICommandRegister.cs | 23 + ...RapidField.SolidInstruments.Command.csproj | 3 + .../Caching/CacheAccessException.cs | 85 ++++ .../Caching/CacheClient.cs | 344 +++++++++++++ .../Caching/ICacheClient.cs | 107 ++++ .../Caching/ICacheReader.cs | 44 ++ .../Caching/ICacheWriter.cs | 41 ++ .../Caching/IDistributedCacheClient.cs | 13 + .../Caching/IInMemoryCacheClient.cs | 25 + .../Caching/InMemoryCacheClient.cs | 243 +++++++++ .../Caching/InMemoryCachingStrategy.cs | 40 ++ .../ConcurrencyControlOperationException.cs | 2 +- .../Concurrency/ConcurrencyControlToken.cs | 2 +- .../InMemoryCachingStrategyExtensions.cs | 317 ++++++++++++ .../Extensions/ObjectExtensions.cs | 225 ++++++++ .../RapidField.SolidInstruments.Core.csproj | 1 + .../Hashing/HashingProcessor.cs | 2 +- .../Secrets/ExportedSecret.cs | 2 +- .../SecretStoreFilePersistenceVehicle.cs | 2 +- .../AssemblyAttributes.cs | 3 +- .../EventRegister.cs | 32 ++ .../Extensions/ICommandMediatorExtensions.cs | 183 +++++++ .../Extensions/IEventRegisterExtensions.cs | 206 ++++++++ .../IEventRegister.cs | 24 + .../AutofacServiceInjector.cs | 4 +- .../DependencyEngine.cs | 2 +- .../Extensions/ContainerBuilderExtensions.cs | 2 + .../Extensions/ContainerBuilderExtensions.cs | 2 + .../AzureServiceBusListeningFacade.cs | 2 +- .../IServiceCollectionExtensions.cs | 2 + .../IServiceCollectionExtensions.cs | 2 + .../CommandMessageRegister.cs | 41 ++ .../CommandMessages/TextualCommandMessage.cs | 41 ++ .../EventMessageRegister.cs | 41 ++ .../Extensions/ICommandMediatorExtensions.cs | 479 ++++++++++++++++++ .../ICommandMessageRegisterExtensions.cs | 54 ++ .../IEventMessageRegisterExtensions.cs | 13 + .../Extensions/IMessageRegisterExtensions.cs | 13 + .../IRequestMessageRegisterExtensions.cs | 43 ++ .../ICommandMessageRegister.cs | 32 ++ .../IEventMessageRegister.cs | 33 ++ .../IMessageRegister.cs | 48 ++ .../IRequestMessageRegister.cs | 25 + .../MessageRegister.cs | 58 +++ .../RequestMessageRegister.cs | 32 ++ .../TransportPrimitives/PrimitiveMessage.cs | 8 +- .../Channel.cs | 2 +- .../DomainModelHttpApiController.cs | 40 ++ .../HttpApiController.cs | 145 +++++- .../RapidField.SolidInstruments.Web.csproj | 1 + .../Caching/InMemoryCacheClientTests.cs | 64 +++ .../Extensions/ObjectExtensionsTests.cs | 15 + .../SimulatedObject.cs | 8 +- .../SimulatedObject.cs | 8 +- 83 files changed, 4615 insertions(+), 25 deletions(-) create mode 100644 doc/namespaces/RapidField.SolidInstruments.Command.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Core.Caching.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.Extensions.md create mode 100644 src/RapidField.SolidInstruments.Command/CommandRegister.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommandTarget.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationSectionCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationSectionCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationValueCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationValueCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConnectionStringCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/GetConnectionStringCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/Configuration/IGetConfigurationObjectCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/Extensions/ICommandMediatorExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Command/Extensions/ICommandRegisterExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Command/ICommandRegister.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/CacheAccessException.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/CacheClient.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/ICacheClient.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/ICacheReader.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/ICacheWriter.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/IDistributedCacheClient.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/IInMemoryCacheClient.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/InMemoryCacheClient.cs create mode 100644 src/RapidField.SolidInstruments.Core/Caching/InMemoryCachingStrategy.cs create mode 100644 src/RapidField.SolidInstruments.Core/Extensions/InMemoryCachingStrategyExtensions.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/EventRegister.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/Extensions/ICommandMediatorExtensions.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/Extensions/IEventRegisterExtensions.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/IEventRegister.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessageRegister.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/TextualCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessageRegister.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/Extensions/ICommandMediatorExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/Extensions/ICommandMessageRegisterExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/Extensions/IEventMessageRegisterExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/Extensions/IMessageRegisterExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/Extensions/IRequestMessageRegisterExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/ICommandMessageRegister.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/IEventMessageRegister.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/IMessageRegister.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/IRequestMessageRegister.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/MessageRegister.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/RequestMessageRegister.cs create mode 100644 src/RapidField.SolidInstruments.Web/DomainModelHttpApiController.cs create mode 100644 test/RapidField.SolidInstruments.Core.UnitTests/Caching/InMemoryCacheClientTests.cs diff --git a/RapidField.SolidInstruments.sln b/RapidField.SolidInstruments.sln index 1db19f90..61894018 100644 --- a/RapidField.SolidInstruments.sln +++ b/RapidField.SolidInstruments.sln @@ -193,8 +193,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "namespaces", "namespaces", doc\namespaces\RapidField.SolidInstruments.Command.Autofac.md = doc\namespaces\RapidField.SolidInstruments.Command.Autofac.md doc\namespaces\RapidField.SolidInstruments.Command.DotNetNative.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Command.DotNetNative.Extensions.md doc\namespaces\RapidField.SolidInstruments.Command.DotNetNative.md = doc\namespaces\RapidField.SolidInstruments.Command.DotNetNative.md + doc\namespaces\RapidField.SolidInstruments.Command.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Command.Extensions.md doc\namespaces\RapidField.SolidInstruments.Command.md = doc\namespaces\RapidField.SolidInstruments.Command.md doc\namespaces\RapidField.SolidInstruments.Core.ArgumentValidation.md = doc\namespaces\RapidField.SolidInstruments.Core.ArgumentValidation.md + doc\namespaces\RapidField.SolidInstruments.Core.Caching.md = doc\namespaces\RapidField.SolidInstruments.Core.Caching.md doc\namespaces\RapidField.SolidInstruments.Core.Concurrency.md = doc\namespaces\RapidField.SolidInstruments.Core.Concurrency.md doc\namespaces\RapidField.SolidInstruments.Core.Domain.md = doc\namespaces\RapidField.SolidInstruments.Core.Domain.md doc\namespaces\RapidField.SolidInstruments.Core.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Core.Extensions.md @@ -250,6 +252,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "namespaces", "namespaces", doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions.md doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.md = doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.md doc\namespaces\RapidField.SolidInstruments.Messaging.EventMessages.md = doc\namespaces\RapidField.SolidInstruments.Messaging.EventMessages.md + doc\namespaces\RapidField.SolidInstruments.Messaging.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Messaging.Extensions.md doc\namespaces\RapidField.SolidInstruments.Messaging.InMemory.md = doc\namespaces\RapidField.SolidInstruments.Messaging.InMemory.md doc\namespaces\RapidField.SolidInstruments.Messaging.md = doc\namespaces\RapidField.SolidInstruments.Messaging.md doc\namespaces\RapidField.SolidInstruments.Messaging.RabbitMq.md = doc\namespaces\RapidField.SolidInstruments.Messaging.RabbitMq.md diff --git a/doc/namespaces/RapidField.SolidInstruments.Command.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.Command.Extensions.md new file mode 100644 index 00000000..c3eb1437 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Command.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Command.Extensions +summary: *content +--- + + + +Exposes extensions that support the command and mediator patterns. + +
+ +![Command label](../images/Label.Command.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Command +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Command +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Command.md b/doc/namespaces/RapidField.SolidInstruments.Command.md index 79aa29af..e78dac51 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Command.md +++ b/doc/namespaces/RapidField.SolidInstruments.Command.md @@ -108,4 +108,10 @@ Exposes the Autofac integration for the Solid Instruments implementations of the
Exposes the native .NET IoC integration for the Solid Instruments implementations of the command and mediator patterns. +
+ +#### [RapidField.SolidInstruments.Command.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.Extensions.html) + +
+Exposes extensions that support the command and mediator patterns.
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Core.Caching.md b/doc/namespaces/RapidField.SolidInstruments.Core.Caching.md new file mode 100644 index 00000000..5b874c0b --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Core.Caching.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Core.Caching +summary: *content +--- + + + +Exposes configurable clients for accessing cached data. + +
+ +![Core label](../images/Label.Core.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Core +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Core +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Core.md b/doc/namespaces/RapidField.SolidInstruments.Core.md index 538f1f28..0a5dedae 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Core.md +++ b/doc/namespaces/RapidField.SolidInstruments.Core.md @@ -38,6 +38,12 @@ Install-Package RapidField.SolidInstruments.Core Defines a fluent pattern for evaluating argument validity. +#### [RapidField.SolidInstruments.Core.Caching](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Core.Caching.html) + +
+Exposes configurable clients for accessing cached data. +
+ #### [RapidField.SolidInstruments.Core.Concurrency](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Core.Concurrency.html)
diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.Extensions.md new file mode 100644 index 00000000..93c5b4b1 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Messaging.Extensions +summary: *content +--- + + + +Exposes extensions that support messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.md index 93daf899..1278aedd 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Messaging.md +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.md @@ -56,6 +56,12 @@ Exposes the native .NET IoC integration for the Solid Instruments messaging abst Exposes various event message types.
+#### [RapidField.SolidInstruments.Messaging.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Extensions.html) + +
+Exposes extensions that support messaging abstractions. +
+ #### [RapidField.SolidInstruments.Messaging.Service](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Service.html)
diff --git a/en-US_User.dic b/en-US_User.dic index 8ee46e27..340dd58f 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -47,6 +47,7 @@ composable configurator configurators contentfiles +creational cryptographic cryptographically csharp diff --git a/example/RapidField.SolidInstruments.Example.Domain.AccessControl.Service/ApplicationServiceExecutor.cs b/example/RapidField.SolidInstruments.Example.Domain.AccessControl.Service/ApplicationServiceExecutor.cs index 627242b0..5856a360 100644 --- a/example/RapidField.SolidInstruments.Example.Domain.AccessControl.Service/ApplicationServiceExecutor.cs +++ b/example/RapidField.SolidInstruments.Example.Domain.AccessControl.Service/ApplicationServiceExecutor.cs @@ -7,6 +7,7 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.InversionOfControl; using RapidField.SolidInstruments.Messaging.DotNetNative.Service; +using RapidField.SolidInstruments.Messaging.Extensions; using RapidField.SolidInstruments.Messaging.Service; using RapidField.SolidInstruments.Service; using System; @@ -269,7 +270,7 @@ private static void TestServiceBusConnectivity(IDependencyScope dependencyScope) { var mediator = dependencyScope.Resolve(); var pingRequestCorrelationIdentifier = Guid.NewGuid(); - var pingResponseMessage = mediator.Process(new PingRequestMessage(pingRequestCorrelationIdentifier)); + var pingResponseMessage = mediator.TransmitRequestMessage(register => register.Ping(pingRequestCorrelationIdentifier)); if (pingResponseMessage is null || pingResponseMessage.CorrelationIdentifier != pingRequestCorrelationIdentifier) { diff --git a/example/RapidField.SolidInstruments.Example.Domain.Identity.Service/ApplicationServiceExecutor.cs b/example/RapidField.SolidInstruments.Example.Domain.Identity.Service/ApplicationServiceExecutor.cs index f74f5453..e78fdb8b 100644 --- a/example/RapidField.SolidInstruments.Example.Domain.Identity.Service/ApplicationServiceExecutor.cs +++ b/example/RapidField.SolidInstruments.Example.Domain.Identity.Service/ApplicationServiceExecutor.cs @@ -7,6 +7,7 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.InversionOfControl; using RapidField.SolidInstruments.Messaging.DotNetNative.Service; +using RapidField.SolidInstruments.Messaging.Extensions; using RapidField.SolidInstruments.Messaging.Service; using RapidField.SolidInstruments.Service; using System; @@ -196,7 +197,7 @@ private static void TestServiceBusConnectivity(IDependencyScope dependencyScope) { var mediator = dependencyScope.Resolve(); var pingRequestCorrelationIdentifier = Guid.NewGuid(); - var pingResponseMessage = mediator.Process(new PingRequestMessage(pingRequestCorrelationIdentifier)); + var pingResponseMessage = mediator.TransmitRequestMessage(register => register.Ping(pingRequestCorrelationIdentifier)); if (pingResponseMessage is null || pingResponseMessage.CorrelationIdentifier != pingRequestCorrelationIdentifier) { diff --git a/src/RapidField.SolidInstruments.Collections/BinaryTreeNode.cs b/src/RapidField.SolidInstruments.Collections/BinaryTreeNode.cs index d67698dd..0745a961 100644 --- a/src/RapidField.SolidInstruments.Collections/BinaryTreeNode.cs +++ b/src/RapidField.SolidInstruments.Collections/BinaryTreeNode.cs @@ -118,7 +118,7 @@ public BinaryTreeNode(T value, BinaryTreeNode leftChild, BinaryTreeNode ri private BinaryTreeNode(T value, BinaryTreeNode leftChild, BinaryTreeNode rightChild, Boolean rejectNullLeftChild, Boolean rejectNullRightChild) : base(value, 2) { - if ((leftChild is null) == false) + if (leftChild is not null) { if (AddChild(leftChild) == false) { @@ -130,7 +130,7 @@ private BinaryTreeNode(T value, BinaryTreeNode leftChild, BinaryTreeNode r leftChild.RejectIf().IsNull(nameof(leftChild)); } - if ((rightChild is null) == false) + if (rightChild is not null) { if (AddChild(rightChild) == false) { diff --git a/src/RapidField.SolidInstruments.Command.Autofac/AutofacCommandHandlerModule.cs b/src/RapidField.SolidInstruments.Command.Autofac/AutofacCommandHandlerModule.cs index 20d308da..31c261aa 100644 --- a/src/RapidField.SolidInstruments.Command.Autofac/AutofacCommandHandlerModule.cs +++ b/src/RapidField.SolidInstruments.Command.Autofac/AutofacCommandHandlerModule.cs @@ -7,6 +7,7 @@ using RapidField.SolidInstruments.Command.Autofac.Extensions; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.InversionOfControl.Autofac; +using RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; @@ -111,6 +112,9 @@ protected AutofacCommandHandlerModule(IConfiguration applicationConfiguration, A /// protected sealed override void Configure(ContainerBuilder configurator, IConfiguration applicationConfiguration) { + configurator.RegisterApplicationConfiguration(applicationConfiguration); + configurator.RegisterConfigurationCommandHandlers(); + foreach (var commandHandlerType in MatchedTypes) { var commandHandlerInterfaceType = commandHandlerType.GetInterfaces().Where(implementedInterface => CommandHandlerInterfaceType.IsAssignableFrom(implementedInterface) && implementedInterface.GenericTypeArguments.Length == 1).FirstOrDefault(); diff --git a/src/RapidField.SolidInstruments.Command.Autofac/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.Command.Autofac/Extensions/ContainerBuilderExtensions.cs index 43794122..850df9de 100644 --- a/src/RapidField.SolidInstruments.Command.Autofac/Extensions/ContainerBuilderExtensions.cs +++ b/src/RapidField.SolidInstruments.Command.Autofac/Extensions/ContainerBuilderExtensions.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using Autofac; +using RapidField.SolidInstruments.Command.Configuration; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using System; @@ -58,6 +59,19 @@ public static void RegisterCommandHandler(this ContainerBuilder target, Type com target.RegisterType(commandHandlerType.RejectIf().IsNull(nameof(commandHandlerType)).OrIf().IsNotSupportedType(commandHandlerInterfaceType, nameof(commandHandlerType))).IfNotRegistered(commandHandlerType).As(commandHandlerInterfaceType).InstancePerDependency(); } + /// + /// Registers transient command handlers for retrieving configuration sections and values. + /// + /// + /// The current . + /// + public static void RegisterConfigurationCommandHandlers(this ContainerBuilder target) + { + target.RegisterCommandHandler(); + target.RegisterCommandHandler(); + target.RegisterCommandHandler(); + } + /// /// Represents the type. /// diff --git a/src/RapidField.SolidInstruments.Command.DotNetNative/DotNetNativeCommandHandlerModule.cs b/src/RapidField.SolidInstruments.Command.DotNetNative/DotNetNativeCommandHandlerModule.cs index 3338b7ab..96f17185 100644 --- a/src/RapidField.SolidInstruments.Command.DotNetNative/DotNetNativeCommandHandlerModule.cs +++ b/src/RapidField.SolidInstruments.Command.DotNetNative/DotNetNativeCommandHandlerModule.cs @@ -7,6 +7,7 @@ using RapidField.SolidInstruments.Command.DotNetNative.Extensions; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; @@ -111,6 +112,9 @@ protected DotNetNativeCommandHandlerModule(IConfiguration applicationConfigurati /// protected sealed override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { + configurator.AddApplicationConfiguration(applicationConfiguration); + configurator.AddConfigurationCommandHandlers(); + foreach (var commandHandlerType in MatchedTypes) { var commandHandlerInterfaceType = commandHandlerType.GetInterfaces().Where(implementedInterface => CommandHandlerInterfaceType.IsAssignableFrom(implementedInterface) && implementedInterface.GenericTypeArguments.Length == 1).FirstOrDefault(); diff --git a/src/RapidField.SolidInstruments.Command.DotNetNative/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.Command.DotNetNative/Extensions/IServiceCollectionExtensions.cs index 0762ca62..81835b35 100644 --- a/src/RapidField.SolidInstruments.Command.DotNetNative/Extensions/IServiceCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.Command.DotNetNative/Extensions/IServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using RapidField.SolidInstruments.Command.Configuration; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using System; @@ -66,6 +67,23 @@ public static IServiceCollection AddCommandHandler(this IServiceCollection targe return target; } + /// + /// Registers transient command handlers for retrieving configuration sections and values. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddConfigurationCommandHandlers(this IServiceCollection target) + { + target.AddCommandHandler(); + target.AddCommandHandler(); + target.AddCommandHandler(); + return target; + } + /// /// Represents the type. /// diff --git a/src/RapidField.SolidInstruments.Command/CommandHandlingException.cs b/src/RapidField.SolidInstruments.Command/CommandHandlingException.cs index dedb235e..31164163 100644 --- a/src/RapidField.SolidInstruments.Command/CommandHandlingException.cs +++ b/src/RapidField.SolidInstruments.Command/CommandHandlingException.cs @@ -45,6 +45,18 @@ public CommandHandlingException(String message) return; } + /// + /// Initializes a new instance of the + /// + /// + /// The exception that is the cause of the current exception. + /// + public CommandHandlingException(Exception innerException) + : this(commandType: null, innerException: innerException) + { + return; + } + /// /// Initializes a new instance of the /// diff --git a/src/RapidField.SolidInstruments.Command/CommandRegister.cs b/src/RapidField.SolidInstruments.Command/CommandRegister.cs new file mode 100644 index 00000000..f97903c6 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/CommandRegister.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents an extensible catalog of available commands. + /// + /// + /// is the default implementation of . + /// + public sealed class CommandRegister : ICommandRegister + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + private CommandRegister() + { + return; + } + + /// + /// Represents a singleton instance of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly ICommandRegister Instance = new CommandRegister(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommand.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommand.cs new file mode 100644 index 00000000..5053387d --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommand.cs @@ -0,0 +1,70 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Represents a command that retrieves and deserializes a configuration value or section. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the configuration object that is produced by interrogating the specified configuration key and target. + /// + [DataContract] + public abstract class GetConfigurationObjectCommand : Command, IGetConfigurationObjectCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A textual key for the configuration value or section to retrieve. + /// + /// + /// The target type of the command. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + protected GetConfigurationObjectCommand(String key, GetConfigurationObjectCommandTarget target) + : base() + { + Key = key.RejectIf().IsNullOrEmpty(nameof(key)); + Target = target.RejectIf().IsEqualToValue(GetConfigurationObjectCommandTarget.Unspecified, nameof(target)); + } + + /// + /// Gets or sets a textual key for the configuration value or section to retrieve. + /// + [DataMember] + public String Key + { + get; + set; + } + + /// + /// Gets or sets the target type of the current . + /// + [DataMember] + public GetConfigurationObjectCommandTarget Target + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommandHandler.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommandHandler.cs new file mode 100644 index 00000000..b595f207 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommandHandler.cs @@ -0,0 +1,98 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Processes a single . + /// + /// + /// The type of the command that is processed by the handler. + /// + /// + /// The type of the configuration object that is produced by interrogating the specified configuration key and target. + /// + public abstract class GetConfigurationObjectCommandHandler : CommandHandler + where TCommand : class, IGetConfigurationObjectCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// Configuration information for the application. + /// + /// + /// is -or- is + /// . + /// + protected GetConfigurationObjectCommandHandler(ICommandMediator mediator, IConfiguration applicationConfiguration) + : base(mediator) + { + ApplicationConfiguration = applicationConfiguration.RejectIf().IsNull(nameof(applicationConfiguration)).TargetArgument; + } + + /// + /// Converts the specified configuration object to the appropriate result type. + /// + /// + /// The configuration value or section to convert. + /// + /// + /// The converted result object. + /// + protected abstract TResult ConvertConfigurationObject(Object configurationObject); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not disposal was invoked by user code. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// Do not process using , as doing so will generally result in + /// infinite-looping; is exposed to this method to facilitate sub-command processing. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// The result that is emitted when processing the command. + /// + protected sealed override TResult Process(TCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => command.Target switch + { + GetConfigurationObjectCommandTarget.ConnectionString => ConvertConfigurationObject(ApplicationConfiguration.GetConnectionString(command.Key)), + GetConfigurationObjectCommandTarget.Section => ConvertConfigurationObject(ApplicationConfiguration.GetSection(command.Key)), + GetConfigurationObjectCommandTarget.Value => ConvertConfigurationObject(ApplicationConfiguration.GetValue(command.Key)), + _ => throw new UnsupportedSpecificationException($"The specified configuration command target, {command.Target}, is not supported.") + }; + + /// + /// Represents configuration information for the application. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly IConfiguration ApplicationConfiguration; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommandTarget.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommandTarget.cs new file mode 100644 index 00000000..474bd266 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationObjectCommandTarget.cs @@ -0,0 +1,40 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Specifies the target type of an . + /// + [DataContract] + public enum GetConfigurationObjectCommandTarget : Int32 + { + /// + /// The target type is not specified. + /// + [EnumMember] + Unspecified = 0, + + /// + /// The associated command targets a connection string with a specified key. + /// + [EnumMember] + ConnectionString = 1, + + /// + /// The associated command targets a configuration section with a specified name. + /// + [EnumMember] + Section = 2, + + /// + /// The associated command targets a configuration value with a specified key. + /// + [EnumMember] + Value = 3 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationSectionCommand.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationSectionCommand.cs new file mode 100644 index 00000000..1724fa6b --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationSectionCommand.cs @@ -0,0 +1,69 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Diagnostics; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Represents a command that retrieves and deserializes a configuration section. + /// + [DataContract] + public sealed class GetConfigurationSectionCommand : GetConfigurationObjectCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A textual key for the configuration section to retrieve. + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + internal GetConfigurationSectionCommand(String key) + : base(key, GetConfigurationObjectCommandTarget.Section) + { + return; + } + + /// + /// Creates and processes a command that retrieves and deserializes a configuration section. + /// + /// + /// The type of the configuration section. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration section to retrieve. + /// + /// + /// The resulting instance, or if the section was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static T Process(ICommandMediator mediator, String key) + where T : class => mediator.RejectIf().IsNull(nameof(mediator)).TargetArgument.Process(new GetConfigurationSectionCommand(key)).Get(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationSectionCommandHandler.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationSectionCommandHandler.cs new file mode 100644 index 00000000..50bb5616 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationSectionCommandHandler.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using System; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Processes a single . + /// + public sealed class GetConfigurationSectionCommandHandler : GetConfigurationObjectCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// Configuration information for the application. + /// + /// + /// is -or- is + /// . + /// + public GetConfigurationSectionCommandHandler(ICommandMediator mediator, IConfiguration applicationConfiguration) + : base(mediator, applicationConfiguration) + { + return; + } + + /// + /// Converts the specified configuration object to the appropriate result type. + /// + /// + /// The configuration value or section to convert. + /// + /// + /// The converted result object. + /// + protected override IConfigurationSection ConvertConfigurationObject(Object configurationObject) => configurationObject as IConfigurationSection; + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not disposal was invoked by user code. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationValueCommand.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationValueCommand.cs new file mode 100644 index 00000000..fd44823f --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationValueCommand.cs @@ -0,0 +1,350 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Diagnostics; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Represents a command that retrieves and deserializes a configuration value. + /// + [DataContract] + public sealed class GetConfigurationValueCommand : GetConfigurationObjectCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + internal GetConfigurationValueCommand(String key) + : base(key, GetConfigurationObjectCommandTarget.Value) + { + return; + } + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Boolean ProcessBoolean(ICommandMediator mediator, String key) => Boolean.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static DateTime ProcessDateTime(ICommandMediator mediator, String key) => DateTime.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Decimal ProcessDecimal(ICommandMediator mediator, String key) => Decimal.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Double ProcessDouble(ICommandMediator mediator, String key) => Double.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Guid ProcessGuid(ICommandMediator mediator, String key) => Guid.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Int16 ProcessInt16(ICommandMediator mediator, String key) => Int16.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Int32 ProcessInt32(ICommandMediator mediator, String key) => Int32.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Int64 ProcessInt64(ICommandMediator mediator, String key) => Int64.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Single ProcessSingle(ICommandMediator mediator, String key) => Single.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static String ProcessString(ICommandMediator mediator, String key) => mediator.RejectIf().IsNull(nameof(mediator)).TargetArgument.Process(new GetConfigurationValueCommand(key)); + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static TimeSpan ProcessTimeSpan(ICommandMediator mediator, String key) => TimeSpan.TryParse(ProcessString(mediator, key), out var result) ? result : default; + + /// + /// Creates and processes a command that retrieves and deserializes a configuration value. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the configuration value to retrieve. + /// + /// + /// The resulting instance, or if the value was not resolved. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Uri ProcessUri(ICommandMediator mediator, String key) => Uri.TryCreate(ProcessString(mediator, key), UriKind.RelativeOrAbsolute, out var result) ? result : default; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationValueCommandHandler.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationValueCommandHandler.cs new file mode 100644 index 00000000..20d3834e --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConfigurationValueCommandHandler.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using System; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Processes a single . + /// + public sealed class GetConfigurationValueCommandHandler : GetConfigurationObjectCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// Configuration information for the application. + /// + /// + /// is -or- is + /// . + /// + public GetConfigurationValueCommandHandler(ICommandMediator mediator, IConfiguration applicationConfiguration) + : base(mediator, applicationConfiguration) + { + return; + } + + /// + /// Converts the specified configuration object to the appropriate result type. + /// + /// + /// The configuration value or section to convert. + /// + /// + /// The converted result object. + /// + protected override String ConvertConfigurationObject(Object configurationObject) => configurationObject as String; + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not disposal was invoked by user code. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConnectionStringCommand.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConnectionStringCommand.cs new file mode 100644 index 00000000..b0543126 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConnectionStringCommand.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Diagnostics; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Represents a command that retrieves a connection string. + /// + [DataContract] + public sealed class GetConnectionStringCommand : GetConfigurationObjectCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A textual key for the connection string to retrieve. + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + internal GetConnectionStringCommand(String name) + : base(name, GetConfigurationObjectCommandTarget.ConnectionString) + { + return; + } + + /// + /// Creates and processes a command that retrieves a connection string. + /// + /// + /// A processing intermediary that is used to process the command. + /// + /// + /// A textual key for the connection string to retrieve. + /// + /// + /// The resulting connection string, or + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static String Process(ICommandMediator mediator, String name) => mediator.RejectIf().IsNull(nameof(mediator)).TargetArgument.Process(new GetConnectionStringCommand(name)); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/GetConnectionStringCommandHandler.cs b/src/RapidField.SolidInstruments.Command/Configuration/GetConnectionStringCommandHandler.cs new file mode 100644 index 00000000..084ff68f --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/GetConnectionStringCommandHandler.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using System; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Processes a single . + /// + public sealed class GetConnectionStringCommandHandler : GetConfigurationObjectCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// Configuration information for the application. + /// + /// + /// is -or- is + /// . + /// + public GetConnectionStringCommandHandler(ICommandMediator mediator, IConfiguration applicationConfiguration) + : base(mediator, applicationConfiguration) + { + return; + } + + /// + /// Converts the specified configuration object to the appropriate result type. + /// + /// + /// The configuration value or section to convert. + /// + /// + /// The converted result object. + /// + protected override String ConvertConfigurationObject(Object configurationObject) => configurationObject as String; + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not disposal was invoked by user code. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Configuration/IGetConfigurationObjectCommand.cs b/src/RapidField.SolidInstruments.Command/Configuration/IGetConfigurationObjectCommand.cs new file mode 100644 index 00000000..4d19dba2 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Configuration/IGetConfigurationObjectCommand.cs @@ -0,0 +1,38 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command.Configuration +{ + /// + /// Represents a command that retrieves and deserializes a configuration value or section. + /// + /// + /// The type of the configuration object that is produced by interrogating the specified configuration key and target. + /// + public interface IGetConfigurationObjectCommand : ICommand + { + /// + /// Gets or sets a textual key for the configuration value or section to retrieve. + /// + [DataMember] + public String Key + { + get; + set; + } + + /// + /// Gets or sets the target type of the current . + /// + [DataMember] + public GetConfigurationObjectCommandTarget Target + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Extensions/ICommandMediatorExtensions.cs b/src/RapidField.SolidInstruments.Command/Extensions/ICommandMediatorExtensions.cs new file mode 100644 index 00000000..a930a930 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Extensions/ICommandMediatorExtensions.cs @@ -0,0 +1,200 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Command.Extensions +{ + /// + /// Extends the interface with command handling features. + /// + public static class ICommandMediatorExtensions + { + /// + /// Processes the specified . + /// + /// + /// The type of the result that is produced by processing the command. + /// + /// + /// The current . + /// + /// + /// A function that returns the command to process. + /// + /// + /// The result that is produced by processing the command. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static TResult Process(this ICommandMediator target, Func> createCommandFunction) + { + try + { + return target.Process(createCommandFunction.RejectIf().IsNull(nameof(createCommandFunction)).TargetArgument(CommandRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(exception); + } + } + + /// + /// Processes the specified . + /// + /// + /// The type of the command to process. + /// + /// + /// The type of the result that is produced by processing the command. + /// + /// + /// The current . + /// + /// + /// A function that returns the command to process. + /// + /// + /// The result that is produced by processing the command. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static TResult Process(this ICommandMediator target, Func createCommandFunction) + where TCommand : class, ICommand + { + try + { + return target.Process(createCommandFunction.RejectIf().IsNull(nameof(createCommandFunction)).TargetArgument(CommandRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TCommand), exception); + } + } + + /// + /// Asynchronously processes the specified . + /// + /// + /// The type of the result that is produced by processing the command. + /// + /// + /// The current . + /// + /// + /// A function that returns the command to process. + /// + /// + /// A task representing the asynchronous operation and containing the result that is produced by processing the command. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Task ProcessAsync(this ICommandMediator target, Func> createCommandFunction) + { + try + { + return target.ProcessAsync(createCommandFunction.RejectIf().IsNull(nameof(createCommandFunction)).TargetArgument(CommandRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(exception); + } + } + + /// + /// Asynchronously processes the specified . + /// + /// + /// The type of the command to process. + /// + /// + /// The type of the result that is produced by processing the command. + /// + /// + /// The current . + /// + /// + /// A function that returns the command to process. + /// + /// + /// A task representing the asynchronous operation and containing the result that is produced by processing the command. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the command. + /// + /// + /// is disposed. + /// + public static Task ProcessAsync(this ICommandMediator target, Func createCommandFunction) + where TCommand : class, ICommand + { + try + { + return target.ProcessAsync(createCommandFunction.RejectIf().IsNull(nameof(createCommandFunction)).TargetArgument(CommandRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TCommand), exception); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Extensions/ICommandRegisterExtensions.cs b/src/RapidField.SolidInstruments.Command/Extensions/ICommandRegisterExtensions.cs new file mode 100644 index 00000000..7c33c204 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/Extensions/ICommandRegisterExtensions.cs @@ -0,0 +1,114 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command.Configuration; +using RapidField.SolidInstruments.Core; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Command.Extensions +{ + /// + /// Extends the interface with general purpose command creation methods. + /// + public static class ICommandRegisterExtensions + { + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// The textual command value. + /// + /// + /// is . + /// + /// + /// A new . + /// + public static TextualCommand FromText(this ICommandRegister target, String value) => new TextualCommand(value); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// The textual command value. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + /// + /// A new . + /// + public static TextualCommand FromText(this ICommandRegister target, String value, IEnumerable labels) => new TextualCommand(value, labels); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A textual key for the configuration section to retrieve. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public static GetConfigurationSectionCommand GetConfigurationSection(this ICommandRegister target, String key) => new GetConfigurationSectionCommand(key); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A textual key for the configuration section to retrieve. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public static GetConfigurationValueCommand GetConfigurationValue(this ICommandRegister target, String key) => new GetConfigurationValueCommand(key); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A textual key for the connection string to retrieve. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public static GetConnectionStringCommand GetConnectionString(this ICommandRegister target, String name) => new GetConnectionStringCommand(name); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/ICommandRegister.cs b/src/RapidField.SolidInstruments.Command/ICommandRegister.cs new file mode 100644 index 00000000..c49b2aac --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/ICommandRegister.cs @@ -0,0 +1,23 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command.Extensions; +using System; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents an extensible catalog of available commands. + /// + /// + /// Consuming libraries may use as an extension method target to expose creational methods for + /// custom commands. See for examples. An instance of an + /// is made available by + /// and + /// . + /// + public interface ICommandRegister + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/RapidField.SolidInstruments.Command.csproj b/src/RapidField.SolidInstruments.Command/RapidField.SolidInstruments.Command.csproj index ed6810df..ca48331f 100644 --- a/src/RapidField.SolidInstruments.Command/RapidField.SolidInstruments.Command.csproj +++ b/src/RapidField.SolidInstruments.Command/RapidField.SolidInstruments.Command.csproj @@ -38,6 +38,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + + + diff --git a/src/RapidField.SolidInstruments.Core/Caching/CacheAccessException.cs b/src/RapidField.SolidInstruments.Core/Caching/CacheAccessException.cs new file mode 100644 index 00000000..d5cc9b13 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/CacheAccessException.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Caching +{ + /// + /// Represents an exception that is raised when a caching operation fails. + /// + public class CacheAccessException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public CacheAccessException() + : this(cacheValueType: null) + { + return; + } + + /// + /// Initializes a new instance of the + /// + /// + /// The type of the cache value that was being processed when the exception was raised. + /// + public CacheAccessException(Type cacheValueType) + : this(cacheValueType: cacheValueType, innerException: null) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The error message that explains the reason for the exception. + /// + public CacheAccessException(String message) + : this(message: message, innerException: null) + { + return; + } + + /// + /// Initializes a new instance of the + /// + /// + /// The type of the cache value that was being processed when the exception was raised. + /// + /// + /// The exception that is the cause of the current exception. + /// + public CacheAccessException(Type cacheValueType, Exception innerException) + : this(cacheValueType is null ? "An exception was raised while accessing the cache or processing the cached object." : $"An exception was raised while processing a cached instance of type {cacheValueType.FullName}.", innerException) + { + CacheValueType = cacheValueType; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception. + /// + public CacheAccessException(String message, Exception innerException) + : base(message, innerException) + { + return; + } + + /// + /// Gets the type of the cache value that was being processed when the exception was raised. + /// + public Type CacheValueType + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Caching/CacheClient.cs b/src/RapidField.SolidInstruments.Core/Caching/CacheClient.cs new file mode 100644 index 00000000..5030714c --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/CacheClient.cs @@ -0,0 +1,344 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Core.Caching +{ + /// + /// Represents a read-write client for accessing strongly-typed cached objects using textual keys. + /// + /// + /// is the default implementation of . + /// + public abstract class CacheClient : Instrument, ICacheClient + { + /// + /// Initializes a new instance of the class. + /// + protected CacheClient() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The concurrency control mode that is used to manage state. The default value is + /// . + /// + /// + /// is equal to . + /// + protected CacheClient(ConcurrencyControlMode stateControlMode) + : base(stateControlMode) + { + return; + } + + /// + /// Removes the cached object using the specified textual key. + /// + /// + /// A textual key which uniquely identifies the cached object to remove. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + public void Invalidate(String key) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + _ = key.RejectIf().IsNullOrEmpty(nameof(key)); + + try + { + Invalidate(key, controlToken); + } + catch (CacheAccessException) + { + throw; + } + catch (Exception exception) + { + throw new CacheAccessException("An exception was raised while invalidating the cached object.", exception); + } + } + } + + /// + /// Attempts to retrieve the cached object using the specified textual key and, failing that, invokes the specified function + /// to produce the object and adds it to the cache. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies the object in the cache. + /// + /// + /// A function that produces the object if it is not found in the cache. + /// + /// + /// The resulting cached object. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// An exception was raised while attempting to access the cache or produce the value. + /// + /// + /// The object is disposed. + /// + public TValue Process(String key, Func produceValueFunction) + where TValue : class + { + _ = produceValueFunction.RejectIf().IsNull(nameof(produceValueFunction)); + + if (TryRead(key, out var value)) + { + return value; + } + + try + { + value = produceValueFunction(); + + if (value is not null) + { + Write(key, value); + } + + return value; + } + catch (CacheAccessException) + { + throw; + } + catch (Exception exception) + { + throw new CacheAccessException(typeof(TValue), exception); + } + } + + /// + /// Asynchronously attempts to retrieve the cached object using the specified textual key and, failing that, invokes the + /// specified function to produce the object and adds it to the cache. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies the object in the cache. + /// + /// + /// A function that produces the object if it is not found in the cache. + /// + /// + /// A task representing the asynchronous operation and containing the resulting cached object. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// An exception was raised while attempting to access the cache or produce the value. + /// + /// + /// The object is disposed. + /// + public Task ProcessAsync(String key, Func produceValueFunction) + where TValue : class => Task.Factory.StartNew(() => Process(key, produceValueFunction)); + + /// + /// Attempts to retrieve the cached object using the specified textual key. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies in the cache. + /// + /// + /// The object to retrieve from the cache, or if the object was not successfully retrieved. + /// + /// + /// if the object was successfully retrieved, otherwise . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + public Boolean TryRead(String key, out TValue value) + where TValue : class + { + value = null; + RejectIfDisposed(); + _ = key.RejectIf().IsNullOrEmpty(nameof(key)); + + try + { + value = TryRead(key); + } + catch (CacheAccessException) + { + throw; + } + catch (Exception exception) + { + throw new CacheAccessException(typeof(TValue), exception); + } + + return value is null ? false : true; + } + + /// + /// Adds or updates the specified object using the specified key. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies in the cache. + /// + /// + /// The object to add or update. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + public void Write(String key, TValue value) + where TValue : class + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + _ = key.RejectIf().IsNullOrEmpty(nameof(key)); + _ = value.RejectIf().IsNull(nameof(value)); + + try + { + Write(key, value, controlToken); + } + catch (CacheAccessException) + { + throw; + } + catch (Exception exception) + { + throw new CacheAccessException(typeof(TValue), exception); + } + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not disposal was invoked by user code. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Removes the cached object using the specified textual key. + /// + /// + /// A textual key which uniquely identifies the cached object to remove. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + protected abstract void Invalidate(String key, IConcurrencyControlToken controlToken); + + /// + /// Attempts to retrieve the cached object using the specified textual key. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies a value in the cache. + /// + /// + /// The object retrieved from the cache, or if the object was not successfully retrieved. + /// + protected abstract TValue TryRead(String key) + where TValue : class; + + /// + /// Adds or updates the specified object using the specified key. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies in the cache. + /// + /// + /// The object to add or update. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void Write(String key, TValue value, IConcurrencyControlToken controlToken) + where TValue : class; + + /// + /// Gets a value indicating whether or not the client is operative. + /// + public virtual Boolean IsOperative => true; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Caching/ICacheClient.cs b/src/RapidField.SolidInstruments.Core/Caching/ICacheClient.cs new file mode 100644 index 00000000..68855ffa --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/ICacheClient.cs @@ -0,0 +1,107 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Core.Caching +{ + /// + /// Represents a read-write client for accessing strongly-typed cached objects using textual keys. + /// + public interface ICacheClient : ICacheReader, ICacheWriter + { + /// + /// Removes the cached object using the specified textual key. + /// + /// + /// A textual key which uniquely identifies the cached object to remove. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + public void Invalidate(String key); + + /// + /// Attempts to retrieve the cached object using the specified textual key and, failing that, invokes the specified function + /// to produce the object and adds it to the cache. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies the object in the cache. + /// + /// + /// A function that produces the object if it is not found in the cache. + /// + /// + /// The resulting cached object. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// An exception was raised while attempting to access the cache or produce the value. + /// + /// + /// The object is disposed. + /// + public TValue Process(String key, Func produceValueFunction) + where TValue : class; + + /// + /// Asynchronously attempts to retrieve the cached object using the specified textual key and, failing that, invokes the + /// specified function to produce the object and adds it to the cache. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies the object in the cache. + /// + /// + /// A function that produces the object if it is not found in the cache. + /// + /// + /// A task representing the asynchronous operation and containing the resulting cached object. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// An exception was raised while attempting to access the cache or produce the value. + /// + /// + /// The object is disposed. + /// + public Task ProcessAsync(String key, Func produceValueFunction) + where TValue : class; + + /// + /// Gets a value indicating whether or not the client is operative. + /// + public Boolean IsOperative + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Caching/ICacheReader.cs b/src/RapidField.SolidInstruments.Core/Caching/ICacheReader.cs new file mode 100644 index 00000000..f46127db --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/ICacheReader.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Caching +{ + /// + /// Represents a read-only client for accessing strongly-typed cached objects using textual keys. + /// + public interface ICacheReader : IDisposable + { + /// + /// Attempts to retrieve the cached object using the specified textual key. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies in the cache. + /// + /// + /// The object to retrieve from the cache, or if the object was not successfully retrieved. + /// + /// + /// if the object was successfully retrieved, otherwise . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + public Boolean TryRead(String key, out TValue value) + where TValue : class; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Caching/ICacheWriter.cs b/src/RapidField.SolidInstruments.Core/Caching/ICacheWriter.cs new file mode 100644 index 00000000..53ff4508 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/ICacheWriter.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Caching +{ + /// + /// Represents a write-only client for accessing strongly-typed cached objects using textual keys. + /// + public interface ICacheWriter : IDisposable + { + /// + /// Adds or updates the specified object using the specified key. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies in the cache. + /// + /// + /// The object to add or update. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + public void Write(String key, TValue value) + where TValue : class; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Caching/IDistributedCacheClient.cs b/src/RapidField.SolidInstruments.Core/Caching/IDistributedCacheClient.cs new file mode 100644 index 00000000..dbb88358 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/IDistributedCacheClient.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Core.Caching +{ + /// + /// Represents a read-write client for accessing strongly-typed, remotely cached objects using textual keys. + /// + public interface IDistributedCacheClient : ICacheClient + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Caching/IInMemoryCacheClient.cs b/src/RapidField.SolidInstruments.Core/Caching/IInMemoryCacheClient.cs new file mode 100644 index 00000000..0f28f8a2 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/IInMemoryCacheClient.cs @@ -0,0 +1,25 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Caching +{ + /// + /// Represents a read-write client for accessing strongly typed, locally cached objects using textual keys. + /// + public interface IInMemoryCacheClient : ICacheClient + { + /// + /// Removes all cached objects. + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + public void Clear(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Caching/InMemoryCacheClient.cs b/src/RapidField.SolidInstruments.Core/Caching/InMemoryCacheClient.cs new file mode 100644 index 00000000..83b1bcd3 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/InMemoryCacheClient.cs @@ -0,0 +1,243 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Extensions; +using System; +using System.Diagnostics; +using System.Threading; + +namespace RapidField.SolidInstruments.Core.Caching +{ + /// + /// Represents a read-write client for accessing strongly typed, locally cached objects using textual keys. + /// + /// + /// is the default implementation of . + /// + public sealed class InMemoryCacheClient : CacheClient, IInMemoryCacheClient + { + /// + /// Initializes a new instance of the class. + /// + public InMemoryCacheClient() + : this(DefaultStrategy) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value specifying the cache access and management behavior of the client. The default value is + /// . + /// + /// + /// is equal to . + /// + public InMemoryCacheClient(InMemoryCachingStrategy strategy) + : base(strategy == InMemoryCachingStrategy.NoCaching ? ConcurrencyControlMode.Unconstrained : ConcurrencyControlMode.ProcessorCountSemaphore) + { + Strategy = strategy.RejectIf().IsEqualToValue(InMemoryCachingStrategy.Unspecified, nameof(strategy)); + LazyCache = IsOperative ? new Lazy(InitializeCache, LazyThreadSafetyMode.ExecutionAndPublication) : null; + LazyCacheOptions = IsOperative ? new Lazy>(InitializeCacheOptions, LazyThreadSafetyMode.ExecutionAndPublication) : null; + } + + /// + /// Removes all cached objects. + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + public void Clear() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + try + { + if (IsOperative) + { + Cache?.Compact(1d); + } + } + catch (Exception exception) + { + throw new CacheAccessException("An exception was raised while clearing the cache.", exception); + } + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not disposal was invoked by user code. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (IsOperative) + { + LazyCache?.Dispose(); + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Removes the cached object using the specified textual key. + /// + /// + /// A textual key which uniquely identifies the cached object to remove. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// An exception was raised while attempting to access the cache. + /// + /// + /// The object is disposed. + /// + protected override void Invalidate(String key, IConcurrencyControlToken controlToken) + { + if (IsOperative) + { + Cache?.Remove(key); + } + } + + /// + /// Attempts to retrieve the cached object using the specified textual key. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies a value in the cache. + /// + /// + /// The object retrieved from the cache, or if the object was not successfully retrieved. + /// + protected override TValue TryRead(String key) + where TValue : class + { + if (IsOperative) + { + return Cache.TryGetValue(key, out var value) ? value as TValue : null; + } + + return null; + } + + /// + /// Adds or updates the specified object using the specified key. + /// + /// + /// The type of the cached object. + /// + /// + /// A textual key which uniquely identifies in the cache. + /// + /// + /// The object to add or update. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Write(String key, TValue value, IConcurrencyControlToken controlToken) + where TValue : class + { + if (IsOperative) + { + var options = Strategy.ToCacheEntryOptions(); + options.Size = value.CalculateSizeInBytes(); + _ = Cache?.Set(key, value, options); + } + } + + /// + /// Initializes the underlying memory cache. + /// + /// + /// A new . + /// + [DebuggerHidden] + private MemoryCache InitializeCache() => new MemoryCache(CacheOptions); + + /// + /// Initializes the configuration options for . + /// + /// + /// A new . + /// + [DebuggerHidden] + private IOptions InitializeCacheOptions() => Strategy.ToCacheOptions(); + + /// + /// Gets a value indicating whether or not the client is operative. + /// + public override Boolean IsOperative => Strategy != InMemoryCachingStrategy.NoCaching; + + /// + /// Gets the underlying memory cache. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private MemoryCache Cache => LazyCache?.Value; + + /// + /// Gets the configuration options for . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private IOptions CacheOptions => LazyCacheOptions?.Value; + + /// + /// Represents a non-operative in-memory cache client. + /// + public static readonly IInMemoryCacheClient NonOperative = new InMemoryCacheClient(InMemoryCachingStrategy.NoCaching); + + /// + /// Represents the default value specifying the cache access and management behavior of the client. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const InMemoryCachingStrategy DefaultStrategy = InMemoryCachingStrategy.Moderate; + + /// + /// Represents the underlying, lazily-initialized memory cache. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Lazy LazyCache; + + /// + /// Represents lazily-initialized configuration options for . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Lazy> LazyCacheOptions; + + /// + /// Represents a value specifying the cache access and management behavior of the client. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly InMemoryCachingStrategy Strategy; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Caching/InMemoryCachingStrategy.cs b/src/RapidField.SolidInstruments.Core/Caching/InMemoryCachingStrategy.cs new file mode 100644 index 00000000..6f569d85 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Caching/InMemoryCachingStrategy.cs @@ -0,0 +1,40 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Caching; +using System; + +namespace RapidField.SolidInstruments.Core +{ + /// + /// Specifies the cache access and management behavior of an . + /// + public enum InMemoryCachingStrategy : Int32 + { + /// + /// The behavior of the in-memory cache client is not specified. + /// + Unspecified = 0, + + /// + /// The client will not perform caching. This value can be used to create non-operative cache clients. + /// + NoCaching = 1, + + /// + /// The client will perform lightweight caching, exercising minimal consumption of memory and processing resources. + /// + Conservative = 2, + + /// + /// The client will perform caching exercising moderate consumption of memory and processing resources. + /// + Moderate = 3, + + /// + /// The client will perform heavy caching, exercising high consumption of memory and processing resources. + /// + Aggressive = 4 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlOperationException.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlOperationException.cs index e1eb161e..8e5a392d 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlOperationException.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlOperationException.cs @@ -56,7 +56,7 @@ public ConcurrencyControlOperationException(Exception innerException) /// The exception that is the cause of the current exception. /// public ConcurrencyControlOperationException(String message, Exception innerException) - : base(message ?? ConstructMessage((innerException is null) == false), innerException) + : base(message ?? ConstructMessage(innerException is not null), innerException) { return; } diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs index 43a34835..929f60ef 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs @@ -401,7 +401,7 @@ internal void Release() } catch (ConcurrencyControlOperationException) { - if ((Context is null) == false && Thread.CurrentThread != GranteeThread) + if (Context is not null && Thread.CurrentThread != GranteeThread) { // Attempt to avoid deadlocks in case the IConcurrencyControl implementation is using the wrong // concurrency control primitive, or making some other unforeseen mistake. diff --git a/src/RapidField.SolidInstruments.Core/Extensions/InMemoryCachingStrategyExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/InMemoryCachingStrategyExtensions.cs new file mode 100644 index 00000000..2979514d --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Extensions/InMemoryCachingStrategyExtensions.cs @@ -0,0 +1,317 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Core.Extensions +{ + /// + /// Extends the enumeration with general purpose features. + /// + public static class InMemoryCachingStrategyExtensions + { + /// + /// Converts the current to a memory cache entry options. + /// + /// + /// The current instance of the . + /// + /// + /// Memory cache entry options representing the current . + /// + [DebuggerHidden] + internal static MemoryCacheEntryOptions ToCacheEntryOptions(this InMemoryCachingStrategy target) => target switch + { + InMemoryCachingStrategy.Aggressive => CacheEntryOptionsForAggressiveStrategy, + InMemoryCachingStrategy.Conservative => CacheEntryOptionsForConservativeStrategy, + InMemoryCachingStrategy.Moderate => CacheEntryOptionsForModerateStrategy, + _ => throw new UnsupportedSpecificationException($"The specified caching strategy, {target}, is not supported."), + }; + + /// + /// Converts the current to a memory cache options. + /// + /// + /// The current instance of the . + /// + /// + /// Memory cache options representing the current . + /// + [DebuggerHidden] + internal static IOptions ToCacheOptions(this InMemoryCachingStrategy target) => target switch + { + InMemoryCachingStrategy.Aggressive => CacheOptionsForAggressiveStrategy, + InMemoryCachingStrategy.Conservative => CacheOptionsForConservativeStrategy, + InMemoryCachingStrategy.Moderate => CacheOptionsForModerateStrategy, + _ => throw new UnsupportedSpecificationException($"The specified caching strategy, {target}, is not supported."), + }; + + /// + /// Gets the amount of physical memory, in bytes, allocated for the current process. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static Int64 AvailableProcessMemoryInBytes => Process.GetCurrentProcess().WorkingSet64; + + /// + /// Gets the settings associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static MemoryCacheEntryOptions CacheEntryOptionsForAggressiveStrategy => new MemoryCacheEntryOptions() + { + AbsoluteExpirationRelativeToNow = AbsoluteExpirationRelativeToNowForAggressiveStrategy, + SlidingExpiration = SlidingExpirationForAggressiveStrategy + }; + + /// + /// Gets the settings associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static MemoryCacheEntryOptions CacheEntryOptionsForConservativeStrategy => new MemoryCacheEntryOptions() + { + AbsoluteExpirationRelativeToNow = AbsoluteExpirationRelativeToNowForConservativeStrategy, + SlidingExpiration = SlidingExpirationForConservativeStrategy + }; + + /// + /// Gets the settings associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static MemoryCacheEntryOptions CacheEntryOptionsForModerateStrategy => new MemoryCacheEntryOptions() + { + AbsoluteExpirationRelativeToNow = AbsoluteExpirationRelativeToNowForModerateStrategy, + SlidingExpiration = SlidingExpirationForModerateStrategy + }; + + /// + /// Gets the settings associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static IOptions CacheOptionsForAggressiveStrategy => new MemoryCacheOptions() + { + CompactionPercentage = CompactionPercentageForAggressiveStrategy, + ExpirationScanFrequency = ExpirationScanFrequencyForAggressiveStrategy, + SizeLimit = SizeLimitForAggressiveStrategy + }; + + /// + /// Gets the settings associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static IOptions CacheOptionsForConservativeStrategy => new MemoryCacheOptions() + { + CompactionPercentage = CompactionPercentageForConservativeStrategy, + ExpirationScanFrequency = ExpirationScanFrequencyForConservativeStrategy, + SizeLimit = SizeLimitForConservativeStrategy + }; + + /// + /// Gets the settings associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static IOptions CacheOptionsForModerateStrategy => new MemoryCacheOptions() + { + CompactionPercentage = CompactionPercentageForModerateStrategy, + ExpirationScanFrequency = ExpirationScanFrequencyForModerateStrategy, + SizeLimit = SizeLimitForModerateStrategy + }; + + /// + /// Gets the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static Int64 SizeLimitForAggressiveStrategy => Convert.ToInt64(Math.Round(AvailableProcessMemoryInBytes * MemoryUsePercentageForAggressiveStrategy, 0)); + + /// + /// Gets the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static Int64 SizeLimitForConservativeStrategy => Convert.ToInt64(Math.Round(AvailableProcessMemoryInBytes * MemoryUsePercentageForConservativeStrategy, 0)); + + /// + /// Gets the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static Int64 SizeLimitForModerateStrategy => Convert.ToInt64(Math.Round(AvailableProcessMemoryInBytes * MemoryUsePercentageForModerateStrategy, 0)); + + /// + /// Represents the setting, in minutes, associated + /// with the strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 AbsoluteExpirationRelativeToNowInMinutesForAggressiveStrategy = 89; + + /// + /// Represents the setting, in minutes, associated + /// with the strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 AbsoluteExpirationRelativeToNowInMinutesForConservativeStrategy = 13; + + /// + /// Represents the setting, in minutes, associated + /// with the strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 AbsoluteExpirationRelativeToNowInMinutesForModerateStrategy = 34; + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Double CompactionPercentageForAggressiveStrategy = 0.08d; + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Double CompactionPercentageForConservativeStrategy = 0.21d; + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Double CompactionPercentageForModerateStrategy = 0.13d; + + /// + /// Represents the setting, in minutes, associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 ExpirationScanFrequencyInMinutesForAggressiveStrategy = 1; + + /// + /// Represents the setting, in minutes, associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 ExpirationScanFrequencyInMinutesForConservativeStrategy = 3; + + /// + /// Represents the setting, in minutes, associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 ExpirationScanFrequencyInMinutesForModerateStrategy = 2; + + /// + /// Represents the memory use percentage setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Double MemoryUsePercentageForAggressiveStrategy = 0.55d; + + /// + /// Represents the memory use percentage setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Double MemoryUsePercentageForConservativeStrategy = 0.08d; + + /// + /// Represents the memory use percentage setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Double MemoryUsePercentageForModerateStrategy = 0.21d; + + /// + /// Represents the setting, in minutes, associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 SlidingExpirationInMinutesForAggressiveStrategy = 34; + + /// + /// Represents the setting, in minutes, associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 SlidingExpirationInMinutesForConservativeStrategy = 5; + + /// + /// Represents the setting, in minutes, associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 SlidingExpirationInMinutesForModerateStrategy = 13; + + /// + /// Represents the setting associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan AbsoluteExpirationRelativeToNowForAggressiveStrategy = TimeSpan.FromMinutes(AbsoluteExpirationRelativeToNowInMinutesForAggressiveStrategy); + + /// + /// Represents the setting associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan AbsoluteExpirationRelativeToNowForConservativeStrategy = TimeSpan.FromMinutes(AbsoluteExpirationRelativeToNowInMinutesForConservativeStrategy); + + /// + /// Represents the setting associated with the + /// strategy . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan AbsoluteExpirationRelativeToNowForModerateStrategy = TimeSpan.FromMinutes(AbsoluteExpirationRelativeToNowInMinutesForModerateStrategy); + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan ExpirationScanFrequencyForAggressiveStrategy = TimeSpan.FromMinutes(ExpirationScanFrequencyInMinutesForAggressiveStrategy); + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan ExpirationScanFrequencyForConservativeStrategy = TimeSpan.FromMinutes(ExpirationScanFrequencyInMinutesForConservativeStrategy); + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan ExpirationScanFrequencyForModerateStrategy = TimeSpan.FromMinutes(ExpirationScanFrequencyInMinutesForModerateStrategy); + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan SlidingExpirationForAggressiveStrategy = TimeSpan.FromMinutes(SlidingExpirationInMinutesForAggressiveStrategy); + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan SlidingExpirationForConservativeStrategy = TimeSpan.FromMinutes(SlidingExpirationInMinutesForConservativeStrategy); + + /// + /// Represents the setting associated with the strategy + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan SlidingExpirationForModerateStrategy = TimeSpan.FromMinutes(SlidingExpirationInMinutesForModerateStrategy); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Extensions/ObjectExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/ObjectExtensions.cs index a38f812d..67866162 100644 --- a/src/RapidField.SolidInstruments.Core/Extensions/ObjectExtensions.cs +++ b/src/RapidField.SolidInstruments.Core/Extensions/ObjectExtensions.cs @@ -3,8 +3,13 @@ // ================================================================================================================================= using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; @@ -15,6 +20,45 @@ namespace RapidField.SolidInstruments.Core.Extensions /// public static class ObjectExtensions { + /// + /// Reflectively interrogates the current object to determine its total size, in bytes, in memory. + /// + /// + /// The current instance of the . + /// + /// + /// The total size of the current object, in bytes. + /// + public static Int32 CalculateSizeInBytes(this Object target) + { + if (target?.GetType().IsValueType ?? false) + { + return Marshal.SizeOf(target); + } + + var pointerSizeInBytes = IntPtr.Size; + var targetSizeInBytes = pointerSizeInBytes; + + if (target is not null) + { + var values = new List(); + + { + var collections = new List(); + var references = new List(); + target.FlattenObjectGraph(collections, references, values); + targetSizeInBytes += (collections.Count + references.Count) * pointerSizeInBytes; + } + + foreach (var value in values) + { + targetSizeInBytes += Marshal.SizeOf(value); + } + } + + return targetSizeInBytes; + } + /// /// Derives a hash code from the current object's type name and serialized representation. /// @@ -78,6 +122,18 @@ internal static Object GetSerializedClone(this Object target) } } + /// + /// Reflectively interrogates the current value object to determine its total size, in bytes, in memory. + /// + /// + /// The current instance of the . + /// + /// + /// The total size of the current value object, in bytes. + /// + [DebuggerHidden] + private static Int32 CalculateValueSizeInBytes(this Object target) => target is null ? 0 : Marshal.SizeOf(target); + /// /// Converts the specified serialized object to its typed equivalent. /// @@ -113,6 +169,137 @@ private static Object Deserialize(DataContractJsonSerializer serializer, Byte[] } } + /// + /// Recursively interrogates the specified collection object's graph and hydrates the specified collections with unique + /// instances of its child objects. + /// + /// + /// The current instance of the . + /// + /// + /// A flattened collection of collection object references to which new, unique instances are added. + /// + /// + /// A flattened collection of non-collection object references to which new, unique instances are added. + /// + /// + /// A collection of values to which new instances are added. + /// + [DebuggerHidden] + private static void FlattenCollectionGraph(this Object target, IList collections, IList references, IList values) + { + if (target is IEnumerable collection && collections.Contains(collection) == false) + { + collections.Add(collection); + + foreach (var element in collection) + { + element?.FlattenObjectGraph(collections, references, values); + } + } + } + + /// + /// Recursively interrogates the specified object's graph and hydrates the specified collections with unique instances of + /// its child objects. + /// + /// + /// The current instance of the . + /// + /// + /// A flattened collection of collection object references to which new, unique instances are added. + /// + /// + /// A flattened collection of non-collection object references to which new, unique instances are added. + /// + /// + /// A collection of values to which new instances are added. + /// + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void FlattenObjectGraph(this Object target, IList collections, IList references, IList values) + { + var targetType = target.GetType(); + var targetTypeCategory = targetType.GetTypeCategory(); + + switch (targetTypeCategory) + { + case ObjectTypeCategory.Collection: + + target.FlattenCollectionGraph(collections, references, values); + break; + + case ObjectTypeCategory.Reference: + + target.FlattenReferenceGraph(targetType, collections, references, values); + break; + + case ObjectTypeCategory.Value: + + values.Add(target); + break; + + default: + + throw new UnsupportedSpecificationException($"The specified object type category, {targetTypeCategory}, is not supported."); + } + } + + /// + /// Recursively interrogates the specified reference object's graph and hydrates the specified collections with unique + /// instances of its child objects. + /// + /// + /// The current instance of the . + /// + /// + /// The type of . + /// + /// + /// A flattened collection of collection object references to which new, unique instances are added. + /// + /// + /// A flattened collection of non-collection object references to which new, unique instances are added. + /// + /// + /// A collection of values to which new instances are added. + /// + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void FlattenReferenceGraph(this Object target, Type targetType, IList collections, IList references, IList values) + { + if (references.Contains(target) == false) + { + references.Add(target); + + foreach (var field in targetType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + field.GetValue(target)?.FlattenObjectGraph(collections, references, values); + } + + foreach (var property in targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (property.CanRead) + { + property.GetValue(target)?.FlattenObjectGraph(collections, references, values); + } + } + } + } + + /// + /// Determines whether an object type is a collection type, non-collection reference type or value type. + /// + /// + /// The current instance of the . + /// + /// + /// The resulting type category. + /// + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ObjectTypeCategory GetTypeCategory(this Type target) => target.IsValueType ? ObjectTypeCategory.Value : (CollectionInterfaceType.IsAssignableFrom(target) ? ObjectTypeCategory.Collection : ObjectTypeCategory.Reference); + /// /// Converts the specified object to a serialized byte array. /// @@ -157,6 +344,18 @@ private static Byte[] Serialize(DataContractJsonSerializer serializer, Object ta [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const String DateTimeSerializationFormatString = "o"; + /// + /// Represents the type. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Type CollectionGenericInterfaceType = typeof(IEnumerable<>); + + /// + /// Represents the type. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Type CollectionInterfaceType = typeof(IEnumerable); + /// /// Represents settings used by to serialize objects. /// @@ -178,5 +377,31 @@ private static Byte[] Serialize(DataContractJsonSerializer serializer, Object ta EmitTypeInformation = EmitTypeInformation.AsNeeded, SerializeReadOnlyTypes = false }; + + /// + /// Specifies whether an object is a collection type, non-collection reference type or value type. + /// + private enum ObjectTypeCategory : Byte + { + /// + /// The object type category is not specified. + /// + Unspecified = 0, + + /// + /// The object type is a collection type. + /// + Collection = 1, + + /// + /// The object type is a non-collection reference type. + /// + Reference = 2, + + /// + /// The object type is a value type. + /// + Value = 3 + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj index 4fc8d6be..c43f235b 100644 --- a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj +++ b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj @@ -40,6 +40,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs index 9f0e8df1..7b48eecc 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs @@ -192,7 +192,7 @@ internal HashingProcessor() /// public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm, Byte[] salt) { - var applySalt = (salt is null) == false; + var applySalt = salt is not null; var saltLengthInBytes = applySalt ? salt.Length : 0; var plaintextLengthInBytes = plaintext.RejectIf().IsNullOrEmpty(nameof(plaintext)).TargetArgument.Length; var saltedPlaintextLengthInBytes = plaintextLengthInBytes + saltLengthInBytes; diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs index 723953f6..0d2c085e 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs @@ -135,7 +135,7 @@ private IReadOnlySecret HydrateSecret(Secret secret) [DataMember] public Boolean HasValue { - get => (Value is null) == false; + get => Value is not null; set { if (value && Value is null) diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStoreFilePersistenceVehicle.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStoreFilePersistenceVehicle.cs index b939b155..f186e822 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStoreFilePersistenceVehicle.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStoreFilePersistenceVehicle.cs @@ -105,7 +105,7 @@ protected override void Dispose(Boolean disposing) { try { - if (DeleteStateFileUponDisposal && (FilePath is null) == false && File.Exists(FilePath)) + if (DeleteStateFileUponDisposal && FilePath is not null && File.Exists(FilePath)) { File.Delete(FilePath); } diff --git a/src/RapidField.SolidInstruments.EventAuthoring/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.EventAuthoring/AssemblyAttributes.cs index a2b07fa8..40421483 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/AssemblyAttributes.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/AssemblyAttributes.cs @@ -5,4 +5,5 @@ using System.Runtime.CompilerServices; [assembly: DisablePrivateReflection()] -[assembly: InternalsVisibleTo("RapidField.SolidInstruments.EventAuthoring.UnitTests")] \ No newline at end of file +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.EventAuthoring.UnitTests")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging")] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/EventRegister.cs b/src/RapidField.SolidInstruments.EventAuthoring/EventRegister.cs new file mode 100644 index 00000000..83ba58b5 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/EventRegister.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Diagnostics; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents an extensible catalog of available events. + /// + /// + /// is the default implementation of . + /// + public sealed class EventRegister : IEventRegister + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + private EventRegister() + { + return; + } + + /// + /// Represents a singleton instance of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly IEventRegister Instance = new EventRegister(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/ICommandMediatorExtensions.cs b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/ICommandMediatorExtensions.cs new file mode 100644 index 00000000..ec5a3399 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/ICommandMediatorExtensions.cs @@ -0,0 +1,183 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.EventAuthoring.Extensions +{ + /// + /// Extends the interface with event handling features. + /// + public static class ICommandMediatorExtensions + { + /// + /// Processes the specified . + /// + /// + /// The current . + /// + /// + /// A function that returns the event to process. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the event. + /// + /// + /// is disposed. + /// + public static void HandleEvent(this ICommandMediator target, Func createEventFunction) + { + try + { + _ = target.Process(createEventFunction.RejectIf().IsNull(nameof(createEventFunction)).TargetArgument(EventRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(exception); + } + } + + /// + /// Processes the specified . + /// + /// + /// The type of the event to process. + /// + /// + /// The current . + /// + /// + /// A function that returns the event to process. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the event. + /// + /// + /// is disposed. + /// + public static void HandleEvent(this ICommandMediator target, Func createEventFunction) + where TEvent : class, IEvent + { + try + { + _ = target.Process(createEventFunction.RejectIf().IsNull(nameof(createEventFunction)).TargetArgument(EventRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TEvent), exception); + } + } + + /// + /// Asynchronously processes the specified . + /// + /// + /// The current . + /// + /// + /// A function that returns the event to process. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the event. + /// + /// + /// is disposed. + /// + public static Task HandleEventAsync(this ICommandMediator target, Func createEventFunction) + { + try + { + return target.ProcessAsync(createEventFunction.RejectIf().IsNull(nameof(createEventFunction)).TargetArgument(EventRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(exception); + } + } + + /// + /// Asynchronously processes the specified . + /// + /// + /// The type of the event to process. + /// + /// + /// The current . + /// + /// + /// A function that returns the event to process. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the event. + /// + /// + /// is disposed. + /// + public static Task HandleEventAsync(this ICommandMediator target, Func createEventFunction) + where TEvent : class, IEvent + { + try + { + return target.ProcessAsync(createEventFunction.RejectIf().IsNull(nameof(createEventFunction)).TargetArgument(EventRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TEvent), exception); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/IEventRegisterExtensions.cs b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/IEventRegisterExtensions.cs new file mode 100644 index 00000000..b15849f4 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/IEventRegisterExtensions.cs @@ -0,0 +1,206 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.EventAuthoring.Extensions +{ + /// + /// Extends the interface with general purpose event creation methods. + /// + public static class IEventRegisterExtensions + { + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// An associated exception. + /// + /// + /// A new . + /// + /// + /// is . + /// + public static ErrorEvent Error(this IEventRegister target, Exception exception) => new ErrorEvent(exception); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// An associated exception. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// A new . + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public static ErrorEvent Error(this IEventRegister target, Exception exception, Guid correlationIdentifier) => new ErrorEvent(exception, correlationIdentifier); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// An associated exception. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + public static ErrorEvent Error(this IEventRegister target, String applicationIdentity, Exception exception) => new ErrorEvent(applicationIdentity, exception); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// An associated exception. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to . + /// + public static ErrorEvent Error(this IEventRegister target, String applicationIdentity, Exception exception, Guid correlationIdentifier) => new ErrorEvent(applicationIdentity, exception, correlationIdentifier); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A new . + /// + /// + /// is . + /// + public static GeneralInformationEvent GeneralInformation(this IEventRegister target, IEnumerable labels) => new GeneralInformationEvent(labels); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A new . + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public static GeneralInformationEvent GeneralInformation(this IEventRegister target, IEnumerable labels, EventVerbosity verbosity) => new GeneralInformationEvent(labels, verbosity); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A new . + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public static GeneralInformationEvent GeneralInformation(this IEventRegister target, IEnumerable labels, EventVerbosity verbosity, String description) => new GeneralInformationEvent(labels, verbosity, description); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// A new . + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public static GeneralInformationEvent GeneralInformation(this IEventRegister target, IEnumerable labels, EventVerbosity verbosity, String description, Guid correlationIdentifier) => new GeneralInformationEvent(labels, verbosity, description, correlationIdentifier); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IEventRegister.cs b/src/RapidField.SolidInstruments.EventAuthoring/IEventRegister.cs new file mode 100644 index 00000000..c47e7b48 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/IEventRegister.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.EventAuthoring.Extensions; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents an extensible catalog of available events. + /// + /// + /// Consuming libraries may use as an extension method target to expose creational methods for + /// custom events. See for examples. An instance of an is + /// made available by + /// and + /// . + /// + public interface IEventRegister + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/AutofacServiceInjector.cs b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/AutofacServiceInjector.cs index 4032edda..ffb7c872 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/AutofacServiceInjector.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/AutofacServiceInjector.cs @@ -49,7 +49,7 @@ protected sealed override void Inject(ContainerBuilder configurator, IServiceCol foreach (var serviceDescriptor in serviceDescriptors) { - if ((serviceDescriptor.ImplementationType is null) == false) + if (serviceDescriptor.ImplementationType is not null) { if (serviceDescriptor.ServiceType.GetTypeInfo().IsGenericTypeDefinition) { @@ -59,7 +59,7 @@ protected sealed override void Inject(ContainerBuilder configurator, IServiceCol configurator.RegisterType(serviceDescriptor.ImplementationType).As(serviceDescriptor.ServiceType).WithLifetime(serviceDescriptor.Lifetime); } - else if ((serviceDescriptor.ImplementationFactory is null) == false) + else if (serviceDescriptor.ImplementationFactory is not null) { configurator.RegisterComponent(RegistrationBuilder.ForDelegate(serviceDescriptor.ServiceType, (context, parameters) => serviceDescriptor.ImplementationFactory(context.Resolve())).WithLifetime(serviceDescriptor.Lifetime).CreateRegistration()); } diff --git a/src/RapidField.SolidInstruments.InversionOfControl/DependencyEngine.cs b/src/RapidField.SolidInstruments.InversionOfControl/DependencyEngine.cs index cf974fd6..88674197 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/DependencyEngine.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/DependencyEngine.cs @@ -414,7 +414,7 @@ internal static TEngine New(IConfiguration app var engine = package.CreateEngine(applicationConfiguration.RejectIf().IsNull(nameof(applicationConfiguration)).TargetArgument, serviceDescriptors); var abstractEngine = engine as DependencyEngine; - if ((abstractEngine is null) == false) + if (abstractEngine is not null) { abstractEngine.Start(); } diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/Extensions/ContainerBuilderExtensions.cs index 21df5488..662e7569 100644 --- a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/Extensions/ContainerBuilderExtensions.cs +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/Extensions/ContainerBuilderExtensions.cs @@ -5,6 +5,7 @@ using Autofac; using Microsoft.Azure.ServiceBus; using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Command.Autofac.Extensions; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Extensions; @@ -43,6 +44,7 @@ public static class ContainerBuilderExtensions public static void RegisterSupportingTypesForAzureServiceBusMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) { target.RegisterApplicationConfiguration(applicationConfiguration); + target.RegisterConfigurationCommandHandlers(); _ = connectionStringConfigurationKeyName.RejectIf().IsNullOrEmpty(nameof(connectionStringConfigurationKeyName)); target.Register(context => diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/Extensions/ContainerBuilderExtensions.cs index ee63f715..efd80ac5 100644 --- a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/Extensions/ContainerBuilderExtensions.cs +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/Extensions/ContainerBuilderExtensions.cs @@ -4,6 +4,7 @@ using Autofac; using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Command.Autofac.Extensions; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Extensions; @@ -220,6 +221,7 @@ public static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBui private static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, RabbitMqMessageTransport transport) { target.RegisterApplicationConfiguration(applicationConfiguration); + target.RegisterConfigurationCommandHandlers(); target.RegisterInstance(transport).IfNotRegistered(typeof(RabbitMqMessageTransport)).AsSelf().SingleInstance(); target.RegisterInstance(transport.CreateConnection()).IfNotRegistered(typeof(IMessageTransportConnection)).AsSelf().SingleInstance(); target.RegisterType().IfNotRegistered(typeof(RabbitMqMessageAdapter)).As>().AsSelf().SingleInstance(); diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs index d7a18256..cc2b997a 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs @@ -161,7 +161,7 @@ protected sealed override Task RouteToDeadLetterQueueAsync(AzureServic [DebuggerHidden] private static Guid ExtractCorrelationIdentifier(AzureServiceBusMessage message) { - if ((message?.CorrelationId is null) == false && Guid.TryParse(message.CorrelationId, out var correlationIdentifier)) + if (message?.CorrelationId is not null && Guid.TryParse(message.CorrelationId, out var correlationIdentifier)) { return correlationIdentifier; } diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/Extensions/IServiceCollectionExtensions.cs index f68f4d6a..5362c1a1 100644 --- a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/Extensions/IServiceCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/Extensions/IServiceCollectionExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using RapidField.SolidInstruments.Command.DotNetNative.Extensions; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Extensions; @@ -49,6 +50,7 @@ public static class IServiceCollectionExtensions public static IServiceCollection AddSupportingTypesForAzureServiceBusMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) { target.AddApplicationConfiguration(applicationConfiguration); + target.AddConfigurationCommandHandlers(); _ = connectionStringConfigurationKeyName.RejectIf().IsNullOrEmpty(nameof(connectionStringConfigurationKeyName)); target.TryAddSingleton((serviceProvider) => diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/Extensions/IServiceCollectionExtensions.cs index 2b432e84..e581927a 100644 --- a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/Extensions/IServiceCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/Extensions/IServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using RapidField.SolidInstruments.Command.DotNetNative.Extensions; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Extensions; @@ -243,6 +244,7 @@ public static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this ISe private static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, RabbitMqMessageTransport transport) { target.AddApplicationConfiguration(applicationConfiguration); + target.AddConfigurationCommandHandlers(); target.TryAddSingleton(transport); target.TryAddSingleton(transport.CreateConnection()); target.TryAddSingleton(); diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessageRegister.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessageRegister.cs new file mode 100644 index 00000000..b27cc2be --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessageRegister.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents an extensible catalog of available command messages. + /// + /// + /// is the default implementation of . + /// + public sealed class CommandMessageRegister : ICommandMessageRegister + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + private CommandMessageRegister() + { + Commands = CommandRegister.Instance; + } + + /// + /// Gets a catalog of available commands. + /// + public ICommandRegister Commands + { + get; + } + + /// + /// Represents a singleton instance of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly ICommandMessageRegister Instance = new CommandMessageRegister(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/TextualCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/TextualCommandMessage.cs new file mode 100644 index 00000000..87dcda79 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/TextualCommandMessage.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command that is described by textual information. + /// + [DataContract] + public sealed class TextualCommandMessage : CommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public TextualCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public TextualCommandMessage(TextualCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessageRegister.cs b/src/RapidField.SolidInstruments.Messaging/EventMessageRegister.cs new file mode 100644 index 00000000..255bb7fc --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessageRegister.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents an extensible catalog of available event messages. + /// + /// + /// is the default implementation of . + /// + public sealed class EventMessageRegister : IEventMessageRegister + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + private EventMessageRegister() + { + Events = EventRegister.Instance; + } + + /// + /// Gets a catalog of available events. + /// + public IEventRegister Events + { + get; + } + + /// + /// Represents a singleton instance of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly IEventMessageRegister Instance = new EventMessageRegister(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/Extensions/ICommandMediatorExtensions.cs b/src/RapidField.SolidInstruments.Messaging/Extensions/ICommandMediatorExtensions.cs new file mode 100644 index 00000000..d4f5cd3e --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/Extensions/ICommandMediatorExtensions.cs @@ -0,0 +1,479 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Messaging.CommandMessages; +using RapidField.SolidInstruments.Messaging.EventMessages; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.Extensions +{ + /// + /// Extends the interface with message handling features. + /// + public static class ICommandMediatorExtensions + { + /// + /// Transmits the specified . + /// + /// + /// The type of the command message to transmit. + /// + /// + /// The current . + /// + /// + /// A function that returns the command message to transmit. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static void TransmitCommandMessage(this ICommandMediator target, Func createMessageFunction) + where TCommandMessage : class, ICommandMessage + { + try + { + _ = target.Process(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(CommandMessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TCommandMessage), exception); + } + } + + /// + /// Asynchronously transmits the specified . + /// + /// + /// The type of the command message to transmit. + /// + /// + /// The current . + /// + /// + /// A function that returns the command message to transmit. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static Task TransmitCommandMessageAsync(this ICommandMediator target, Func createMessageFunction) + where TCommandMessage : class, ICommandMessage + { + try + { + return target.ProcessAsync(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(CommandMessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TCommandMessage), exception); + } + } + + /// + /// Transmits the specified . + /// + /// + /// The type of the event message to transmit. + /// + /// + /// The current . + /// + /// + /// A function that returns the event message to transmit. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static void TransmitEventMessage(this ICommandMediator target, Func createMessageFunction) + where TEventMessage : class, IMessage, IEventMessageBase + { + try + { + _ = target.Process(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(EventMessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TEventMessage), exception); + } + } + + /// + /// Asynchronously transmits the specified . + /// + /// + /// The type of the event message to transmit. + /// + /// + /// The current . + /// + /// + /// A function that returns the event message to transmit. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static Task TransmitEventMessageAsync(this ICommandMediator target, Func createMessageFunction) + where TEventMessage : class, IMessage, IEventMessageBase + { + try + { + return target.ProcessAsync(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(EventMessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TEventMessage), exception); + } + } + + /// + /// Transmits the specified . + /// + /// + /// The type of the message to transmit. + /// + /// + /// The type of the result that is produced by processing the message. + /// + /// + /// The current . + /// + /// + /// A function that returns the message to transmit. + /// + /// + /// The result that is produced by processing the message. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static TResult TransmitMessage(this ICommandMediator target, Func createMessageFunction) + where TMessage : class, IMessage + { + try + { + return target.Process(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(MessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TMessage), exception); + } + } + + /// + /// Asynchronously transmits the specified . + /// + /// + /// The type of the message to transmit. + /// + /// + /// The type of the result that is produced by processing the message. + /// + /// + /// The current . + /// + /// + /// A function that returns the message to transmit. + /// + /// + /// A task representing the asynchronous operation and containing the result that is produced by processing the message. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static Task TransmitMessageAsync(this ICommandMediator target, Func createMessageFunction) + where TMessage : class, IMessage + { + try + { + return target.ProcessAsync(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(MessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TMessage), exception); + } + } + + /// + /// Transmits the specified . + /// + /// + /// The type of the response message that is produced by processing the request message. + /// + /// + /// The current . + /// + /// + /// A function that returns the request message to transmit. + /// + /// + /// The response message that is produced by processing the request message. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static TResponseMessage TransmitRequestMessage(this ICommandMediator target, Func> createMessageFunction) + where TResponseMessage : class, IResponseMessage + { + try + { + return target.Process(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(RequestMessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(exception); + } + } + + /// + /// Transmits the specified . + /// + /// + /// The type of the request message to transmit. + /// + /// + /// The type of the response message that is produced by processing the request message. + /// + /// + /// The current . + /// + /// + /// A function that returns the request message to transmit. + /// + /// + /// The response message that is produced by processing the request message. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static TResponseMessage TransmitRequestMessage(this ICommandMediator target, Func createMessageFunction) + where TRequestMessage : class, IRequestMessage + where TResponseMessage : class, IResponseMessage + { + try + { + return target.Process(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(RequestMessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TRequestMessage), exception); + } + } + + /// + /// Asynchronously transmits the specified . + /// + /// + /// The type of the response message that is produced by processing the request message. + /// + /// + /// The current . + /// + /// + /// A function that returns the request message to transmit. + /// + /// + /// A task representing the asynchronous operation and containing the response message that is produced by processing the + /// request message. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static Task TransmitRequestMessageAsync(this ICommandMediator target, Func> createMessageFunction) + where TResponseMessage : class, IResponseMessage + { + try + { + return target.ProcessAsync(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(RequestMessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(exception); + } + } + + /// + /// Asynchronously transmits the specified . + /// + /// + /// The type of the request message to transmit. + /// + /// + /// The type of the response message that is produced by processing the request message. + /// + /// + /// The current . + /// + /// + /// A function that returns the request message to transmit. + /// + /// + /// A task representing the asynchronous operation and containing the response message that is produced by processing the + /// request message. + /// + /// + /// is . + /// + /// + /// An exception was raised while processing the message. + /// + /// + /// is disposed. + /// + public static Task TransmitRequestMessageAsync(this ICommandMediator target, Func createMessageFunction) + where TRequestMessage : class, IRequestMessage + where TResponseMessage : class, IResponseMessage + { + try + { + return target.ProcessAsync(createMessageFunction.RejectIf().IsNull(nameof(createMessageFunction)).TargetArgument(RequestMessageRegister.Instance)); + } + catch (CommandHandlingException) + { + throw; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception exception) + { + throw new CommandHandlingException(typeof(TRequestMessage), exception); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/Extensions/ICommandMessageRegisterExtensions.cs b/src/RapidField.SolidInstruments.Messaging/Extensions/ICommandMessageRegisterExtensions.cs new file mode 100644 index 00000000..75cb49bc --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/Extensions/ICommandMessageRegisterExtensions.cs @@ -0,0 +1,54 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command.Extensions; +using RapidField.SolidInstruments.Messaging.CommandMessages; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Messaging.Extensions +{ + /// + /// Extends the interface with general purpose message creation methods. + /// + public static class ICommandMessageRegisterExtensions + { + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// The textual command value. + /// + /// + /// is . + /// + /// + /// A new . + /// + public static TextualCommandMessage FromText(this ICommandMessageRegister target, String value) => new TextualCommandMessage(target.Commands.FromText(value)); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// The textual command value. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + /// + /// A new . + /// + public static TextualCommandMessage FromText(this ICommandMessageRegister target, String value, IEnumerable labels) => new TextualCommandMessage(target.Commands.FromText(value, labels)); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/Extensions/IEventMessageRegisterExtensions.cs b/src/RapidField.SolidInstruments.Messaging/Extensions/IEventMessageRegisterExtensions.cs new file mode 100644 index 00000000..c014ba6a --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/Extensions/IEventMessageRegisterExtensions.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Messaging.Extensions +{ + /// + /// Extends the interface with general purpose message creation methods. + /// + public static class IEventMessageRegisterExtensions + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/Extensions/IMessageRegisterExtensions.cs b/src/RapidField.SolidInstruments.Messaging/Extensions/IMessageRegisterExtensions.cs new file mode 100644 index 00000000..2bf88718 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/Extensions/IMessageRegisterExtensions.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Messaging.Extensions +{ + /// + /// Extends the interface with general purpose message creation methods. + /// + public static class IMessageRegisterExtensions + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/Extensions/IRequestMessageRegisterExtensions.cs b/src/RapidField.SolidInstruments.Messaging/Extensions/IRequestMessageRegisterExtensions.cs new file mode 100644 index 00000000..75cebb3e --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/Extensions/IRequestMessageRegisterExtensions.cs @@ -0,0 +1,43 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Messaging.Service; +using System; + +namespace RapidField.SolidInstruments.Messaging.Extensions +{ + /// + /// Extends the interface with general purpose message creation methods. + /// + public static class IRequestMessageRegisterExtensions + { + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A new . + /// + public static PingRequestMessage Ping(this IRequestMessageRegister target) => target.Ping(Guid.NewGuid()); + + /// + /// Creates a new . + /// + /// + /// The current . + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// A new . + /// + /// + /// is equal to . + /// + public static PingRequestMessage Ping(this IRequestMessageRegister target, Guid correlationIdentifier) => new PingRequestMessage(correlationIdentifier); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/ICommandMessageRegister.cs b/src/RapidField.SolidInstruments.Messaging/ICommandMessageRegister.cs new file mode 100644 index 00000000..38d28205 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/ICommandMessageRegister.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Messaging.Extensions; +using System; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents an extensible catalog of available command messages. + /// + /// + /// Consuming libraries may use as an extension method target to expose creational + /// methods for custom messages. See for examples. An instance of an + /// is made available by + /// + /// and + /// . + /// + public interface ICommandMessageRegister + { + /// + /// Gets a catalog of available commands. + /// + public ICommandRegister Commands + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IEventMessageRegister.cs b/src/RapidField.SolidInstruments.Messaging/IEventMessageRegister.cs new file mode 100644 index 00000000..95a74b76 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/IEventMessageRegister.cs @@ -0,0 +1,33 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.EventAuthoring; +using RapidField.SolidInstruments.Messaging.Extensions; +using System; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents an extensible catalog of available event messages. + /// + /// + /// Consuming libraries may use as an extension method target to expose creational methods + /// for custom messages. See for examples. An instance of an + /// is made available by + /// + /// and + /// . + /// + public interface IEventMessageRegister + { + /// + /// Gets a catalog of available events. + /// + public IEventRegister Events + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageRegister.cs b/src/RapidField.SolidInstruments.Messaging/IMessageRegister.cs new file mode 100644 index 00000000..fe98bb5b --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/IMessageRegister.cs @@ -0,0 +1,48 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Messaging.Extensions; +using System; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents an extensible catalog of available messages. + /// + /// + /// Consuming libraries may use as an extension method target to expose creational methods for + /// custom messages. See for examples. An instance of an + /// is made available by + /// + /// and + /// . + /// + public interface IMessageRegister + { + /// + /// Gets a catalog of available command messages. + /// + public ICommandMessageRegister CommandMessages + { + get; + } + + /// + /// Gets a catalog of available event messages. + /// + public IEventMessageRegister EventMessages + { + get; + } + + /// + /// Gets a catalog of available request messages. + /// + public IRequestMessageRegister RequestMessages + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IRequestMessageRegister.cs b/src/RapidField.SolidInstruments.Messaging/IRequestMessageRegister.cs new file mode 100644 index 00000000..775b01ad --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/IRequestMessageRegister.cs @@ -0,0 +1,25 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Messaging.Extensions; +using System; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents an extensible catalog of available request messages. + /// + /// + /// Consuming libraries may use as an extension method target to expose creational + /// methods for custom messages. See for examples. An instance of an + /// is made available by + /// + /// and + /// . + /// + public interface IRequestMessageRegister + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageRegister.cs b/src/RapidField.SolidInstruments.Messaging/MessageRegister.cs new file mode 100644 index 00000000..3cae2bda --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/MessageRegister.cs @@ -0,0 +1,58 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents an extensible catalog of available messages. + /// + /// + /// is the default implementation of . + /// + public sealed class MessageRegister : IMessageRegister + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + private MessageRegister() + { + CommandMessages = CommandMessageRegister.Instance; + EventMessages = EventMessageRegister.Instance; + RequestMessages = RequestMessageRegister.Instance; + } + + /// + /// Gets a catalog of available command messages. + /// + public ICommandMessageRegister CommandMessages + { + get; + } + + /// + /// Gets a catalog of available event messages. + /// + public IEventMessageRegister EventMessages + { + get; + } + + /// + /// Gets a catalog of available request messages. + /// + public IRequestMessageRegister RequestMessages + { + get; + } + + /// + /// Represents a singleton instance of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly IMessageRegister Instance = new MessageRegister(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/RequestMessageRegister.cs b/src/RapidField.SolidInstruments.Messaging/RequestMessageRegister.cs new file mode 100644 index 00000000..55396173 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/RequestMessageRegister.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents an extensible catalog of available request messages. + /// + /// + /// is the default implementation of . + /// + public sealed class RequestMessageRegister : IRequestMessageRegister + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + private RequestMessageRegister() + { + return; + } + + /// + /// Represents a singleton instance of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly IRequestMessageRegister Instance = new RequestMessageRegister(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs index 8de1e819..a0fd96b1 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs @@ -209,7 +209,7 @@ internal MessageLockToken LockToken /// /// An error occurred during serialization or deserialization. /// - [DataMember(Name = "Body", Order = 3)] + [DataMember(Name = SerializedBodyDataMemberName, Order = 3)] internal String SerializedBody { get => SerializeBody(); @@ -251,6 +251,12 @@ private Type BodyType [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal const SerializationFormat DefaultBodySerializationFormat = SerializationFormat.CompressedJson; + /// + /// Represents the name of in serialization contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String SerializedBodyDataMemberName = "Body"; + /// /// Represents the text encoding that is used to encode and decode . /// diff --git a/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs b/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs index e0942ae9..7cdc7b5c 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs @@ -324,7 +324,7 @@ public Task> ReadAsync(Int32 index, Int32 lookBehindLength, Int argumentOutOfRangeExceptionParameterName = nameof(lookAheadLength); } - if ((argumentOutOfRangeExceptionParameterName is null) == false) + if (argumentOutOfRangeExceptionParameterName is not null) { return InvalidReadBehavior switch { diff --git a/src/RapidField.SolidInstruments.Web/DomainModelHttpApiController.cs b/src/RapidField.SolidInstruments.Web/DomainModelHttpApiController.cs new file mode 100644 index 00000000..da401e87 --- /dev/null +++ b/src/RapidField.SolidInstruments.Web/DomainModelHttpApiController.cs @@ -0,0 +1,40 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.Web +{ + /// + /// Processes HTTP requests representing domain model CRUD operations for an API endpoint. + /// + /// + /// The type of the unique primary identifier for the associated model type. + /// + /// + /// The type of the domain model for which the controller processes CRUD operations. + /// + public abstract class DomainModelHttpApiController : HttpApiController + where TDomainModelIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + { + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainModelHttpApiController(ICommandMediator mediator) + : base(mediator) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Web/HttpApiController.cs b/src/RapidField.SolidInstruments.Web/HttpApiController.cs index 0f3a4f37..4fac406f 100644 --- a/src/RapidField.SolidInstruments.Web/HttpApiController.cs +++ b/src/RapidField.SolidInstruments.Web/HttpApiController.cs @@ -2,12 +2,155 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using Microsoft.AspNetCore.Mvc; +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using System; +using System.Diagnostics; +using System.Net; + namespace RapidField.SolidInstruments.Web { /// /// Processes HTTP requests for an API endpoint. /// - public abstract class HttpApiController + public abstract class HttpApiController : ControllerBase { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected HttpApiController(ICommandMediator mediator) + : base() + { + Mediator = mediator.RejectIf().IsNull(nameof(mediator)).TargetArgument; + } + + /// + /// Creates a . + /// + /// + /// The exception that was raised which represents the server error. The default value is . + /// + /// + /// A . + /// + protected BadRequestObjectResult BadRequest(Exception exception) => BadRequest(exception, null); + + /// + /// Creates a . + /// + /// + /// A message to include with the HTTP response. The default value is . + /// + /// + /// A . + /// + protected BadRequestObjectResult BadRequest(String message) => BadRequest(null, message); + + /// + /// Creates a . + /// + /// + /// The exception that was raised which represents the server error. The default value is . + /// + /// + /// A message to include with the HTTP response. The default value is . + /// + /// + /// A . + /// + protected BadRequestObjectResult BadRequest(Exception exception, String message) + { + var processedMessage = (message.IsNullOrEmpty() ? exception?.Message : message) ?? DefaultBadRequestMessage; + return BadRequest(error: ConveysStackTraceInFailureResponses ? $"{processedMessage} {exception?.StackTrace}" : processedMessage); + } + + /// + /// Creates an with HTTP status code 500. + /// + /// + /// An with HTTP status code 500. + /// + protected ObjectResult InternalServerError() => InternalServerError(null, null); + + /// + /// Creates an with HTTP status code 500. + /// + /// + /// The exception that was raised which represents the server error. The default value is . + /// + /// + /// An with HTTP status code 500. + /// + protected ObjectResult InternalServerError(Exception exception) => InternalServerError(exception, null); + + /// + /// Creates an with HTTP status code 500. + /// + /// + /// A message to include with the HTTP response. The default value is . + /// + /// + /// An with HTTP status code 500. + /// + protected ObjectResult InternalServerError(String message) => InternalServerError(null, message); + + /// + /// Creates an with HTTP status code 500. + /// + /// + /// The exception that was raised which represents the server error. The default value is . + /// + /// + /// A message to include with the HTTP response. The default value is . + /// + /// + /// An with HTTP status code 500. + /// + protected ObjectResult InternalServerError(Exception exception, String message) + { + var processedMessage = (message.IsNullOrEmpty() ? exception?.Message : message) ?? DefaultInternalServerErrorMessage; + return StatusCode((Int32)HttpStatusCode.InternalServerError, ConveysStackTraceInFailureResponses ? $"{processedMessage} {exception?.StackTrace}" : processedMessage); + } + + /// + /// Represents a value indicating whether or not stack trace information is included with failure responses. + /// + protected virtual Boolean ConveysStackTraceInFailureResponses => DefaultConveysStackTraceInFailureResponses; + + /// + /// Represents the standard route for an HTTP API controller. + /// + protected const String StandardControllerRoute = "[controller]"; + + /// + /// Represents processing intermediary that is used to process sub-commands. + /// + protected readonly ICommandMediator Mediator; + + /// + /// Represents the default error message that is provided by . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DefaultBadRequestMessage = "The request was improperly formed or invalid."; + + /// + /// Represents the default value indicating whether or not stack trace information is included with failure responses. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Boolean DefaultConveysStackTraceInFailureResponses = false; + + /// + /// Represents the default error message that is provided by . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DefaultInternalServerErrorMessage = "An error occurred on the server while processing the request."; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Web/RapidField.SolidInstruments.Web.csproj b/src/RapidField.SolidInstruments.Web/RapidField.SolidInstruments.Web.csproj index 79c30e43..a21abe0b 100644 --- a/src/RapidField.SolidInstruments.Web/RapidField.SolidInstruments.Web.csproj +++ b/src/RapidField.SolidInstruments.Web/RapidField.SolidInstruments.Web.csproj @@ -40,6 +40,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Caching/InMemoryCacheClientTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Caching/InMemoryCacheClientTests.cs new file mode 100644 index 00000000..545633cd --- /dev/null +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Caching/InMemoryCacheClientTests.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Core.Caching; +using RapidField.SolidInstruments.Core.Extensions; +using System; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Core.UnitTests.Caching +{ + [TestClass] + public class InMemoryCacheClientTests + { + [TestMethod] + public void Process_ShouldProduceDesiredResults_ForAggressiveStrategy() => Process_ShouldProduceDesiredResults(InMemoryCachingStrategy.Aggressive, true); + + [TestMethod] + public void Process_ShouldProduceDesiredResults_ForConservativeStrategy() => Process_ShouldProduceDesiredResults(InMemoryCachingStrategy.Conservative, true); + + [TestMethod] + public void Process_ShouldProduceDesiredResults_ForModerateStrategy() => Process_ShouldProduceDesiredResults(InMemoryCachingStrategy.Moderate, true); + + [TestMethod] + public void Process_ShouldProduceDesiredResults_ForNoCachingStrategy() => Process_ShouldProduceDesiredResults(InMemoryCachingStrategy.NoCaching, false); + + private void Process_ShouldProduceDesiredResults(InMemoryCachingStrategy strategy, Boolean cacheShouldBeOperative) + { + // Arrange. + var keyOne = "foo"; + var keyTwo = "bar"; + using var randomnessProvider = RandomNumberGenerator.Create(); + var produceValueFunction = new Func(() => SimulatedModel.Random(randomnessProvider)); + using var target = new InMemoryCacheClient(strategy); + + // Act. + var targetIsOperative = target.IsOperative; + var resultOne = target.Process(keyOne, produceValueFunction); + var resultTwo = target.Process(keyTwo, produceValueFunction); + var resultThree = target.Process(keyOne, produceValueFunction); + var resultFour = target.Process(keyTwo, produceValueFunction); + var resultOneHashCode = resultOne.GetImpliedHashCode(); + var resultTwoHashCode = resultTwo.GetImpliedHashCode(); + var resultThreeHashCode = resultThree.GetImpliedHashCode(); + var resultFourHashCode = resultFour.GetImpliedHashCode(); + var resultsOneAndTwoAreEqual = resultOneHashCode == resultTwoHashCode; + var resultsOneAndThreeAreEqual = resultOneHashCode == resultThreeHashCode; + var resultsOneAndFourAreEqual = resultOneHashCode == resultFourHashCode; + var resultsTwoAndThreeAreEqual = resultTwoHashCode == resultThreeHashCode; + var resultsTwoAndFourAreEqual = resultTwoHashCode == resultFourHashCode; + var resultsThreeAndFourAreEqual = resultThreeHashCode == resultFourHashCode; + + // Assert. + Assert.AreEqual(targetIsOperative, cacheShouldBeOperative); + Assert.IsFalse(resultsOneAndTwoAreEqual); + Assert.AreEqual(resultsOneAndThreeAreEqual, cacheShouldBeOperative); + Assert.IsFalse(resultsOneAndFourAreEqual); + Assert.IsFalse(resultsTwoAndThreeAreEqual); + Assert.AreEqual(resultsTwoAndFourAreEqual, cacheShouldBeOperative); + Assert.IsFalse(resultsThreeAndFourAreEqual); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Extensions/ObjectExtensionsTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Extensions/ObjectExtensionsTests.cs index e013953c..9a51f2de 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Extensions/ObjectExtensionsTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Extensions/ObjectExtensionsTests.cs @@ -4,6 +4,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Core.Extensions; using System; using System.Collections.Generic; using System.Security.Cryptography; @@ -13,6 +14,20 @@ namespace RapidField.SolidInstruments.Core.UnitTests.Extensions [TestClass] public class ObjectExtensionsTests { + [TestMethod] + public void CalculateSizeInBytes_ShouldProduceAccurateResult_ForNullTarget() + { + // Arrange. + var expectedResult = IntPtr.Size; + var target = (Object)null; + + // Act. + var result = target.CalculateSizeInBytes(); + + // Assert. + Assert.AreEqual(expectedResult, result); + } + [TestMethod] public void GetImpliedHashCode_ShouldProduceIdenticalHashCodes_ForIdenticalObjects() { diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs b/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs index 449da5c4..5ae0de72 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs @@ -123,19 +123,19 @@ public Boolean Equals(SimulatedObject other) { return false; } - else if (DecimalValues is null && (other.DecimalValues is null) == false) + else if (DecimalValues is null && other.DecimalValues is not null) { return false; } - else if ((DecimalValues is null) == false && other.DecimalValues is null) + else if (DecimalValues is not null && other.DecimalValues is null) { return false; } - else if (NestedObjects is null && (other.NestedObjects is null) == false) + else if (NestedObjects is null && other.NestedObjects is not null) { return false; } - else if ((NestedObjects is null) == false && other.NestedObjects is null) + else if (NestedObjects is not null && other.NestedObjects is null) { return false; } diff --git a/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs b/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs index df325ab6..66a05fd0 100644 --- a/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs +++ b/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs @@ -123,19 +123,19 @@ public Boolean Equals(SimulatedObject other) { return false; } - else if (DecimalValues is null && (other.DecimalValues is null) == false) + else if (DecimalValues is null && other.DecimalValues is not null) { return false; } - else if ((DecimalValues is null) == false && other.DecimalValues is null) + else if (DecimalValues is not null && other.DecimalValues is null) { return false; } - else if (NestedObjects is null && (other.NestedObjects is null) == false) + else if (NestedObjects is null && other.NestedObjects is not null) { return false; } - else if ((NestedObjects is null) == false && other.NestedObjects is null) + else if (NestedObjects is not null && other.NestedObjects is null) { return false; }