From 8ec73f22ab0c9b8763d75e0445a858efd7b7ff32 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Tue, 20 Aug 2024 08:49:03 -0400 Subject: [PATCH 01/22] migrated subscription storage migrated subscription storage into the ContractConnection from underlying connections to reduce repeated code. --- .../Interfaces/IContractConnection.cs | 2 +- Connectors/ActiveMQ/ActiveMQ.csproj | 24 ++++ Connectors/ActiveMQ/Connection.cs | 119 ++++++++++++++++++ Connectors/ActiveMQ/Readme.md | 22 ++++ .../Subscriptions/PublishSubscription.cs | 62 +++++++++ Connectors/Kafka/Connection.cs | 14 --- Connectors/KubeMQ/Connection.cs | 14 --- Connectors/NATS/Connection.cs | 14 --- Core/ContractConnection.cs | 32 ++++- .../Subscriptions/IInternalSubscription.cs | 15 +++ Core/SubscriptionCollection.cs | 55 ++++++++ Core/Subscriptions/PubSubSubscription.cs | 4 +- .../QueryResponseSubscription.cs | 4 +- Core/Subscriptions/SubscriptionBase.cs | 17 ++- MQContract.sln | 9 +- 15 files changed, 355 insertions(+), 52 deletions(-) create mode 100644 Connectors/ActiveMQ/ActiveMQ.csproj create mode 100644 Connectors/ActiveMQ/Connection.cs create mode 100644 Connectors/ActiveMQ/Readme.md create mode 100644 Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs create mode 100644 Core/Interfaces/Subscriptions/IInternalSubscription.cs create mode 100644 Core/SubscriptionCollection.cs diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index d485437..403857b 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -6,7 +6,7 @@ namespace MQContract.Interfaces /// /// This interface represents the Core class for the MQContract system, IE the ContractConnection /// - public interface IContractConnection + public interface IContractConnection : IDisposable { /// /// Called to Ping the underlying system to obtain both information and ensure it is up. Not all Services support this method. diff --git a/Connectors/ActiveMQ/ActiveMQ.csproj b/Connectors/ActiveMQ/ActiveMQ.csproj new file mode 100644 index 0000000..21a7d28 --- /dev/null +++ b/Connectors/ActiveMQ/ActiveMQ.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + MQContract.$(MSBuildProjectName) + MQContract.$(MSBuildProjectName) + true + $(MSBuildProjectDirectory)\Readme.md + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs new file mode 100644 index 0000000..d422454 --- /dev/null +++ b/Connectors/ActiveMQ/Connection.cs @@ -0,0 +1,119 @@ +using Apache.NMS; +using Apache.NMS.Util; +using MQContract.Interfaces.Service; +using MQContract.Messages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.ActiveMQ +{ + internal class Connection : IMessageServiceConnection + { + private const string MESSAGE_TYPE_HEADER_ID = "_MessageType"; + + private bool disposedValue; + + private readonly IConnectionFactory connectionFactory; + private readonly IConnection connection; + private readonly ISession session; + private readonly IMessageProducer producer; + + public Connection(Uri ConnectUri){ + connectionFactory = new NMSConnectionFactory(ConnectUri); + connection = connectionFactory.CreateConnection(); + connection.Start(); + session = connection.CreateSession(); + producer = session.CreateProducer(); + } + + public int? MaxMessageBodySize => 4*1024*1024; + + /// + /// The default timeout to use for RPC calls when not specified by the class or in the call. + /// DEFAULT:1 minute if not specified inside the connection options + /// + public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + + public Task PingAsync() + => throw new NotImplementedException(); + + private async Task ProduceMessage(ServiceMessage message) + { + var msg = await session.CreateBytesMessageAsync(message.Data.ToArray()); + msg.NMSMessageId=message.ID; + msg.Properties[MESSAGE_TYPE_HEADER_ID] = message.MessageTypeID; + foreach (var key in message.Header.Keys) + msg.Properties[key] = message.Header[key]; + return msg; + } + + internal static RecievedServiceMessage ProduceMessage(string channel, IMessage message) + { + var headers = new Dictionary(); + foreach(var key in message.Properties.Keys.OfType()) + { + if (!Equals(key, MESSAGE_TYPE_HEADER_ID)) + headers.Add(key, (string)message.Properties[key]); + } + return new( + message.NMSMessageId, + (string)message.Properties[MESSAGE_TYPE_HEADER_ID], + channel, + new(headers), + message.Body() + ); + } + + public async Task PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + { + try + { + await producer.SendAsync(SessionUtil.GetTopic(session, message.Channel), await ProduceMessage(message)); + return new TransmissionResult(message.ID); + } + catch (Exception ex) + { + return new TransmissionResult(message.ID, Error: ex.Message); + } + } + + public Task QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + producer.Dispose(); + session.Dispose(); + connection.Stop(); + connection.Dispose(); + } + disposedValue=true; + } + } + + void IDisposable.Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Connectors/ActiveMQ/Readme.md b/Connectors/ActiveMQ/Readme.md new file mode 100644 index 0000000..a9144dd --- /dev/null +++ b/Connectors/ActiveMQ/Readme.md @@ -0,0 +1,22 @@ + +# MQContract.ActiveMQ + +## Contents + +- [Connection](#T-MQContract-ActiveMQ-Connection 'MQContract.ActiveMQ.Connection') + - [DefaultTimout](#P-MQContract-ActiveMQ-Connection-DefaultTimout 'MQContract.ActiveMQ.Connection.DefaultTimout') + + +## Connection `type` + +##### Namespace + +MQContract.ActiveMQ + + +### DefaultTimout `property` + +##### Summary + +The default timeout to use for RPC calls when not specified by the class or in the call. +DEFAULT:1 minute if not specified inside the connection options diff --git a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs new file mode 100644 index 0000000..09ffe8e --- /dev/null +++ b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs @@ -0,0 +1,62 @@ +using Apache.NMS; +using Apache.NMS.Util; +using MQContract.Interfaces.Service; +using MQContract.Messages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.ActiveMQ.Subscriptions +{ + internal class PublishSubscription(ISession session, string channel, Action messageRecieved, Action errorRecieved) : IServiceSubscription + { + private bool disposedValue; + private IMessageConsumer? consumer; + + internal async Task StartAsync(CancellationToken cancellationToken) + { + consumer = await session.CreateConsumerAsync(SessionUtil.GetTopic(session, channel)); + consumer.Listener+=ConsumeMessage; + cancellationToken.Register(async () => await EndAsync()); + } + + private void ConsumeMessage(IMessage message) + { + try + { + messageRecieved(Connection.ProduceMessage(channel, message)); + }catch(Exception e) + { + errorRecieved(e); + } + } + + public async Task EndAsync() + { + if (consumer!=null) + await consumer.CloseAsync(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + EndAsync().Wait(); + consumer?.Dispose(); + } + disposedValue=true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 0cb4ed7..bc0fe03 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -22,8 +22,6 @@ public class Connection(ClientConfig clientConfig) : IMessageServiceConnection private readonly IProducer producer = new ProducerBuilder(clientConfig).Build(); private readonly ClientConfig clientConfig = clientConfig; - private readonly List subscriptions = []; - private readonly SemaphoreSlim dataLock = new(1, 1); private readonly Guid Identifier = Guid.NewGuid(); private bool disposedValue; @@ -237,9 +235,6 @@ private static TaskCompletionSource StartResponseListener(Cl channel, cancellationToken); subscription.Run(); - await dataLock.WaitAsync(cancellationToken); - subscriptions.Add(subscription); - dataLock.Release(); return subscription; } @@ -303,9 +298,6 @@ await producer.ProduceAsync( channel, cancellationToken); subscription.Run(); - await dataLock.WaitAsync(cancellationToken); - subscriptions.Add(subscription); - dataLock.Release(); return subscription; } @@ -319,13 +311,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - dataLock.Wait(); - foreach (var sub in subscriptions) - sub.EndAsync().Wait(); - subscriptions.Clear(); producer.Dispose(); - dataLock.Release(); - dataLock.Dispose(); } disposedValue=true; } diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 96151de..0609a85 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -23,8 +23,6 @@ public class Connection : IMessageServiceConnection private readonly ConnectionOptions connectionOptions; private readonly KubeClient client; - private readonly List subscriptions = []; - private readonly SemaphoreSlim dataLock = new(1, 1); private bool disposedValue; /// @@ -209,9 +207,6 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa cancellationToken ); sub.Run(); - await dataLock.WaitAsync(cancellationToken); - subscriptions.Add(sub); - dataLock.Release(); return sub; } @@ -239,9 +234,6 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa cancellationToken ); sub.Run(); - await dataLock.WaitAsync(cancellationToken); - subscriptions.Add(sub); - dataLock.Release(); return sub; } /// @@ -254,13 +246,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - dataLock.Wait(); - foreach (var sub in subscriptions) - sub.EndAsync().Wait(); - subscriptions.Clear(); client.Dispose(); - dataLock.Release(); - dataLock.Dispose(); } disposedValue=true; } diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index b9565bd..5bdb5c8 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -22,8 +22,6 @@ public class Connection : IMessageServiceConnection private readonly NatsConnection natsConnection; private readonly NatsJSContext natsJSContext; private readonly ILogger? logger; - private readonly List subscriptions = []; - private readonly SemaphoreSlim dataLock = new(1, 1); private bool disposedValue; /// @@ -229,9 +227,6 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa cancellationToken ); subscription.Run(); - await dataLock.WaitAsync(cancellationToken); - subscriptions.Add(subscription); - dataLock.Release(); return subscription; } @@ -260,9 +255,6 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa cancellationToken ); sub.Run(); - await dataLock.WaitAsync(cancellationToken); - subscriptions.Add(sub); - dataLock.Release(); return sub; } @@ -276,13 +268,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - dataLock.Wait(); - foreach (var sub in subscriptions) - sub.EndAsync().Wait(); - subscriptions.Clear(); Task.Run(async () => await natsConnection.DisposeAsync()).Wait(); - dataLock.Release(); - dataLock.Dispose(); } disposedValue=true; } diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index 325874d..05a021f 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -33,6 +33,8 @@ public class ContractConnection(IMessageServiceConnection serviceConnection, { private readonly SemaphoreSlim dataLock = new(1, 1); private IEnumerable typeFactories = []; + private bool disposedValue; + private readonly SubscriptionCollection subscriptions = new(); private IMessageFactory GetMessageFactory(bool ignoreMessageHeader = false) where T : class { @@ -101,13 +103,17 @@ public async Task SubscribeAsync(Func,Task messageRecieved, errorRecieved, (originalChannel)=>MapChannel(ChannelMapper.MapTypes.PublishSubscription,originalChannel), + subscriptions, channel:channel, group:group, synchronous:synchronous, options:options, logger:logger); - if (await subscription.EstablishSubscriptionAsync(serviceConnection,cancellationToken)) + if (await subscription.EstablishSubscriptionAsync(serviceConnection, cancellationToken)) + { + await subscriptions.AddAsync(subscription); return subscription; + } throw new SubscriptionFailedException(); } @@ -224,14 +230,38 @@ public async Task SubscribeQueryResponseAsync(Func MapChannel(ChannelMapper.MapTypes.QuerySubscription, originalChannel), + subscriptions, channel: channel, group: group, synchronous: synchronous, options: options, logger: logger); if (await subscription.EstablishSubscriptionAsync(serviceConnection, cancellationToken)) + { + await subscriptions.AddAsync(subscription); return subscription; + } throw new SubscriptionFailedException(); } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + subscriptions.Dispose(); + serviceConnection.Dispose(); + } + disposedValue=true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/Core/Interfaces/Subscriptions/IInternalSubscription.cs b/Core/Interfaces/Subscriptions/IInternalSubscription.cs new file mode 100644 index 0000000..1cbd784 --- /dev/null +++ b/Core/Interfaces/Subscriptions/IInternalSubscription.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.Interfaces.Subscriptions +{ + internal interface IInternalSubscription : ISubscription + { + Guid ID { get; } + + Task EndAsync(bool remove); + } +} diff --git a/Core/SubscriptionCollection.cs b/Core/SubscriptionCollection.cs new file mode 100644 index 0000000..3cde989 --- /dev/null +++ b/Core/SubscriptionCollection.cs @@ -0,0 +1,55 @@ +using MQContract.Interfaces; +using MQContract.Interfaces.Subscriptions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract +{ + internal class SubscriptionCollection + : IDisposable + { + private readonly SemaphoreSlim dataLock = new(1, 1); + private readonly List subscriptions = []; + private bool disposedValue; + + public async Task AddAsync(IInternalSubscription subscription) + { + await dataLock.WaitAsync(); + subscriptions.Add(subscription); + dataLock.Release(); + } + + public async Task RemoveAsync(Guid ID) + { + await dataLock.WaitAsync(); + subscriptions.RemoveAll(sub=>Equals(sub.ID, ID)); + dataLock.Release(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + dataLock.Wait(); + Task.WaitAll(subscriptions.Select(sub => sub.EndAsync(false)).ToArray()); + dataLock.Release(); + dataLock.Dispose(); + subscriptions.Clear(); + } + disposedValue=true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Core/Subscriptions/PubSubSubscription.cs b/Core/Subscriptions/PubSubSubscription.cs index b1a02c8..23c689f 100644 --- a/Core/Subscriptions/PubSubSubscription.cs +++ b/Core/Subscriptions/PubSubSubscription.cs @@ -8,9 +8,9 @@ namespace MQContract.Subscriptions { internal sealed class PubSubSubscription(IMessageFactory messageFactory, Func,Task> messageRecieved, Action errorRecieved, - Func> mapChannel, + Func> mapChannel, SubscriptionCollection collection, string? channel = null, string? group = null, bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) - : SubscriptionBase(mapChannel,channel,synchronous) + : SubscriptionBase(mapChannel,collection,channel,synchronous) where T : class { private readonly Channel dataChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index a899c59..c5db27b 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -8,10 +8,10 @@ namespace MQContract.Subscriptions { internal sealed class QueryResponseSubscription(IMessageFactory queryMessageFactory,IMessageFactory responseMessageFactory, Func, Task>> messageRecieved, Action errorRecieved, - Func> mapChannel, + Func> mapChannel, SubscriptionCollection collection, string? channel = null, string? group = null, bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) - : SubscriptionBase(mapChannel,channel,synchronous),ISubscription + : SubscriptionBase(mapChannel,collection,channel,synchronous),ISubscription where Q : class where R : class { diff --git a/Core/Subscriptions/SubscriptionBase.cs b/Core/Subscriptions/SubscriptionBase.cs index fba7ec2..ed966e5 100644 --- a/Core/Subscriptions/SubscriptionBase.cs +++ b/Core/Subscriptions/SubscriptionBase.cs @@ -1,21 +1,27 @@ using MQContract.Attributes; using MQContract.Interfaces; using MQContract.Interfaces.Service; +using MQContract.Interfaces.Subscriptions; using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace MQContract.Subscriptions { - internal abstract class SubscriptionBase : ISubscription + internal abstract class SubscriptionBase : IInternalSubscription where T : class { protected IServiceSubscription? serviceSubscription; + private readonly SubscriptionCollection collection; protected readonly CancellationTokenSource token = new(); private bool disposedValue; protected string MessageChannel { get; private init; } protected bool Synchronous { get; private init; } - protected SubscriptionBase(Func> mapChannel, string? channel=null,bool synchronous = false){ + public Guid ID { get; private init; } + + protected SubscriptionBase(Func> mapChannel, SubscriptionCollection collection, string? channel=null,bool synchronous = false){ + ID = Guid.NewGuid(); + this.collection=collection; var chan = channel??typeof(T).GetCustomAttribute(false)?.Name??throw new MessageChannelNullException(); Synchronous = synchronous; var tsk = mapChannel(chan); @@ -30,13 +36,18 @@ protected void SyncToken(CancellationToken cancellationToken) protected virtual void InternalDispose() { } - public async Task EndAsync() + public async Task EndAsync(bool remove) { if (serviceSubscription!=null) await serviceSubscription.EndAsync(); await token.CancelAsync(); + if (remove) + await collection.RemoveAsync(ID); } + public Task EndAsync() + => EndAsync(true); + protected void Dispose(bool disposing) { if (!disposedValue) diff --git a/MQContract.sln b/MQContract.sln index c44657a..9885199 100644 --- a/MQContract.sln +++ b/MQContract.sln @@ -25,7 +25,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NATSSample", "Samples\NATSS EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kafka", "Connectors\Kafka\Kafka.csproj", "{E3CE4E3B-8500-4888-BBA5-0256A129C9F5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KafkaSample", "Samples\KafkaSample\KafkaSample.csproj", "{76FFF0EF-C7F4-4D07-9CE1-CA695037CC11}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KafkaSample", "Samples\KafkaSample\KafkaSample.csproj", "{76FFF0EF-C7F4-4D07-9CE1-CA695037CC11}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActiveMQ", "Connectors\ActiveMQ\ActiveMQ.csproj", "{3DF8097C-D24F-4AB9-98E3-A17607ECCE25}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -73,6 +75,10 @@ Global {76FFF0EF-C7F4-4D07-9CE1-CA695037CC11}.Debug|Any CPU.Build.0 = Debug|Any CPU {76FFF0EF-C7F4-4D07-9CE1-CA695037CC11}.Release|Any CPU.ActiveCfg = Release|Any CPU {76FFF0EF-C7F4-4D07-9CE1-CA695037CC11}.Release|Any CPU.Build.0 = Release|Any CPU + {3DF8097C-D24F-4AB9-98E3-A17607ECCE25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DF8097C-D24F-4AB9-98E3-A17607ECCE25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DF8097C-D24F-4AB9-98E3-A17607ECCE25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DF8097C-D24F-4AB9-98E3-A17607ECCE25}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -85,6 +91,7 @@ Global {5A27E00D-B132-4292-9D98-272A172F883A} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} {E3CE4E3B-8500-4888-BBA5-0256A129C9F5} = {FCAD12F9-6992-44D7-8E78-464181584E06} {76FFF0EF-C7F4-4D07-9CE1-CA695037CC11} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} + {3DF8097C-D24F-4AB9-98E3-A17607ECCE25} = {FCAD12F9-6992-44D7-8E78-464181584E06} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F4431E3A-7CBE-469C-A7FE-0A48F6768E02} From 882f14246d71053652d6baeb3c9bf0f446923d38 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Tue, 20 Aug 2024 09:12:12 -0400 Subject: [PATCH 02/22] moved to AsyncDisposable moved items from Disposable to AsyncDisposable to allow for Tasks required for the disposal of some items --- .../Interfaces/IContractConnection.cs | 2 +- Abstractions/Interfaces/ISubscription.cs | 2 +- .../Service/IMessageServiceConnection.cs | 2 +- .../Service/IServiceSubscription.cs | 2 +- .../SubscribeQueryResponseTests.cs | 2 +- .../ContractConnectionTests/SubscribeTests.cs | 2 +- Connectors/ActiveMQ/Connection.cs | 19 +++-------- .../Subscriptions/PublishSubscription.cs | 16 ++------- Connectors/Kafka/Connection.cs | 22 ++---------- Connectors/Kafka/Readme.md | 26 -------------- .../Kafka/Subscriptions/SubscriptionBase.cs | 17 ++++++++++ Connectors/KubeMQ/Connection.cs | 22 ++---------- Connectors/KubeMQ/Readme.md | 26 -------------- Connectors/KubeMQ/SDK/KubeClient.cs | 30 ++++++---------- .../KubeMQ/Subscriptions/SubscriptionBase.cs | 22 ++++-------- Connectors/NATS/Connection.cs | 21 ++---------- Connectors/NATS/Readme.md | 26 -------------- .../NATS/Subscriptions/SubscriptionBase.cs | 16 +++------ Core/ContractConnection.cs | 16 ++------- Core/SubscriptionCollection.cs | 34 +++++++------------ Core/Subscriptions/SubscriptionBase.cs | 21 ++++-------- Samples/KafkaSample/Program.cs | 8 ++--- Samples/KubeMQSample/Program.cs | 8 ++--- Samples/NATSSample/Program.cs | 8 ++--- 24 files changed, 93 insertions(+), 277 deletions(-) diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index 403857b..05bd812 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -6,7 +6,7 @@ namespace MQContract.Interfaces /// /// This interface represents the Core class for the MQContract system, IE the ContractConnection /// - public interface IContractConnection : IDisposable + public interface IContractConnection : IAsyncDisposable { /// /// Called to Ping the underlying system to obtain both information and ensure it is up. Not all Services support this method. diff --git a/Abstractions/Interfaces/ISubscription.cs b/Abstractions/Interfaces/ISubscription.cs index 3a92f16..cdb8914 100644 --- a/Abstractions/Interfaces/ISubscription.cs +++ b/Abstractions/Interfaces/ISubscription.cs @@ -3,7 +3,7 @@ /// /// This interface represents a Contract Connection Subscription and is used to house and end the subscription /// - public interface ISubscription : IDisposable + public interface ISubscription : IAsyncDisposable { /// /// Called to end (close off) the subscription diff --git a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs index 1d3e565..7580990 100644 --- a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs @@ -6,7 +6,7 @@ namespace MQContract.Interfaces.Service /// Defines an underlying service connection. This interface is used to allow for the creation of multiple underlying connection types to support the ability to use common code while /// being able to run against 1 or more Message services. /// - public interface IMessageServiceConnection: IDisposable + public interface IMessageServiceConnection: IAsyncDisposable { /// /// Maximum supported message body size in bytes diff --git a/Abstractions/Interfaces/Service/IServiceSubscription.cs b/Abstractions/Interfaces/Service/IServiceSubscription.cs index 160ac7d..05a9551 100644 --- a/Abstractions/Interfaces/Service/IServiceSubscription.cs +++ b/Abstractions/Interfaces/Service/IServiceSubscription.cs @@ -3,7 +3,7 @@ /// /// Represents an underlying service level subscription /// - public interface IServiceSubscription : IDisposable + public interface IServiceSubscription : IAsyncDisposable { /// /// Called to end the subscription diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs index 376ff4b..0771c30 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs @@ -596,7 +596,7 @@ public async Task TestSubscribeQueryResponseAsyncWithDisposal() Exception? disposeError = null; try { - subscription.Dispose(); + await subscription.DisposeAsync(); } catch (Exception e) { diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs index 85eafbc..7c5195a 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs @@ -569,7 +569,7 @@ public async Task TestSubscribeAsyncWithDisposal() Exception? disposeError = null; try { - subscription.Dispose(); + await subscription.DisposeAsync(); }catch(Exception e) { disposeError=e; diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index d422454..1b84e10 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -95,25 +95,16 @@ public Task QueryAsync(ServiceMessage message, TimeSpan time throw new NotImplementedException(); } - protected virtual void Dispose(bool disposing) + public async ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) - { - producer.Dispose(); - session.Dispose(); - connection.Stop(); - connection.Dispose(); - } disposedValue=true; + producer.Dispose(); + session.Dispose(); + await connection.StopAsync(); + connection.Dispose(); } } - - void IDisposable.Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs index 09ffe8e..c60975f 100644 --- a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs +++ b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs @@ -39,24 +39,14 @@ public async Task EndAsync() await consumer.CloseAsync(); } - protected virtual void Dispose(bool disposing) + public async ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) - { - EndAsync().Wait(); - consumer?.Dispose(); - } disposedValue=true; + await EndAsync(); + consumer?.Dispose(); } } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index bc0fe03..56f3d77 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -301,30 +301,14 @@ await producer.ProduceAsync( return subscription; } - /// - /// Called to dispose of the resources used - /// - /// Indicates if it is disposing - protected virtual void Dispose(bool disposing) + public ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) - { - producer.Dispose(); - } disposedValue=true; + producer.Dispose(); } - } - - /// - /// Called to dispose of the resources used - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + return ValueTask.CompletedTask; } } } diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index 6124ca2..649b0f2 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -7,8 +7,6 @@ - [#ctor(clientConfig)](#M-MQContract-Kafka-Connection-#ctor-Confluent-Kafka-ClientConfig- 'MQContract.Kafka.Connection.#ctor(Confluent.Kafka.ClientConfig)') - [DefaultTimout](#P-MQContract-Kafka-Connection-DefaultTimout 'MQContract.Kafka.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Kafka-Connection-MaxMessageBodySize 'MQContract.Kafka.Connection.MaxMessageBodySize') - - [Dispose(disposing)](#M-MQContract-Kafka-Connection-Dispose-System-Boolean- 'MQContract.Kafka.Connection.Dispose(System.Boolean)') - - [Dispose()](#M-MQContract-Kafka-Connection-Dispose 'MQContract.Kafka.Connection.Dispose') - [PingAsync()](#M-MQContract-Kafka-Connection-PingAsync 'MQContract.Kafka.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Kafka-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -63,30 +61,6 @@ DEFAULT:1 minute if not specified inside the connection options The maximum message body size allowed - -### Dispose(disposing) `method` - -##### Summary - -Called to dispose of the resources used - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| disposing | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if it is disposing | - - -### Dispose() `method` - -##### Summary - -Called to dispose of the resources used - -##### Parameters - -This method has no parameters. - ### PingAsync() `method` diff --git a/Connectors/Kafka/Subscriptions/SubscriptionBase.cs b/Connectors/Kafka/Subscriptions/SubscriptionBase.cs index 6525e0d..d615195 100644 --- a/Connectors/Kafka/Subscriptions/SubscriptionBase.cs +++ b/Connectors/Kafka/Subscriptions/SubscriptionBase.cs @@ -54,5 +54,22 @@ public void Dispose() Dispose(disposing: true); GC.SuppressFinalize(this); } + + public async ValueTask DisposeAsync() + { + if (!disposedValue) + { + disposedValue=true; + if (!cancelToken.IsCancellationRequested) + await cancelToken.CancelAsync(); + try + { + Consumer.Close(); + } + catch (Exception) { } + Consumer.Dispose(); + cancelToken.Dispose(); + } + } } } diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 0609a85..c2fc53b 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -236,30 +236,14 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa sub.Run(); return sub; } - /// - /// Called to dispose of the resources used - /// - /// Indicates if it is disposing - protected virtual void Dispose(bool disposing) + + public async ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) - { - client.Dispose(); - } disposedValue=true; + await client.DisposeAsync(); } } - - /// - /// Called to dispose of the resources used - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Connectors/KubeMQ/Readme.md b/Connectors/KubeMQ/Readme.md index 7bac41d..c00d1b4 100644 --- a/Connectors/KubeMQ/Readme.md +++ b/Connectors/KubeMQ/Readme.md @@ -18,8 +18,6 @@ - [#ctor(options)](#M-MQContract-KubeMQ-Connection-#ctor-MQContract-KubeMQ-ConnectionOptions- 'MQContract.KubeMQ.Connection.#ctor(MQContract.KubeMQ.ConnectionOptions)') - [DefaultTimout](#P-MQContract-KubeMQ-Connection-DefaultTimout 'MQContract.KubeMQ.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-KubeMQ-Connection-MaxMessageBodySize 'MQContract.KubeMQ.Connection.MaxMessageBodySize') - - [Dispose(disposing)](#M-MQContract-KubeMQ-Connection-Dispose-System-Boolean- 'MQContract.KubeMQ.Connection.Dispose(System.Boolean)') - - [Dispose()](#M-MQContract-KubeMQ-Connection-Dispose 'MQContract.KubeMQ.Connection.Dispose') - [PingAsync()](#M-MQContract-KubeMQ-Connection-PingAsync 'MQContract.KubeMQ.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -338,30 +336,6 @@ DEFAULT:30 seconds if not specified inside the connection options The maximum message body size allowed - -### Dispose(disposing) `method` - -##### Summary - -Called to dispose of the resources used - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| disposing | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if it is disposing | - - -### Dispose() `method` - -##### Summary - -Called to dispose of the resources used - -##### Parameters - -This method has no parameters. - ### PingAsync() `method` diff --git a/Connectors/KubeMQ/SDK/KubeClient.cs b/Connectors/KubeMQ/SDK/KubeClient.cs index ce90fdb..55809e6 100644 --- a/Connectors/KubeMQ/SDK/KubeClient.cs +++ b/Connectors/KubeMQ/SDK/KubeClient.cs @@ -6,7 +6,7 @@ namespace MQContract.KubeMQ.SDK.Connection { - internal class KubeClient : IDisposable + internal class KubeClient : IAsyncDisposable { private const int RETRY_COUNT = 5; @@ -163,31 +163,21 @@ internal AsyncServerStreamingCall SubscribeToEvents(Subscribe subs return client.SubscribeToEvents(subscribe, headers: headers, cancellationToken: cancellationToken); }); - protected virtual void Dispose(bool disposing) + public async ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) + disposedValue=true; + try { - try - { - channel.ShutdownAsync().Wait(); - } - catch (Exception ex) - { - logger?.LogError(ex,"Error shutting down grpc Kube Channel: {ErrorMessage}",ex.Message); - } - channel.Dispose(); + await channel.ShutdownAsync(); } - disposedValue=true; + catch (Exception ex) + { + logger?.LogError(ex, "Error shutting down grpc Kube Channel: {ErrorMessage}", ex.Message); + } + channel.Dispose(); } } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs b/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs index 135d53d..e892976 100644 --- a/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs +++ b/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs @@ -24,10 +24,10 @@ public void Run() cancelToken.Cancel(); }); - cancelToken.Token.Register(() => + cancelToken.Token.Register(async () => { active = false; - client.Dispose(); + await client.DisposeAsync(); }); Task.Run(async () => { @@ -88,28 +88,20 @@ public async Task EndAsync() try { await cancelToken.CancelAsync(); - client.Dispose(); + await client.DisposeAsync(); cancelToken.Dispose(); } catch{ } } } - protected virtual void Dispose(bool disposing) + public async ValueTask DisposeAsync() { - if (!disposedValue) + if (!disposedValue && active) { - if (disposing && active) - EndAsync().Wait(); - disposedValue = true; + disposedValue=true; + await EndAsync(); } } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index 5bdb5c8..41e9217 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -258,30 +258,13 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa return sub; } - /// - /// Called to dispose of the resources used - /// - /// Indicates if it is disposing - protected virtual void Dispose(bool disposing) + public async ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) - { - Task.Run(async () => await natsConnection.DisposeAsync()).Wait(); - } disposedValue=true; + await natsConnection.DisposeAsync(); } } - - /// - /// Called to dispose of the resources used - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Connectors/NATS/Readme.md b/Connectors/NATS/Readme.md index d9ede67..3bc6524 100644 --- a/Connectors/NATS/Readme.md +++ b/Connectors/NATS/Readme.md @@ -8,8 +8,6 @@ - [DefaultTimout](#P-MQContract-NATS-Connection-DefaultTimout 'MQContract.NATS.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-NATS-Connection-MaxMessageBodySize 'MQContract.NATS.Connection.MaxMessageBodySize') - [CreateStreamAsync(streamConfig,cancellationToken)](#M-MQContract-NATS-Connection-CreateStreamAsync-NATS-Client-JetStream-Models-StreamConfig,System-Threading-CancellationToken- 'MQContract.NATS.Connection.CreateStreamAsync(NATS.Client.JetStream.Models.StreamConfig,System.Threading.CancellationToken)') - - [Dispose(disposing)](#M-MQContract-NATS-Connection-Dispose-System-Boolean- 'MQContract.NATS.Connection.Dispose(System.Boolean)') - - [Dispose()](#M-MQContract-NATS-Connection-Dispose 'MQContract.NATS.Connection.Dispose') - [PingAsync()](#M-MQContract-NATS-Connection-PingAsync 'MQContract.NATS.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-NATS-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-NATS-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -82,30 +80,6 @@ The stream creation result | streamConfig | [NATS.Client.JetStream.Models.StreamConfig](#T-NATS-Client-JetStream-Models-StreamConfig 'NATS.Client.JetStream.Models.StreamConfig') | The configuration settings for the stream | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -### Dispose(disposing) `method` - -##### Summary - -Called to dispose of the resources used - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| disposing | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if it is disposing | - - -### Dispose() `method` - -##### Summary - -Called to dispose of the resources used - -##### Parameters - -This method has no parameters. - ### PingAsync() `method` diff --git a/Connectors/NATS/Subscriptions/SubscriptionBase.cs b/Connectors/NATS/Subscriptions/SubscriptionBase.cs index cb8999f..c0bc7db 100644 --- a/Connectors/NATS/Subscriptions/SubscriptionBase.cs +++ b/Connectors/NATS/Subscriptions/SubscriptionBase.cs @@ -42,23 +42,15 @@ public async Task EndAsync() try { await cancelToken.CancelAsync(); } catch { } } - protected virtual void Dispose(bool disposing) + public async ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) - { - cancelToken.Cancel(); - cancelToken.Dispose(); - } disposedValue=true; + if (!cancelToken.IsCancellationRequested) + await cancelToken.CancelAsync(); + cancelToken.Dispose(); } } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index 05a021f..f429c96 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -244,24 +244,14 @@ public async Task SubscribeQueryResponseAsync(Func subscriptions = []; @@ -22,34 +22,24 @@ public async Task AddAsync(IInternalSubscription subscription) dataLock.Release(); } - public async Task RemoveAsync(Guid ID) - { - await dataLock.WaitAsync(); - subscriptions.RemoveAll(sub=>Equals(sub.ID, ID)); - dataLock.Release(); - } - - protected virtual void Dispose(bool disposing) + public async ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) - { - dataLock.Wait(); - Task.WaitAll(subscriptions.Select(sub => sub.EndAsync(false)).ToArray()); - dataLock.Release(); - dataLock.Dispose(); - subscriptions.Clear(); - } - disposedValue=true; + disposedValue = true; + await dataLock.WaitAsync(); + await Task.WhenAll(subscriptions.Select(sub => sub.EndAsync(false)).ToArray()); + dataLock.Release(); + dataLock.Dispose(); + subscriptions.Clear(); } } - public void Dispose() + public async Task RemoveAsync(Guid ID) { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + await dataLock.WaitAsync(); + subscriptions.RemoveAll(sub=>Equals(sub.ID, ID)); + dataLock.Release(); } } } diff --git a/Core/Subscriptions/SubscriptionBase.cs b/Core/Subscriptions/SubscriptionBase.cs index ed966e5..59ccbf2 100644 --- a/Core/Subscriptions/SubscriptionBase.cs +++ b/Core/Subscriptions/SubscriptionBase.cs @@ -48,26 +48,17 @@ public async Task EndAsync(bool remove) public Task EndAsync() => EndAsync(true); - protected void Dispose(bool disposing) + public async ValueTask DisposeAsync() { if (!disposedValue) { - if (disposing) - { - EndAsync().Wait(); - InternalDispose(); - serviceSubscription?.Dispose(); - token.Dispose(); - } disposedValue=true; + await EndAsync(); + InternalDispose(); + if (serviceSubscription!=null) + await serviceSubscription!.EndAsync(); + token.Dispose(); } } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Samples/KafkaSample/Program.cs b/Samples/KafkaSample/Program.cs index 65a5b92..770c292 100644 --- a/Samples/KafkaSample/Program.cs +++ b/Samples/KafkaSample/Program.cs @@ -10,7 +10,7 @@ sourceCancel.Cancel(); }; -using var serviceConnection = new Connection(new Confluent.Kafka.ClientConfig() +await using var serviceConnection = new Connection(new Confluent.Kafka.ClientConfig() { ClientId="KafkaSample", BootstrapServers="localhost:56497" @@ -18,7 +18,7 @@ var contractConnection = new ContractConnection(serviceConnection); -using var arrivalSubscription = await contractConnection.SubscribeAsync( +await using var arrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); @@ -28,7 +28,7 @@ cancellationToken: sourceCancel.Token ); -using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( +await using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( (greeting) => { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); @@ -41,7 +41,7 @@ cancellationToken: sourceCancel.Token ); -using var storedArrivalSubscription = await contractConnection.SubscribeAsync( +await using var storedArrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); diff --git a/Samples/KubeMQSample/Program.cs b/Samples/KubeMQSample/Program.cs index c03c8ef..c000098 100644 --- a/Samples/KubeMQSample/Program.cs +++ b/Samples/KubeMQSample/Program.cs @@ -10,7 +10,7 @@ sourceCancel.Cancel(); }; -using var serviceConnection = new Connection(new ConnectionOptions() +await using var serviceConnection = new Connection(new ConnectionOptions() { Logger=new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider().CreateLogger("Messages"), ClientId="KubeMQSample" @@ -18,7 +18,7 @@ var contractConnection = new ContractConnection(serviceConnection); -using var arrivalSubscription = await contractConnection.SubscribeAsync( +await using var arrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); @@ -28,7 +28,7 @@ cancellationToken: sourceCancel.Token ); -using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( +await using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( (greeting) => { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); @@ -41,7 +41,7 @@ cancellationToken: sourceCancel.Token ); -using var storedArrivalSubscription = await contractConnection.SubscribeAsync( +await using var storedArrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); diff --git a/Samples/NATSSample/Program.cs b/Samples/NATSSample/Program.cs index 8c96795..c2fa282 100644 --- a/Samples/NATSSample/Program.cs +++ b/Samples/NATSSample/Program.cs @@ -11,7 +11,7 @@ sourceCancel.Cancel(); }; -using var serviceConnection = new Connection(new NATS.Client.Core.NatsOpts() +await using var serviceConnection = new Connection(new NATS.Client.Core.NatsOpts() { LoggerFactory=new Microsoft.Extensions.Logging.LoggerFactory(), Name="NATSSample" @@ -25,7 +25,7 @@ var contractConnection = new ContractConnection(serviceConnection,channelMapper:mapper); -using var arrivalSubscription = await contractConnection.SubscribeAsync( +await using var arrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); @@ -35,7 +35,7 @@ cancellationToken: sourceCancel.Token ); -using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( +await using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( (greeting) => { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); @@ -48,7 +48,7 @@ cancellationToken: sourceCancel.Token ); -using var storedArrivalSubscription = await contractConnection.SubscribeAsync( +await using var storedArrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); From 9fefc94dac93a516d8e9164632243c869ee1be11 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Tue, 20 Aug 2024 16:01:24 -0400 Subject: [PATCH 03/22] migrated to valuetask migrated most functionality calls to ValueTask instead of Task or just generic results to allow for more asynchronous concepts --- Abstractions/Exceptions.cs | 3 + .../Conversion/IMessageConverter.cs | 4 +- .../Interfaces/Encoding/IMessageEncoder.cs | 4 +- .../Encoding/IMessageTypeEncoder.cs | 4 +- .../Encrypting/IMessageEncryptor.cs | 4 +- .../Interfaces/IContractConnection.cs | 12 ++-- Abstractions/Interfaces/ISubscription.cs | 2 +- .../Service/IMessageServiceConnection.cs | 10 ++-- .../Service/IServiceSubscription.cs | 2 +- Abstractions/Readme.md | 48 ++++++++-------- AutomatedTesting/ChannelMapperTests.cs | 20 +++---- .../ContractConnectionTests/PublishTests.cs | 24 ++++---- .../ContractConnectionTests/QueryTests.cs | 30 +++++----- .../SubscribeQueryResponseTests.cs | 54 +++++++++--------- .../ContractConnectionTests/SubscribeTests.cs | 55 +++++++++++++++---- .../BasicMessageToNameAndVersionMessage.cs | 4 +- .../NoChannelMessageToBasicMessage.cs | 4 +- .../Encoders/TestMessageEncoder.cs | 8 +-- .../TestMessageEncoderWithInjection.cs | 8 +-- .../Encryptors/TestMessageEncryptor.cs | 8 +-- .../TestMessageEncryptorWithInjection.cs | 8 +-- Connectors/ActiveMQ/Connection.cs | 12 ++-- .../Subscriptions/PublishSubscription.cs | 2 +- Connectors/Kafka/Connection.cs | 14 ++--- Connectors/Kafka/Readme.md | 6 +- .../Kafka/Subscriptions/SubscriptionBase.cs | 33 ++--------- Connectors/KubeMQ/Connection.cs | 16 +++--- Connectors/KubeMQ/Readme.md | 6 +- .../KubeMQ/Subscriptions/QuerySubscription.cs | 2 +- .../KubeMQ/Subscriptions/SubscriptionBase.cs | 4 +- Connectors/NATS/Connection.cs | 12 ++-- Connectors/NATS/Readme.md | 6 +- .../NATS/Subscriptions/QuerySubscription.cs | 2 +- .../NATS/Subscriptions/SubscriptionBase.cs | 2 +- Core/ContractConnection.cs | 33 +++++------ Core/Defaults/JsonEncoder.cs | 12 ++-- Core/Defaults/NonEncryptor.cs | 7 ++- Core/Factories/ConversionPath.cs | 19 ++++--- Core/Factories/MessageTypeFactory.cs | 18 +++--- Core/Interfaces/Conversion/IConversionPath.cs | 4 +- Core/Interfaces/Factories/IMessageFactory.cs | 2 +- .../Subscriptions/IInternalSubscription.cs | 2 +- Core/SubscriptionCollection.cs | 2 +- Core/Subscriptions/PubSubSubscription.cs | 2 +- .../QueryResponseSubscription.cs | 8 +-- Core/Subscriptions/SubscriptionBase.cs | 6 +- Core/Utility.cs | 11 +++- 47 files changed, 295 insertions(+), 264 deletions(-) diff --git a/Abstractions/Exceptions.cs b/Abstractions/Exceptions.cs index a3d8013..5733908 100644 --- a/Abstractions/Exceptions.cs +++ b/Abstractions/Exceptions.cs @@ -1,10 +1,12 @@ using MQContract.Interfaces.Service; +using System.Diagnostics.CodeAnalysis; namespace MQContract { /// /// An exception thrown when the options supplied to an underlying system connection are not of an expected type. /// + [ExcludeFromCodeCoverage(Justification ="This exception is only really thrown from underlying service connection implementations")] public sealed class InvalidChannelOptionsTypeException : InvalidCastException { @@ -47,6 +49,7 @@ public static void ThrowIfNotNullAndNotOfType(IServiceChannelOptions? options,IE /// /// An exception thrown when there are options supplied to an underlying system connection that does not support options for that particular instance /// + [ExcludeFromCodeCoverage(Justification = "This exception is only really thrown from underlying service connection implementations")] public sealed class NoChannelOptionsAvailableException : Exception { diff --git a/Abstractions/Interfaces/Conversion/IMessageConverter.cs b/Abstractions/Interfaces/Conversion/IMessageConverter.cs index 89b9def..1b7b29c 100644 --- a/Abstractions/Interfaces/Conversion/IMessageConverter.cs +++ b/Abstractions/Interfaces/Conversion/IMessageConverter.cs @@ -7,13 +7,13 @@ /// /// The source message type /// The destination message type - public interface IMessageConverter + public interface IMessageConverter { /// /// Called to convert a message from type T to type V /// /// The message to convert /// The source message converted to the destination type V - V Convert(T source); + ValueTask ConvertAsync(T source); } } diff --git a/Abstractions/Interfaces/Encoding/IMessageEncoder.cs b/Abstractions/Interfaces/Encoding/IMessageEncoder.cs index c4d163c..0c77507 100644 --- a/Abstractions/Interfaces/Encoding/IMessageEncoder.cs +++ b/Abstractions/Interfaces/Encoding/IMessageEncoder.cs @@ -13,7 +13,7 @@ public interface IMessageEncoder /// The type of message being encoded /// The message being encoded /// A byte array of the message in it's encoded form that will be transmitted - byte[] Encode(T message); + ValueTask EncodeAsync(T message); /// /// Called to decode a message from a byte array @@ -21,6 +21,6 @@ public interface IMessageEncoder /// The type of message being decoded /// A stream representing the byte array data that was transmitted as the message body in KubeMQ /// Null when fails or the value of T that was encoded inside the stream - T? Decode(Stream stream); + ValueTask DecodeAsync(Stream stream); } } diff --git a/Abstractions/Interfaces/Encoding/IMessageTypeEncoder.cs b/Abstractions/Interfaces/Encoding/IMessageTypeEncoder.cs index ddd7338..726b3a0 100644 --- a/Abstractions/Interfaces/Encoding/IMessageTypeEncoder.cs +++ b/Abstractions/Interfaces/Encoding/IMessageTypeEncoder.cs @@ -12,12 +12,12 @@ public interface IMessageTypeEncoder /// /// The message value to encode /// The message encoded as a byte array - byte[] Encode(T message); + ValueTask EncodeAsync(T message); /// /// Called to decode the message from a byte stream into the specified type /// /// The byte stream containing the encoded message /// null if the Decode fails, otherwise an instance of the message decoded from the stream - T? Decode(Stream stream); + ValueTask DecodeAsync(Stream stream); } } diff --git a/Abstractions/Interfaces/Encrypting/IMessageEncryptor.cs b/Abstractions/Interfaces/Encrypting/IMessageEncryptor.cs index 6b1d20d..0710e3c 100644 --- a/Abstractions/Interfaces/Encrypting/IMessageEncryptor.cs +++ b/Abstractions/Interfaces/Encrypting/IMessageEncryptor.cs @@ -15,7 +15,7 @@ public interface IMessageEncryptor /// The stream representing the message body binary data /// The message headers that were provided by the message /// A decrypted stream of the message body - Stream Decrypt(Stream stream, MessageHeader headers); + ValueTask DecryptAsync(Stream stream, MessageHeader headers); /// /// Called to encrypt the message body prior to transmitting a message @@ -23,6 +23,6 @@ public interface IMessageEncryptor /// The original unencrypted body data /// The headers that are desired to attache to the message if needed /// An encrypted byte array of the message body - byte[] Encrypt(byte[] data, out Dictionary headers); + ValueTask EncryptAsync(byte[] data, out Dictionary headers); } } diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index 05bd812..a0229a0 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -12,7 +12,7 @@ public interface IContractConnection : IAsyncDisposable /// Called to Ping the underlying system to obtain both information and ensure it is up. Not all Services support this method. /// /// - Task PingAsync(); + ValueTask PingAsync(); /// /// Called to send a message into the underlying service Pub/Sub style /// @@ -23,7 +23,7 @@ public interface IContractConnection : IAsyncDisposable /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A result indicating the tranmission results - Task PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to create a subscription into the underlying service Pub/Sub style @@ -38,7 +38,7 @@ public interface IContractConnection : IAsyncDisposable /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A subscription instance that can be ended when desired - Task SubscribeAsync(Func,Task> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false,bool synchronous=false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeAsync(Func,Task> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false,bool synchronous=false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to send a message into the underlying service in the Query/Response style @@ -52,7 +52,7 @@ public interface IContractConnection : IAsyncDisposable /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A result indicating the success or failure as well as the returned message - Task> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; /// @@ -67,7 +67,7 @@ public interface IContractConnection : IAsyncDisposable /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A result indicating the success or failure as well as the returned message - Task> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class; /// /// Called to create a subscription into the underlying service Query/Reponse style @@ -83,7 +83,7 @@ public interface IContractConnection : IAsyncDisposable /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A subscription instance that can be ended when desired - Task SubscribeQueryResponseAsync(Func,Task>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeQueryResponseAsync(Func,Task>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; } diff --git a/Abstractions/Interfaces/ISubscription.cs b/Abstractions/Interfaces/ISubscription.cs index cdb8914..bb77e8b 100644 --- a/Abstractions/Interfaces/ISubscription.cs +++ b/Abstractions/Interfaces/ISubscription.cs @@ -9,6 +9,6 @@ public interface ISubscription : IAsyncDisposable /// Called to end (close off) the subscription /// /// A task that is ending the subscription and closing off the resources for it - Task EndAsync(); + ValueTask EndAsync(); } } diff --git a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs index 7580990..f1e8a65 100644 --- a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs @@ -20,7 +20,7 @@ public interface IMessageServiceConnection: IAsyncDisposable /// Implemented Ping call if avaialble for the underlying service /// /// A Ping Result - Task PingAsync(); + ValueTask PingAsync(); /// /// Implements a publish call to publish the given message /// @@ -28,7 +28,7 @@ public interface IMessageServiceConnection: IAsyncDisposable /// The Service Channel Options instance that was supplied at the Contract Connection level /// A cancellation token /// A transmission result instance indicating the result - Task PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); /// /// Implements a call to create a subscription to a given channel as a member of a given group /// @@ -39,7 +39,7 @@ public interface IMessageServiceConnection: IAsyncDisposable /// The Service Channel Options instance that was supplied at the Contract Connection level /// A cancellation token /// A service subscription object - Task SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); /// /// Implements a call to submit a response query request into the underlying service /// @@ -48,7 +48,7 @@ public interface IMessageServiceConnection: IAsyncDisposable /// The Service Channel Options instance that was supplied at the Contract Connection level /// A cancellation token /// A Query Result instance based on what happened - Task QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); /// /// Implements a call to create a subscription to a given channel as a member of a given group for responding to queries /// @@ -59,6 +59,6 @@ public interface IMessageServiceConnection: IAsyncDisposable /// The Service Channel Options instance that was supplied at the Contract Connection level /// A cancellation token /// A service subscription object - Task SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); } } diff --git a/Abstractions/Interfaces/Service/IServiceSubscription.cs b/Abstractions/Interfaces/Service/IServiceSubscription.cs index 05a9551..d652b9e 100644 --- a/Abstractions/Interfaces/Service/IServiceSubscription.cs +++ b/Abstractions/Interfaces/Service/IServiceSubscription.cs @@ -9,6 +9,6 @@ public interface IServiceSubscription : IAsyncDisposable /// Called to end the subscription /// /// A task to allow for asynchronous ending of the subscription - Task EndAsync(); + ValueTask EndAsync(); } } diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index 7919b03..5dbbf5d 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -15,13 +15,13 @@ - [Header](#P-MQContract-Interfaces-Messages-IEncodedMessage-Header 'MQContract.Interfaces.Messages.IEncodedMessage.Header') - [MessageTypeID](#P-MQContract-Interfaces-Messages-IEncodedMessage-MessageTypeID 'MQContract.Interfaces.Messages.IEncodedMessage.MessageTypeID') - [IMessageConverter\`2](#T-MQContract-Interfaces-Conversion-IMessageConverter`2 'MQContract.Interfaces.Conversion.IMessageConverter`2') - - [Convert(source)](#M-MQContract-Interfaces-Conversion-IMessageConverter`2-Convert-`0- 'MQContract.Interfaces.Conversion.IMessageConverter`2.Convert(`0)') + - [ConvertAsync(source)](#M-MQContract-Interfaces-Conversion-IMessageConverter`2-ConvertAsync-`0- 'MQContract.Interfaces.Conversion.IMessageConverter`2.ConvertAsync(`0)') - [IMessageEncoder](#T-MQContract-Interfaces-Encoding-IMessageEncoder 'MQContract.Interfaces.Encoding.IMessageEncoder') - - [Decode\`\`1(stream)](#M-MQContract-Interfaces-Encoding-IMessageEncoder-Decode``1-System-IO-Stream- 'MQContract.Interfaces.Encoding.IMessageEncoder.Decode``1(System.IO.Stream)') - - [Encode\`\`1(message)](#M-MQContract-Interfaces-Encoding-IMessageEncoder-Encode``1-``0- 'MQContract.Interfaces.Encoding.IMessageEncoder.Encode``1(``0)') + - [DecodeAsync\`\`1(stream)](#M-MQContract-Interfaces-Encoding-IMessageEncoder-DecodeAsync``1-System-IO-Stream- 'MQContract.Interfaces.Encoding.IMessageEncoder.DecodeAsync``1(System.IO.Stream)') + - [EncodeAsync\`\`1(message)](#M-MQContract-Interfaces-Encoding-IMessageEncoder-EncodeAsync``1-``0- 'MQContract.Interfaces.Encoding.IMessageEncoder.EncodeAsync``1(``0)') - [IMessageEncryptor](#T-MQContract-Interfaces-Encrypting-IMessageEncryptor 'MQContract.Interfaces.Encrypting.IMessageEncryptor') - - [Decrypt(stream,headers)](#M-MQContract-Interfaces-Encrypting-IMessageEncryptor-Decrypt-System-IO-Stream,MQContract-Messages-MessageHeader- 'MQContract.Interfaces.Encrypting.IMessageEncryptor.Decrypt(System.IO.Stream,MQContract.Messages.MessageHeader)') - - [Encrypt(data,headers)](#M-MQContract-Interfaces-Encrypting-IMessageEncryptor-Encrypt-System-Byte[],System-Collections-Generic-Dictionary{System-String,System-String}@- 'MQContract.Interfaces.Encrypting.IMessageEncryptor.Encrypt(System.Byte[],System.Collections.Generic.Dictionary{System.String,System.String}@)') + - [DecryptAsync(stream,headers)](#M-MQContract-Interfaces-Encrypting-IMessageEncryptor-DecryptAsync-System-IO-Stream,MQContract-Messages-MessageHeader- 'MQContract.Interfaces.Encrypting.IMessageEncryptor.DecryptAsync(System.IO.Stream,MQContract.Messages.MessageHeader)') + - [EncryptAsync(data,headers)](#M-MQContract-Interfaces-Encrypting-IMessageEncryptor-EncryptAsync-System-Byte[],System-Collections-Generic-Dictionary{System-String,System-String}@- 'MQContract.Interfaces.Encrypting.IMessageEncryptor.EncryptAsync(System.Byte[],System.Collections.Generic.Dictionary{System.String,System.String}@)') - [IMessageServiceConnection](#T-MQContract-Interfaces-Service-IMessageServiceConnection 'MQContract.Interfaces.Service.IMessageServiceConnection') - [DefaultTimout](#P-MQContract-Interfaces-Service-IMessageServiceConnection-DefaultTimout 'MQContract.Interfaces.Service.IMessageServiceConnection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Interfaces-Service-IMessageServiceConnection-MaxMessageBodySize 'MQContract.Interfaces.Service.IMessageServiceConnection.MaxMessageBodySize') @@ -29,10 +29,10 @@ - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-Task{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [IMessageTypeEncoder\`1](#T-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1') - - [Decode(stream)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-Decode-System-IO-Stream- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.Decode(System.IO.Stream)') - - [Encode(message)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-Encode-`0- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.Encode(`0)') + - [DecodeAsync(stream)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-DecodeAsync-System-IO-Stream- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.DecodeAsync(System.IO.Stream)') + - [EncodeAsync(message)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-EncodeAsync-`0- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.EncodeAsync(`0)') - [IMessageTypeEncryptor\`1](#T-MQContract-Interfaces-Encrypting-IMessageTypeEncryptor`1 'MQContract.Interfaces.Encrypting.IMessageTypeEncryptor`1') - [IRecievedMessage\`1](#T-MQContract-Interfaces-IRecievedMessage`1 'MQContract.Interfaces.IRecievedMessage`1') - [Headers](#P-MQContract-Interfaces-IRecievedMessage`1-Headers 'MQContract.Interfaces.IRecievedMessage`1.Headers') @@ -330,8 +330,8 @@ message of type V | T | The source message type | | V | The destination message type | - -### Convert(source) `method` + +### ConvertAsync(source) `method` ##### Summary @@ -360,8 +360,8 @@ An implementation of this is used to encode/decode message bodies when specified for a connection. This is to allow for an override of the default encoding of Json for the messages. - -### Decode\`\`1(stream) `method` + +### DecodeAsync\`\`1(stream) `method` ##### Summary @@ -383,8 +383,8 @@ Null when fails or the value of T that was encoded inside the stream | ---- | ----------- | | T | The type of message being decoded | - -### Encode\`\`1(message) `method` + +### EncodeAsync\`\`1(message) `method` ##### Summary @@ -419,8 +419,8 @@ An implementation of this is used to encrypt/decrypt message bodies when specified for a connection. This is to allow for extended message security if desired. - -### Decrypt(stream,headers) `method` + +### DecryptAsync(stream,headers) `method` ##### Summary @@ -437,8 +437,8 @@ A decrypted stream of the message body | stream | [System.IO.Stream](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.IO.Stream 'System.IO.Stream') | The stream representing the message body binary data | | headers | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The message headers that were provided by the message | - -### Encrypt(data,headers) `method` + +### EncryptAsync(data,headers) `method` ##### Summary @@ -557,7 +557,7 @@ A service subscription object | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - + ### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` ##### Summary @@ -572,7 +572,7 @@ A service subscription object | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}}') | The callback to be invoked when a message is recieved, returning the response message | +| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | The callback to be invoked when a message is recieved, returning the response message | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to subscribe to | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription groupt to subscribe as | @@ -597,8 +597,8 @@ This is used to override the default Json and the Global one for the connection | ---- | ----------- | | T | The type of message that this encoder supports | - -### Decode(stream) `method` + +### DecodeAsync(stream) `method` ##### Summary @@ -614,8 +614,8 @@ null if the Decode fails, otherwise an instance of the message decoded from the | ---- | ---- | ----------- | | stream | [System.IO.Stream](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.IO.Stream 'System.IO.Stream') | The byte stream containing the encoded message | - -### Encode(message) `method` + +### EncodeAsync(message) `method` ##### Summary diff --git a/AutomatedTesting/ChannelMapperTests.cs b/AutomatedTesting/ChannelMapperTests.cs index 3e405b4..8d50acc 100644 --- a/AutomatedTesting/ChannelMapperTests.cs +++ b/AutomatedTesting/ChannelMapperTests.cs @@ -805,7 +805,7 @@ public async Task TestQuerySubscribeMapWithStringToString() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -834,7 +834,7 @@ public async Task TestQuerySubscribeMapWithStringToString() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -849,7 +849,7 @@ public async Task TestQuerySubscribeMapWithStringToFunction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -892,7 +892,7 @@ public async Task TestQuerySubscribeMapWithStringToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -907,7 +907,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -948,7 +948,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -963,7 +963,7 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -1005,7 +1005,7 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -1020,7 +1020,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -1061,7 +1061,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } diff --git a/AutomatedTesting/ContractConnectionTests/PublishTests.cs b/AutomatedTesting/ContractConnectionTests/PublishTests.cs index 2246aa5..1deb667 100644 --- a/AutomatedTesting/ContractConnectionTests/PublishTests.cs +++ b/AutomatedTesting/ContractConnectionTests/PublishTests.cs @@ -210,8 +210,8 @@ public async Task TestPublishAsyncWithGlobalEncoder() .ReturnsAsync(transmissionResult); var globalEncoder = new Mock(); - globalEncoder.Setup(x => x.Encode(It.IsAny())) - .Returns(encodedData); + globalEncoder.Setup(x => x.EncodeAsync(It.IsAny())) + .ReturnsAsync(encodedData); var contractConnection = new ContractConnection(serviceConnection.Object, defaultMessageEncoder: globalEncoder.Object); #endregion @@ -236,7 +236,7 @@ public async Task TestPublishAsyncWithGlobalEncoder() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - globalEncoder.Verify(x => x.Encode(It.IsAny()), Times.Once); + globalEncoder.Verify(x => x.EncodeAsync(It.IsAny()), Times.Once); #endregion } @@ -259,8 +259,8 @@ public async Task TestPublishAsyncWithGlobalEncryptor() .ReturnsAsync(transmissionResult); var globalEncryptor = new Mock(); - globalEncryptor.Setup(x => x.Encrypt(Capture.In(binaries), out headers)) - .Returns((byte[] binary, Dictionary h) => binary.Reverse().ToArray()); + globalEncryptor.Setup(x => x.EncryptAsync(Capture.In(binaries), out headers)) + .ReturnsAsync((byte[] binary, Dictionary h) => binary.Reverse().ToArray()); var contractConnection = new ContractConnection(serviceConnection.Object, defaultMessageEncryptor: globalEncryptor.Object); #endregion @@ -287,7 +287,7 @@ public async Task TestPublishAsyncWithGlobalEncryptor() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - globalEncryptor.Verify(x => x.Encrypt(It.IsAny(), out headers), Times.Once); + globalEncryptor.Verify(x => x.EncryptAsync(It.IsAny(), out headers), Times.Once); #endregion } @@ -417,7 +417,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedEncoder() Assert.AreEqual(0, messages[0].Header.Keys.Count()); Assert.AreEqual("U-CustomEncoderMessage-0.0.0.0", messages[0].MessageTypeID); Assert.IsTrue(messages[0].Data.Length>0); - Assert.AreEqual(testMessage, new TestMessageEncoder().Decode(new MemoryStream(messages[0].Data.ToArray()))); + Assert.AreEqual(testMessage, await new TestMessageEncoder().DecodeAsync(new MemoryStream(messages[0].Data.ToArray()))); #endregion #region Verify @@ -464,7 +464,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedServiceInjectableEncoder Assert.AreEqual("U-CustomEncoderWithInjectionMessage-0.0.0.0", messages[0].MessageTypeID); Assert.IsTrue(messages[0].Data.Length>0); Assert.AreEqual(testMessage, - new TestMessageEncoderWithInjection(services.GetRequiredService()).Decode(new MemoryStream(messages[0].Data.ToArray())) + await new TestMessageEncoderWithInjection(services.GetRequiredService()).DecodeAsync(new MemoryStream(messages[0].Data.ToArray())) ); #endregion @@ -507,7 +507,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedEncryptor() Assert.AreEqual(typeof(CustomEncryptorMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); Assert.AreEqual("U-CustomEncryptorMessage-0.0.0.0", messages[0].MessageTypeID); Assert.IsTrue(messages[0].Data.Length>0); - var decodedData = new TestMessageEncryptor().Decrypt(new MemoryStream(messages[0].Data.ToArray()), messages[0].Header); + var decodedData = await new TestMessageEncryptor().DecryptAsync(new MemoryStream(messages[0].Data.ToArray()), messages[0].Header); Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(decodedData)); #endregion @@ -552,7 +552,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedServiceInjectableEncrypt Assert.AreEqual(typeof(CustomEncryptorWithInjectionMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); Assert.AreEqual("U-CustomEncryptorWithInjectionMessage-0.0.0.0", messages[0].MessageTypeID); Assert.IsTrue(messages[0].Data.Length>0); - var decodedData = new TestMessageEncryptorWithInjection(services.GetRequiredService()).Decrypt(new MemoryStream(messages[0].Data.ToArray()), messages[0].Header); + var decodedData = await new TestMessageEncryptorWithInjection(services.GetRequiredService()).DecryptAsync(new MemoryStream(messages[0].Data.ToArray()), messages[0].Header); Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(decodedData)); #endregion @@ -583,7 +583,7 @@ public async Task TestPublishAsyncWithNoMessageChannelThrowsError() #region Act var stopwatch = Stopwatch.StartNew(); - var exception = await Assert.ThrowsExceptionAsync(() => contractConnection.PublishAsync(testMessage)); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.PublishAsync(testMessage)); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); #endregion @@ -623,7 +623,7 @@ public async Task TestPublishAsyncWithToLargeAMessageThrowsError() #region Act var stopwatch = Stopwatch.StartNew(); - var exception = await Assert.ThrowsExceptionAsync(() => contractConnection.PublishAsync(testMessage)); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.PublishAsync(testMessage)); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); #endregion diff --git a/AutomatedTesting/ContractConnectionTests/QueryTests.cs b/AutomatedTesting/ContractConnectionTests/QueryTests.cs index 8665193..879f5c0 100644 --- a/AutomatedTesting/ContractConnectionTests/QueryTests.cs +++ b/AutomatedTesting/ContractConnectionTests/QueryTests.cs @@ -328,10 +328,10 @@ public async Task TestQueryAsyncWithGlobalEncoder() .Returns(defaultTimeout); var globalEncoder = new Mock(); - globalEncoder.Setup(x => x.Encode(It.IsAny())) - .Returns(encodedData); - globalEncoder.Setup(x => x.Decode(It.IsAny())) - .Returns((Stream str) => + globalEncoder.Setup(x => x.EncodeAsync(It.IsAny())) + .ReturnsAsync(encodedData); + globalEncoder.Setup(x => x.DecodeAsync(It.IsAny())) + .ReturnsAsync((Stream str) => { var reader = new StreamReader(str); var result = new BasicResponseMessage(reader.ReadToEnd()); @@ -398,10 +398,10 @@ public async Task TestQueryAsyncWithGlobalEncryptor() .Returns(defaultTimeout); var globalEncryptor = new Mock(); - globalEncryptor.Setup(x => x.Encrypt(Capture.In(binaries), out headers)) - .Returns((byte[] binary, Dictionary h) => binary.Reverse().ToArray()); - globalEncryptor.Setup(x => x.Decrypt(It.IsAny(), It.IsAny())) - .Returns((Stream source, MessageHeader headers) => + globalEncryptor.Setup(x => x.EncryptAsync(Capture.In(binaries), out headers)) + .ReturnsAsync((byte[] binary, Dictionary h) => binary.Reverse().ToArray()); + globalEncryptor.Setup(x => x.DecryptAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync((Stream source, MessageHeader headers) => { var buff = new byte[source.Length]; source.Read(buff, 0, buff.Length); @@ -657,7 +657,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncoder() Assert.AreEqual(0, messages[0].Header.Keys.Count()); Assert.AreEqual("U-CustomEncoderMessage-0.0.0.0", messages[0].MessageTypeID); Assert.IsTrue(messages[0].Data.Length>0); - Assert.AreEqual(testMessage, new TestMessageEncoder().Decode(new MemoryStream(messages[0].Data.ToArray()))); + Assert.AreEqual(testMessage, await new TestMessageEncoder().DecodeAsync(new MemoryStream(messages[0].Data.ToArray()))); Assert.AreEqual(responseMessage, result.Result); #endregion @@ -715,7 +715,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncoder() Assert.AreEqual("U-CustomEncoderWithInjectionMessage-0.0.0.0", messages[0].MessageTypeID); Assert.IsTrue(messages[0].Data.Length>0); Assert.AreEqual(testMessage, - new TestMessageEncoderWithInjection(services.GetRequiredService()).Decode(new MemoryStream(messages[0].Data.ToArray())) + await new TestMessageEncoderWithInjection(services.GetRequiredService()).DecodeAsync(new MemoryStream(messages[0].Data.ToArray())) ); Assert.AreEqual(responseMessage, result.Result); #endregion @@ -770,7 +770,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncryptor() Assert.AreEqual(defaultTimeout, timeouts[0]); Assert.AreEqual("U-CustomEncryptorMessage-0.0.0.0", messages[0].MessageTypeID); Assert.IsTrue(messages[0].Data.Length>0); - var decodedData = new TestMessageEncryptor().Decrypt(new MemoryStream(messages[0].Data.ToArray()), messages[0].Header); + var decodedData = await new TestMessageEncryptor().DecryptAsync(new MemoryStream(messages[0].Data.ToArray()), messages[0].Header); Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(decodedData)); Assert.AreEqual(responseMessage, result.Result); #endregion @@ -827,7 +827,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncryptor Assert.AreEqual(defaultTimeout, timeouts[0]); Assert.AreEqual("U-CustomEncryptorWithInjectionMessage-0.0.0.0", messages[0].MessageTypeID); Assert.IsTrue(messages[0].Data.Length>0); - var decodedData = new TestMessageEncryptorWithInjection(services.GetRequiredService()).Decrypt(new MemoryStream(messages[0].Data.ToArray()), messages[0].Header); + var decodedData = await new TestMessageEncryptorWithInjection(services.GetRequiredService()).DecryptAsync(new MemoryStream(messages[0].Data.ToArray()), messages[0].Header); Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(decodedData)); Assert.AreEqual(responseMessage, result.Result); #endregion @@ -866,7 +866,7 @@ public async Task TestQueryAsyncWithNoMessageChannelThrowsError() #region Act var stopwatch = Stopwatch.StartNew(); - var exception = await Assert.ThrowsExceptionAsync(() => contractConnection.QueryAsync(testMessage)); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage)); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); #endregion @@ -913,7 +913,7 @@ public async Task TestQueryAsyncWithToLargeAMessageThrowsError() #region Act var stopwatch = Stopwatch.StartNew(); - var exception = await Assert.ThrowsExceptionAsync(() => contractConnection.QueryAsync(testMessage)); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage)); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); #endregion @@ -1085,7 +1085,7 @@ public async Task TestQueryAsyncWithNoReturnType() #region Act var stopwatch = Stopwatch.StartNew(); - var exception = await Assert.ThrowsExceptionAsync(() => contractConnection.QueryAsync(testMessage)); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage)); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); #endregion diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs index 0771c30..d895983 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs @@ -18,7 +18,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #region Arrange var serviceSubscription = new Mock(); - var recievedActions = new List>>(); + var recievedActions = new List>>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); @@ -26,7 +26,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - Capture.In>>(recievedActions), + Capture.In>>(recievedActions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), @@ -89,7 +89,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion @@ -106,7 +106,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -141,7 +141,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -157,7 +157,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), Capture.In(groups), @@ -190,7 +190,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -206,7 +206,7 @@ public async Task TestSubscribeQueryResponseAsyncWithServiceChannelOptions() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), @@ -233,7 +233,7 @@ public async Task TestSubscribeQueryResponseAsyncWithServiceChannelOptions() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -246,7 +246,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), @@ -258,7 +258,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var exception = await Assert.ThrowsExceptionAsync(() => contractConnection.SubscribeQueryResponseAsync( + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeQueryResponseAsync( (msg) => Task.FromResult>(null), (error) => { }) ); @@ -272,7 +272,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -283,7 +283,7 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() #region Arrange var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), @@ -295,7 +295,7 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var exception = await Assert.ThrowsExceptionAsync(() => contractConnection.SubscribeQueryResponseAsync( + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeQueryResponseAsync( (msg) => Task.FromResult>(null), (error) => { }) ); @@ -307,7 +307,7 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -318,11 +318,11 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() #region Arrange var serviceSubscription = new Mock(); serviceSubscription.Setup(x => x.EndAsync()) - .Returns(Task.CompletedTask); + .Returns(ValueTask.CompletedTask); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), @@ -347,7 +347,7 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion @@ -359,7 +359,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() #region Arrange var serviceSubscription = new Mock(); - var recievedActions = new List>>(); + var recievedActions = new List>>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); @@ -367,7 +367,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - Capture.In>>(recievedActions), + Capture.In>>(recievedActions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), @@ -440,7 +440,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion @@ -452,7 +452,7 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() #region Arrange var serviceSubscription = new Mock(); - var recievedActions = new List>>(); + var recievedActions = new List>>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); @@ -460,7 +460,7 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - Capture.In>>(recievedActions), + Capture.In>>(recievedActions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), @@ -513,7 +513,7 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion @@ -525,7 +525,7 @@ public async Task TestSubscribeQueryResponseAsyncWithDisposal() #region Arrange var serviceSubscription = new Mock(); - var recievedActions = new List>>(); + var recievedActions = new List>>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); @@ -533,7 +533,7 @@ public async Task TestSubscribeQueryResponseAsyncWithDisposal() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - Capture.In>>(recievedActions), + Capture.In>>(recievedActions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), @@ -606,7 +606,7 @@ public async Task TestSubscribeQueryResponseAsyncWithDisposal() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs index 7c5195a..53a092c 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs @@ -36,7 +36,7 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); - return Task.FromResult(transmissionResult); + return ValueTask.FromResult(transmissionResult); }); var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); @@ -209,7 +209,7 @@ public async Task TestSubscribeAsyncNoMessageChannelThrowsError() #endregion #region Act - var exception = await Assert.ThrowsExceptionAsync(() => contractConnection.SubscribeAsync((msg) => Task.CompletedTask, (error) => { })); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeAsync((msg) => Task.CompletedTask, (error) => { })); #endregion #region Assert @@ -238,7 +238,7 @@ public async Task TestSubscribeAsyncReturnFailedSubscription() #endregion #region Act - var exception = await Assert.ThrowsExceptionAsync(()=>contractConnection.SubscribeAsync(msg => Task.CompletedTask, err => { })); + var exception = await Assert.ThrowsExceptionAsync(async ()=> await contractConnection.SubscribeAsync(msg => Task.CompletedTask, err => { })); #endregion #region Assert @@ -257,7 +257,7 @@ public async Task TestSubscribeAsyncCleanup() #region Arrange var serviceSubscription = new Mock(); serviceSubscription.Setup(x => x.EndAsync()) - .Returns(Task.CompletedTask); + .Returns(ValueTask.CompletedTask); var serviceConnection = new Mock(); @@ -284,6 +284,39 @@ public async Task TestSubscribeAsyncCleanup() #endregion } + [TestMethod] + public async Task TestSubscriptionsCleanup() + { + #region Arrange + var serviceSubscription = new Mock(); + serviceSubscription.Setup(x => x.EndAsync()) + .Returns(ValueTask.CompletedTask); + + var serviceConnection = new Mock(); + + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.Object); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var subscription = await contractConnection.SubscribeAsync(msg => Task.CompletedTask, err => { }); + await contractConnection.DisposeAsync(); + #endregion + + #region Assert + Assert.IsNotNull(subscription); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + serviceSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + [TestMethod] public async Task TestSubscribeAsyncWithSynchronousActions() { @@ -308,7 +341,7 @@ public async Task TestSubscribeAsyncWithSynchronousActions() serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); - return Task.FromResult(transmissionResult); + return ValueTask.FromResult(transmissionResult); }); var message1 = new BasicMessage("TestSubscribeAsyncWithSynchronousActions1"); @@ -391,7 +424,7 @@ public async Task TestSubscribeAsyncWithErrorTriggeringInOurAction() serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); - return Task.FromResult(transmissionResult); + return ValueTask.FromResult(transmissionResult); }); var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); @@ -458,7 +491,7 @@ public async Task TestSubscribeAsyncWithCorruptMetaDataHeaderException() serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); - return Task.FromResult(transmissionResult); + return ValueTask.FromResult(transmissionResult); }); var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); @@ -523,7 +556,7 @@ public async Task TestSubscribeAsyncWithDisposal() serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); - return Task.FromResult(transmissionResult); + return ValueTask.FromResult(transmissionResult); }); var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); @@ -608,7 +641,7 @@ public async Task TestSubscribeAsyncWithSingleConversion() serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); - return Task.FromResult(transmissionResult); + return ValueTask.FromResult(transmissionResult); }); var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); @@ -684,7 +717,7 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); - return Task.FromResult(transmissionResult); + return ValueTask.FromResult(transmissionResult); }); var message = new NoChannelMessage("TestSubscribeAsyncWithNoExtendedAspects"); @@ -760,7 +793,7 @@ public async Task TestSubscribeAsyncWithNoConversionPath() serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); - return Task.FromResult(transmissionResult); + return ValueTask.FromResult(transmissionResult); }); var message = new BasicQueryMessage("TestSubscribeAsyncWithNoExtendedAspects"); diff --git a/AutomatedTesting/Converters/BasicMessageToNameAndVersionMessage.cs b/AutomatedTesting/Converters/BasicMessageToNameAndVersionMessage.cs index dff9977..a8691ef 100644 --- a/AutomatedTesting/Converters/BasicMessageToNameAndVersionMessage.cs +++ b/AutomatedTesting/Converters/BasicMessageToNameAndVersionMessage.cs @@ -5,7 +5,7 @@ namespace AutomatedTesting.Converters { internal class BasicMessageToNameAndVersionMessage : IMessageConverter { - public NamedAndVersionedMessage Convert(BasicMessage source) - => new(source.Name); + public ValueTask ConvertAsync(BasicMessage source) + => ValueTask.FromResult(new(source.Name)); } } diff --git a/AutomatedTesting/Converters/NoChannelMessageToBasicMessage.cs b/AutomatedTesting/Converters/NoChannelMessageToBasicMessage.cs index 82a2337..0be5d88 100644 --- a/AutomatedTesting/Converters/NoChannelMessageToBasicMessage.cs +++ b/AutomatedTesting/Converters/NoChannelMessageToBasicMessage.cs @@ -5,7 +5,7 @@ namespace AutomatedTesting.Converters { internal class NoChannelMessageToBasicMessage : IMessageConverter { - public BasicMessage Convert(NoChannelMessage source) - => new(source.TestName); + public ValueTask ConvertAsync(NoChannelMessage source) + => ValueTask.FromResult(new(source.TestName)); } } diff --git a/AutomatedTesting/Encoders/TestMessageEncoder.cs b/AutomatedTesting/Encoders/TestMessageEncoder.cs index 9b00a39..507f5a9 100644 --- a/AutomatedTesting/Encoders/TestMessageEncoder.cs +++ b/AutomatedTesting/Encoders/TestMessageEncoder.cs @@ -6,10 +6,10 @@ namespace AutomatedTesting.Encoders { internal class TestMessageEncoder : IMessageTypeEncoder { - public CustomEncoderMessage? Decode(Stream stream) - => new CustomEncoderMessage(Encoding.ASCII.GetString(new BinaryReader(stream).ReadBytes((int)stream.Length))); + public ValueTask DecodeAsync(Stream stream) + => ValueTask.FromResult(new CustomEncoderMessage(Encoding.ASCII.GetString(new BinaryReader(stream).ReadBytes((int)stream.Length)))); - public byte[] Encode(CustomEncoderMessage message) - => Encoding.ASCII.GetBytes(message.TestName); + public ValueTask EncodeAsync(CustomEncoderMessage message) + => ValueTask.FromResult(Encoding.ASCII.GetBytes(message.TestName)); } } diff --git a/AutomatedTesting/Encoders/TestMessageEncoderWithInjection.cs b/AutomatedTesting/Encoders/TestMessageEncoderWithInjection.cs index b0c4541..1326df1 100644 --- a/AutomatedTesting/Encoders/TestMessageEncoderWithInjection.cs +++ b/AutomatedTesting/Encoders/TestMessageEncoderWithInjection.cs @@ -8,14 +8,14 @@ namespace AutomatedTesting.Encoders internal class TestMessageEncoderWithInjection(IInjectableService injectableService) : IMessageTypeEncoder { - public CustomEncoderWithInjectionMessage? Decode(Stream stream) + public ValueTask DecodeAsync(Stream stream) { var message = Encoding.ASCII.GetString(new BinaryReader(stream).ReadBytes((int)stream.Length)); Assert.IsTrue(message.StartsWith($"{injectableService.Name}:")); - return new CustomEncoderWithInjectionMessage(message.Substring($"{injectableService.Name}:".Length)); + return ValueTask.FromResult(new CustomEncoderWithInjectionMessage(message.Substring($"{injectableService.Name}:".Length))); } - public byte[] Encode(CustomEncoderWithInjectionMessage message) - => Encoding.ASCII.GetBytes($"{injectableService.Name}:{message.TestName}"); + public ValueTask EncodeAsync(CustomEncoderWithInjectionMessage message) + => ValueTask.FromResult(Encoding.ASCII.GetBytes($"{injectableService.Name}:{message.TestName}")); } } diff --git a/AutomatedTesting/Encryptors/TestMessageEncryptor.cs b/AutomatedTesting/Encryptors/TestMessageEncryptor.cs index 7bfa2a1..c6f307b 100644 --- a/AutomatedTesting/Encryptors/TestMessageEncryptor.cs +++ b/AutomatedTesting/Encryptors/TestMessageEncryptor.cs @@ -8,21 +8,21 @@ internal class TestMessageEncryptor : IMessageTypeEncryptor DecryptAsync(Stream stream, MessageHeader headers) { Assert.IsNotNull(headers); Assert.IsTrue(headers.Keys.Contains(HeaderKey)); Assert.AreEqual(HeaderValue, headers[HeaderKey]); var data = new BinaryReader(stream).ReadBytes((int)stream.Length); - return new MemoryStream(data.Reverse().ToArray()); + return ValueTask.FromResult(new MemoryStream(data.Reverse().ToArray())); } - public byte[] Encrypt(byte[] data, out Dictionary headers) + public ValueTask EncryptAsync(byte[] data, out Dictionary headers) { headers = new([ new(HeaderKey,HeaderValue) ]); - return data.Reverse().ToArray(); + return ValueTask.FromResult(data.Reverse().ToArray()); } } } diff --git a/AutomatedTesting/Encryptors/TestMessageEncryptorWithInjection.cs b/AutomatedTesting/Encryptors/TestMessageEncryptorWithInjection.cs index 289f938..8150625 100644 --- a/AutomatedTesting/Encryptors/TestMessageEncryptorWithInjection.cs +++ b/AutomatedTesting/Encryptors/TestMessageEncryptorWithInjection.cs @@ -9,21 +9,21 @@ internal class TestMessageEncryptorWithInjection(IInjectableService injectableSe { private const string HeaderKey = "TestMessageEncryptorWithInjectionKey"; - public Stream Decrypt(Stream stream, MessageHeader headers) + public ValueTask DecryptAsync(Stream stream, MessageHeader headers) { Assert.IsNotNull(headers); Assert.IsTrue(headers.Keys.Contains(HeaderKey)); Assert.AreEqual(injectableService.Name, headers[HeaderKey]); var data = new BinaryReader(stream).ReadBytes((int)stream.Length); - return new MemoryStream(data.Reverse().ToArray()); + return ValueTask.FromResult(new MemoryStream(data.Reverse().ToArray())); } - public byte[] Encrypt(byte[] data, out Dictionary headers) + public ValueTask EncryptAsync(byte[] data, out Dictionary headers) { headers = new([ new(HeaderKey,injectableService.Name) ]); - return data.Reverse().ToArray(); + return ValueTask.FromResult(data.Reverse().ToArray()); } } } diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index 1b84e10..0213b83 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -37,10 +37,10 @@ public Connection(Uri ConnectUri){ /// public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); - public Task PingAsync() + public ValueTask PingAsync() => throw new NotImplementedException(); - private async Task ProduceMessage(ServiceMessage message) + private async ValueTask ProduceMessage(ServiceMessage message) { var msg = await session.CreateBytesMessageAsync(message.Data.ToArray()); msg.NMSMessageId=message.ID; @@ -67,7 +67,7 @@ internal static RecievedServiceMessage ProduceMessage(string channel, IMessage m ); } - public async Task PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { try { @@ -80,17 +80,17 @@ public async Task PublishAsync(ServiceMessage message, IServ } } - public Task QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public Task SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public Task SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } diff --git a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs index c60975f..ff2d359 100644 --- a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs +++ b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs @@ -33,7 +33,7 @@ private void ConsumeMessage(IMessage message) } } - public async Task EndAsync() + public async ValueTask EndAsync() { if (consumer!=null) await consumer.CloseAsync(); diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 56f3d77..183849e 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -89,7 +89,7 @@ internal static MessageHeader ExtractHeaders(Headers header, out string? message /// /// Throws NotImplementedException /// Thrown because Kafka does not support this particular action - public Task PingAsync() + public ValueTask PingAsync() => throw new NotImplementedException(); /// @@ -100,7 +100,7 @@ public Task PingAsync() /// A cancellation token /// Transmition result identifying if it worked or not /// Thrown if options was supplied because there are no implemented options for this call - public async Task PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { NoChannelOptionsAvailableException.ThrowIfNotNull(options); try @@ -133,7 +133,7 @@ public async Task PublishAsync(ServiceMessage message, IServ /// Thrown when the query fails to execute /// Thrown when the responding instance has provided an error /// Thrown when there is no response to be found for the query - public async Task QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(options); InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); @@ -222,7 +222,7 @@ private static TaskCompletionSource StartResponseListener(Cl /// /// /// Thrown if options was supplied because there are no implemented options for this call - public async Task SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { NoChannelOptionsAvailableException.ThrowIfNotNull(options); var subscription = new PublishSubscription( @@ -234,7 +234,7 @@ private static TaskCompletionSource StartResponseListener(Cl errorRecieved, channel, cancellationToken); - subscription.Run(); + await subscription.Run(); return subscription; } @@ -249,7 +249,7 @@ private static TaskCompletionSource StartResponseListener(Cl /// A cancellation token /// A subscription instance /// Thrown when options is not null and is not an instance of the type QueryChannelOptions - public async Task SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); var subscription = new QuerySubscription( @@ -297,7 +297,7 @@ await producer.ProduceAsync( errorRecieved, channel, cancellationToken); - subscription.Run(); + await subscription.Run(); return subscription; } diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index 649b0f2..d825cef 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -11,7 +11,7 @@ - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Kafka-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-Task{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryChannelOptions](#T-MQContract-Kafka-Options-QueryChannelOptions 'MQContract.Kafka.Options.QueryChannelOptions') - [#ctor(ReplyChannel)](#M-MQContract-Kafka-Options-QueryChannelOptions-#ctor-System-String- 'MQContract.Kafka.Options.QueryChannelOptions.#ctor(System.String)') - [ReplyChannel](#P-MQContract-Kafka-Options-QueryChannelOptions-ReplyChannel 'MQContract.Kafka.Options.QueryChannelOptions.ReplyChannel') @@ -166,7 +166,7 @@ Called to create a subscription to the underlying Kafka server | ---- | ----------- | | [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | - + ### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` ##### Summary @@ -181,7 +181,7 @@ A subscription instance | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | +| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | diff --git a/Connectors/Kafka/Subscriptions/SubscriptionBase.cs b/Connectors/Kafka/Subscriptions/SubscriptionBase.cs index d615195..b8cb77b 100644 --- a/Connectors/Kafka/Subscriptions/SubscriptionBase.cs +++ b/Connectors/Kafka/Subscriptions/SubscriptionBase.cs @@ -10,51 +10,28 @@ internal abstract class SubscriptionBase(Confluent.Kafka.IConsumer { cancelToken.Cancel(); }); + var resultSource = new TaskCompletionSource(); Task.Run(async () => { Consumer.Subscribe(Channel); + resultSource.TrySetResult(); await RunAction(); Consumer.Close(); }); + return resultSource.Task; } - public async Task EndAsync() + public async ValueTask EndAsync() { try { await cancelToken.CancelAsync(); } catch { } } - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - if (!cancellationToken.IsCancellationRequested) - cancelToken.Cancel(); - try - { - Consumer.Close(); - } - catch (Exception) { } - Consumer.Dispose(); - cancelToken.Dispose(); - } - disposedValue=true; - } - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - public async ValueTask DisposeAsync() { if (!disposedValue) diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index c2fc53b..476c69b 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -79,13 +79,13 @@ private KubeClient EstablishConnection() /// /// The Ping result, specically a PingResponse instance /// Thrown when the Ping fails - public Task PingAsync() + public ValueTask PingAsync() { var watch = new Stopwatch(); watch.Start(); var res = client.Ping()??throw new UnableToConnectException(); watch.Stop(); - return Task.FromResult(new PingResponse(res,watch.Elapsed)); + return ValueTask.FromResult(new PingResponse(res,watch.Elapsed)); } internal static MapField ConvertMessageHeader(MessageHeader header) @@ -107,7 +107,7 @@ internal static MessageHeader ConvertMessageHeader(MapField head /// A cancellation token /// Transmition result identifying if it worked or not /// /// Thrown when an attempt to pass an options object that is not of the type PublishChannelOptions - public async Task PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); try { @@ -146,7 +146,7 @@ public async Task PublishAsync(ServiceMessage message, IServ /// Thrown if options was supplied because there are no implemented options for this call /// Thrown when the response from KubeMQ is null /// Thrown when there is an RPC exception from the KubeMQ server - public async Task QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { NoChannelOptionsAvailableException.ThrowIfNotNull(options); try @@ -193,7 +193,7 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa /// A cancellation token /// A subscription instance /// Thrown when options is not null and is not an instance of the type StoredEventsSubscriptionOptions - public async Task SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); var sub = new PubSubscription( @@ -207,7 +207,7 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa cancellationToken ); sub.Run(); - return sub; + return ValueTask.FromResult(sub); } /// @@ -221,7 +221,7 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa /// A cancellation token /// A subscription instance /// Thrown if options was supplied because there are no implemented options for this call - public async Task SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { NoChannelOptionsAvailableException.ThrowIfNotNull(options); var sub = new QuerySubscription( @@ -234,7 +234,7 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa cancellationToken ); sub.Run(); - return sub; + return ValueTask.FromResult(sub); } public async ValueTask DisposeAsync() diff --git a/Connectors/KubeMQ/Readme.md b/Connectors/KubeMQ/Readme.md index c00d1b4..a52f373 100644 --- a/Connectors/KubeMQ/Readme.md +++ b/Connectors/KubeMQ/Readme.md @@ -22,7 +22,7 @@ - [PublishAsync(message,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-Task{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [ConnectionOptions](#T-MQContract-KubeMQ-ConnectionOptions 'MQContract.KubeMQ.ConnectionOptions') - [Address](#P-MQContract-KubeMQ-ConnectionOptions-Address 'MQContract.KubeMQ.ConnectionOptions.Address') - [AuthToken](#P-MQContract-KubeMQ-ConnectionOptions-AuthToken 'MQContract.KubeMQ.ConnectionOptions.AuthToken') @@ -438,7 +438,7 @@ A subscription instance | ---- | ----------- | | [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when options is not null and is not an instance of the type StoredEventsSubscriptionOptions | - + ### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` ##### Summary @@ -453,7 +453,7 @@ A subscription instance | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | +| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | diff --git a/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs b/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs index e42f9ba..977a12d 100644 --- a/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs +++ b/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs @@ -7,7 +7,7 @@ namespace MQContract.KubeMQ.Subscriptions { internal class QuerySubscription(ConnectionOptions options, KubeClient client, - Func> messageRecieved, Action errorRecieved, + Func> messageRecieved, Action errorRecieved, string channel, string group, CancellationToken cancellationToken) : SubscriptionBase(options.Logger,options.ReconnectInterval,client,errorRecieved,cancellationToken) { diff --git a/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs b/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs index e892976..066ac1a 100644 --- a/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs +++ b/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs @@ -54,7 +54,7 @@ public void Run() case StatusCode.Cancelled: case StatusCode.PermissionDenied: case StatusCode.Aborted: - EndAsync().Wait(); + await EndAsync(); break; case StatusCode.Unknown: case StatusCode.Unavailable: @@ -80,7 +80,7 @@ public void Run() }); } - public async Task EndAsync() + public async ValueTask EndAsync() { if (active) { diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index 41e9217..35d37fe 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -77,7 +77,7 @@ public ValueTask CreateStreamAsync(StreamConfig streamConfig,Canc /// Called to ping the NATS.io service /// /// The Ping Result including service information - public async Task PingAsync() + public async ValueTask PingAsync() => new PingResult(natsConnection.ServerInfo?.Host??string.Empty, natsConnection.ServerInfo?.Version??string.Empty, await natsConnection.PingAsync() @@ -129,7 +129,7 @@ internal static NatsHeaders ProduceQueryError(Exception exception,string message /// A cancellation token /// Transmition result identifying if it worked or not /// Thrown when an attempt to pass an options object that is not of the type StreamPublishChannelOptions - public async Task PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); try @@ -172,7 +172,7 @@ await natsConnection.PublishAsync( /// The resulting response /// Thrown if options was supplied because there are no implemented options for this call /// Thrown when an error comes from the responding service - public async Task QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { NoChannelOptionsAvailableException.ThrowIfNotNull(options); var result = await natsConnection.RequestAsync( @@ -204,7 +204,7 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa /// A cancellation token /// A subscription instance /// Thrown when options is not null and is not an instance of the type StreamPublishSubscriberOptions - public async Task SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); IInternalServiceSubscription? subscription = null; @@ -241,7 +241,7 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa /// A cancellation token /// A subscription instance /// /// Thrown if options was supplied because there are no implemented options for this call - public async Task SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { NoChannelOptionsAvailableException.ThrowIfNotNull(options); var sub = new QuerySubscription( @@ -255,7 +255,7 @@ public async Task QueryAsync(ServiceMessage message, TimeSpa cancellationToken ); sub.Run(); - return sub; + return ValueTask.FromResult(sub); } public async ValueTask DisposeAsync() diff --git a/Connectors/NATS/Readme.md b/Connectors/NATS/Readme.md index 3bc6524..365932c 100644 --- a/Connectors/NATS/Readme.md +++ b/Connectors/NATS/Readme.md @@ -12,7 +12,7 @@ - [PublishAsync(message,options,cancellationToken)](#M-MQContract-NATS-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-NATS-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-Task{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [StreamPublishChannelOptions](#T-MQContract-NATS-Options-StreamPublishChannelOptions 'MQContract.NATS.Options.StreamPublishChannelOptions') - [#ctor(Config)](#M-MQContract-NATS-Options-StreamPublishChannelOptions-#ctor-NATS-Client-JetStream-Models-StreamConfig- 'MQContract.NATS.Options.StreamPublishChannelOptions.#ctor(NATS.Client.JetStream.Models.StreamConfig)') - [Config](#P-MQContract-NATS-Options-StreamPublishChannelOptions-Config 'MQContract.NATS.Options.StreamPublishChannelOptions.Config') @@ -175,7 +175,7 @@ A subscription instance | ---- | ----------- | | [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when options is not null and is not an instance of the type StreamPublishSubscriberOptions | - + ### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` ##### Summary @@ -190,7 +190,7 @@ A subscription instance | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.Task{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | +| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The queueGroup to use for the subscription | diff --git a/Connectors/NATS/Subscriptions/QuerySubscription.cs b/Connectors/NATS/Subscriptions/QuerySubscription.cs index 217b8ec..49add8a 100644 --- a/Connectors/NATS/Subscriptions/QuerySubscription.cs +++ b/Connectors/NATS/Subscriptions/QuerySubscription.cs @@ -4,7 +4,7 @@ namespace MQContract.NATS.Subscriptions { internal class QuerySubscription(IAsyncEnumerable> asyncEnumerable, - Func> messageRecieved, Action errorRecieved, + Func> messageRecieved, Action errorRecieved, CancellationToken cancellationToken) : SubscriptionBase(cancellationToken) { protected override async Task RunAction() diff --git a/Connectors/NATS/Subscriptions/SubscriptionBase.cs b/Connectors/NATS/Subscriptions/SubscriptionBase.cs index c0bc7db..1ece8dc 100644 --- a/Connectors/NATS/Subscriptions/SubscriptionBase.cs +++ b/Connectors/NATS/Subscriptions/SubscriptionBase.cs @@ -37,7 +37,7 @@ public void Run() RunAction(); } - public async Task EndAsync() + public async ValueTask EndAsync() { try { await cancelToken.CancelAsync(); } catch { } } diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index f429c96..69c7833 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -59,7 +59,7 @@ private Task MapChannel(ChannelMapper.MapTypes mapType, string originalC /// Called to execute a ping against the service layer /// /// The ping result from the service layer, if supported - public Task PingAsync() + public ValueTask PingAsync() => serviceConnection.PingAsync(); /// @@ -72,7 +72,7 @@ public Task PingAsync() /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token /// An instance of the TransmissionResult record to indicate success or failure and an ID - public async Task PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + public async ValueTask PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where T : class => await serviceConnection.PublishAsync( await ProduceServiceMessage(ChannelMapper.MapTypes.Publish,message, channel: channel, messageHeader: messageHeader), @@ -80,7 +80,7 @@ await ProduceServiceMessage(ChannelMapper.MapTypes.Publish,message, channel: cancellationToken ); - private async Task ProduceServiceMessage(ChannelMapper.MapTypes mapType,T message, string? channel = null, MessageHeader? messageHeader = null) where T : class + private async ValueTask ProduceServiceMessage(ChannelMapper.MapTypes mapType,T message, string? channel = null, MessageHeader? messageHeader = null) where T : class => await GetMessageFactory().ConvertMessageAsync(message, channel, messageHeader,(originalChannel)=>MapChannel(mapType,originalChannel)); /// @@ -97,7 +97,7 @@ private async Task ProduceServiceMessage(ChannelMapper.MapTyp /// A cancellation token /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public async Task SubscribeAsync(Func,Task> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class + public async ValueTask SubscribeAsync(Func,Task> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class { var subscription = new PubSubSubscription(GetMessageFactory(ignoreMessageHeader), messageRecieved, @@ -117,10 +117,10 @@ public async Task SubscribeAsync(Func,Task throw new SubscriptionFailedException(); } - private async Task> ExecuteQueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + private async ValueTask> ExecuteQueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class - => ProduceResultAsync(await serviceConnection.QueryAsync( + => await ProduceResultAsync(await serviceConnection.QueryAsync( await ProduceServiceMessage(ChannelMapper.MapTypes.Query,message, channel: channel, messageHeader: messageHeader), timeout??TimeSpan.FromMilliseconds(typeof(Q).GetCustomAttribute()?.Value??serviceConnection.DefaultTimout.TotalMilliseconds), options, @@ -139,7 +139,7 @@ await ProduceServiceMessage(ChannelMapper.MapTypes.Query,message, channel: ch /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token /// A QueryResult that will contain the response message and or an error - public async Task> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class => await ExecuteQueryAsync(message, timeout: timeout, channel: channel, messageHeader: messageHeader, options: options, cancellationToken: cancellationToken); @@ -156,34 +156,35 @@ await ProduceServiceMessage(ChannelMapper.MapTypes.Query,message, channel: ch /// A cancellation token /// A QueryResult that will contain the response message and or an error /// Thrown when the supplied Query type does not have a QueryResponseTypeAttribute and therefore a response type cannot be determined - public async Task> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, + public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where Q : class { var responseType = (typeof(Q).GetCustomAttribute(false)?.ResponseType)??throw new UnknownResponseTypeException("ResponseType", typeof(Q)); #pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields var methodInfo = typeof(ContractConnection).GetMethod(nameof(ContractConnection.ExecuteQueryAsync), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(typeof(Q), responseType!); #pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields - var task = (Task)methodInfo.Invoke(this, [ + var queryResult = (dynamic?)await Utility.InvokeMethodAsync( + methodInfo, + this, [ message, timeout, channel, messageHeader, options, cancellationToken - ])!; - await task.ConfigureAwait(false); - var queryResult = ((dynamic)task).Result; - return new QueryResult(queryResult.ID, queryResult.Header, queryResult.Result, queryResult.Error); + ] + ); + return new QueryResult(queryResult?.ID??string.Empty, queryResult?.Header??new MessageHeader([]), queryResult?.Result, queryResult?.Error); } - private QueryResult ProduceResultAsync(ServiceQueryResult queryResult) where R : class + private async ValueTask> ProduceResultAsync(ServiceQueryResult queryResult) where R : class { try { return new( queryResult.ID, queryResult.Header, - Result: GetMessageFactory().ConvertMessage(logger, queryResult) + Result: await GetMessageFactory().ConvertMessageAsync(logger, queryResult) ); }catch(QueryResponseException qre) { @@ -220,7 +221,7 @@ private QueryResult ProduceResultAsync(ServiceQueryResult queryResult) whe /// A cancellation token /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public async Task SubscribeQueryResponseAsync(Func, Task>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask SubscribeQueryResponseAsync(Func, Task>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where Q : class where R : class { diff --git a/Core/Defaults/JsonEncoder.cs b/Core/Defaults/JsonEncoder.cs index 60f73db..5516b90 100644 --- a/Core/Defaults/JsonEncoder.cs +++ b/Core/Defaults/JsonEncoder.cs @@ -14,10 +14,14 @@ internal class JsonEncoder : IMessageTypeEncoder ReadCommentHandling=JsonCommentHandling.Skip }; - public T? Decode(Stream stream) - => JsonSerializer.Deserialize(stream, options: JsonOptions); + public async ValueTask DecodeAsync(Stream stream) + => await JsonSerializer.DeserializeAsync(stream, options: JsonOptions); - public byte[] Encode(T message) - => System.Text.UTF8Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message, options: JsonOptions)); + public async ValueTask EncodeAsync(T message) + { + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, message, options: JsonOptions); + return ms.ToArray(); + } } } diff --git a/Core/Defaults/NonEncryptor.cs b/Core/Defaults/NonEncryptor.cs index d91b95b..a14c1ce 100644 --- a/Core/Defaults/NonEncryptor.cs +++ b/Core/Defaults/NonEncryptor.cs @@ -5,12 +5,13 @@ namespace MQContract.Defaults { internal class NonEncryptor : IMessageTypeEncryptor { - public Stream Decrypt(Stream stream, MessageHeader headers) => stream; + public ValueTask DecryptAsync(Stream stream, MessageHeader headers) + => ValueTask.FromResult(stream); - public byte[] Encrypt(byte[] data, out Dictionary headers) + public ValueTask EncryptAsync(byte[] data, out Dictionary headers) { headers = []; - return data; + return ValueTask.FromResult(data); } } } diff --git a/Core/Factories/ConversionPath.cs b/Core/Factories/ConversionPath.cs index c53744c..e487e82 100644 --- a/Core/Factories/ConversionPath.cs +++ b/Core/Factories/ConversionPath.cs @@ -31,27 +31,30 @@ public ConversionPath(IEnumerable path, IEnumerable types, IMessag messageEncryptor = (IMessageTypeEncryptor)(serviceProvider!=null ? ActivatorUtilities.CreateInstance(serviceProvider, encryptorType) : Activator.CreateInstance(encryptorType)!); } - public V? ConvertMessage(ILogger? logger, IEncodedMessage message, Stream? dataStream = null) + public async ValueTask ConvertMessageAsync(ILogger? logger, IEncodedMessage message, Stream? dataStream = null) { - dataStream = (globalMessageEncryptor!=null && messageEncryptor is NonEncryptor ? globalMessageEncryptor : messageEncryptor).Decrypt(dataStream??new MemoryStream(message.Data.ToArray()), message.Header); - object? result = (globalMessageEncoder!=null && messageEncoder is JsonEncoder ? globalMessageEncoder.Decode(dataStream) : messageEncoder.Decode(dataStream)); + dataStream = await (globalMessageEncryptor!=null && messageEncryptor is NonEncryptor ? globalMessageEncryptor : messageEncryptor).DecryptAsync(dataStream??new MemoryStream(message.Data.ToArray()), message.Header); + object? result = await (globalMessageEncoder!=null && messageEncoder is JsonEncoder ? globalMessageEncoder.DecodeAsync(dataStream) : messageEncoder.DecodeAsync(dataStream)); foreach (var converter in path) { logger?.LogTrace("Attempting to convert {SourceType} to {DestiniationType} through converters for {IntermediateType}", Utility.TypeName(), Utility.TypeName(), Utility.TypeName(ExtractGenericArguements(converter.GetType())[0])); - result = ExecuteConverter(converter, result, ExtractGenericArguements(converter.GetType())[1]); + result = await ExecuteConverter(converter, result, ExtractGenericArguements(converter.GetType())[1]); } return (V?)result; } private static Type[] ExtractGenericArguements(Type t) => t.GetInterfaces().First(iface => iface.IsGenericType && iface.GetGenericTypeDefinition()==typeof(IMessageConverter<,>)).GetGenericArguments(); - private static object? ExecuteConverter(object converter, object? source, Type destination) + private static async ValueTask ExecuteConverter(object converter, object? source, Type destination) { if (source==null) return null; - return typeof(IMessageConverter<,>).MakeGenericType(source.GetType(), destination) - .GetMethod("Convert")! - .Invoke(converter, [source]); + return await Utility.InvokeMethodAsync( + typeof(IMessageConverter<,>).MakeGenericType(source.GetType(), destination) + .GetMethod("ConvertAsync")!, + converter, + [source] + ); } } } diff --git a/Core/Factories/MessageTypeFactory.cs b/Core/Factories/MessageTypeFactory.cs index 413d4b5..1c873f0 100644 --- a/Core/Factories/MessageTypeFactory.cs +++ b/Core/Factories/MessageTypeFactory.cs @@ -146,21 +146,21 @@ private static bool IsMessageTypeMatch(string metaData, Type t, out bool isCompr return false; } - public async Task ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader, Func>? mapChannel=null) + public async ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader, Func>? mapChannel=null) { channel ??= messageChannel; if (string.IsNullOrWhiteSpace(channel)) throw new MessageChannelNullException(); - var encodedData = messageEncoder?.Encode(message)??globalMessageEncoder!.Encode(message); - var body = messageEncryptor?.Encrypt(encodedData, out var messageHeaders)??globalMessageEncryptor!.Encrypt(encodedData, out messageHeaders); + var encodedData = await (messageEncoder?.EncodeAsync(message)??globalMessageEncoder!.EncodeAsync(message)); + var body = await (messageEncryptor?.EncryptAsync(encodedData, out var messageHeaders)??globalMessageEncryptor!.EncryptAsync(encodedData, out messageHeaders)); var metaData = string.Empty; if (body.Length>maxMessageSize) { using var ms = new MemoryStream(); var zip = new GZipStream(ms, System.IO.Compression.CompressionLevel.SmallestSize, false); - await zip.WriteAsync(body, 0, body.Length); + await zip.WriteAsync(body); await zip.FlushAsync(); body = ms.ToArray(); metaData = "C"; @@ -177,7 +177,7 @@ public async Task ConvertMessageAsync(T message, string? channel return new ServiceMessage(Guid.NewGuid().ToString(), metaData, channel, new MessageHeader(messageHeader, messageHeaders), body); } - T? IConversionPath.ConvertMessage(ILogger? logger, IEncodedMessage message, Stream? dataStream) + async ValueTask IConversionPath.ConvertMessageAsync(ILogger? logger, IEncodedMessage message, Stream? dataStream) { if (!IgnoreMessageHeader) #pragma warning disable S3236 // Caller information arguments should not be provided explicitly @@ -191,11 +191,11 @@ public async Task ConvertMessageAsync(T message, string? channel if (IgnoreMessageHeader || IsMessageTypeMatch(message.MessageTypeID, typeof(T), out compressed)) { dataStream = (compressed ? new GZipStream(new MemoryStream(message.Data.ToArray()), System.IO.Compression.CompressionMode.Decompress) : new MemoryStream(message.Data.ToArray())); - dataStream = messageEncryptor?.Decrypt(dataStream, message.Header)??globalMessageEncryptor!.Decrypt(dataStream, message.Header); + dataStream = await (messageEncryptor?.DecryptAsync(dataStream, message.Header)??globalMessageEncryptor!.DecryptAsync(dataStream, message.Header)); if (messageEncoder!=null) - result = messageEncoder.Decode(dataStream); + result = await messageEncoder.DecodeAsync(dataStream); else - result = globalMessageEncoder!.Decode(dataStream); + result = await globalMessageEncoder!.DecodeAsync(dataStream); } else { @@ -210,7 +210,7 @@ public async Task ConvertMessageAsync(T message, string? channel if (converter==null) throw new InvalidCastException(); dataStream = (compressed ? new GZipStream(new MemoryStream(message.Data.ToArray()), System.IO.Compression.CompressionMode.Decompress) : new MemoryStream(message.Data.ToArray())); - result = converter.ConvertMessage(logger, message, dataStream: dataStream); + result = await converter.ConvertMessageAsync(logger, message, dataStream: dataStream); } if (Equals(result, default(T?))) throw new MessageConversionException(typeof(T), converter?.GetType()??GetType()); diff --git a/Core/Interfaces/Conversion/IConversionPath.cs b/Core/Interfaces/Conversion/IConversionPath.cs index 63b9a2a..ead9f2e 100644 --- a/Core/Interfaces/Conversion/IConversionPath.cs +++ b/Core/Interfaces/Conversion/IConversionPath.cs @@ -3,9 +3,9 @@ namespace MQContract.Interfaces.Conversion { - internal interface IConversionPath + internal interface IConversionPath where T : class { - T? ConvertMessage(ILogger? logger, IEncodedMessage message, Stream? dataStream = null); + ValueTask ConvertMessageAsync(ILogger? logger, IEncodedMessage message, Stream? dataStream = null); } } diff --git a/Core/Interfaces/Factories/IMessageFactory.cs b/Core/Interfaces/Factories/IMessageFactory.cs index 2d0efe1..c3a1f73 100644 --- a/Core/Interfaces/Factories/IMessageFactory.cs +++ b/Core/Interfaces/Factories/IMessageFactory.cs @@ -5,6 +5,6 @@ namespace MQContract.Interfaces.Factories { internal interface IMessageFactory : IMessageTypeFactory, IConversionPath where T : class { - Task ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader,Func>? mapChannel=null); + ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader,Func>? mapChannel=null); } } diff --git a/Core/Interfaces/Subscriptions/IInternalSubscription.cs b/Core/Interfaces/Subscriptions/IInternalSubscription.cs index 1cbd784..32fab8f 100644 --- a/Core/Interfaces/Subscriptions/IInternalSubscription.cs +++ b/Core/Interfaces/Subscriptions/IInternalSubscription.cs @@ -10,6 +10,6 @@ internal interface IInternalSubscription : ISubscription { Guid ID { get; } - Task EndAsync(bool remove); + ValueTask EndAsync(bool remove); } } diff --git a/Core/SubscriptionCollection.cs b/Core/SubscriptionCollection.cs index e4b8f23..8de3709 100644 --- a/Core/SubscriptionCollection.cs +++ b/Core/SubscriptionCollection.cs @@ -28,7 +28,7 @@ public async ValueTask DisposeAsync() { disposedValue = true; await dataLock.WaitAsync(); - await Task.WhenAll(subscriptions.Select(sub => sub.EndAsync(false)).ToArray()); + await Task.WhenAll(subscriptions.Select(sub => sub.EndAsync(false).AsTask())); dataLock.Release(); dataLock.Dispose(); subscriptions.Clear(); diff --git a/Core/Subscriptions/PubSubSubscription.cs b/Core/Subscriptions/PubSubSubscription.cs index 23c689f..39dbef6 100644 --- a/Core/Subscriptions/PubSubSubscription.cs +++ b/Core/Subscriptions/PubSubSubscription.cs @@ -48,7 +48,7 @@ private void EstablishReader() { try { - var taskMessage = messageFactory.ConvertMessage(logger, message) + var taskMessage = await messageFactory.ConvertMessageAsync(logger, message) ??throw new InvalidCastException($"Unable to convert incoming message {message.MessageTypeID} to {typeof(T).FullName}"); await messageRecieved(new RecievedMessage(message.ID,taskMessage!,message.Header,message.RecievedTimestamp,DateTime.Now)); } diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index c5db27b..308ac30 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -11,13 +11,13 @@ internal sealed class QueryResponseSubscription(IMessageFactory queryMes Func> mapChannel, SubscriptionCollection collection, string? channel = null, string? group = null, bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) - : SubscriptionBase(mapChannel,collection,channel,synchronous),ISubscription + : SubscriptionBase(mapChannel,collection,channel,synchronous) where Q : class where R : class { private readonly ManualResetEventSlim manualResetEvent = new(true); - public async Task EstablishSubscriptionAsync(IMessageServiceConnection connection, CancellationToken cancellationToken = new CancellationToken()) + public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnection connection, CancellationToken cancellationToken = new CancellationToken()) { SyncToken(cancellationToken); serviceSubscription = await connection.SubscribeQueryAsync( @@ -31,7 +31,7 @@ internal sealed class QueryResponseSubscription(IMessageFactory queryMes return serviceSubscription!=null; } - private async Task ProcessServiceMessageAsync(RecievedServiceMessage message) + private async ValueTask ProcessServiceMessageAsync(RecievedServiceMessage message) { if (Synchronous) manualResetEvent.Wait(cancellationToken:token.Token); @@ -39,7 +39,7 @@ private async Task ProcessServiceMessageAsync(RecievedServiceMes ServiceMessage? response = null; try { - var taskMessage = queryMessageFactory.ConvertMessage(logger, message) + var taskMessage = await queryMessageFactory.ConvertMessageAsync(logger, message) ??throw new InvalidCastException($"Unable to convert incoming message {message.MessageTypeID} to {typeof(Q).FullName}"); var result = await messageRecieved(new RecievedMessage(message.ID, taskMessage,message.Header,message.RecievedTimestamp,DateTime.Now)); response = await responseMessageFactory.ConvertMessageAsync(result.Message, message.Channel, new MessageHeader(result.Headers)); diff --git a/Core/Subscriptions/SubscriptionBase.cs b/Core/Subscriptions/SubscriptionBase.cs index 59ccbf2..955b297 100644 --- a/Core/Subscriptions/SubscriptionBase.cs +++ b/Core/Subscriptions/SubscriptionBase.cs @@ -30,13 +30,13 @@ protected SubscriptionBase(Func> mapChannel, SubscriptionCo } protected void SyncToken(CancellationToken cancellationToken) - => cancellationToken.Register(() => EndAsync().Wait()); + => cancellationToken.Register(async () => await EndAsync()); [ExcludeFromCodeCoverage(Justification ="Virtual function that is implemented elsewhere")] protected virtual void InternalDispose() { } - public async Task EndAsync(bool remove) + public async ValueTask EndAsync(bool remove) { if (serviceSubscription!=null) await serviceSubscription.EndAsync(); @@ -45,7 +45,7 @@ public async Task EndAsync(bool remove) await collection.RemoveAsync(ID); } - public Task EndAsync() + public ValueTask EndAsync() => EndAsync(true); public async ValueTask DisposeAsync() diff --git a/Core/Utility.cs b/Core/Utility.cs index f20adb4..fcfe103 100644 --- a/Core/Utility.cs +++ b/Core/Utility.cs @@ -1,4 +1,6 @@ -namespace MQContract +using System.Reflection; + +namespace MQContract { internal static class Utility { @@ -12,5 +14,12 @@ internal static string TypeName(Type type) result=result[..result.IndexOf('`')]; return result; } + + internal static async ValueTask InvokeMethodAsync(MethodInfo method,object container, object?[]? parameters) + { + var valueTask = method.Invoke(container, parameters)!; + await (Task)valueTask.GetType().GetMethod(nameof(ValueTask.AsTask))!.Invoke(valueTask, null)!; + return valueTask.GetType().GetProperty(nameof(ValueTask.Result))!.GetValue(valueTask); + } } } From 0fe760b4fb7d1f751553b7c26af0657033707c49 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Wed, 21 Aug 2024 11:59:16 -0400 Subject: [PATCH 04/22] completed migration to ValueTask changed all items available to use ValueTask instead of Task for efficiency --- .../Interfaces/IContractConnection.cs | 4 +- Abstractions/Readme.md | 12 +-- AutomatedTesting/ChannelMapperTests.cs | 66 ++++++------ .../SubscribeQueryResponseTests.cs | 22 ++-- .../ContractConnectionTests/SubscribeTests.cs | 32 +++--- .../Subscriptions/PublishSubscription.cs | 2 +- Connectors/Kafka/Connection.cs | 7 +- Connectors/Kafka/Readme.md | 16 +++ .../Subscriptions/PublishSubscription.cs | 4 +- .../Kafka/Subscriptions/QuerySubscription.cs | 4 +- .../Kafka/Subscriptions/SubscriptionBase.cs | 2 +- Connectors/KubeMQ/Connection.cs | 7 +- Connectors/KubeMQ/Readme.md | 16 +++ .../KubeMQ/Subscriptions/PubSubscription.cs | 4 +- .../KubeMQ/Subscriptions/QuerySubscription.cs | 2 +- .../KubeMQ/Subscriptions/SubscriptionBase.cs | 2 +- Connectors/NATS/Connection.cs | 5 + Connectors/NATS/Readme.md | 16 +++ Core/ChannelMapper.cs | 38 +++---- Core/ContractConnection.cs | 14 ++- Core/Factories/MessageTypeFactory.cs | 2 +- Core/Interfaces/Factories/IMessageFactory.cs | 2 +- Core/Readme.md | 100 ++++++++++-------- Core/Subscriptions/PubSubSubscription.cs | 6 +- .../QueryResponseSubscription.cs | 4 +- Core/Subscriptions/SubscriptionBase.cs | 4 +- Samples/KafkaSample/Program.cs | 6 +- Samples/KubeMQSample/Program.cs | 6 +- Samples/NATSSample/Program.cs | 6 +- 29 files changed, 248 insertions(+), 163 deletions(-) diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index a0229a0..5b13188 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -38,7 +38,7 @@ public interface IContractConnection : IAsyncDisposable /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A subscription instance that can be ended when desired - ValueTask SubscribeAsync(Func,Task> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false,bool synchronous=false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeAsync(Func,ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false,bool synchronous=false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to send a message into the underlying service in the Query/Response style @@ -83,7 +83,7 @@ public interface IContractConnection : IAsyncDisposable /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A subscription instance that can be ended when desired - ValueTask SubscribeQueryResponseAsync(Func,Task>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeQueryResponseAsync(Func,ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; } diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index 5dbbf5d..060c761 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -8,8 +8,8 @@ - [PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync\`\`1(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync\`\`2(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-Task},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.Task},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-Task{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.Task{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [IEncodedMessage](#T-MQContract-Interfaces-Messages-IEncodedMessage 'MQContract.Interfaces.Messages.IEncodedMessage') - [Data](#P-MQContract-Interfaces-Messages-IEncodedMessage-Data 'MQContract.Interfaces.Messages.IEncodedMessage.Data') - [Header](#P-MQContract-Interfaces-Messages-IEncodedMessage-Header 'MQContract.Interfaces.Messages.IEncodedMessage.Header') @@ -217,7 +217,7 @@ A result indicating the success or failure as well as the returned message | Q | The type of message to send for the query | | R | The type of message to expect back for the response | - + ### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken) `method` ##### Summary @@ -232,7 +232,7 @@ A subscription instance that can be ended when desired | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.Task}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.Task}') | The callback invoked when a new message is recieved | +| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask}') | The callback invoked when a new message is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | @@ -247,7 +247,7 @@ A subscription instance that can be ended when desired | ---- | ----------- | | T | The type of message to listen for | - + ### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken) `method` ##### Summary @@ -262,7 +262,7 @@ A subscription instance that can be ended when desired | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.Task{MQContract.Messages.QueryResponseMessage{\`\`1}}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.Task{MQContract.Messages.QueryResponseMessage{``1}}}') | The callback invoked when a new message is recieved expecting a response of the type response | +| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{\`\`1}}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}}') | The callback invoked when a new message is recieved expecting a response of the type response | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback invoked when an error occurs. | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | diff --git a/AutomatedTesting/ChannelMapperTests.cs b/AutomatedTesting/ChannelMapperTests.cs index 8d50acc..abbfa20 100644 --- a/AutomatedTesting/ChannelMapperTests.cs +++ b/AutomatedTesting/ChannelMapperTests.cs @@ -78,8 +78,8 @@ public async Task TestPublishMapWithStringToFunction() .AddPublishMap(typeof(BasicMessage).GetCustomAttribute(false)?.Name!, (originalChannel) => { if (Equals(originalChannel, typeof(BasicMessage).GetCustomAttribute(false)?.Name)) - return Task.FromResult(newChannel); - return Task.FromResult(originalChannel); + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -132,7 +132,7 @@ public async Task TestPublishMapWithMatchToFunction() (channelName)=>Equals(channelName, otherChannel) ,(originalChannel) => { - return Task.FromResult(newChannel); + return ValueTask.FromResult(newChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -184,8 +184,8 @@ public async Task TestPublishMapWithDefaultFunction() .AddDefaultPublishMap((originalChannel) => { if (Equals(originalChannel, typeof(BasicMessage).GetCustomAttribute(false)?.Name)) - return Task.FromResult(newChannel); - return Task.FromResult(originalChannel); + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -237,7 +237,7 @@ public async Task TestPublishMapWithSingleMapAndWithDefaultFunction() .AddPublishMap(typeof(BasicMessage).GetCustomAttribute(false)?.Name!,newChannel) .AddDefaultPublishMap((originalChannel) => { - return Task.FromResult(originalChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -290,7 +290,7 @@ public async Task TestPublishSubscribeMapWithStringToString() #region Act var subscription = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }); #endregion @@ -329,8 +329,8 @@ public async Task TestPublishSubscribeMapWithStringToFunction() (originalChannel) => { if (Equals(originalChannel, typeof(BasicMessage).GetCustomAttribute(false)?.Name)) - return Task.FromResult(newChannel); - return Task.FromResult(originalChannel); + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -338,10 +338,10 @@ public async Task TestPublishSubscribeMapWithStringToFunction() #region Act var subscription1 = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }); var subscription2 = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }, channel:otherChannel); #endregion @@ -382,7 +382,7 @@ public async Task TestPublishSubscribeMapWithMatchToFunction() (channelName) => Equals(channelName, otherChannel), (originalChannel) => { - return Task.FromResult(newChannel); + return ValueTask.FromResult(newChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -390,10 +390,10 @@ public async Task TestPublishSubscribeMapWithMatchToFunction() #region Act var subscription1 = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }); var subscription2 = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }, channel: otherChannel); #endregion @@ -434,8 +434,8 @@ public async Task TestPublishSubscribeMapWithDefaultFunction() (originalChannel) => { if (Equals(originalChannel, typeof(BasicMessage).GetCustomAttribute(false)?.Name)) - return Task.FromResult(newChannel); - return Task.FromResult(originalChannel); + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -443,10 +443,10 @@ public async Task TestPublishSubscribeMapWithDefaultFunction() #region Act var subscription1 = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }); var subscription2 = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }, channel: otherChannel); #endregion @@ -487,7 +487,7 @@ public async Task TestPublishSubscribeMapWithSingleMapAndWithDefaultFunction() .AddDefaultPublishSubscriptionMap( (originalChannel) => { - return Task.FromResult(originalChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -495,10 +495,10 @@ public async Task TestPublishSubscribeMapWithSingleMapAndWithDefaultFunction() #region Act var subscription1 = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }); var subscription2 = await contractConnection.SubscribeAsync( - (msg) => Task.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }, channel: otherChannel); #endregion @@ -598,8 +598,8 @@ public async Task TestQueryMapWithStringToFunction() (originalChannel) => { if (Equals(originalChannel, typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name)) - return Task.FromResult(newChannel); - return Task.FromResult(originalChannel); + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -655,7 +655,7 @@ public async Task TestQueryMapWithMatchToFunction() .AddQueryMap((channelName) => Equals(channelName, otherChannel) , (originalChannel) => { - return Task.FromResult(newChannel); + return ValueTask.FromResult(newChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -711,8 +711,8 @@ public async Task TestQueryMapWithDefaultFunction() .AddDefaultQueryMap((originalChannel) => { if (Equals(originalChannel, typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name)) - return Task.FromResult(newChannel); - return Task.FromResult(originalChannel); + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -768,7 +768,7 @@ public async Task TestQueryMapWithSingleMapAndWithDefaultFunction() .AddQueryMap(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name!, newChannel) .AddDefaultQueryMap((originalChannel) => { - return Task.FromResult(originalChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -865,8 +865,8 @@ public async Task TestQuerySubscribeMapWithStringToFunction() (originalChannel) => { if (Equals(originalChannel, typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name)) - return Task.FromResult(newChannel); - return Task.FromResult(originalChannel); + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -922,7 +922,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() (channelName) => Equals(channelName, otherChannel), (originalChannel) => { - return Task.FromResult(newChannel); + return ValueTask.FromResult(newChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -978,8 +978,8 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() (originalChannel) => { if (Equals(originalChannel, typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name)) - return Task.FromResult(newChannel); - return Task.FromResult(originalChannel); + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); @@ -1035,7 +1035,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() .AddDefaultQuerySubscriptionMap( (originalChannel) => { - return Task.FromResult(originalChannel); + return ValueTask.FromResult(originalChannel); }); var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs index d895983..e865531 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs @@ -54,7 +54,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() var exceptions = new List(); var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { messages.Add(msg); - return Task.FromResult(new QueryResponseMessage(responseMessage,null)); + return ValueTask.FromResult(new QueryResponseMessage(responseMessage,null)); }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.QueryAsync(message); @@ -120,13 +120,13 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var subscription1 = await contractConnection.SubscribeQueryResponseAsync( - (msg) => Task.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { }, channel:channelName); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var subscription2 = await contractConnection.SubscribeQueryResponseAsync( - (msg) => Task.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { }, channel: channelName); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. @@ -170,13 +170,13 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var subscription1 = await contractConnection.SubscribeQueryResponseAsync( - (msg) => Task.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { }, group: groupName); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var subscription2 = await contractConnection.SubscribeQueryResponseAsync( - (msg) => Task.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { }); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. #endregion @@ -219,7 +219,7 @@ public async Task TestSubscribeQueryResponseAsyncWithServiceChannelOptions() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var subscription = await contractConnection.SubscribeQueryResponseAsync( - (msg) => Task.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { }, options:serviceChannelOptions); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. @@ -259,7 +259,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeQueryResponseAsync( - (msg) => Task.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { }) ); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. @@ -296,7 +296,7 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeQueryResponseAsync( - (msg) => Task.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { }) ); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. @@ -335,7 +335,7 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var subscription = await contractConnection.SubscribeQueryResponseAsync( - (msg) => Task.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { } ); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. @@ -396,7 +396,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() var exceptions = new List(); var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { messages.Add(msg); - return Task.FromResult(new QueryResponseMessage(responseMessage, null)); + return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); }, (error) => exceptions.Add(error), synchronous:true); var stopwatch = Stopwatch.StartNew(); @@ -561,7 +561,7 @@ public async Task TestSubscribeQueryResponseAsyncWithDisposal() var exceptions = new List(); var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { messages.Add(msg); - return Task.FromResult(new QueryResponseMessage(responseMessage, null)); + return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.QueryAsync(message); diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs index 53a092c..b9063ec 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs @@ -50,7 +50,7 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { messages.Add(msg); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.PublishAsync(message); @@ -106,8 +106,8 @@ public async Task TestSubscribeAsyncWithSpecificChannel() #endregion #region Act - var subscription1 = await contractConnection.SubscribeAsync((msg) => Task.CompletedTask, (error) => { },channel:channelName); - var subscription2 = await contractConnection.SubscribeAsync((msg) => Task.CompletedTask, (error) => { }, channel: channelName); + var subscription1 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { },channel:channelName); + var subscription2 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }, channel: channelName); #endregion #region Assert @@ -142,8 +142,8 @@ public async Task TestSubscribeAsyncWithSpecificGroup() #endregion #region Act - var subscription1 = await contractConnection.SubscribeAsync((msg) => Task.CompletedTask, (error) => { }, group:groupName); - var subscription2 = await contractConnection.SubscribeAsync((msg) => Task.CompletedTask, (error) => { }); + var subscription1 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }, group:groupName); + var subscription2 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }); #endregion #region Assert @@ -178,7 +178,7 @@ public async Task TestSubscribeAsyncWithServiceChannelOptions() #endregion #region Act - var subscription = await contractConnection.SubscribeAsync((msg) => Task.CompletedTask, (error) => { }, options:serviceChannelOptions); + var subscription = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }, options:serviceChannelOptions); #endregion #region Assert @@ -209,7 +209,7 @@ public async Task TestSubscribeAsyncNoMessageChannelThrowsError() #endregion #region Act - var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeAsync((msg) => Task.CompletedTask, (error) => { })); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { })); #endregion #region Assert @@ -238,7 +238,7 @@ public async Task TestSubscribeAsyncReturnFailedSubscription() #endregion #region Act - var exception = await Assert.ThrowsExceptionAsync(async ()=> await contractConnection.SubscribeAsync(msg => Task.CompletedTask, err => { })); + var exception = await Assert.ThrowsExceptionAsync(async ()=> await contractConnection.SubscribeAsync(msg => ValueTask.CompletedTask, err => { })); #endregion #region Assert @@ -269,7 +269,7 @@ public async Task TestSubscribeAsyncCleanup() #endregion #region Act - var subscription = await contractConnection.SubscribeAsync(msg => Task.CompletedTask, err => { }); + var subscription = await contractConnection.SubscribeAsync(msg => ValueTask.CompletedTask, err => { }); await subscription.EndAsync(); #endregion @@ -302,7 +302,7 @@ public async Task TestSubscriptionsCleanup() #endregion #region Act - var subscription = await contractConnection.SubscribeAsync(msg => Task.CompletedTask, err => { }); + var subscription = await contractConnection.SubscribeAsync(msg => ValueTask.CompletedTask, err => { }); await contractConnection.DisposeAsync(); #endregion @@ -356,7 +356,7 @@ public async Task TestSubscribeAsyncWithSynchronousActions() var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { messages.Add(msg); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => exceptions.Add(error),synchronous:true); var stopwatch = Stopwatch.StartNew(); var result1 = await contractConnection.PublishAsync(message1); @@ -502,7 +502,7 @@ public async Task TestSubscribeAsyncWithCorruptMetaDataHeaderException() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeAsync((msg) => { return Task.CompletedTask; }, (error) => exceptions.Add(error)); + var subscription = await contractConnection.SubscribeAsync((msg) => { return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.PublishAsync(message); stopwatch.Stop(); @@ -570,7 +570,7 @@ public async Task TestSubscribeAsyncWithDisposal() var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { messages.Add(msg); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.PublishAsync(message); @@ -655,7 +655,7 @@ public async Task TestSubscribeAsyncWithSingleConversion() var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { messages.Add(msg); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.PublishAsync(message,channel:typeof(NamedAndVersionedMessage).GetCustomAttribute(false)?.Name); @@ -731,7 +731,7 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { messages.Add(msg); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.PublishAsync(message, channel: typeof(NamedAndVersionedMessage).GetCustomAttribute(false)?.Name); @@ -807,7 +807,7 @@ public async Task TestSubscribeAsyncWithNoConversionPath() var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { messages.Add(msg); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.PublishAsync(message, channel: typeof(NamedAndVersionedMessage).GetCustomAttribute(false)?.Name); diff --git a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs index ff2d359..e4a2ceb 100644 --- a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs +++ b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs @@ -15,7 +15,7 @@ internal class PublishSubscription(ISession session, string channel, Action new MessageHeader( + => new( header .Where(h => !Equals(h.Key, REPLY_ID) @@ -301,12 +301,17 @@ await producer.ProduceAsync( return subscription; } + /// + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// + /// A task required for disposal public ValueTask DisposeAsync() { if (!disposedValue) { disposedValue=true; producer.Dispose(); + GC.SuppressFinalize(this); } return ValueTask.CompletedTask; } diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index d825cef..00d03cb 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -7,6 +7,7 @@ - [#ctor(clientConfig)](#M-MQContract-Kafka-Connection-#ctor-Confluent-Kafka-ClientConfig- 'MQContract.Kafka.Connection.#ctor(Confluent.Kafka.ClientConfig)') - [DefaultTimout](#P-MQContract-Kafka-Connection-DefaultTimout 'MQContract.Kafka.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Kafka-Connection-MaxMessageBodySize 'MQContract.Kafka.Connection.MaxMessageBodySize') + - [DisposeAsync()](#M-MQContract-Kafka-Connection-DisposeAsync 'MQContract.Kafka.Connection.DisposeAsync') - [PingAsync()](#M-MQContract-Kafka-Connection-PingAsync 'MQContract.Kafka.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Kafka-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -61,6 +62,21 @@ DEFAULT:1 minute if not specified inside the connection options The maximum message body size allowed + +### DisposeAsync() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Returns + +A task required for disposal + +##### Parameters + +This method has no parameters. + ### PingAsync() `method` diff --git a/Connectors/Kafka/Subscriptions/PublishSubscription.cs b/Connectors/Kafka/Subscriptions/PublishSubscription.cs index 0df5d5c..159a2dd 100644 --- a/Connectors/Kafka/Subscriptions/PublishSubscription.cs +++ b/Connectors/Kafka/Subscriptions/PublishSubscription.cs @@ -6,7 +6,7 @@ namespace MQContract.Kafka.Subscriptions internal class PublishSubscription(Confluent.Kafka.IConsumer consumer, Action messageRecieved, Action errorRecieved, string channel, CancellationToken cancellationToken) : SubscriptionBase(consumer,channel,cancellationToken) { - protected override Task RunAction() + protected override ValueTask RunAction() { while (!cancelToken.IsCancellationRequested) { @@ -29,7 +29,7 @@ protected override Task RunAction() } finally { } } - return Task.CompletedTask; + return ValueTask.CompletedTask; } } } diff --git a/Connectors/Kafka/Subscriptions/QuerySubscription.cs b/Connectors/Kafka/Subscriptions/QuerySubscription.cs index 55e2a5e..1aeed1b 100644 --- a/Connectors/Kafka/Subscriptions/QuerySubscription.cs +++ b/Connectors/Kafka/Subscriptions/QuerySubscription.cs @@ -3,10 +3,10 @@ namespace MQContract.Kafka.Subscriptions { - internal class QuerySubscription(Confluent.Kafka.IConsumer consumer, Func,Task> messageRecieved, Action errorRecieved, string channel, CancellationToken cancellationToken) + internal class QuerySubscription(Confluent.Kafka.IConsumer consumer, Func, ValueTask> messageRecieved, Action errorRecieved, string channel, CancellationToken cancellationToken) : SubscriptionBase(consumer,channel,cancellationToken) { - protected override async Task RunAction() + protected override async ValueTask RunAction() { while (!cancelToken.IsCancellationRequested) { diff --git a/Connectors/Kafka/Subscriptions/SubscriptionBase.cs b/Connectors/Kafka/Subscriptions/SubscriptionBase.cs index b8cb77b..fe5b2d9 100644 --- a/Connectors/Kafka/Subscriptions/SubscriptionBase.cs +++ b/Connectors/Kafka/Subscriptions/SubscriptionBase.cs @@ -9,7 +9,7 @@ internal abstract class SubscriptionBase(Confluent.Kafka.IConsumer diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 476c69b..9917931 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -236,13 +236,18 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti sub.Run(); return ValueTask.FromResult(sub); } - + + /// + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// + /// A task required for disposal public async ValueTask DisposeAsync() { if (!disposedValue) { disposedValue=true; await client.DisposeAsync(); + GC.SuppressFinalize(this); } } } diff --git a/Connectors/KubeMQ/Readme.md b/Connectors/KubeMQ/Readme.md index a52f373..4d7d419 100644 --- a/Connectors/KubeMQ/Readme.md +++ b/Connectors/KubeMQ/Readme.md @@ -18,6 +18,7 @@ - [#ctor(options)](#M-MQContract-KubeMQ-Connection-#ctor-MQContract-KubeMQ-ConnectionOptions- 'MQContract.KubeMQ.Connection.#ctor(MQContract.KubeMQ.ConnectionOptions)') - [DefaultTimout](#P-MQContract-KubeMQ-Connection-DefaultTimout 'MQContract.KubeMQ.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-KubeMQ-Connection-MaxMessageBodySize 'MQContract.KubeMQ.Connection.MaxMessageBodySize') + - [DisposeAsync()](#M-MQContract-KubeMQ-Connection-DisposeAsync 'MQContract.KubeMQ.Connection.DisposeAsync') - [PingAsync()](#M-MQContract-KubeMQ-Connection-PingAsync 'MQContract.KubeMQ.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -336,6 +337,21 @@ DEFAULT:30 seconds if not specified inside the connection options The maximum message body size allowed + +### DisposeAsync() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Returns + +A task required for disposal + +##### Parameters + +This method has no parameters. + ### PingAsync() `method` diff --git a/Connectors/KubeMQ/Subscriptions/PubSubscription.cs b/Connectors/KubeMQ/Subscriptions/PubSubscription.cs index 3e16391..71f992d 100644 --- a/Connectors/KubeMQ/Subscriptions/PubSubscription.cs +++ b/Connectors/KubeMQ/Subscriptions/PubSubscription.cs @@ -31,10 +31,10 @@ protected override AsyncServerStreamingCall EstablishCall() cancelToken.Token); } - protected override Task MessageRecieved(EventReceive message) + protected override ValueTask MessageRecieved(EventReceive message) { messageRecieved(new(message.EventID,message.Metadata,message.Channel,Connection.ConvertMessageHeader(message.Tags),message.Body.ToArray())); - return Task.CompletedTask; + return ValueTask.CompletedTask; } } } diff --git a/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs b/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs index 977a12d..77adfb8 100644 --- a/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs +++ b/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs @@ -26,7 +26,7 @@ protected override AsyncServerStreamingCall EstablishCall() cancelToken.Token); } - protected override async Task MessageRecieved(Request message) + protected override async ValueTask MessageRecieved(Request message) { ServiceMessage? result; try diff --git a/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs b/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs index 066ac1a..9b580f9 100644 --- a/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs +++ b/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs @@ -15,7 +15,7 @@ internal abstract class SubscriptionBase(ILogger? logger,int reconnectInterva protected readonly CancellationTokenSource cancelToken = new(); protected abstract AsyncServerStreamingCall EstablishCall(); - protected abstract Task MessageRecieved(T message); + protected abstract ValueTask MessageRecieved(T message); public void Run() { diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index 35d37fe..3764abd 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -258,12 +258,17 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti return ValueTask.FromResult(sub); } + /// + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// + /// A task required for disposal public async ValueTask DisposeAsync() { if (!disposedValue) { disposedValue=true; await natsConnection.DisposeAsync(); + GC.SuppressFinalize(this); } } } diff --git a/Connectors/NATS/Readme.md b/Connectors/NATS/Readme.md index 365932c..c2d1ebe 100644 --- a/Connectors/NATS/Readme.md +++ b/Connectors/NATS/Readme.md @@ -8,6 +8,7 @@ - [DefaultTimout](#P-MQContract-NATS-Connection-DefaultTimout 'MQContract.NATS.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-NATS-Connection-MaxMessageBodySize 'MQContract.NATS.Connection.MaxMessageBodySize') - [CreateStreamAsync(streamConfig,cancellationToken)](#M-MQContract-NATS-Connection-CreateStreamAsync-NATS-Client-JetStream-Models-StreamConfig,System-Threading-CancellationToken- 'MQContract.NATS.Connection.CreateStreamAsync(NATS.Client.JetStream.Models.StreamConfig,System.Threading.CancellationToken)') + - [DisposeAsync()](#M-MQContract-NATS-Connection-DisposeAsync 'MQContract.NATS.Connection.DisposeAsync') - [PingAsync()](#M-MQContract-NATS-Connection-PingAsync 'MQContract.NATS.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-NATS-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-NATS-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -80,6 +81,21 @@ The stream creation result | streamConfig | [NATS.Client.JetStream.Models.StreamConfig](#T-NATS-Client-JetStream-Models-StreamConfig 'NATS.Client.JetStream.Models.StreamConfig') | The configuration settings for the stream | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +### DisposeAsync() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Returns + +A task required for disposal + +##### Parameters + +This method has no parameters. + ### PingAsync() `method` diff --git a/Core/ChannelMapper.cs b/Core/ChannelMapper.cs index 336f8f8..a60b94b 100644 --- a/Core/ChannelMapper.cs +++ b/Core/ChannelMapper.cs @@ -13,20 +13,20 @@ internal enum MapTypes QuerySubscription } - private sealed record ChannelMap(MapTypes Type,Func IsMatch,Func> Change); + private sealed record ChannelMap(MapTypes Type,Func IsMatch,Func> Change); private readonly List channelMaps = []; private ChannelMapper Append(MapTypes type, string originalChannel, string newChannel) - => Append(type, (key) => Equals(key, originalChannel), (key) => Task.FromResult(newChannel)); + => Append(type, (key) => Equals(key, originalChannel), (key) => ValueTask.FromResult(newChannel)); - private ChannelMapper Append(MapTypes type, string originalChannel, Func> change) + private ChannelMapper Append(MapTypes type, string originalChannel, Func> change) => Append(type, (key) => Equals(key, originalChannel), change); - private ChannelMapper Append(MapTypes type, Func> change) + private ChannelMapper Append(MapTypes type, Func> change) => Append(type, (key) => true, change); - private ChannelMapper Append(MapTypes type,Func isMatch,Func> change) + private ChannelMapper Append(MapTypes type,Func isMatch,Func> change) { channelMaps.Add(new(type, isMatch, change)); return this; @@ -47,7 +47,7 @@ public ChannelMapper AddPublishMap(string originalChannel,string newChannel) /// The original channel that is being used in the connection /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddPublishMap(string originalChannel, Func> mapFunction) + public ChannelMapper AddPublishMap(string originalChannel, Func> mapFunction) => Append(MapTypes.Publish, originalChannel, mapFunction); /// @@ -56,14 +56,14 @@ public ChannelMapper AddPublishMap(string originalChannel, FuncA callback that will return true if the supplied function will mape that channel /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddPublishMap(Func isMatch, Func> mapFunction) + public ChannelMapper AddPublishMap(Func isMatch, Func> mapFunction) => Append(MapTypes.Publish, isMatch, mapFunction); /// /// Add a default map function to call for publish calls /// /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddDefaultPublishMap(Func> mapFunction) + public ChannelMapper AddDefaultPublishMap(Func> mapFunction) => Append(MapTypes.Publish, mapFunction); /// /// Add a direct map for pub/sub subscription calls @@ -79,7 +79,7 @@ public ChannelMapper AddPublishSubscriptionMap(string originalChannel, string ne /// The original channel that is being used in the connection /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddPublishSubscriptionMap(string originalChannel, Func> mapFunction) + public ChannelMapper AddPublishSubscriptionMap(string originalChannel, Func> mapFunction) => Append(MapTypes.PublishSubscription, originalChannel, mapFunction); /// /// Add a map function call pair for pub/sub subscription calls @@ -87,14 +87,14 @@ public ChannelMapper AddPublishSubscriptionMap(string originalChannel, FuncA callback that will return true if the supplied function will mape that channel /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddPublishSubscriptionMap(Func isMatch, Func> mapFunction) + public ChannelMapper AddPublishSubscriptionMap(Func isMatch, Func> mapFunction) => Append(MapTypes.PublishSubscription, isMatch, mapFunction); /// /// Add a default map function to call for pub/sub subscription calls /// /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddDefaultPublishSubscriptionMap(Func> mapFunction) + public ChannelMapper AddDefaultPublishSubscriptionMap(Func> mapFunction) => Append(MapTypes.PublishSubscription, mapFunction); /// /// Add a direct map for query calls @@ -110,7 +110,7 @@ public ChannelMapper AddQueryMap(string originalChannel, string newChannel) /// The original channel that is being used in the connection /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddQueryMap(string originalChannel, Func> mapFunction) + public ChannelMapper AddQueryMap(string originalChannel, Func> mapFunction) => Append(MapTypes.Query, originalChannel, mapFunction); /// /// Add a map function call pair for query calls @@ -118,14 +118,14 @@ public ChannelMapper AddQueryMap(string originalChannel, FuncA callback that will return true if the supplied function will mape that channel /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddQueryMap(Func isMatch, Func> mapFunction) + public ChannelMapper AddQueryMap(Func isMatch, Func> mapFunction) => Append(MapTypes.Query, isMatch, mapFunction); /// /// Add a default map function to call for query calls /// /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddDefaultQueryMap(Func> mapFunction) + public ChannelMapper AddDefaultQueryMap(Func> mapFunction) => Append(MapTypes.Query, mapFunction); /// /// Add a direct map for query/response subscription calls @@ -141,7 +141,7 @@ public ChannelMapper AddQuerySubscriptionMap(string originalChannel, string newC /// The original channel that is being used in the connection /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddQuerySubscriptionMap(string originalChannel, Func> mapFunction) + public ChannelMapper AddQuerySubscriptionMap(string originalChannel, Func> mapFunction) => Append(MapTypes.QuerySubscription, originalChannel, mapFunction); /// /// Add a map function call pair for query/response subscription calls @@ -149,20 +149,20 @@ public ChannelMapper AddQuerySubscriptionMap(string originalChannel, FuncA callback that will return true if the supplied function will mape that channel /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddQuerySubscriptionMap(Func isMatch, Func> mapFunction) + public ChannelMapper AddQuerySubscriptionMap(Func isMatch, Func> mapFunction) => Append(MapTypes.QuerySubscription, isMatch, mapFunction); /// /// Add a default map function to call for query/response subscription calls /// /// A function to be called with the channel supplied expecting a mapped channel name /// The current instance of the Channel Mapper - public ChannelMapper AddDefaultQuerySubscriptionMap(Func> mapFunction) + public ChannelMapper AddDefaultQuerySubscriptionMap(Func> mapFunction) => Append(MapTypes.QuerySubscription, mapFunction); - internal Task MapChannel(MapTypes mapType,string originalChannel) + internal ValueTask MapChannel(MapTypes mapType,string originalChannel) { var map = channelMaps.Find(m=>Equals(m.Type,mapType) && m.IsMatch(originalChannel)); - return map?.Change(originalChannel)??Task.FromResult(originalChannel); + return map?.Change(originalChannel)??ValueTask.FromResult(originalChannel); } } } diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index 69c7833..cbb8b0a 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -52,8 +52,8 @@ private IMessageFactory GetMessageFactory(bool ignoreMessageHeader = false return result; } - private Task MapChannel(ChannelMapper.MapTypes mapType, string originalChannel) - => channelMapper?.MapChannel(mapType, originalChannel)??Task.FromResult(originalChannel); + private ValueTask MapChannel(ChannelMapper.MapTypes mapType, string originalChannel) + => channelMapper?.MapChannel(mapType, originalChannel)??ValueTask.FromResult(originalChannel); /// /// Called to execute a ping against the service layer @@ -97,7 +97,7 @@ private async ValueTask ProduceServiceMessage(ChannelMapper.M /// A cancellation token /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public async ValueTask SubscribeAsync(Func,Task> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class + public async ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class { var subscription = new PubSubSubscription(GetMessageFactory(ignoreMessageHeader), messageRecieved, @@ -221,7 +221,7 @@ private async ValueTask> ProduceResultAsync(ServiceQueryResult /// A cancellation token /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public async ValueTask SubscribeQueryResponseAsync(Func, Task>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask SubscribeQueryResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where Q : class where R : class { @@ -245,6 +245,11 @@ public async ValueTask SubscribeQueryResponseAsync(Func + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// + /// A task required for disposal + public async ValueTask DisposeAsync() { if (!disposedValue) @@ -252,6 +257,7 @@ public async ValueTask DisposeAsync() disposedValue=true; await subscriptions.DisposeAsync(); await serviceConnection.DisposeAsync(); + GC.SuppressFinalize(this); } } } diff --git a/Core/Factories/MessageTypeFactory.cs b/Core/Factories/MessageTypeFactory.cs index 1c873f0..914df68 100644 --- a/Core/Factories/MessageTypeFactory.cs +++ b/Core/Factories/MessageTypeFactory.cs @@ -146,7 +146,7 @@ private static bool IsMessageTypeMatch(string metaData, Type t, out bool isCompr return false; } - public async ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader, Func>? mapChannel=null) + public async ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader, Func>? mapChannel=null) { channel ??= messageChannel; if (string.IsNullOrWhiteSpace(channel)) diff --git a/Core/Interfaces/Factories/IMessageFactory.cs b/Core/Interfaces/Factories/IMessageFactory.cs index c3a1f73..6726f9a 100644 --- a/Core/Interfaces/Factories/IMessageFactory.cs +++ b/Core/Interfaces/Factories/IMessageFactory.cs @@ -5,6 +5,6 @@ namespace MQContract.Interfaces.Factories { internal interface IMessageFactory : IMessageTypeFactory, IConversionPath where T : class { - ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader,Func>? mapChannel=null); + ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader,Func>? mapChannel=null); } } diff --git a/Core/Readme.md b/Core/Readme.md index 92fe66c..3b0b276 100644 --- a/Core/Readme.md +++ b/Core/Readme.md @@ -4,30 +4,31 @@ ## Contents - [ChannelMapper](#T-MQContract-ChannelMapper 'MQContract.ChannelMapper') - - [AddDefaultPublishMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultPublishMap-System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddDefaultPublishMap(System.Func{System.String,System.Threading.Tasks.Task{System.String}})') - - [AddDefaultPublishSubscriptionMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultPublishSubscriptionMap-System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddDefaultPublishSubscriptionMap(System.Func{System.String,System.Threading.Tasks.Task{System.String}})') - - [AddDefaultQueryMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultQueryMap-System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddDefaultQueryMap(System.Func{System.String,System.Threading.Tasks.Task{System.String}})') - - [AddDefaultQuerySubscriptionMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultQuerySubscriptionMap-System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddDefaultQuerySubscriptionMap(System.Func{System.String,System.Threading.Tasks.Task{System.String}})') + - [AddDefaultPublishMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultPublishMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultPublishMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddDefaultPublishSubscriptionMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultPublishSubscriptionMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultPublishSubscriptionMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddDefaultQueryMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultQueryMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultQueryMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddDefaultQuerySubscriptionMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultQuerySubscriptionMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultQuerySubscriptionMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddPublishMap(originalChannel,newChannel)](#M-MQContract-ChannelMapper-AddPublishMap-System-String,System-String- 'MQContract.ChannelMapper.AddPublishMap(System.String,System.String)') - - [AddPublishMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddPublishMap-System-String,System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddPublishMap(System.String,System.Func{System.String,System.Threading.Tasks.Task{System.String}})') - - [AddPublishMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddPublishMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddPublishMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.Task{System.String}})') + - [AddPublishMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddPublishMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddPublishMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddPublishMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddPublishMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddPublishMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddPublishSubscriptionMap(originalChannel,newChannel)](#M-MQContract-ChannelMapper-AddPublishSubscriptionMap-System-String,System-String- 'MQContract.ChannelMapper.AddPublishSubscriptionMap(System.String,System.String)') - - [AddPublishSubscriptionMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddPublishSubscriptionMap-System-String,System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddPublishSubscriptionMap(System.String,System.Func{System.String,System.Threading.Tasks.Task{System.String}})') - - [AddPublishSubscriptionMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddPublishSubscriptionMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddPublishSubscriptionMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.Task{System.String}})') + - [AddPublishSubscriptionMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddPublishSubscriptionMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddPublishSubscriptionMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddPublishSubscriptionMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddPublishSubscriptionMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddPublishSubscriptionMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddQueryMap(originalChannel,newChannel)](#M-MQContract-ChannelMapper-AddQueryMap-System-String,System-String- 'MQContract.ChannelMapper.AddQueryMap(System.String,System.String)') - - [AddQueryMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddQueryMap-System-String,System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddQueryMap(System.String,System.Func{System.String,System.Threading.Tasks.Task{System.String}})') - - [AddQueryMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQueryMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddQueryMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.Task{System.String}})') + - [AddQueryMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddQueryMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQueryMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddQueryMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQueryMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQueryMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddQuerySubscriptionMap(originalChannel,newChannel)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-String,System-String- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.String,System.String)') - - [AddQuerySubscriptionMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-String,System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.String,System.Func{System.String,System.Threading.Tasks.Task{System.String}})') - - [AddQuerySubscriptionMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-Task{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.Task{System.String}})') + - [AddQuerySubscriptionMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddQuerySubscriptionMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [ContractConnection](#T-MQContract-ContractConnection 'MQContract.ContractConnection') - [#ctor(serviceConnection,defaultMessageEncoder,defaultMessageEncryptor,serviceProvider,logger,channelMapper)](#M-MQContract-ContractConnection-#ctor-MQContract-Interfaces-Service-IMessageServiceConnection,MQContract-Interfaces-Encoding-IMessageEncoder,MQContract-Interfaces-Encrypting-IMessageEncryptor,System-IServiceProvider,Microsoft-Extensions-Logging-ILogger,MQContract-ChannelMapper- 'MQContract.ContractConnection.#ctor(MQContract.Interfaces.Service.IMessageServiceConnection,MQContract.Interfaces.Encoding.IMessageEncoder,MQContract.Interfaces.Encrypting.IMessageEncryptor,System.IServiceProvider,Microsoft.Extensions.Logging.ILogger,MQContract.ChannelMapper)') + - [DisposeAsync()](#M-MQContract-ContractConnection-DisposeAsync 'MQContract.ContractConnection.DisposeAsync') - [PingAsync()](#M-MQContract-ContractConnection-PingAsync 'MQContract.ContractConnection.PingAsync') - [PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync\`\`1(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync\`\`2(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-Task},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.Task},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-Task{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.Task{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [MessageChannelNullException](#T-MQContract-MessageChannelNullException 'MQContract.MessageChannelNullException') - [MessageConversionException](#T-MQContract-MessageConversionException 'MQContract.MessageConversionException') - [QueryResponseException](#T-MQContract-QueryResponseException 'MQContract.QueryResponseException') @@ -45,7 +46,7 @@ MQContract Used to map channel names depending on the usage of the channel when necessary - + ### AddDefaultPublishMap(mapFunction) `method` ##### Summary @@ -60,9 +61,9 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | - + ### AddDefaultPublishSubscriptionMap(mapFunction) `method` ##### Summary @@ -77,9 +78,9 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | - + ### AddDefaultQueryMap(mapFunction) `method` ##### Summary @@ -94,9 +95,9 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | - + ### AddDefaultQuerySubscriptionMap(mapFunction) `method` ##### Summary @@ -111,7 +112,7 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | ### AddPublishMap(originalChannel,newChannel) `method` @@ -131,7 +132,7 @@ The current instance of the Channel Mapper | originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | | newChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel to map it to | - + ### AddPublishMap(originalChannel,mapFunction) `method` ##### Summary @@ -147,9 +148,9 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | | originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | - + ### AddPublishMap(isMatch,mapFunction) `method` ##### Summary @@ -165,7 +166,7 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | | isMatch | [System.Func{System.String,System.Boolean}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Boolean}') | A callback that will return true if the supplied function will mape that channel | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | ### AddPublishSubscriptionMap(originalChannel,newChannel) `method` @@ -185,7 +186,7 @@ The current instance of the Channel Mapper | originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | | newChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel to map it to | - + ### AddPublishSubscriptionMap(originalChannel,mapFunction) `method` ##### Summary @@ -201,9 +202,9 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | | originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | - + ### AddPublishSubscriptionMap(isMatch,mapFunction) `method` ##### Summary @@ -219,7 +220,7 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | | isMatch | [System.Func{System.String,System.Boolean}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Boolean}') | A callback that will return true if the supplied function will mape that channel | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | ### AddQueryMap(originalChannel,newChannel) `method` @@ -239,7 +240,7 @@ The current instance of the Channel Mapper | originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | | newChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel to map it to | - + ### AddQueryMap(originalChannel,mapFunction) `method` ##### Summary @@ -255,9 +256,9 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | | originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | - + ### AddQueryMap(isMatch,mapFunction) `method` ##### Summary @@ -273,7 +274,7 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | | isMatch | [System.Func{System.String,System.Boolean}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Boolean}') | A callback that will return true if the supplied function will mape that channel | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | ### AddQuerySubscriptionMap(originalChannel,newChannel) `method` @@ -293,7 +294,7 @@ The current instance of the Channel Mapper | originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | | newChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel to map it to | - + ### AddQuerySubscriptionMap(originalChannel,mapFunction) `method` ##### Summary @@ -309,9 +310,9 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | | originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | - + ### AddQuerySubscriptionMap(isMatch,mapFunction) `method` ##### Summary @@ -327,7 +328,7 @@ The current instance of the Channel Mapper | Name | Type | Description | | ---- | ---- | ----------- | | isMatch | [System.Func{System.String,System.Boolean}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Boolean}') | A callback that will return true if the supplied function will mape that channel | -| mapFunction | [System.Func{System.String,System.Threading.Tasks.Task{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.Task{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | ## ContractConnection `type` @@ -365,6 +366,21 @@ This is the primary class for this library and is used to create a Contract styl | channelMapper | [MQContract.ChannelMapper](#T-MQContract-ChannelMapper 'MQContract.ChannelMapper') | An instance of a ChannelMapper used to translate channels from one instance to another based on class channel attributes or supplied channels if necessary. For example, it might be necessary for a Nats.IO instance when you are trying to read from a stored message stream that is comprised of another channel or set of channels | + +### DisposeAsync() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Returns + +A task required for disposal + +##### Parameters + +This method has no parameters. + ### PingAsync() `method` @@ -470,7 +486,7 @@ A QueryResult that will contain the response message and or an error | Q | The type of message to transmit for the Query | | R | The type of message expected as a response | - + ### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken) `method` ##### Summary @@ -485,7 +501,7 @@ An instance of the Subscription that can be held or called to end | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.Task}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.Task}') | The callback to be executed when a message is recieved | +| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask}') | The callback to be executed when a message is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to be executed when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | @@ -506,7 +522,7 @@ An instance of the Subscription that can be held or called to end | ---- | ----------- | | [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - + ### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken) `method` ##### Summary @@ -521,7 +537,7 @@ An instance of the Subscription that can be held or called to end | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.Task{MQContract.Messages.QueryResponseMessage{\`\`1}}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.Task{MQContract.Messages.QueryResponseMessage{``1}}}') | The callback to be executed when a message is recieved and expects a returned response | +| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{\`\`1}}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}}') | The callback to be executed when a message is recieved and expects a returned response | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to be executed when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | diff --git a/Core/Subscriptions/PubSubSubscription.cs b/Core/Subscriptions/PubSubSubscription.cs index 39dbef6..c1816a9 100644 --- a/Core/Subscriptions/PubSubSubscription.cs +++ b/Core/Subscriptions/PubSubSubscription.cs @@ -7,8 +7,8 @@ namespace MQContract.Subscriptions { - internal sealed class PubSubSubscription(IMessageFactory messageFactory, Func,Task> messageRecieved, Action errorRecieved, - Func> mapChannel, SubscriptionCollection collection, + internal sealed class PubSubSubscription(IMessageFactory messageFactory, Func, ValueTask> messageRecieved, Action errorRecieved, + Func> mapChannel, SubscriptionCollection collection, string? channel = null, string? group = null, bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) : SubscriptionBase(mapChannel,collection,channel,synchronous) where T : class @@ -19,7 +19,7 @@ internal sealed class PubSubSubscription(IMessageFactory messageFactory, F SingleWriter=true }); - public async Task EstablishSubscriptionAsync(IMessageServiceConnection connection, CancellationToken cancellationToken = new CancellationToken()) + public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnection connection, CancellationToken cancellationToken = new CancellationToken()) { SyncToken(cancellationToken); serviceSubscription = await connection.SubscribeAsync( diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index 308ac30..f208ac9 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -7,8 +7,8 @@ namespace MQContract.Subscriptions { internal sealed class QueryResponseSubscription(IMessageFactory queryMessageFactory,IMessageFactory responseMessageFactory, - Func, Task>> messageRecieved, Action errorRecieved, - Func> mapChannel, SubscriptionCollection collection, + Func, ValueTask>> messageRecieved, Action errorRecieved, + Func> mapChannel, SubscriptionCollection collection, string? channel = null, string? group = null, bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) : SubscriptionBase(mapChannel,collection,channel,synchronous) diff --git a/Core/Subscriptions/SubscriptionBase.cs b/Core/Subscriptions/SubscriptionBase.cs index 955b297..f4ae5a4 100644 --- a/Core/Subscriptions/SubscriptionBase.cs +++ b/Core/Subscriptions/SubscriptionBase.cs @@ -19,12 +19,12 @@ internal abstract class SubscriptionBase : IInternalSubscription public Guid ID { get; private init; } - protected SubscriptionBase(Func> mapChannel, SubscriptionCollection collection, string? channel=null,bool synchronous = false){ + protected SubscriptionBase(Func> mapChannel, SubscriptionCollection collection, string? channel=null,bool synchronous = false){ ID = Guid.NewGuid(); this.collection=collection; var chan = channel??typeof(T).GetCustomAttribute(false)?.Name??throw new MessageChannelNullException(); Synchronous = synchronous; - var tsk = mapChannel(chan); + var tsk = mapChannel(chan).AsTask(); tsk.Wait(); MessageChannel=tsk.Result; } diff --git a/Samples/KafkaSample/Program.cs b/Samples/KafkaSample/Program.cs index 770c292..f41326f 100644 --- a/Samples/KafkaSample/Program.cs +++ b/Samples/KafkaSample/Program.cs @@ -22,7 +22,7 @@ (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Announcement error: {error.Message}"), cancellationToken: sourceCancel.Token @@ -33,7 +33,7 @@ { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return Task.FromResult>( + return ValueTask.FromResult>( new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the Kafka sample") ); }, @@ -45,7 +45,7 @@ (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), cancellationToken: sourceCancel.Token diff --git a/Samples/KubeMQSample/Program.cs b/Samples/KubeMQSample/Program.cs index c000098..e491942 100644 --- a/Samples/KubeMQSample/Program.cs +++ b/Samples/KubeMQSample/Program.cs @@ -22,7 +22,7 @@ (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Announcement error: {error.Message}"), cancellationToken: sourceCancel.Token @@ -33,7 +33,7 @@ { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return Task.FromResult>( + return ValueTask.FromResult>( new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the KubeMQ sample") ); }, @@ -45,7 +45,7 @@ (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), options:new StoredEventsSubscriptionOptions(StoredEventsSubscriptionOptions.MessageReadStyle.StartNewOnly), diff --git a/Samples/NATSSample/Program.cs b/Samples/NATSSample/Program.cs index c2fa282..6d95126 100644 --- a/Samples/NATSSample/Program.cs +++ b/Samples/NATSSample/Program.cs @@ -29,7 +29,7 @@ (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Announcement error: {error.Message}"), cancellationToken: sourceCancel.Token @@ -40,7 +40,7 @@ { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return Task.FromResult>( + return ValueTask.FromResult>( new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the NATSio sample") ); }, @@ -52,7 +52,7 @@ (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return Task.CompletedTask; + return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), options:new StreamPublishSubscriberOptions(), From 4224ee257ed24cbc8c3e4ad15b4f481c6e257039 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Wed, 28 Aug 2024 11:25:29 -0400 Subject: [PATCH 05/22] Removed Disposal Defaults Removed the Disposal Defaults in base interfaces to allow underlying classes to specify as well as implemented code to handle the disposals Added in End/Close calls for both subscriptions and Connections Updated unit testing accordingly Updated the samples to demonstrate how to properly close off the items in a console application due to some Task Scheduling concepts causing locks. --- .../Interfaces/IContractConnection.cs | 47 +++- Abstractions/Interfaces/ISubscription.cs | 2 +- .../Service/IMessageServiceConnection.cs | 7 +- .../Service/IServiceSubscription.cs | 2 +- Abstractions/Messages/MessageHeader.cs | 4 +- .../Messages/RecievedServiceMessage.cs | 5 +- Abstractions/Readme.md | 111 ++++++++- AutomatedTesting/ChannelMapperTests.cs | 18 +- .../ContractConnectionTests/CleanupTests.cs | 145 +++++++++++ .../SubscribeQueryResponseTests.cs | 232 +++++++++++++----- .../ContractConnectionTests/SubscribeTests.cs | 81 +++++- .../Messages/MessageHeaderTests.cs | 163 ++++++++++++ Connectors/ActiveMQ/Connection.cs | 26 +- Connectors/Kafka/Connection.cs | 34 ++- Connectors/Kafka/Readme.md | 28 +++ Connectors/KubeMQ/Connection.cs | 29 ++- Connectors/KubeMQ/Readme.md | 28 +++ Connectors/NATS/Connection.cs | 39 ++- Connectors/NATS/Readme.md | 28 +++ .../NATS/Subscriptions/PublishSubscription.cs | 6 +- .../NATS/Subscriptions/QuerySubscription.cs | 10 +- .../NATS/Subscriptions/StreamSubscription.cs | 16 +- .../NATS/Subscriptions/SubscriptionBase.cs | 37 +-- Core/ContractConnection.cs | 131 +++++++--- .../Subscriptions/IInternalSubscription.cs | 15 -- Core/Readme.md | 123 +++++++++- Core/SubscriptionCollection.cs | 45 ---- Core/Subscriptions/PubSubSubscription.cs | 57 ++--- .../QueryResponseSubscription.cs | 29 ++- Core/Subscriptions/SubscriptionBase.cs | 57 +++-- Samples/KafkaSample/Program.cs | 20 +- Samples/KubeMQSample/Program.cs | 20 +- Samples/NATSSample/Program.cs | 24 +- 33 files changed, 1276 insertions(+), 343 deletions(-) create mode 100644 AutomatedTesting/ContractConnectionTests/CleanupTests.cs create mode 100644 AutomatedTesting/Messages/MessageHeaderTests.cs delete mode 100644 Core/Interfaces/Subscriptions/IInternalSubscription.cs delete mode 100644 Core/SubscriptionCollection.cs diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index 5b13188..192c41e 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -6,7 +6,7 @@ namespace MQContract.Interfaces /// /// This interface represents the Core class for the MQContract system, IE the ContractConnection /// - public interface IContractConnection : IAsyncDisposable + public interface IContractConnection : IDisposable,IAsyncDisposable { /// /// Called to Ping the underlying system to obtain both information and ensure it is up. Not all Services support this method. @@ -26,7 +26,7 @@ public interface IContractConnection : IAsyncDisposable ValueTask PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// - /// Called to create a subscription into the underlying service Pub/Sub style + /// Called to create a subscription into the underlying service Pub/Sub style and have the messages processed asynchronously /// /// The type of message to listen for /// The callback invoked when a new message is recieved @@ -34,11 +34,24 @@ public interface IContractConnection : IAsyncDisposable /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class - /// Inddicates if the callbacks for a recieved message should be called synchronously or asynchronously /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A subscription instance that can be ended when desired - ValueTask SubscribeAsync(Func,ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false,bool synchronous=false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeAsync(Func,ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + where T : class; + /// + /// Called to create a subscription into the underlying service Pub/Sub style and have the messages processed syncrhonously + /// + /// The type of message to listen for + /// The callback invoked when a new message is recieved + /// The callback to invoke when an error occurs + /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. + /// The subscription group if desired (typically used when multiple instances of the same system are running) + /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class + /// Any required Service Channel Options that will be passed down to the service Connection + /// A cancellation token + /// A subscription instance that can be ended when desired + ValueTask SubscribeAsync(Action> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to send a message into the underlying service in the Query/Response style @@ -70,7 +83,23 @@ public interface IContractConnection : IAsyncDisposable ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class; /// - /// Called to create a subscription into the underlying service Query/Reponse style + /// Called to create a subscription into the underlying service Query/Reponse style and have the messages processed asynchronously + /// + /// The type of message to listen for + /// The type of message to respond with + /// The callback invoked when a new message is recieved expecting a response of the type response + /// The callback invoked when an error occurs. + /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. + /// The subscription group if desired (typically used when multiple instances of the same system are running) + /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class + /// Any required Service Channel Options that will be passed down to the service Connection + /// A cancellation token + /// A subscription instance that can be ended when desired + ValueTask SubscribeQueryAsyncResponseAsync(Func,ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + where Q : class + where R : class; + /// + /// Called to create a subscription into the underlying service Query/Reponse style and have the messages processed synchronously /// /// The type of message to listen for /// The type of message to respond with @@ -79,12 +108,16 @@ public interface IContractConnection : IAsyncDisposable /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class - /// Inddicates if the callbacks for a recieved message should be called synchronously or asynchronously /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A subscription instance that can be ended when desired - ValueTask SubscribeQueryResponseAsync(Func,ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; + /// + /// Called to close off the contract connection and close it's underlying service connection + /// + /// A task for the closure of the connection + ValueTask CloseAsync(); } } diff --git a/Abstractions/Interfaces/ISubscription.cs b/Abstractions/Interfaces/ISubscription.cs index bb77e8b..cc6bb7b 100644 --- a/Abstractions/Interfaces/ISubscription.cs +++ b/Abstractions/Interfaces/ISubscription.cs @@ -3,7 +3,7 @@ /// /// This interface represents a Contract Connection Subscription and is used to house and end the subscription /// - public interface ISubscription : IAsyncDisposable + public interface ISubscription : IDisposable, IAsyncDisposable { /// /// Called to end (close off) the subscription diff --git a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs index f1e8a65..7546f54 100644 --- a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs @@ -6,7 +6,7 @@ namespace MQContract.Interfaces.Service /// Defines an underlying service connection. This interface is used to allow for the creation of multiple underlying connection types to support the ability to use common code while /// being able to run against 1 or more Message services. /// - public interface IMessageServiceConnection: IAsyncDisposable + public interface IMessageServiceConnection { /// /// Maximum supported message body size in bytes @@ -60,5 +60,10 @@ public interface IMessageServiceConnection: IAsyncDisposable /// A cancellation token /// A service subscription object ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + /// + /// Implements a call to close off the connection when the ContractConnection is closed + /// + /// A task that the close is running in + ValueTask CloseAsync(); } } diff --git a/Abstractions/Interfaces/Service/IServiceSubscription.cs b/Abstractions/Interfaces/Service/IServiceSubscription.cs index d652b9e..1d238c4 100644 --- a/Abstractions/Interfaces/Service/IServiceSubscription.cs +++ b/Abstractions/Interfaces/Service/IServiceSubscription.cs @@ -3,7 +3,7 @@ /// /// Represents an underlying service level subscription /// - public interface IServiceSubscription : IAsyncDisposable + public interface IServiceSubscription { /// /// Called to end the subscription diff --git a/Abstractions/Messages/MessageHeader.cs b/Abstractions/Messages/MessageHeader.cs index 4f0ecf5..1d08e98 100644 --- a/Abstractions/Messages/MessageHeader.cs +++ b/Abstractions/Messages/MessageHeader.cs @@ -21,7 +21,9 @@ public MessageHeader(Dictionary? headers) public MessageHeader(MessageHeader? originalHeader, Dictionary? appendedHeader) : this( (appendedHeader?.AsEnumerable().Select(pair => new KeyValuePair(pair.Key, pair.Value??string.Empty))?? []) - .Concat(originalHeader?.Keys.Select(k => new KeyValuePair(k, originalHeader?[k]!))?? []) + .Concat(originalHeader?.Keys + .Where(k => !(appendedHeader?? []).Any(pair=>Equals(k,pair.Key))) + .Select(k => new KeyValuePair(k, originalHeader?[k]!))?? []) .DistinctBy(k => k.Key) ) { } diff --git a/Abstractions/Messages/RecievedServiceMessage.cs b/Abstractions/Messages/RecievedServiceMessage.cs index 8ffcb56..55cb5bc 100644 --- a/Abstractions/Messages/RecievedServiceMessage.cs +++ b/Abstractions/Messages/RecievedServiceMessage.cs @@ -1,4 +1,6 @@ -namespace MQContract.Messages +using System.Diagnostics.CodeAnalysis; + +namespace MQContract.Messages { /// /// A Recieved Service Message that gets passed back up into the Contract Connection when a message is recieved from the underlying service connection @@ -8,6 +10,7 @@ /// The channel the message was recieved on /// The message headers that came through /// The binary content of the message that should be the encoded class + [ExcludeFromCodeCoverage(Justification ="This is a record class and has nothing to test")] public record RecievedServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header, ReadOnlyMemory Data) : ServiceMessage(ID,MessageTypeID,Channel,Header,Data) { diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index 060c761..fad19a2 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -4,12 +4,15 @@ ## Contents - [IContractConnection](#T-MQContract-Interfaces-IContractConnection 'MQContract.Interfaces.IContractConnection') + - [CloseAsync()](#M-MQContract-Interfaces-IContractConnection-CloseAsync 'MQContract.Interfaces.IContractConnection.CloseAsync') - [PingAsync()](#M-MQContract-Interfaces-IContractConnection-PingAsync 'MQContract.Interfaces.IContractConnection.PingAsync') - [PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync\`\`1(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync\`\`2(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [IEncodedMessage](#T-MQContract-Interfaces-Messages-IEncodedMessage 'MQContract.Interfaces.Messages.IEncodedMessage') - [Data](#P-MQContract-Interfaces-Messages-IEncodedMessage-Data 'MQContract.Interfaces.Messages.IEncodedMessage.Data') - [Header](#P-MQContract-Interfaces-Messages-IEncodedMessage-Header 'MQContract.Interfaces.Messages.IEncodedMessage.Header') @@ -25,6 +28,7 @@ - [IMessageServiceConnection](#T-MQContract-Interfaces-Service-IMessageServiceConnection 'MQContract.Interfaces.Service.IMessageServiceConnection') - [DefaultTimout](#P-MQContract-Interfaces-Service-IMessageServiceConnection-DefaultTimout 'MQContract.Interfaces.Service.IMessageServiceConnection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Interfaces-Service-IMessageServiceConnection-MaxMessageBodySize 'MQContract.Interfaces.Service.IMessageServiceConnection.MaxMessageBodySize') + - [CloseAsync()](#M-MQContract-Interfaces-Service-IMessageServiceConnection-CloseAsync 'MQContract.Interfaces.Service.IMessageServiceConnection.CloseAsync') - [PingAsync()](#M-MQContract-Interfaces-Service-IMessageServiceConnection-PingAsync 'MQContract.Interfaces.Service.IMessageServiceConnection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -117,6 +121,21 @@ MQContract.Interfaces This interface represents the Core class for the MQContract system, IE the ContractConnection + +### CloseAsync() `method` + +##### Summary + +Called to close off the contract connection and close it's underlying service connection + +##### Returns + +A task for the closure of the connection + +##### Parameters + +This method has no parameters. + ### PingAsync() `method` @@ -217,12 +236,12 @@ A result indicating the success or failure as well as the returned message | Q | The type of message to send for the query | | R | The type of message to expect back for the response | - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` ##### Summary -Called to create a subscription into the underlying service Pub/Sub style +Called to create a subscription into the underlying service Pub/Sub style and have the messages processed asynchronously ##### Returns @@ -237,7 +256,6 @@ A subscription instance that can be ended when desired | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | -| synchronous | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Inddicates if the callbacks for a recieved message should be called synchronously or asynchronously | | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -247,12 +265,41 @@ A subscription instance that can be ended when desired | ---- | ----------- | | T | The type of message to listen for | - -### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` ##### Summary -Called to create a subscription into the underlying service Query/Reponse style +Called to create a subscription into the underlying service Pub/Sub style and have the messages processed syncrhonously + +##### Returns + +A subscription instance that can be ended when desired + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Action{MQContract.Interfaces.IRecievedMessage{\`\`0}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Interfaces.IRecievedMessage{``0}}') | The callback invoked when a new message is recieved | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | +| ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | The type of message to listen for | + + +### SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +##### Summary + +Called to create a subscription into the underlying service Query/Reponse style and have the messages processed asynchronously ##### Returns @@ -267,7 +314,36 @@ A subscription instance that can be ended when desired | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | -| synchronous | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Inddicates if the callbacks for a recieved message should be called synchronously or asynchronously | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| Q | The type of message to listen for | +| R | The type of message to respond with | + + +### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +##### Summary + +Called to create a subscription into the underlying service Query/Reponse style and have the messages processed synchronously + +##### Returns + +A subscription instance that can be ended when desired + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},MQContract.Messages.QueryResponseMessage{\`\`1}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}}') | The callback invoked when a new message is recieved expecting a response of the type response | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback invoked when an error occurs. | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | +| ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -481,6 +557,21 @@ The default timeout to use for RPC calls when it's not specified Maximum supported message body size in bytes + +### CloseAsync() `method` + +##### Summary + +Implements a call to close off the connection when the ContractConnection is closed + +##### Returns + +A task that the close is running in + +##### Parameters + +This method has no parameters. + ### PingAsync() `method` diff --git a/AutomatedTesting/ChannelMapperTests.cs b/AutomatedTesting/ChannelMapperTests.cs index abbfa20..4f74d23 100644 --- a/AutomatedTesting/ChannelMapperTests.cs +++ b/AutomatedTesting/ChannelMapperTests.cs @@ -821,7 +821,7 @@ public async Task TestQuerySubscribeMapWithStringToString() #endregion #region Act - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => {}); #endregion @@ -873,10 +873,10 @@ public async Task TestQuerySubscribeMapWithStringToFunction() #endregion #region Act - var subscription1 = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => { }); - var subscription2 = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => { }, channel:otherChannel); @@ -929,10 +929,10 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() #endregion #region Act - var subscription1 = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => { }); - var subscription2 = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => { }, channel: otherChannel); @@ -986,10 +986,10 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() #endregion #region Act - var subscription1 = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => { }); - var subscription2 = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => { }, channel: otherChannel); @@ -1042,10 +1042,10 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() #endregion #region Act - var subscription1 = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => { }); - var subscription2 = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { throw new NotImplementedException(); }, (error) => { }, channel: otherChannel); diff --git a/AutomatedTesting/ContractConnectionTests/CleanupTests.cs b/AutomatedTesting/ContractConnectionTests/CleanupTests.cs new file mode 100644 index 0000000..53647ae --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/CleanupTests.cs @@ -0,0 +1,145 @@ +using Moq; +using MQContract.Interfaces.Service; +using MQContract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutomatedTesting.ContractConnectionTests +{ + [TestClass] + public class CleanupTests + { + [TestMethod] + public async Task TestCloseAsync() + { + #region Arrange + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.CloseAsync()) + .Returns(ValueTask.CompletedTask); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + await contractConnection.CloseAsync(); + #endregion + + #region Assert + #endregion + + #region Verify + serviceConnection.Verify(x => x.CloseAsync(), Times.Once); + #endregion + } + + [TestMethod] + public void TestDispose() + { + #region Arrange + var serviceConnection = new Mock(); + + var contractConnection = new ContractConnection(serviceConnection.As().Object); + #endregion + + #region Act + contractConnection.Dispose(); + #endregion + + #region Assert + #endregion + + #region Verify + serviceConnection.Verify(x => x.Dispose(), Times.Once); + #endregion + } + + [TestMethod] + public void TestDisposeWithAsyncDispose() + { + #region Arrange + var serviceConnection = new Mock(); + serviceConnection.Setup(x=>x.DisposeAsync()).Returns(ValueTask.CompletedTask); + + var contractConnection = new ContractConnection(serviceConnection.As().Object); + #endregion + + #region Act + contractConnection.Dispose(); + #endregion + + #region Assert + #endregion + + #region Verify + serviceConnection.Verify(x => x.DisposeAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestDisposeAsyncWithDispose() + { + #region Arrange + var serviceConnection = new Mock(); + + var contractConnection = new ContractConnection(serviceConnection.As().Object); + #endregion + + #region Act + await contractConnection.DisposeAsync(); + #endregion + + #region Assert + #endregion + + #region Verify + serviceConnection.Verify(x => x.Dispose(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestDisposeAsyncWithDisposeAsync() + { + #region Arrange + var serviceConnection = new Mock(); + + var contractConnection = new ContractConnection(serviceConnection.As().Object); + #endregion + + #region Act + await contractConnection.DisposeAsync(); + #endregion + + #region Assert + #endregion + + #region Verify + serviceConnection.Verify(x => x.DisposeAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestDisposeAsyncWithDisposeAsyncAndDispose() + { + #region Arrange + var serviceConnection = new Mock(); + + var contractConnection = new ContractConnection(serviceConnection.As().As().Object); + #endregion + + #region Act + await contractConnection.DisposeAsync(); + #endregion + + #region Assert + #endregion + + #region Verify + serviceConnection.Verify(x => x.DisposeAsync(), Times.Once); + serviceConnection.As().Verify(x => x.Dispose(), Times.Never); + #endregion + } + } +} diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs index e865531..df905c6 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs @@ -6,6 +6,11 @@ using MQContract; using System.Diagnostics; using System.Reflection; +using MQContract.Interfaces.Encoding; +using System.Linq; +using MQContract.Messages; +using System.Linq.Expressions; +using System; namespace AutomatedTesting.ContractConnectionTests { @@ -52,7 +57,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { messages.Add(msg); return ValueTask.FromResult(new QueryResponseMessage(responseMessage,null)); }, (error) => exceptions.Add(error)); @@ -119,13 +124,13 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var subscription1 = await contractConnection.SubscribeQueryResponseAsync( + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync( (msg) => ValueTask.FromResult>(null), (error) => { }, channel:channelName); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var subscription2 = await contractConnection.SubscribeQueryResponseAsync( + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync( (msg) => ValueTask.FromResult>(null), (error) => { }, channel: channelName); @@ -169,13 +174,13 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var subscription1 = await contractConnection.SubscribeQueryResponseAsync( + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync( (msg) => ValueTask.FromResult>(null), (error) => { }, group: groupName); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var subscription2 = await contractConnection.SubscribeQueryResponseAsync( + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync( (msg) => ValueTask.FromResult>(null), (error) => { }); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. @@ -218,7 +223,7 @@ public async Task TestSubscribeQueryResponseAsyncWithServiceChannelOptions() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var subscription = await contractConnection.SubscribeQueryResponseAsync( + var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync( (msg) => ValueTask.FromResult>(null), (error) => { }, options:serviceChannelOptions); @@ -258,7 +263,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeQueryResponseAsync( + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeQueryAsyncResponseAsync( (msg) => ValueTask.FromResult>(null), (error) => { }) ); @@ -295,7 +300,7 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeQueryResponseAsync( + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.SubscribeQueryAsyncResponseAsync( (msg) => ValueTask.FromResult>(null), (error) => { }) ); @@ -334,7 +339,7 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var subscription = await contractConnection.SubscribeQueryResponseAsync( + var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync( (msg) => ValueTask.FromResult>(null), (error) => { } ); @@ -396,9 +401,8 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() var exceptions = new List(); var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { messages.Add(msg); - return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); - }, (error) => exceptions.Add(error), - synchronous:true); + return new(responseMessage, null); + }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result1 = await contractConnection.QueryAsync(message1); stopwatch.Stop(); @@ -479,7 +483,6 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() var contractConnection = new ContractConnection(serviceConnection.Object); var message = new BasicQueryMessage("TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction"); - var responseMessage = new BasicResponseMessage("TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction"); var exception = new NullReferenceException("TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction"); #endregion @@ -520,23 +523,165 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() } [TestMethod] - public async Task TestSubscribeQueryResponseAsyncWithDisposal() + public async Task TestSubscribeQueryResponseAsyncEndAsync() + { + #region Arrange + var serviceSubscription = new Mock(); + serviceSubscription.Setup(x => x.EndAsync()) + .Returns(ValueTask.CompletedTask); + + var serviceConnection = new Mock(); + + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.Object); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + throw new NotImplementedException(); + }, (error) => { }); + await subscription.EndAsync(); + #endregion + + #region Assert + Assert.IsNotNull(subscription); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestSubscribeQueryResponseAsyncAsyncCleanup() + { + #region Arrange + var serviceSubscription = new Mock(); + serviceSubscription.Setup(x => x.DisposeAsync()) + .Returns(ValueTask.CompletedTask); + + var serviceConnection = new Mock(); + + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.As().Object); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + throw new NotImplementedException(); + }, (error) => { }); + await subscription.DisposeAsync(); + #endregion + + #region Assert + Assert.IsNotNull(subscription); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceSubscription.Verify(x => x.DisposeAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestSubscribeQueryResponseAsyncWithNonAsyncCleanup() + { + #region Arrange + var serviceSubscription = new Mock(); + var serviceConnection = new Mock(); + + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.As().Object); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + throw new NotImplementedException(); + }, (error) => { }); + await subscription.DisposeAsync(); + #endregion + + #region Assert + Assert.IsNotNull(subscription); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceSubscription.Verify(x => x.Dispose(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestSubscribeQueryResponseAsyncSubscriptionsCleanup() { #region Arrange + var serviceSubscription = new Mock(); + + var serviceConnection = new Mock(); + + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.As().Object); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + throw new NotImplementedException(); + }, (error) => { }); + subscription.Dispose(); + #endregion + + #region Assert + Assert.IsNotNull(subscription); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceSubscription.Verify(x => x.Dispose(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestSubscribeQueryResponseAsyncWithThrowsConversionError() + { + #region Arrange + var message = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); + var serviceSubscription = new Mock(); + var globalConverter = new Mock(); + globalConverter.Setup(x => x.DecodeAsync(It.IsAny())) + .Returns(ValueTask.FromResult(null)); + globalConverter.Setup(x => x.EncodeAsync(It.IsAny())) + .Returns(ValueTask.FromResult([])); + globalConverter.Setup(x => x.DecodeAsync(It.IsAny())) + .Returns(ValueTask.FromResult(message)); + globalConverter.Setup(x => x.EncodeAsync(It.IsAny())) + .Returns(ValueTask.FromResult([])); var recievedActions = new List>>(); - var errorActions = new List>(); - var channels = new List(); - var groups = new List(); - var serviceMessages = new List(); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(recievedActions), - Capture.In>(errorActions), - Capture.In(channels), - Capture.In(groups), + It.IsAny>(), + It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); @@ -544,65 +689,34 @@ public async Task TestSubscribeQueryResponseAsyncWithDisposal() .Returns(async (ServiceMessage message, TimeSpan timeout, IServiceChannelOptions options, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); - serviceMessages.Add(rmessage); var result = await recievedActions[0](rmessage); return Helper.ProduceQueryResult(result); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = new ContractConnection(serviceConnection.Object,defaultMessageEncoder:globalConverter.Object); - var message = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); + var responseMessage = new BasicResponseMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); - var exception = new NullReferenceException("TestSubscribeQueryResponseWithNoExtendedAspects"); #endregion #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { messages.Add(msg); return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); - }, (error) => exceptions.Add(error)); + }, (error) => exceptions.Add(error),ignoreMessageHeader:true); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.QueryAsync(message); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); - - foreach (var act in errorActions) - act(exception); #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); - Assert.IsNotNull(subscription); Assert.IsNotNull(result); - Assert.AreEqual(1, recievedActions.Count); - Assert.AreEqual(1, channels.Count); - Assert.AreEqual(1, groups.Count); - Assert.AreEqual(1, serviceMessages.Count); - Assert.AreEqual(1, errorActions.Count); - Assert.AreEqual(1, exceptions.Count); - Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); - Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); - Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); - Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); - Assert.AreEqual(message, messages[0].Message); - Assert.AreEqual(exception, exceptions[0]); - Assert.IsFalse(result.IsError); - Assert.IsNull(result.Error); - Assert.AreEqual(result.Result, responseMessage); - System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].RecievedTimestamp).TotalMilliseconds}ms"); - Exception? disposeError = null; - try - { - await subscription.DisposeAsync(); - } - catch (Exception e) - { - disposeError=e; - } - Assert.IsNull(disposeError); + Assert.IsTrue(result.IsError); + Assert.IsFalse(string.IsNullOrWhiteSpace(result.Error)); + Assert.IsTrue(result.Error.Contains(typeof(BasicResponseMessage).FullName!)); #endregion #region Verify diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs index b9063ec..1751ca6 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs @@ -251,8 +251,9 @@ public async Task TestSubscribeAsyncReturnFailedSubscription() #endregion } + [TestMethod] - public async Task TestSubscribeAsyncCleanup() + public async Task TestSubscriptionsEndAsync() { #region Arrange var serviceSubscription = new Mock(); @@ -285,25 +286,25 @@ public async Task TestSubscribeAsyncCleanup() } [TestMethod] - public async Task TestSubscriptionsCleanup() + public async Task TestSubscribeAsyncCleanup() { #region Arrange - var serviceSubscription = new Mock(); - serviceSubscription.Setup(x => x.EndAsync()) + var serviceSubscription = new Mock(); + serviceSubscription.Setup(x => x.DisposeAsync()) .Returns(ValueTask.CompletedTask); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(serviceSubscription.Object); + .ReturnsAsync(serviceSubscription.As().Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act var subscription = await contractConnection.SubscribeAsync(msg => ValueTask.CompletedTask, err => { }); - await contractConnection.DisposeAsync(); + await subscription.DisposeAsync(); #endregion #region Assert @@ -313,7 +314,68 @@ public async Task TestSubscriptionsCleanup() #region Verify serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - serviceSubscription.Verify(x => x.EndAsync(), Times.Once); + serviceSubscription.Verify(x => x.DisposeAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestSubscribeAsyncWithNonAsyncCleanup() + { + #region Arrange + var serviceSubscription = new Mock(); + var serviceConnection = new Mock(); + + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.As().Object); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var subscription = await contractConnection.SubscribeAsync(msg => ValueTask.CompletedTask, err => { }); + await subscription.DisposeAsync(); + #endregion + + #region Assert + Assert.IsNotNull(subscription); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + serviceSubscription.Verify(x => x.Dispose(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestSubscriptionsCleanup() + { + #region Arrange + var serviceSubscription = new Mock(); + + var serviceConnection = new Mock(); + + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.As().Object); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var subscription = await contractConnection.SubscribeAsync(msg => ValueTask.CompletedTask, err => { }); + subscription.Dispose(); + #endregion + + #region Assert + Assert.IsNotNull(subscription); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion } @@ -356,8 +418,7 @@ public async Task TestSubscribeAsyncWithSynchronousActions() var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { messages.Add(msg); - return ValueTask.CompletedTask; - }, (error) => exceptions.Add(error),synchronous:true); + }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result1 = await contractConnection.PublishAsync(message1); stopwatch.Stop(); @@ -602,7 +663,7 @@ public async Task TestSubscribeAsyncWithDisposal() Exception? disposeError = null; try { - await subscription.DisposeAsync(); + await subscription.EndAsync(); }catch(Exception e) { disposeError=e; diff --git a/AutomatedTesting/Messages/MessageHeaderTests.cs b/AutomatedTesting/Messages/MessageHeaderTests.cs new file mode 100644 index 0000000..ea8a01d --- /dev/null +++ b/AutomatedTesting/Messages/MessageHeaderTests.cs @@ -0,0 +1,163 @@ +using Moq; +using MQContract.Interfaces.Service; +using MQContract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutomatedTesting.Messages +{ + [TestClass] + public class MessageHeaderTests + { + [TestMethod] + public void TestMessageHeaderPrimaryConstructor() + { + #region Arrange + IEnumerable> data = [ + new KeyValuePair("key1","value1"), + new KeyValuePair("key2","value2") + ]; + #endregion + + #region Act + var header = new MessageHeader(data); + #endregion + + #region Assert + Assert.AreEqual(2, header.Keys.Count()); + Assert.IsTrue(data.All(pair=>header.Keys.Contains(pair.Key) && Equals(header[pair.Key],pair.Value))); + #endregion + + #region Verify + #endregion + } + + [TestMethod] + public void TestMessageHeaderDictionaryConstructor() + { + #region Arrange + var data = new Dictionary([ + new KeyValuePair("key1","value1"), + new KeyValuePair("key2","value2") + ]); + #endregion + + #region Act + var header = new MessageHeader(data); + #endregion + + #region Assert + Assert.AreEqual(2, header.Keys.Count()); + Assert.IsTrue(data.All(pair => header.Keys.Contains(pair.Key) && Equals(header[pair.Key], pair.Value))); + #endregion + + #region Verify + #endregion + } + + [TestMethod] + public void TestMessageHeaderMergeConstructorWithOriginalAndExtension() + { + #region Arrange + var originalHeader = new MessageHeader([ + new KeyValuePair("key1","value1"), + new KeyValuePair("key2","value2") + ]); + var data = new Dictionary([ + new KeyValuePair("key3","value3"), + new KeyValuePair("key4","value4") + ]); + #endregion + + #region Act + var header = new MessageHeader(originalHeader,data); + #endregion + + #region Assert + Assert.AreEqual(4, header.Keys.Count()); + Assert.IsTrue(originalHeader.Keys.All(k => header.Keys.Contains(k) && Equals(header[k], originalHeader[k]))); + Assert.IsTrue(data.All(pair => header.Keys.Contains(pair.Key) && Equals(header[pair.Key], pair.Value))); + #endregion + + #region Verify + #endregion + } + + [TestMethod] + public void TestMessageHeaderMergeConstructorWithOriginalAndNullExtension() + { + #region Arrange + var originalHeader = new MessageHeader([ + new KeyValuePair("key1","value1"), + new KeyValuePair("key2","value2") + ]); + #endregion + + #region Act + var header = new MessageHeader(originalHeader, null); + #endregion + + #region Assert + Assert.AreEqual(2, header.Keys.Count()); + Assert.IsTrue(originalHeader.Keys.All(k => header.Keys.Contains(k) && Equals(header[k], originalHeader[k]))); + #endregion + + #region Verify + #endregion + } + + [TestMethod] + public void TestMessageHeaderMergeConstructorWithNullOriginalAndExtension() + { + #region Arrange + var data = new Dictionary([ + new KeyValuePair("key3","value3"), + new KeyValuePair("key4","value4") + ]); + #endregion + + #region Act + var header = new MessageHeader(null, data); + #endregion + + #region Assert + Assert.AreEqual(2, header.Keys.Count()); + Assert.IsTrue(data.All(pair => header.Keys.Contains(pair.Key) && Equals(header[pair.Key], pair.Value))); + #endregion + + #region Verify + #endregion + } + + [TestMethod] + public void TestMessageHeaderMergeConstructorWithOriginalAndExtensionWithSameKeys() + { + #region Arrange + var originalHeader = new MessageHeader([ + new KeyValuePair("key1","value1"), + new KeyValuePair("key2","value2") + ]); + var data = new Dictionary([ + new KeyValuePair("key1","value3"), + new KeyValuePair("key2","value4") + ]); + #endregion + + #region Act + var header = new MessageHeader(originalHeader, data); + #endregion + + #region Assert + Assert.AreEqual(2, header.Keys.Count()); + Assert.IsTrue(originalHeader.Keys.All(k => header.Keys.Contains(k) && !Equals(header[k], originalHeader[k]))); + Assert.IsTrue(data.All(pair => header.Keys.Contains(pair.Key) && Equals(header[pair.Key], pair.Value))); + #endregion + + #region Verify + #endregion + } + } +} diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index 0213b83..5fe1fd2 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -15,7 +15,7 @@ internal class Connection : IMessageServiceConnection private const string MESSAGE_TYPE_HEADER_ID = "_MessageType"; private bool disposedValue; - + private readonly IConnectionFactory connectionFactory; private readonly IConnection connection; private readonly ISession session; @@ -95,16 +95,36 @@ public ValueTask QueryAsync(ServiceMessage message, TimeSpan throw new NotImplementedException(); } + public async ValueTask CloseAsync() + => await connection.StopAsync(); + public async ValueTask DisposeAsync() + { + await connection.StopAsync().ConfigureAwait(false); + + Dispose(disposing: false); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) { if (!disposedValue) { - disposedValue=true; + if (disposing) + connection.Stop(); + producer.Dispose(); session.Dispose(); - await connection.StopAsync(); connection.Dispose(); + disposedValue=true; } } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 8d4c8bc..3d854fd 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -12,7 +12,7 @@ namespace MQContract.Kafka /// This is the MessageServiceConnection implementation for using Kafka /// /// - public class Connection(ClientConfig clientConfig) : IMessageServiceConnection + public sealed class Connection(ClientConfig clientConfig) : IMessageServiceConnection { private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; private const string QUERY_IDENTIFIER_HEADER = "_QueryClientID"; @@ -301,19 +301,45 @@ await producer.ProduceAsync( return subscription; } + /// + /// Called to close off the underlying Kafka Connection + /// + /// + public ValueTask CloseAsync() + { + Dispose(true); + return ValueTask.CompletedTask; + } + /// /// Called to dispose of the object correctly and allow it to clean up it's resources /// /// A task required for disposal public ValueTask DisposeAsync() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + private void Dispose(bool disposing) { if (!disposedValue) { + if (disposing) + producer.Dispose(); disposedValue=true; - producer.Dispose(); - GC.SuppressFinalize(this); } - return ValueTask.CompletedTask; + } + + /// + /// Called to dispose of the required resources + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } } diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index 00d03cb..8688bf8 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -7,6 +7,8 @@ - [#ctor(clientConfig)](#M-MQContract-Kafka-Connection-#ctor-Confluent-Kafka-ClientConfig- 'MQContract.Kafka.Connection.#ctor(Confluent.Kafka.ClientConfig)') - [DefaultTimout](#P-MQContract-Kafka-Connection-DefaultTimout 'MQContract.Kafka.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Kafka-Connection-MaxMessageBodySize 'MQContract.Kafka.Connection.MaxMessageBodySize') + - [CloseAsync()](#M-MQContract-Kafka-Connection-CloseAsync 'MQContract.Kafka.Connection.CloseAsync') + - [Dispose()](#M-MQContract-Kafka-Connection-Dispose 'MQContract.Kafka.Connection.Dispose') - [DisposeAsync()](#M-MQContract-Kafka-Connection-DisposeAsync 'MQContract.Kafka.Connection.DisposeAsync') - [PingAsync()](#M-MQContract-Kafka-Connection-PingAsync 'MQContract.Kafka.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -62,6 +64,32 @@ DEFAULT:1 minute if not specified inside the connection options The maximum message body size allowed + +### CloseAsync() `method` + +##### Summary + +Called to close off the underlying Kafka Connection + +##### Returns + + + +##### Parameters + +This method has no parameters. + + +### Dispose() `method` + +##### Summary + +Called to dispose of the required resources + +##### Parameters + +This method has no parameters. + ### DisposeAsync() `method` diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 9917931..6abebf0 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -17,7 +17,7 @@ namespace MQContract.KubeMQ /// /// This is the MessageServiceConnection implementation for using KubeMQ /// - public class Connection : IMessageServiceConnection + public sealed class Connection : IMessageServiceConnection,IDisposable,IAsyncDisposable { private static readonly Regex regURL = new("^http(s)?://(.+)$", RegexOptions.Compiled|RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500)); @@ -236,19 +236,42 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti sub.Run(); return ValueTask.FromResult(sub); } + /// + /// Called to close the underlying KubeMQ Client connection + /// + /// + public ValueTask CloseAsync() + => client.DisposeAsync(); /// /// Called to dispose of the object correctly and allow it to clean up it's resources /// /// A task required for disposal public async ValueTask DisposeAsync() + { + await client.DisposeAsync(); + + Dispose(disposing: false); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) { if (!disposedValue) { + if (disposing) + client.DisposeAsync().AsTask().Wait(); disposedValue=true; - await client.DisposeAsync(); - GC.SuppressFinalize(this); } } + /// + /// Called to dispose of the underlying resources + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/Connectors/KubeMQ/Readme.md b/Connectors/KubeMQ/Readme.md index 4d7d419..2039250 100644 --- a/Connectors/KubeMQ/Readme.md +++ b/Connectors/KubeMQ/Readme.md @@ -18,6 +18,8 @@ - [#ctor(options)](#M-MQContract-KubeMQ-Connection-#ctor-MQContract-KubeMQ-ConnectionOptions- 'MQContract.KubeMQ.Connection.#ctor(MQContract.KubeMQ.ConnectionOptions)') - [DefaultTimout](#P-MQContract-KubeMQ-Connection-DefaultTimout 'MQContract.KubeMQ.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-KubeMQ-Connection-MaxMessageBodySize 'MQContract.KubeMQ.Connection.MaxMessageBodySize') + - [CloseAsync()](#M-MQContract-KubeMQ-Connection-CloseAsync 'MQContract.KubeMQ.Connection.CloseAsync') + - [Dispose()](#M-MQContract-KubeMQ-Connection-Dispose 'MQContract.KubeMQ.Connection.Dispose') - [DisposeAsync()](#M-MQContract-KubeMQ-Connection-DisposeAsync 'MQContract.KubeMQ.Connection.DisposeAsync') - [PingAsync()](#M-MQContract-KubeMQ-Connection-PingAsync 'MQContract.KubeMQ.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -337,6 +339,32 @@ DEFAULT:30 seconds if not specified inside the connection options The maximum message body size allowed + +### CloseAsync() `method` + +##### Summary + +Called to close the underlying KubeMQ Client connection + +##### Returns + + + +##### Parameters + +This method has no parameters. + + +### Dispose() `method` + +##### Summary + +Called to dispose of the underlying resources + +##### Parameters + +This method has no parameters. + ### DisposeAsync() `method` diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index 3764abd..256d130 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -13,7 +13,7 @@ namespace MQContract.NATS /// /// This is the MessageServiceConnection implementation for using NATS.io /// - public class Connection : IMessageServiceConnection + public sealed class Connection : IMessageServiceConnection, IAsyncDisposable,IDisposable { private const string MESSAGE_IDENTIFIER_HEADER = "_MessageID"; private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; @@ -206,14 +206,14 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti /// Thrown when options is not null and is not an instance of the type StreamPublishSubscriberOptions public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { - InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); + InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); IInternalServiceSubscription? subscription = null; if (options is StreamPublishSubscriberOptions subscriberOptions) { if (subscriberOptions.StreamConfig!=null) await CreateStreamAsync(subscriberOptions.StreamConfig, cancellationToken); var consumer = await natsJSContext.CreateOrUpdateConsumerAsync(subscriberOptions.StreamConfig?.Name??channel, subscriberOptions.ConsumerConfig??new ConsumerConfig(group) { AckPolicy = ConsumerConfigAckPolicy.Explicit }, cancellationToken); - subscription = new StreamSubscription(consumer, messageRecieved, errorRecieved, cancellationToken); + subscription = new StreamSubscription(consumer, messageRecieved, errorRecieved); } else subscription = new PublishSubscription( @@ -223,8 +223,7 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti cancellationToken: cancellationToken ), messageRecieved, - errorRecieved, - cancellationToken + errorRecieved ); subscription.Run(); return subscription; @@ -251,25 +250,47 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti cancellationToken: cancellationToken ), messageRecieved, - errorRecieved, - cancellationToken + errorRecieved ); sub.Run(); return ValueTask.FromResult(sub); } + /// + /// Called to close off the contract connection and close it's underlying service connection + /// + /// A task for the closure of the connection + public ValueTask CloseAsync() + => natsConnection.DisposeAsync(); /// /// Called to dispose of the object correctly and allow it to clean up it's resources /// /// A task required for disposal public async ValueTask DisposeAsync() + { + await natsConnection.DisposeAsync().ConfigureAwait(true); + + Dispose(disposing: false); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) { if (!disposedValue) { + if (disposing) + natsConnection.DisposeAsync().AsTask().Wait(); disposedValue=true; - await natsConnection.DisposeAsync(); - GC.SuppressFinalize(this); } } + /// + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/Connectors/NATS/Readme.md b/Connectors/NATS/Readme.md index c2d1ebe..ef7f638 100644 --- a/Connectors/NATS/Readme.md +++ b/Connectors/NATS/Readme.md @@ -7,7 +7,9 @@ - [#ctor(options)](#M-MQContract-NATS-Connection-#ctor-NATS-Client-Core-NatsOpts- 'MQContract.NATS.Connection.#ctor(NATS.Client.Core.NatsOpts)') - [DefaultTimout](#P-MQContract-NATS-Connection-DefaultTimout 'MQContract.NATS.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-NATS-Connection-MaxMessageBodySize 'MQContract.NATS.Connection.MaxMessageBodySize') + - [CloseAsync()](#M-MQContract-NATS-Connection-CloseAsync 'MQContract.NATS.Connection.CloseAsync') - [CreateStreamAsync(streamConfig,cancellationToken)](#M-MQContract-NATS-Connection-CreateStreamAsync-NATS-Client-JetStream-Models-StreamConfig,System-Threading-CancellationToken- 'MQContract.NATS.Connection.CreateStreamAsync(NATS.Client.JetStream.Models.StreamConfig,System.Threading.CancellationToken)') + - [Dispose()](#M-MQContract-NATS-Connection-Dispose 'MQContract.NATS.Connection.Dispose') - [DisposeAsync()](#M-MQContract-NATS-Connection-DisposeAsync 'MQContract.NATS.Connection.DisposeAsync') - [PingAsync()](#M-MQContract-NATS-Connection-PingAsync 'MQContract.NATS.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-NATS-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -63,6 +65,21 @@ DEFAULT: 30 seconds The maximum message body size allowed. DEFAULT: 1MB + +### CloseAsync() `method` + +##### Summary + +Called to close off the contract connection and close it's underlying service connection + +##### Returns + +A task for the closure of the connection + +##### Parameters + +This method has no parameters. + ### CreateStreamAsync(streamConfig,cancellationToken) `method` @@ -81,6 +98,17 @@ The stream creation result | streamConfig | [NATS.Client.JetStream.Models.StreamConfig](#T-NATS-Client-JetStream-Models-StreamConfig 'NATS.Client.JetStream.Models.StreamConfig') | The configuration settings for the stream | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +### Dispose() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Parameters + +This method has no parameters. + ### DisposeAsync() `method` diff --git a/Connectors/NATS/Subscriptions/PublishSubscription.cs b/Connectors/NATS/Subscriptions/PublishSubscription.cs index 94fce0d..6ea6cfb 100644 --- a/Connectors/NATS/Subscriptions/PublishSubscription.cs +++ b/Connectors/NATS/Subscriptions/PublishSubscription.cs @@ -4,12 +4,12 @@ namespace MQContract.NATS.Subscriptions { internal class PublishSubscription(IAsyncEnumerable> asyncEnumerable, - Action messageRecieved, Action errorRecieved, - CancellationToken cancellationToken) : SubscriptionBase(cancellationToken) + Action messageRecieved, Action errorRecieved) + : SubscriptionBase() { protected override async Task RunAction() { - await foreach (var msg in asyncEnumerable.WithCancellation(cancelToken.Token)) + await foreach (var msg in asyncEnumerable.WithCancellation(CancelToken)) { try { diff --git a/Connectors/NATS/Subscriptions/QuerySubscription.cs b/Connectors/NATS/Subscriptions/QuerySubscription.cs index 49add8a..520fd82 100644 --- a/Connectors/NATS/Subscriptions/QuerySubscription.cs +++ b/Connectors/NATS/Subscriptions/QuerySubscription.cs @@ -4,12 +4,12 @@ namespace MQContract.NATS.Subscriptions { internal class QuerySubscription(IAsyncEnumerable> asyncEnumerable, - Func> messageRecieved, Action errorRecieved, - CancellationToken cancellationToken) : SubscriptionBase(cancellationToken) + Func> messageRecieved, Action errorRecieved) + : SubscriptionBase() { protected override async Task RunAction() { - await foreach (var msg in asyncEnumerable.WithCancellation(cancelToken.Token)) + await foreach (var msg in asyncEnumerable.WithCancellation(CancelToken)) { var recievedMessage = ExtractMessage(msg); try @@ -19,7 +19,7 @@ await msg.ReplyAsync( result.Data.ToArray(), headers: Connection.ExtractHeader(result), replyTo: msg.ReplyTo, - cancellationToken: cancelToken.Token + cancellationToken: CancelToken ); } catch (Exception ex) @@ -30,7 +30,7 @@ await msg.ReplyAsync( responseData, replyTo: msg.ReplyTo, headers:headers, - cancellationToken: cancelToken.Token + cancellationToken: CancelToken ); } } diff --git a/Connectors/NATS/Subscriptions/StreamSubscription.cs b/Connectors/NATS/Subscriptions/StreamSubscription.cs index 63dcd2f..d92c46e 100644 --- a/Connectors/NATS/Subscriptions/StreamSubscription.cs +++ b/Connectors/NATS/Subscriptions/StreamSubscription.cs @@ -4,18 +4,18 @@ namespace MQContract.NATS.Subscriptions { internal class StreamSubscription(INatsJSConsumer consumer, Action messageRecieved, - Action errorRecieved, CancellationToken cancellationToken) - : SubscriptionBase(cancellationToken) + Action errorRecieved) + : SubscriptionBase() { protected override async Task RunAction() { - while (!cancelToken.Token.IsCancellationRequested) + while (!CancelToken.IsCancellationRequested) { try { - await consumer.RefreshAsync(cancelToken.Token); // or try to recreate consumer + await consumer.RefreshAsync(CancelToken); // or try to recreate consumer - await foreach (var msg in consumer.ConsumeAsync().WithCancellation(cancelToken.Token)) + await foreach (var msg in consumer.ConsumeAsync().WithCancellation(CancelToken)) { var success = true; try @@ -26,10 +26,10 @@ protected override async Task RunAction() { success=false; errorRecieved(ex); - await msg.NakAsync(cancellationToken: cancelToken.Token); + await msg.NakAsync(cancellationToken: CancelToken); } if (success) - await msg.AckAsync(cancellationToken: cancelToken.Token); + await msg.AckAsync(cancellationToken: CancelToken); } } catch (NatsJSProtocolException e) @@ -40,7 +40,7 @@ protected override async Task RunAction() { errorRecieved(e); // log exception - await Task.Delay(1000, cancelToken.Token); // backoff + await Task.Delay(1000, CancelToken); // backoff } } } diff --git a/Connectors/NATS/Subscriptions/SubscriptionBase.cs b/Connectors/NATS/Subscriptions/SubscriptionBase.cs index 1ece8dc..9d4e017 100644 --- a/Connectors/NATS/Subscriptions/SubscriptionBase.cs +++ b/Connectors/NATS/Subscriptions/SubscriptionBase.cs @@ -4,10 +4,12 @@ namespace MQContract.NATS.Subscriptions { - internal abstract class SubscriptionBase(CancellationToken cancellationToken) : IInternalServiceSubscription + internal abstract class SubscriptionBase() : IInternalServiceSubscription,IDisposable { + private readonly CancellationTokenSource CancelTokenSource = new(); private bool disposedValue; - protected readonly CancellationTokenSource cancelToken = new(); + + protected CancellationToken CancelToken => CancelTokenSource.Token; protected static RecievedServiceMessage ExtractMessage(NatsJSMsg recievedMessage) => ExtractMessage(recievedMessage.Headers, recievedMessage.Subject, recievedMessage.Data); @@ -29,28 +31,35 @@ private static RecievedServiceMessage ExtractMessage(NatsHeaders? headers, strin protected abstract Task RunAction(); public void Run() - { - cancellationToken.Register(() => - { - cancelToken.Cancel(); - }); - RunAction(); - } + => RunAction(); public async ValueTask EndAsync() { - try { await cancelToken.CancelAsync(); } catch { } + if (!CancelTokenSource.IsCancellationRequested) + { + System.Diagnostics.Debug.WriteLine("Calling Cancel Async inside NATS subscription..."); + await CancelTokenSource.CancelAsync(); + System.Diagnostics.Debug.WriteLine("COmpleted Cancel Async inside NATS subscription"); + } } - public async ValueTask DisposeAsync() + protected virtual void Dispose(bool disposing) { if (!disposedValue) { + if (disposing&&!CancelTokenSource.IsCancellationRequested) + CancelTokenSource.Cancel(); + + CancelTokenSource.Dispose(); disposedValue=true; - if (!cancelToken.IsCancellationRequested) - await cancelToken.CancelAsync(); - cancelToken.Dispose(); } } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index cbb8b0a..cdd8639 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -23,7 +23,7 @@ namespace MQContract /// An instance of a ChannelMapper used to translate channels from one instance to another based on class channel attributes or supplied channels if necessary. /// For example, it might be necessary for a Nats.IO instance when you are trying to read from a stored message stream that is comprised of another channel or set of channels /// - public class ContractConnection(IMessageServiceConnection serviceConnection, + public sealed class ContractConnection(IMessageServiceConnection serviceConnection, IMessageEncoder? defaultMessageEncoder = null, IMessageEncryptor? defaultMessageEncryptor = null, IServiceProvider? serviceProvider = null, @@ -34,7 +34,6 @@ public class ContractConnection(IMessageServiceConnection serviceConnection, private readonly SemaphoreSlim dataLock = new(1, 1); private IEnumerable typeFactories = []; private bool disposedValue; - private readonly SubscriptionCollection subscriptions = new(); private IMessageFactory GetMessageFactory(bool ignoreMessageHeader = false) where T : class { @@ -84,7 +83,7 @@ private async ValueTask ProduceServiceMessage(ChannelMapper.M => await GetMessageFactory().ConvertMessageAsync(message, channel, messageHeader,(originalChannel)=>MapChannel(mapType,originalChannel)); /// - /// Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging + /// Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages asynchronously /// /// The type of message to listen for /// The callback to be executed when a message is recieved @@ -92,28 +91,48 @@ private async ValueTask ProduceServiceMessage(ChannelMapper.M /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// Set true if the desire the messageRecieved callback to be called such that it waits for the call to complete prior to calling for the next message /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public async ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class + public ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class + => SubscribeAsync(messageRecieved, errorRecieved, channel, group, ignoreMessageHeader, false, options, cancellationToken); + + /// + /// Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages synchronously + /// + /// The type of message to listen for + /// The callback to be executed when a message is recieved + /// The callback to be executed when an error occurs + /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on + /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) + /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T + /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary + /// A cancellation token + /// An instance of the Subscription that can be held or called to end + /// An exception thrown when the subscription has failed to establish + public ValueTask SubscribeAsync(Action> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class + => SubscribeAsync((msg) => + { + messageRecieved(msg); + return ValueTask.CompletedTask; + }, + errorRecieved, channel, group, ignoreMessageHeader, true, options, cancellationToken); + + private async ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel, string? group, bool ignoreMessageHeader,bool synchronous, IServiceChannelOptions? options, CancellationToken cancellationToken) + where T : class { var subscription = new PubSubSubscription(GetMessageFactory(ignoreMessageHeader), messageRecieved, errorRecieved, - (originalChannel)=>MapChannel(ChannelMapper.MapTypes.PublishSubscription,originalChannel), - subscriptions, - channel:channel, - group:group, - synchronous:synchronous, - options:options, - logger:logger); + (originalChannel) => MapChannel(ChannelMapper.MapTypes.PublishSubscription, originalChannel), + channel: channel, + group: group, + synchronous: synchronous, + options: options, + logger: logger); if (await subscription.EstablishSubscriptionAsync(serviceConnection, cancellationToken)) - { - await subscriptions.AddAsync(subscription); return subscription; - } throw new SubscriptionFailedException(); } @@ -207,7 +226,26 @@ private async ValueTask> ProduceResultAsync(ServiceQueryResult } /// - /// Creates a subscription with the underlying service layer for the Query/Response style + /// Creates a subscription with the underlying service layer for the Query/Response style processing messages asynchronously + /// + /// The expected message type for the Query + /// The expected message type for the Response + /// The callback to be executed when a message is recieved and expects a returned response + /// The callback to be executed when an error occurs + /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on + /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) + /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T + /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary + /// A cancellation token + /// An instance of the Subscription that can be held or called to end + /// An exception thrown when the subscription has failed to establish + public ValueTask SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + where Q : class + where R : class + => SubscribeQueryResponseAsync(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,false,options,cancellationToken); + + /// + /// Creates a subscription with the underlying service layer for the Query/Response style processing messages synchronously /// /// The expected message type for the Query /// The expected message type for the Response @@ -216,49 +254,84 @@ private async ValueTask> ProduceResultAsync(ServiceQueryResult /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// Set true if the desire the messageRecieved callback to be called such that it waits for the call to complete prior to calling for the next message /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public async ValueTask SubscribeQueryResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, bool synchronous = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + where Q : class + where R : class + => SubscribeQueryResponseAsync((msg) => + { + var result = messageRecieved(msg); + return ValueTask.FromResult(result); + }, errorRecieved, channel, group, ignoreMessageHeader, true, options, cancellationToken); + + private async ValueTask SubscribeQueryResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, IServiceChannelOptions? options, CancellationToken cancellationToken) where Q : class where R : class { - var subscription = new QueryResponseSubscription( + var subscription = new QueryResponseSubscription( GetMessageFactory(ignoreMessageHeader), GetMessageFactory(), messageRecieved, errorRecieved, (originalChannel) => MapChannel(ChannelMapper.MapTypes.QuerySubscription, originalChannel), - subscriptions, channel: channel, group: group, synchronous: synchronous, options: options, logger: logger); if (await subscription.EstablishSubscriptionAsync(serviceConnection, cancellationToken)) - { - await subscriptions.AddAsync(subscription); return subscription; - } throw new SubscriptionFailedException(); } /// - /// Called to dispose of the object correctly and allow it to clean up it's resources + /// Called to close off this connection and it's underlying service connection /// - /// A task required for disposal + /// + public ValueTask CloseAsync() + => serviceConnection?.CloseAsync()??ValueTask.CompletedTask; - public async ValueTask DisposeAsync() + private void Dispose(bool disposing) { if (!disposedValue) { + if (disposing) + { + if (serviceConnection is IDisposable disposable) + disposable.Dispose(); + else if (serviceConnection is IAsyncDisposable asyncDisposable) + asyncDisposable.DisposeAsync().AsTask().Wait(); + } disposedValue=true; - await subscriptions.DisposeAsync(); - await serviceConnection.DisposeAsync(); - GC.SuppressFinalize(this); } } + + /// + /// Called to dispose of the resources contained within + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Called to dispose of the resources contained within + /// + /// A task of the underlying resources being disposed + public async ValueTask DisposeAsync() + { + if (serviceConnection is IAsyncDisposable asyncDisposable) + await asyncDisposable.DisposeAsync().ConfigureAwait(true); + else if (serviceConnection is IDisposable disposable) + disposable.Dispose(); + + Dispose(false); + GC.SuppressFinalize(this); + } } } diff --git a/Core/Interfaces/Subscriptions/IInternalSubscription.cs b/Core/Interfaces/Subscriptions/IInternalSubscription.cs deleted file mode 100644 index 32fab8f..0000000 --- a/Core/Interfaces/Subscriptions/IInternalSubscription.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MQContract.Interfaces.Subscriptions -{ - internal interface IInternalSubscription : ISubscription - { - Guid ID { get; } - - ValueTask EndAsync(bool remove); - } -} diff --git a/Core/Readme.md b/Core/Readme.md index 3b0b276..15fb2fd 100644 --- a/Core/Readme.md +++ b/Core/Readme.md @@ -22,13 +22,17 @@ - [AddQuerySubscriptionMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [ContractConnection](#T-MQContract-ContractConnection 'MQContract.ContractConnection') - [#ctor(serviceConnection,defaultMessageEncoder,defaultMessageEncryptor,serviceProvider,logger,channelMapper)](#M-MQContract-ContractConnection-#ctor-MQContract-Interfaces-Service-IMessageServiceConnection,MQContract-Interfaces-Encoding-IMessageEncoder,MQContract-Interfaces-Encrypting-IMessageEncryptor,System-IServiceProvider,Microsoft-Extensions-Logging-ILogger,MQContract-ChannelMapper- 'MQContract.ContractConnection.#ctor(MQContract.Interfaces.Service.IMessageServiceConnection,MQContract.Interfaces.Encoding.IMessageEncoder,MQContract.Interfaces.Encrypting.IMessageEncryptor,System.IServiceProvider,Microsoft.Extensions.Logging.ILogger,MQContract.ChannelMapper)') + - [CloseAsync()](#M-MQContract-ContractConnection-CloseAsync 'MQContract.ContractConnection.CloseAsync') + - [Dispose()](#M-MQContract-ContractConnection-Dispose 'MQContract.ContractConnection.Dispose') - [DisposeAsync()](#M-MQContract-ContractConnection-DisposeAsync 'MQContract.ContractConnection.DisposeAsync') - [PingAsync()](#M-MQContract-ContractConnection-PingAsync 'MQContract.ContractConnection.PingAsync') - [PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync\`\`1(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [QueryAsync\`\`2(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [MessageChannelNullException](#T-MQContract-MessageChannelNullException 'MQContract.MessageChannelNullException') - [MessageConversionException](#T-MQContract-MessageConversionException 'MQContract.MessageConversionException') - [QueryResponseException](#T-MQContract-QueryResponseException 'MQContract.QueryResponseException') @@ -366,16 +370,42 @@ This is the primary class for this library and is used to create a Contract styl | channelMapper | [MQContract.ChannelMapper](#T-MQContract-ChannelMapper 'MQContract.ChannelMapper') | An instance of a ChannelMapper used to translate channels from one instance to another based on class channel attributes or supplied channels if necessary. For example, it might be necessary for a Nats.IO instance when you are trying to read from a stored message stream that is comprised of another channel or set of channels | + +### CloseAsync() `method` + +##### Summary + +Called to close off this connection and it's underlying service connection + +##### Returns + + + +##### Parameters + +This method has no parameters. + + +### Dispose() `method` + +##### Summary + +Called to dispose of the resources contained within + +##### Parameters + +This method has no parameters. + ### DisposeAsync() `method` ##### Summary -Called to dispose of the object correctly and allow it to clean up it's resources +Called to dispose of the resources contained within ##### Returns -A task required for disposal +A task of the underlying resources being disposed ##### Parameters @@ -486,12 +516,12 @@ A QueryResult that will contain the response message and or an error | Q | The type of message to transmit for the Query | | R | The type of message expected as a response | - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` ##### Summary -Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging +Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages asynchronously ##### Returns @@ -506,7 +536,6 @@ An instance of the Subscription that can be held or called to end | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| synchronous | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Set true if the desire the messageRecieved callback to be called such that it waits for the call to complete prior to calling for the next message | | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -522,12 +551,47 @@ An instance of the Subscription that can be held or called to end | ---- | ----------- | | [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - -### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,synchronous,options,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` ##### Summary -Creates a subscription with the underlying service layer for the Query/Response style +Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages synchronously + +##### Returns + +An instance of the Subscription that can be held or called to end + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Action{MQContract.Interfaces.IRecievedMessage{\`\`0}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Interfaces.IRecievedMessage{``0}}') | The callback to be executed when a message is recieved | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to be executed when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | +| ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | The type of message to listen for | + +##### Exceptions + +| Name | Description | +| ---- | ----------- | +| [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | + + +### SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +##### Summary + +Creates a subscription with the underlying service layer for the Query/Response style processing messages asynchronously ##### Returns @@ -542,7 +606,42 @@ An instance of the Subscription that can be held or called to end | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| synchronous | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Set true if the desire the messageRecieved callback to be called such that it waits for the call to complete prior to calling for the next message | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| Q | The expected message type for the Query | +| R | The expected message type for the Response | + +##### Exceptions + +| Name | Description | +| ---- | ----------- | +| [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | + + +### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +##### Summary + +Creates a subscription with the underlying service layer for the Query/Response style processing messages synchronously + +##### Returns + +An instance of the Subscription that can be held or called to end + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},MQContract.Messages.QueryResponseMessage{\`\`1}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}}') | The callback to be executed when a message is recieved and expects a returned response | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to be executed when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | +| ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Core/SubscriptionCollection.cs b/Core/SubscriptionCollection.cs deleted file mode 100644 index 8de3709..0000000 --- a/Core/SubscriptionCollection.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MQContract.Interfaces; -using MQContract.Interfaces.Subscriptions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MQContract -{ - internal class SubscriptionCollection - : IAsyncDisposable - { - private readonly SemaphoreSlim dataLock = new(1, 1); - private readonly List subscriptions = []; - private bool disposedValue; - - public async Task AddAsync(IInternalSubscription subscription) - { - await dataLock.WaitAsync(); - subscriptions.Add(subscription); - dataLock.Release(); - } - - public async ValueTask DisposeAsync() - { - if (!disposedValue) - { - disposedValue = true; - await dataLock.WaitAsync(); - await Task.WhenAll(subscriptions.Select(sub => sub.EndAsync(false).AsTask())); - dataLock.Release(); - dataLock.Dispose(); - subscriptions.Clear(); - } - } - - public async Task RemoveAsync(Guid ID) - { - await dataLock.WaitAsync(); - subscriptions.RemoveAll(sub=>Equals(sub.ID, ID)); - dataLock.Release(); - } - } -} diff --git a/Core/Subscriptions/PubSubSubscription.cs b/Core/Subscriptions/PubSubSubscription.cs index c1816a9..57199f7 100644 --- a/Core/Subscriptions/PubSubSubscription.cs +++ b/Core/Subscriptions/PubSubSubscription.cs @@ -8,63 +8,40 @@ namespace MQContract.Subscriptions { internal sealed class PubSubSubscription(IMessageFactory messageFactory, Func, ValueTask> messageRecieved, Action errorRecieved, - Func> mapChannel, SubscriptionCollection collection, + Func> mapChannel, string? channel = null, string? group = null, bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) - : SubscriptionBase(mapChannel,collection,channel,synchronous) + : SubscriptionBase(mapChannel,channel,synchronous) where T : class { - private readonly Channel dataChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() + public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnection connection,CancellationToken cancellationToken) { - SingleReader=true, - SingleWriter=true - }); - - public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnection connection, CancellationToken cancellationToken = new CancellationToken()) - { - SyncToken(cancellationToken); serviceSubscription = await connection.SubscribeAsync( - async serviceMessage=>await dataChannel.Writer.WriteAsync(serviceMessage,token.Token), + async serviceMessage=> await ProcessMessage(serviceMessage), error=>errorRecieved(error), MessageChannel, group??Guid.NewGuid().ToString(), options:options, - cancellationToken:token.Token + cancellationToken:cancellationToken ); if (serviceSubscription==null) return false; - EstablishReader(); return true; } - private void EstablishReader() + private async ValueTask ProcessMessage(RecievedServiceMessage serviceMessage) { - Task.Run(async () => + try { - while (await dataChannel.Reader.WaitToReadAsync(token.Token)) - { - while (dataChannel.Reader.TryRead(out var message)) - { - var tsk = Task.Run(async () => - { - try - { - var taskMessage = await messageFactory.ConvertMessageAsync(logger, message) - ??throw new InvalidCastException($"Unable to convert incoming message {message.MessageTypeID} to {typeof(T).FullName}"); - await messageRecieved(new RecievedMessage(message.ID,taskMessage!,message.Header,message.RecievedTimestamp,DateTime.Now)); - } - catch (Exception e) - { - errorRecieved(e); - } - }); - if (Synchronous) - await tsk; - } - } - }); + var taskMessage = await messageFactory.ConvertMessageAsync(logger, serviceMessage) + ??throw new InvalidCastException($"Unable to convert incoming message {serviceMessage.MessageTypeID} to {typeof(T).FullName}"); + var tsk = messageRecieved(new RecievedMessage(serviceMessage.ID, taskMessage!, serviceMessage.Header, serviceMessage.RecievedTimestamp, DateTime.Now)); + if (Synchronous) + await tsk.ConfigureAwait(false); + } + catch (Exception e) + { + errorRecieved(e); + } } - - protected override void InternalDispose() - =>dataChannel.Writer.Complete(); } } diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index f208ac9..d6b81ac 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -8,33 +8,33 @@ namespace MQContract.Subscriptions { internal sealed class QueryResponseSubscription(IMessageFactory queryMessageFactory,IMessageFactory responseMessageFactory, Func, ValueTask>> messageRecieved, Action errorRecieved, - Func> mapChannel, SubscriptionCollection collection, + Func> mapChannel, string? channel = null, string? group = null, bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) - : SubscriptionBase(mapChannel,collection,channel,synchronous) + : SubscriptionBase(mapChannel,channel,synchronous) where Q : class where R : class { - private readonly ManualResetEventSlim manualResetEvent = new(true); + private ManualResetEventSlim? manualResetEvent = new(true); + private CancellationTokenSource? token = new(); - public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnection connection, CancellationToken cancellationToken = new CancellationToken()) + public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnection connection, CancellationToken cancellationToken) { - SyncToken(cancellationToken); serviceSubscription = await connection.SubscribeQueryAsync( serviceMessage => ProcessServiceMessageAsync(serviceMessage), error => errorRecieved(error), MessageChannel, group??Guid.NewGuid().ToString(), options: options, - cancellationToken: token.Token + cancellationToken: cancellationToken ); return serviceSubscription!=null; } private async ValueTask ProcessServiceMessageAsync(RecievedServiceMessage message) { - if (Synchronous) - manualResetEvent.Wait(cancellationToken:token.Token); + if (Synchronous&&!(token?.IsCancellationRequested??false)) + manualResetEvent!.Wait(cancellationToken:token!.Token); Exception? error = null; ServiceMessage? response = null; try @@ -49,13 +49,22 @@ private async ValueTask ProcessServiceMessageAsync(RecievedServi error=e; } if (Synchronous) - manualResetEvent.Set(); + manualResetEvent!.Set(); if (error!=null) return ErrorServiceMessage.Produce(message.Channel,error); return response??ErrorServiceMessage.Produce(message.Channel, new NullReferenceException()); } protected override void InternalDispose() - =>manualResetEvent.Dispose(); + { + if (token!=null) + { + token.Cancel(); + manualResetEvent?.Dispose(); + token.Dispose(); + token=null; + manualResetEvent=null; + } + } } } diff --git a/Core/Subscriptions/SubscriptionBase.cs b/Core/Subscriptions/SubscriptionBase.cs index f4ae5a4..d9d70cf 100644 --- a/Core/Subscriptions/SubscriptionBase.cs +++ b/Core/Subscriptions/SubscriptionBase.cs @@ -1,27 +1,24 @@ using MQContract.Attributes; using MQContract.Interfaces; using MQContract.Interfaces.Service; -using MQContract.Interfaces.Subscriptions; using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace MQContract.Subscriptions { - internal abstract class SubscriptionBase : IInternalSubscription + internal abstract class SubscriptionBase : ISubscription where T : class { protected IServiceSubscription? serviceSubscription; - private readonly SubscriptionCollection collection; - protected readonly CancellationTokenSource token = new(); private bool disposedValue; + protected string MessageChannel { get; private init; } protected bool Synchronous { get; private init; } public Guid ID { get; private init; } - protected SubscriptionBase(Func> mapChannel, SubscriptionCollection collection, string? channel=null,bool synchronous = false){ + protected SubscriptionBase(Func> mapChannel, string? channel=null,bool synchronous = false){ ID = Guid.NewGuid(); - this.collection=collection; var chan = channel??typeof(T).GetCustomAttribute(false)?.Name??throw new MessageChannelNullException(); Synchronous = synchronous; var tsk = mapChannel(chan).AsTask(); @@ -29,36 +26,48 @@ protected SubscriptionBase(Func> mapChannel, Subscript MessageChannel=tsk.Result; } - protected void SyncToken(CancellationToken cancellationToken) - => cancellationToken.Register(async () => await EndAsync()); - - [ExcludeFromCodeCoverage(Justification ="Virtual function that is implemented elsewhere")] + [ExcludeFromCodeCoverage(Justification = "Virtual function that is implemented elsewhere")] protected virtual void InternalDispose() - { } + { } - public async ValueTask EndAsync(bool remove) + public async ValueTask EndAsync() { if (serviceSubscription!=null) + { + System.Diagnostics.Debug.WriteLine("Calling subscription end async..."); await serviceSubscription.EndAsync(); - await token.CancelAsync(); - if (remove) - await collection.RemoveAsync(ID); + System.Diagnostics.Debug.WriteLine("Subscription ended async"); + serviceSubscription=null; + } } - public ValueTask EndAsync() - => EndAsync(true); - - public async ValueTask DisposeAsync() + protected virtual void Dispose(bool disposing) { if (!disposedValue) { - disposedValue=true; - await EndAsync(); + if (disposing && serviceSubscription is IDisposable disposable) + disposable.Dispose(); InternalDispose(); - if (serviceSubscription!=null) - await serviceSubscription!.EndAsync(); - token.Dispose(); + disposedValue=true; } } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + public async ValueTask DisposeAsync() + { + if (serviceSubscription is IAsyncDisposable asyncDisposable) + await asyncDisposable.DisposeAsync().ConfigureAwait(true); + else if (serviceSubscription is IDisposable disposable) + disposable.Dispose(); + + Dispose(false); + GC.SuppressFinalize(this); + } } } diff --git a/Samples/KafkaSample/Program.cs b/Samples/KafkaSample/Program.cs index f41326f..8085a9e 100644 --- a/Samples/KafkaSample/Program.cs +++ b/Samples/KafkaSample/Program.cs @@ -18,7 +18,7 @@ var contractConnection = new ContractConnection(serviceConnection); -await using var arrivalSubscription = await contractConnection.SubscribeAsync( +var announcementSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); @@ -28,20 +28,20 @@ cancellationToken: sourceCancel.Token ); -await using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( +var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( (greeting) => { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return ValueTask.FromResult>( - new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the Kafka sample") + return new( + $"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the Kafka sample" ); }, (error) => Console.WriteLine($"Greeting error: {error.Message}"), cancellationToken: sourceCancel.Token ); -await using var storedArrivalSubscription = await contractConnection.SubscribeAsync( +var storedArrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); @@ -51,6 +51,16 @@ cancellationToken: sourceCancel.Token ); +sourceCancel.Token.Register(async () => +{ + await Task.WhenAll( + announcementSubscription.EndAsync().AsTask(), + greetingSubscription.EndAsync().AsTask(), + storedArrivalSubscription.EndAsync().AsTask() + ).ConfigureAwait(true); + await contractConnection.CloseAsync().ConfigureAwait(true); +}, true); + var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); diff --git a/Samples/KubeMQSample/Program.cs b/Samples/KubeMQSample/Program.cs index e491942..18f6a05 100644 --- a/Samples/KubeMQSample/Program.cs +++ b/Samples/KubeMQSample/Program.cs @@ -18,7 +18,7 @@ var contractConnection = new ContractConnection(serviceConnection); -await using var arrivalSubscription = await contractConnection.SubscribeAsync( +var announcementSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); @@ -28,20 +28,18 @@ cancellationToken: sourceCancel.Token ); -await using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( +var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( (greeting) => { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return ValueTask.FromResult>( - new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the KubeMQ sample") - ); + return new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the KubeMQ sample"); }, (error) => Console.WriteLine($"Greeting error: {error.Message}"), cancellationToken: sourceCancel.Token ); -await using var storedArrivalSubscription = await contractConnection.SubscribeAsync( +var storedArrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); @@ -52,6 +50,16 @@ cancellationToken: sourceCancel.Token ); +sourceCancel.Token.Register(async () => +{ + await Task.WhenAll( + announcementSubscription.EndAsync().AsTask(), + greetingSubscription.EndAsync().AsTask(), + storedArrivalSubscription.EndAsync().AsTask() + ).ConfigureAwait(true); + await contractConnection.CloseAsync().ConfigureAwait(true); +}, true); + var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken:sourceCancel.Token); Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); diff --git a/Samples/NATSSample/Program.cs b/Samples/NATSSample/Program.cs index 6d95126..375bf90 100644 --- a/Samples/NATSSample/Program.cs +++ b/Samples/NATSSample/Program.cs @@ -11,7 +11,7 @@ sourceCancel.Cancel(); }; -await using var serviceConnection = new Connection(new NATS.Client.Core.NatsOpts() +var serviceConnection = new Connection(new NATS.Client.Core.NatsOpts() { LoggerFactory=new Microsoft.Extensions.Logging.LoggerFactory(), Name="NATSSample" @@ -25,7 +25,7 @@ var contractConnection = new ContractConnection(serviceConnection,channelMapper:mapper); -await using var arrivalSubscription = await contractConnection.SubscribeAsync( +var announcementSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); @@ -35,30 +35,38 @@ cancellationToken: sourceCancel.Token ); -await using var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( +var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( (greeting) => { Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return ValueTask.FromResult>( - new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the NATSio sample") - ); + return new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the NATSio sample"); }, (error) => Console.WriteLine($"Greeting error: {error.Message}"), cancellationToken: sourceCancel.Token ); -await using var storedArrivalSubscription = await contractConnection.SubscribeAsync( +var storedArrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), - options:new StreamPublishSubscriberOptions(), + options: new StreamPublishSubscriberOptions(), cancellationToken: sourceCancel.Token ); +sourceCancel.Token.Register(async () => +{ + await Task.WhenAll( + announcementSubscription.EndAsync().AsTask(), + greetingSubscription.EndAsync().AsTask(), + storedArrivalSubscription.EndAsync().AsTask() + ).ConfigureAwait(true); + await contractConnection.CloseAsync().ConfigureAwait(true); +}, true); + var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); From fe784f8ad6d0499a3b5379b8ab7172b813eca2d1 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Wed, 4 Sep 2024 15:11:36 -0400 Subject: [PATCH 06/22] adding unit test pr adding in unit test pr call --- .github/workflows/unit-test-report.yml | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/unit-test-report.yml diff --git a/.github/workflows/unit-test-report.yml b/.github/workflows/unit-test-report.yml new file mode 100644 index 0000000..c8a4b72 --- /dev/null +++ b/.github/workflows/unit-test-report.yml @@ -0,0 +1,36 @@ +name: "Dot Net Test Reporter" + +on: + pull_request_target: + types: [ opened, synchronize ] + +permissions: + pull-requests: write + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: | + 8.0.x + - name: Restore dependencies + run: dotnet restore -p:TargetFramework=net8.0 AutomatedTesting + - name: Build-8.0 + run: dotnet build --framework net8.0 --no-restore AutomatedTesting + - name: Test-8.0 + run: dotnet test --framework net8.0 --no-build --verbosity normal AutomatedTesting + - name: report results + uses: bibipkins/dotnet-test-reporter@v1.4.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + comment-title: 'Unit Test Results' + results-path: ./**/*.trx + coverage-path: ./**/coverage.xml + coverage-threshold: 90 \ No newline at end of file From 3419479b73774eb9a31395de90bbed03ab2685e3 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Wed, 4 Sep 2024 15:13:22 -0400 Subject: [PATCH 07/22] migrated query response migrated query response concepts up to the contract connection level where underlying connections do not support that messaging style and adjusted/created/implemented new interfaces accordingly. --- .../QueryResponseChannelAttribute.cs | 16 + .../Interfaces/IContractConnection.cs | 8 +- .../Service/IMessageServiceConnection.cs | 25 - .../IPingableMessageServiceConnection.cs | 16 + .../IQueryableMessageServiceConnection.cs | 31 ++ Abstractions/Messages/MessageHeader.cs | 4 +- Abstractions/Readme.md | 202 +++++--- AutomatedTesting/ChannelMapperTests.cs | 394 +++++++++++++++- .../ContractConnectionTests/CleanupTests.cs | 5 - .../ContractConnectionTests/PingTests.cs | 2 +- .../ContractConnectionTests/QueryTests.cs | 38 +- .../QueryWithoutQueryResponseTests.cs | 439 ++++++++++++++++++ .../SubscribeQueryResponseTests.cs | 32 +- ...beQueryResponseWithoutQueryResponseTest.cs | 94 ++++ .../Messages/BasicQueryMessage.cs | 1 + .../Messages/MessageHeaderTests.cs | 17 +- Connectors/ActiveMQ/Connection.cs | 55 +-- Connectors/ActiveMQ/Exceptions.cs | 18 + .../Subscriptions/PublishSubscription.cs | 52 --- .../Subscriptions/SubscriptionBase.cs | 53 +++ Connectors/Kafka/Connection.cs | 196 +------- .../Kafka/Options/QueryChannelOptions.cs | 12 - Connectors/Kafka/Readme.md | 123 ----- .../Subscriptions/PublishSubscription.cs | 5 +- .../Kafka/Subscriptions/QuerySubscription.cs | 26 -- .../Kafka/Subscriptions/SubscriptionBase.cs | 8 +- Connectors/KubeMQ/Connection.cs | 2 +- Connectors/NATS/Connection.cs | 2 +- Core/ChannelMapper.cs | 39 +- Core/ContractConnection.cs | 66 ++- Core/Exceptions.cs | 27 ++ Core/Readme.md | 127 ++++- Core/Subscriptions/PubSubSubscription.cs | 1 - Core/Subscriptions/QueryResponseHelper.cs | 78 ++++ .../QueryResponseSubscription.cs | 45 +- Samples/KafkaSample/Program.cs | 6 +- Samples/KubeMQSample/Program.cs | 1 - Samples/NATSSample/Program.cs | 1 - 38 files changed, 1621 insertions(+), 646 deletions(-) create mode 100644 Abstractions/Attributes/QueryResponseChannelAttribute.cs create mode 100644 Abstractions/Interfaces/Service/IPingableMessageServiceConnection.cs create mode 100644 Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs create mode 100644 AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs create mode 100644 AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs create mode 100644 Connectors/ActiveMQ/Exceptions.cs delete mode 100644 Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs create mode 100644 Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs delete mode 100644 Connectors/Kafka/Options/QueryChannelOptions.cs delete mode 100644 Connectors/Kafka/Subscriptions/QuerySubscription.cs create mode 100644 Core/Subscriptions/QueryResponseHelper.cs diff --git a/Abstractions/Attributes/QueryResponseChannelAttribute.cs b/Abstractions/Attributes/QueryResponseChannelAttribute.cs new file mode 100644 index 0000000..18db2e7 --- /dev/null +++ b/Abstractions/Attributes/QueryResponseChannelAttribute.cs @@ -0,0 +1,16 @@ +namespace MQContract.Attributes +{ + /// + /// Used to allow the specification of a response channel to be used without supplying it to the contract calls. + /// IMPORTANT: This particular attribute and the response channel argument are only used when the underlying connection does not support QueryResponse messaging. + /// + /// The name of the channel to use for responses + [AttributeUsage(AttributeTargets.Class)] + public class QueryResponseChannelAttribute(string name) : Attribute + { + /// + /// The Name of the response channel + /// + public string Name => name; + } +} diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index 192c41e..e828bde 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -61,11 +61,13 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// The message to send /// The allowed timeout prior to a response being recieved /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. + /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is + /// only used when the underlying connection does not support a QueryResponse style messaging. /// The headers to pass along with the message /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A result indicating the success or failure as well as the returned message - ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel=null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; /// @@ -76,11 +78,13 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// The message to send /// The allowed timeout prior to a response being recieved /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. + /// /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is + /// only used when the underlying connection does not support a QueryResponse style messaging. /// The headers to pass along with the message /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token /// A result indicating the success or failure as well as the returned message - ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null,string? responseChannel=null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class; /// /// Called to create a subscription into the underlying service Query/Reponse style and have the messages processed asynchronously diff --git a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs index 7546f54..01e97d9 100644 --- a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs @@ -17,11 +17,6 @@ public interface IMessageServiceConnection /// TimeSpan DefaultTimout { get; } /// - /// Implemented Ping call if avaialble for the underlying service - /// - /// A Ping Result - ValueTask PingAsync(); - /// /// Implements a publish call to publish the given message /// /// The message to publish @@ -41,26 +36,6 @@ public interface IMessageServiceConnection /// A service subscription object ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); /// - /// Implements a call to submit a response query request into the underlying service - /// - /// The message to query with - /// The timeout for recieving a response - /// The Service Channel Options instance that was supplied at the Contract Connection level - /// A cancellation token - /// A Query Result instance based on what happened - ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); - /// - /// Implements a call to create a subscription to a given channel as a member of a given group for responding to queries - /// - /// The callback to be invoked when a message is recieved, returning the response message - /// The callback to invoke when an exception occurs - /// The name of the channel to subscribe to - /// The subscription groupt to subscribe as - /// The Service Channel Options instance that was supplied at the Contract Connection level - /// A cancellation token - /// A service subscription object - ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); - /// /// Implements a call to close off the connection when the ContractConnection is closed /// /// A task that the close is running in diff --git a/Abstractions/Interfaces/Service/IPingableMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IPingableMessageServiceConnection.cs new file mode 100644 index 0000000..4dee87c --- /dev/null +++ b/Abstractions/Interfaces/Service/IPingableMessageServiceConnection.cs @@ -0,0 +1,16 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Service +{ + /// + /// Extends the base MessageServiceConnection Interface to support service pinging + /// + public interface IPingableMessageServiceConnection : IMessageServiceConnection + { + /// + /// Implemented Ping call if avaialble for the underlying service + /// + /// A Ping Result + ValueTask PingAsync(); + } +} diff --git a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs new file mode 100644 index 0000000..925c081 --- /dev/null +++ b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs @@ -0,0 +1,31 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Service +{ + /// + /// Extends the base MessageServiceConnection Interface to Response Query messaging methodology if the underlying service supports it + /// + public interface IQueryableMessageServiceConnection : IMessageServiceConnection + { + /// + /// Implements a call to submit a response query request into the underlying service + /// + /// The message to query with + /// The timeout for recieving a response + /// The Service Channel Options instance that was supplied at the Contract Connection level + /// A cancellation token + /// A Query Result instance based on what happened + ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + /// + /// Implements a call to create a subscription to a given channel as a member of a given group for responding to queries + /// + /// The callback to be invoked when a message is recieved, returning the response message + /// The callback to invoke when an exception occurs + /// The name of the channel to subscribe to + /// The subscription groupt to subscribe as + /// The Service Channel Options instance that was supplied at the Contract Connection level + /// A cancellation token + /// A service subscription object + ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + } +} diff --git a/Abstractions/Messages/MessageHeader.cs b/Abstractions/Messages/MessageHeader.cs index 1d08e98..feedade 100644 --- a/Abstractions/Messages/MessageHeader.cs +++ b/Abstractions/Messages/MessageHeader.cs @@ -20,7 +20,9 @@ public MessageHeader(Dictionary? headers) /// The additional properties to add public MessageHeader(MessageHeader? originalHeader, Dictionary? appendedHeader) : this( - (appendedHeader?.AsEnumerable().Select(pair => new KeyValuePair(pair.Key, pair.Value??string.Empty))?? []) + (appendedHeader?.AsEnumerable() + .Where(pair=>pair.Value!=null) + .Select(pair => new KeyValuePair(pair.Key, pair.Value!))?? []) .Concat(originalHeader?.Keys .Where(k => !(appendedHeader?? []).Any(pair=>Equals(k,pair.Key))) .Select(k => new KeyValuePair(k, originalHeader?[k]!))?? []) diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index fad19a2..2d35140 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -7,8 +7,8 @@ - [CloseAsync()](#M-MQContract-Interfaces-IContractConnection-CloseAsync 'MQContract.Interfaces.IContractConnection.CloseAsync') - [PingAsync()](#M-MQContract-Interfaces-IContractConnection-PingAsync 'MQContract.Interfaces.IContractConnection.PingAsync') - [PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync\`\`1(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync\`\`2(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') @@ -29,15 +29,17 @@ - [DefaultTimout](#P-MQContract-Interfaces-Service-IMessageServiceConnection-DefaultTimout 'MQContract.Interfaces.Service.IMessageServiceConnection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Interfaces-Service-IMessageServiceConnection-MaxMessageBodySize 'MQContract.Interfaces.Service.IMessageServiceConnection.MaxMessageBodySize') - [CloseAsync()](#M-MQContract-Interfaces-Service-IMessageServiceConnection-CloseAsync 'MQContract.Interfaces.Service.IMessageServiceConnection.CloseAsync') - - [PingAsync()](#M-MQContract-Interfaces-Service-IMessageServiceConnection-PingAsync 'MQContract.Interfaces.Service.IMessageServiceConnection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [IMessageTypeEncoder\`1](#T-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1') - [DecodeAsync(stream)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-DecodeAsync-System-IO-Stream- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.DecodeAsync(System.IO.Stream)') - [EncodeAsync(message)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-EncodeAsync-`0- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.EncodeAsync(`0)') - [IMessageTypeEncryptor\`1](#T-MQContract-Interfaces-Encrypting-IMessageTypeEncryptor`1 'MQContract.Interfaces.Encrypting.IMessageTypeEncryptor`1') +- [IPingableMessageServiceConnection](#T-MQContract-Interfaces-Service-IPingableMessageServiceConnection 'MQContract.Interfaces.Service.IPingableMessageServiceConnection') + - [PingAsync()](#M-MQContract-Interfaces-Service-IPingableMessageServiceConnection-PingAsync 'MQContract.Interfaces.Service.IPingableMessageServiceConnection.PingAsync') +- [IQueryableMessageServiceConnection](#T-MQContract-Interfaces-Service-IQueryableMessageServiceConnection 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection') + - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [IRecievedMessage\`1](#T-MQContract-Interfaces-IRecievedMessage`1 'MQContract.Interfaces.IRecievedMessage`1') - [Headers](#P-MQContract-Interfaces-IRecievedMessage`1-Headers 'MQContract.Interfaces.IRecievedMessage`1.Headers') - [ID](#P-MQContract-Interfaces-IRecievedMessage`1-ID 'MQContract.Interfaces.IRecievedMessage`1.ID') @@ -77,6 +79,9 @@ - [Host](#P-MQContract-Messages-PingResult-Host 'MQContract.Messages.PingResult.Host') - [ResponseTime](#P-MQContract-Messages-PingResult-ResponseTime 'MQContract.Messages.PingResult.ResponseTime') - [Version](#P-MQContract-Messages-PingResult-Version 'MQContract.Messages.PingResult.Version') +- [QueryResponseChannelAttribute](#T-MQContract-Attributes-QueryResponseChannelAttribute 'MQContract.Attributes.QueryResponseChannelAttribute') + - [#ctor(name)](#M-MQContract-Attributes-QueryResponseChannelAttribute-#ctor-System-String- 'MQContract.Attributes.QueryResponseChannelAttribute.#ctor(System.String)') + - [Name](#P-MQContract-Attributes-QueryResponseChannelAttribute-Name 'MQContract.Attributes.QueryResponseChannelAttribute.Name') - [QueryResponseMessage\`1](#T-MQContract-Messages-QueryResponseMessage`1 'MQContract.Messages.QueryResponseMessage`1') - [#ctor(Message,Headers)](#M-MQContract-Messages-QueryResponseMessage`1-#ctor-`0,System-Collections-Generic-Dictionary{System-String,System-String}- 'MQContract.Messages.QueryResponseMessage`1.#ctor(`0,System.Collections.Generic.Dictionary{System.String,System.String})') - [Headers](#P-MQContract-Messages-QueryResponseMessage`1-Headers 'MQContract.Messages.QueryResponseMessage`1.Headers') @@ -178,8 +183,8 @@ A result indicating the tranmission results | ---- | ----------- | | T | The type of message to send | - -### QueryAsync\`\`1(message,timeout,channel,messageHeader,options,cancellationToken) `method` + +### QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken) `method` ##### Summary @@ -197,6 +202,8 @@ A result indicating the success or failure as well as the returned message | message | [\`\`0](#T-``0 '``0') | The message to send | | timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The allowed timeout prior to a response being recieved | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | +| responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is +only used when the underlying connection does not support a QueryResponse style messaging. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The headers to pass along with the message | | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -207,8 +214,8 @@ A result indicating the success or failure as well as the returned message | ---- | ----------- | | Q | The type of message to send for the query | - -### QueryAsync\`\`2(message,timeout,channel,messageHeader,options,cancellationToken) `method` + +### QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken) `method` ##### Summary @@ -225,6 +232,8 @@ A result indicating the success or failure as well as the returned message | message | [\`\`0](#T-``0 '``0') | The message to send | | timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The allowed timeout prior to a response being recieved | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | +| responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is +only used when the underlying connection does not support a QueryResponse style messaging. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The headers to pass along with the message | | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -572,21 +581,6 @@ A task that the close is running in This method has no parameters. - -### PingAsync() `method` - -##### Summary - -Implemented Ping call if avaialble for the underlying service - -##### Returns - -A Ping Result - -##### Parameters - -This method has no parameters. - ### PublishAsync(message,options,cancellationToken) `method` @@ -606,26 +600,6 @@ A transmission result instance indicating the result | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -### QueryAsync(message,timeout,options,cancellationToken) `method` - -##### Summary - -Implements a call to submit a response query request into the underlying service - -##### Returns - -A Query Result instance based on what happened - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The message to query with | -| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout for recieving a response | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - ### SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` @@ -648,28 +622,6 @@ A service subscription object | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` - -##### Summary - -Implements a call to create a subscription to a given channel as a member of a given group for responding to queries - -##### Returns - -A service subscription object - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | The callback to be invoked when a message is recieved, returning the response message | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to subscribe to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription groupt to subscribe as | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - ## IMessageTypeEncoder\`1 `type` @@ -741,6 +693,85 @@ as well as the default of not encrypting the message body | ---- | ----------- | | T | The type of message that this encryptor supports | + +## IPingableMessageServiceConnection `type` + +##### Namespace + +MQContract.Interfaces.Service + +##### Summary + +Extends the base MessageServiceConnection Interface to support service pinging + + +### PingAsync() `method` + +##### Summary + +Implemented Ping call if avaialble for the underlying service + +##### Returns + +A Ping Result + +##### Parameters + +This method has no parameters. + + +## IQueryableMessageServiceConnection `type` + +##### Namespace + +MQContract.Interfaces.Service + +##### Summary + +Extends the base MessageServiceConnection Interface to Response Query messaging methodology if the underlying service supports it + + +### QueryAsync(message,timeout,options,cancellationToken) `method` + +##### Summary + +Implements a call to submit a response query request into the underlying service + +##### Returns + +A Query Result instance based on what happened + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The message to query with | +| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout for recieving a response | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + + +### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +##### Summary + +Implements a call to create a subscription to a given channel as a member of a given group for responding to queries + +##### Returns + +A service subscription object + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | The callback to be invoked when a message is recieved, returning the response message | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to subscribe to | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription groupt to subscribe as | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + ## IRecievedMessage\`1 `type` @@ -1278,6 +1309,45 @@ How long it took for the server to respond The version of the service running, if provided + +## QueryResponseChannelAttribute `type` + +##### Namespace + +MQContract.Attributes + +##### Summary + +Used to allow the specification of a response channel to be used without supplying it to the contract calls. +IMPORTANT: This particular attribute and the response channel argument are only used when the underlying connection does not support QueryResponse messaging. + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| name | [T:MQContract.Attributes.QueryResponseChannelAttribute](#T-T-MQContract-Attributes-QueryResponseChannelAttribute 'T:MQContract.Attributes.QueryResponseChannelAttribute') | The name of the channel to use for responses | + + +### #ctor(name) `constructor` + +##### Summary + +Used to allow the specification of a response channel to be used without supplying it to the contract calls. +IMPORTANT: This particular attribute and the response channel argument are only used when the underlying connection does not support QueryResponse messaging. + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| name | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to use for responses | + + +### Name `property` + +##### Summary + +The Name of the response channel + ## QueryResponseMessage\`1 `type` diff --git a/AutomatedTesting/ChannelMapperTests.cs b/AutomatedTesting/ChannelMapperTests.cs index 4f74d23..9776b0e 100644 --- a/AutomatedTesting/ChannelMapperTests.cs +++ b/AutomatedTesting/ChannelMapperTests.cs @@ -534,7 +534,7 @@ public async Task TestQueryMapWithStringToString() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -585,7 +585,7 @@ public async Task TestQueryMapWithStringToFunction() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -643,7 +643,7 @@ public async Task TestQueryMapWithMatchToFunction() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -699,7 +699,7 @@ public async Task TestQueryMapWithDefaultFunction() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -756,7 +756,7 @@ public async Task TestQueryMapWithSingleMapAndWithDefaultFunction() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -803,7 +803,7 @@ public async Task TestQuerySubscribeMapWithStringToString() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -847,7 +847,7 @@ public async Task TestQuerySubscribeMapWithStringToFunction() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -905,7 +905,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -961,7 +961,7 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -1018,7 +1018,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -1065,5 +1065,379 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } + + private const string REPLY_CHANNEL_HEADER = "_QueryReplyChannel"; + + [TestMethod] + public async Task TestQueryResponseMapWithStringToString() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); + var mockSubscription = new Mock(); + + var defaultTimeout = TimeSpan.FromMinutes(1); + + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + if (message.Header[REPLY_CHANNEL_HEADER]!=null) + channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var responseChannel = "Gretting.Response"; + + var newChannel = $"{responseChannel}-Modded"; + var mapper = new ChannelMapper() + .AddQueryResponseMap(responseChannel, newChannel); + + var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result = await contractConnection.QueryAsync(testMessage,responseChannel:responseChannel); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(channels, 1, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result); + Assert.IsNull(result.Error); + Assert.IsFalse(result.IsError); + Assert.AreEqual(newChannel, channels[0]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestQueryResponseMapWithStringToFunction() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); + var mockSubscription = new Mock(); + + var defaultTimeout = TimeSpan.FromMinutes(1); + + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + if (message.Header[REPLY_CHANNEL_HEADER]!=null) + channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var responseChannel = "Gretting.Response"; + + var newChannel = $"{responseChannel}-Modded"; + var otherChannel = $"{responseChannel}-OtherChannel"; + var mapper = new ChannelMapper() + .AddQueryResponseMap(responseChannel, + (originalChannel) => + { + if (Equals(originalChannel, responseChannel)) + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); + }); + + var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result1 = await contractConnection.QueryAsync(testMessage,responseChannel: responseChannel); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + var result2 = await contractConnection.QueryAsync(testMessage, responseChannel: otherChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(channels, 2, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result1); + Assert.IsNotNull(result2); + Assert.AreEqual(newChannel, channels[0]); + Assert.AreEqual(otherChannel, channels[1]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(2)); + mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); + #endregion + } + + [TestMethod] + public async Task TestQueryResponseMapWithMatchToFunction() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); + var mockSubscription = new Mock(); + + var defaultTimeout = TimeSpan.FromMinutes(1); + + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + if (message.Header[REPLY_CHANNEL_HEADER]!=null) + channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var responseChannel = "Gretting.Response"; + + var newChannel = $"{responseChannel}-Modded"; + var otherChannel = $"{responseChannel}-OtherChannel"; + var mapper = new ChannelMapper() + .AddQueryResponseMap((channelName) => Equals(channelName, otherChannel) + , (originalChannel) => + { + return ValueTask.FromResult(newChannel); + }); + + var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result1 = await contractConnection.QueryAsync(testMessage,responseChannel: responseChannel); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + var result2 = await contractConnection.QueryAsync(testMessage, responseChannel: otherChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(channels, 2, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result1); + Assert.IsNotNull(result2); + Assert.AreEqual(responseChannel, channels[0]); + Assert.AreEqual(newChannel, channels[1]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(2)); + mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); + #endregion + } + + [TestMethod] + public async Task TestQueryResponseMapWithDefaultFunction() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); + var mockSubscription = new Mock(); + + var defaultTimeout = TimeSpan.FromMinutes(1); + + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + if (message.Header[REPLY_CHANNEL_HEADER]!=null) + channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var responseChannel = "Gretting.Response"; + + var newChannel = $"{responseChannel}-Modded"; + var otherChannel = $"{responseChannel}-OtherChannel"; + var mapper = new ChannelMapper() + .AddDefaultQueryResponseMap((originalChannel) => + { + if (Equals(originalChannel, responseChannel)) + return ValueTask.FromResult(newChannel); + return ValueTask.FromResult(originalChannel); + }); + + var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result1 = await contractConnection.QueryAsync(testMessage,responseChannel: responseChannel); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + var result2 = await contractConnection.QueryAsync(testMessage, responseChannel: otherChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(channels, 2, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result1); + Assert.IsNotNull(result2); + Assert.AreEqual(newChannel, channels[0]); + Assert.AreEqual(otherChannel, channels[1]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(2)); + mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); + #endregion + } + + [TestMethod] + public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); + var mockSubscription = new Mock(); + + var defaultTimeout = TimeSpan.FromMinutes(1); + + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + if (message.Header[REPLY_CHANNEL_HEADER]!=null) + channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var responseChannel = "Gretting.Response"; + + var newChannel = $"{responseChannel}-Modded"; + var otherChannel = $"{responseChannel}-OtherChannel"; + var mapper = new ChannelMapper() + .AddQueryResponseMap(responseChannel, newChannel) + .AddDefaultQueryResponseMap((originalChannel) => + { + return ValueTask.FromResult(originalChannel); + }); + + var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result1 = await contractConnection.QueryAsync(testMessage,responseChannel: responseChannel); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + var result2 = await contractConnection.QueryAsync(testMessage, responseChannel: otherChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(channels, 2, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result1); + Assert.IsNotNull(result2); + Assert.AreEqual(newChannel, channels[0]); + Assert.AreEqual(otherChannel, channels[1]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(2)); + mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); + #endregion + } } } diff --git a/AutomatedTesting/ContractConnectionTests/CleanupTests.cs b/AutomatedTesting/ContractConnectionTests/CleanupTests.cs index 53647ae..4b8e986 100644 --- a/AutomatedTesting/ContractConnectionTests/CleanupTests.cs +++ b/AutomatedTesting/ContractConnectionTests/CleanupTests.cs @@ -1,11 +1,6 @@ using Moq; using MQContract.Interfaces.Service; using MQContract; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace AutomatedTesting.ContractConnectionTests { diff --git a/AutomatedTesting/ContractConnectionTests/PingTests.cs b/AutomatedTesting/ContractConnectionTests/PingTests.cs index 8e510aa..c3e01cf 100644 --- a/AutomatedTesting/ContractConnectionTests/PingTests.cs +++ b/AutomatedTesting/ContractConnectionTests/PingTests.cs @@ -13,7 +13,7 @@ public async Task TestPingAsync() #region Arrange var pingResult = new PingResult("TestHost", "1.0.0", TimeSpan.FromSeconds(5)); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.PingAsync()) .ReturnsAsync(pingResult); diff --git a/AutomatedTesting/ContractConnectionTests/QueryTests.cs b/AutomatedTesting/ContractConnectionTests/QueryTests.cs index 879f5c0..1e13caa 100644 --- a/AutomatedTesting/ContractConnectionTests/QueryTests.cs +++ b/AutomatedTesting/ContractConnectionTests/QueryTests.cs @@ -38,7 +38,7 @@ public async Task TestQueryAsyncWithNoExtendedAspects() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -93,7 +93,7 @@ public async Task TestQueryAsyncWithDifferentChannelName() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -148,7 +148,7 @@ public async Task TestQueryAsyncWithMessageHeaders() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -208,7 +208,7 @@ public async Task TestQueryAsyncWithTimeout() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -263,7 +263,7 @@ public async Task TestQueryAsyncWithCompressionDueToMessageSize() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -321,7 +321,7 @@ public async Task TestQueryAsyncWithGlobalEncoder() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -391,7 +391,7 @@ public async Task TestQueryAsyncWithGlobalEncryptor() new KeyValuePair("test","test") ]); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -459,7 +459,7 @@ public async Task TestQueryAsyncWithTimeoutAttribute() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -516,7 +516,7 @@ public async Task TestQueryAsyncWithServiceChannelOptions() List options = []; var serviceChannelOptions = new TestServiceChannelOptions("QWueryAsync"); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), Capture.In(options), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -574,7 +574,7 @@ public async Task TestQueryAsyncWithNamedAndVersionedMessage() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -629,7 +629,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncoder() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -686,7 +686,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncoder() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -743,7 +743,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncryptor() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -800,7 +800,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncryptor List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -855,7 +855,7 @@ public async Task TestQueryAsyncWithNoMessageChannelThrowsError() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -900,7 +900,7 @@ public async Task TestQueryAsyncWithToLargeAMessageThrowsError() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -948,7 +948,7 @@ public async Task TestQueryAsyncWithTwoDifferentMessageTypes() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -1019,7 +1019,7 @@ public async Task TestQueryAsyncWithAttributeReturnType() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) @@ -1074,7 +1074,7 @@ public async Task TestQueryAsyncWithNoReturnType() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) diff --git a/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs new file mode 100644 index 0000000..5b89071 --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs @@ -0,0 +1,439 @@ +using AutomatedTesting.Messages; +using Moq; +using MQContract.Attributes; +using MQContract.Interfaces.Service; +using MQContract; +using System.Diagnostics; +using System.Text.Json; +using System.Reflection; +using Castle.Core.Internal; + +namespace AutomatedTesting.ContractConnectionTests +{ + [TestClass] + public class QueryWithoutQueryResponseTests + { + private const string REPLY_CHANNEL_HEADER = "_QueryReplyChannel"; + private const string QUERY_IDENTIFIER_HEADER = "_QueryClientID"; + private const string REPLY_ID = "_QueryReplyID"; + + [TestMethod] + public async Task TestQueryAsyncWithNoExtendedAspects() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + var responseChannel = "BasicQuery.Response"; + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + + var defaultTimeout = TimeSpan.FromMinutes(1); + + var mockSubscription = new Mock(); + + List messages = []; + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + Capture.In(channels), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + messages.Add(message); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result = await contractConnection.QueryAsync(testMessage,responseChannel:responseChannel); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result); + Assert.IsNull(result.Error); + Assert.IsFalse(result.IsError); + Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); + Assert.AreEqual(1, channels.Count); + Assert.AreEqual(responseChannel, channels[0]); + Assert.AreEqual(1, messages.Count); + Assert.IsTrue(messages[0].Data.Length>0); + Assert.AreEqual(3, messages[0].Header.Keys.Count()); + Assert.AreEqual(responseChannel, messages[0].Header[REPLY_CHANNEL_HEADER]); + Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(new MemoryStream(messages[0].Data.ToArray()))); + Assert.AreEqual(responseMessage, result.Result); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(),It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestQueryAsyncWithAHeaderValue() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + var responseChannel = "BasicQuery.Response"; + var headerKey = "MyHeaderKey"; + var headerValue = "MyHeaderValue"; + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + + var defaultTimeout = TimeSpan.FromMinutes(1); + + var mockSubscription = new Mock(); + + List messages = []; + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + Capture.In(channels), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + messages.Add(message); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result = await contractConnection.QueryAsync(testMessage, messageHeader: new([new KeyValuePair(headerKey,headerValue)]), responseChannel: responseChannel); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result); + Assert.IsNull(result.Error); + Assert.IsFalse(result.IsError); + Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); + Assert.AreEqual(1, channels.Count); + Assert.AreEqual(responseChannel, channels[0]); + Assert.AreEqual(1, messages.Count); + Assert.IsTrue(messages[0].Data.Length>0); + Assert.AreEqual(4, messages[0].Header.Keys.Count()); + Assert.AreEqual(responseChannel, messages[0].Header[REPLY_CHANNEL_HEADER]); + Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(new MemoryStream(messages[0].Data.ToArray()))); + Assert.AreEqual(responseMessage, result.Result); + Assert.AreEqual(1, result.Header.Keys.Count()); + Assert.AreEqual(headerValue, result.Header[headerKey]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestQueryAsyncWithASecondMessageNotMatchingTheRequestBeingSent() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + var responseChannel = "BasicQuery.Response"; + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + + var defaultTimeout = TimeSpan.FromMinutes(1); + + var mockSubscription = new Mock(); + + List messages = []; + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + Capture.In(channels), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + messages.Add(message); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, new([ + new KeyValuePair(QUERY_IDENTIFIER_HEADER,Guid.NewGuid().ToString()), + new KeyValuePair(REPLY_ID,Guid.NewGuid().ToString()), + new KeyValuePair(REPLY_CHANNEL_HEADER,responseChannel) + ]), responseData); + foreach (var action in messageActions) + action(resp); + resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result = await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result); + Assert.IsNull(result.Error); + Assert.IsFalse(result.IsError); + Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); + Assert.AreEqual(1, channels.Count); + Assert.AreEqual(responseChannel, channels[0]); + Assert.AreEqual(1, messages.Count); + Assert.IsTrue(messages[0].Data.Length>0); + Assert.AreEqual(3, messages[0].Header.Keys.Count()); + Assert.AreEqual(responseChannel, messages[0].Header[REPLY_CHANNEL_HEADER]); + Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(new MemoryStream(messages[0].Data.ToArray()))); + Assert.AreEqual(responseMessage, result.Result); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestQueryAsyncWithTheAttributeChannel() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + var responseChannel = typeof(BasicQueryMessage).GetAttribute()?.Name; + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + + var defaultTimeout = TimeSpan.FromMinutes(1); + + var mockSubscription = new Mock(); + + List messages = []; + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + Capture.In(channels), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + messages.Add(message); + var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel!, message.Header, responseData); + foreach (var action in messageActions) + action(resp); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result = await contractConnection.QueryAsync(testMessage); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result); + Assert.IsNull(result.Error); + Assert.IsFalse(result.IsError); + Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); + Assert.AreEqual(1, channels.Count); + Assert.AreEqual(responseChannel, channels[0]); + Assert.AreEqual(1, messages.Count); + Assert.IsTrue(messages[0].Data.Length>0); + Assert.AreEqual(3, messages[0].Header.Keys.Count()); + Assert.AreEqual(responseChannel, messages[0].Header[REPLY_CHANNEL_HEADER]); + Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(new MemoryStream(messages[0].Data.ToArray()))); + Assert.AreEqual(responseMessage, result.Result); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestQueryAsyncFailingToCreateSubscription() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseChannel = "BasicQuery.Response"; + var defaultTimeout = TimeSpan.FromMinutes(1); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Returns(ValueTask.FromResult(null)); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var error = await Assert.ThrowsExceptionAsync(async()=> await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel)); + #endregion + + #region Assert + Assert.IsNotNull(error); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestQueryAsyncFailingWithNoResponseChannel() + { + #region Arrange + var testMessage = new BasicResponseMessage("testMessage"); + var defaultTimeout = TimeSpan.FromMinutes(1); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Returns(ValueTask.FromResult(null)); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var error = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage,channel:"Test")); + #endregion + + #region Assert + Assert.IsNotNull(error); + Assert.AreEqual("responseChannel", error.ParamName); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Never); + #endregion + } + + [TestMethod] + public async Task TestQueryAsyncWithTimeoutException() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseChannel = "BasicQuery.Response"; + + + var defaultTimeout = TimeSpan.FromSeconds(5); + + var mockSubscription = new Mock(); + + List messages = []; + List> messageActions = []; + List channels = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + Capture.In(channels), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + messages.Add(message); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var contractConnection = new ContractConnection(serviceConnection.Object); + #endregion + + #region Act + var error = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel,timeout:defaultTimeout)); + #endregion + + #region Assert + Assert.IsNotNull(error); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + } +} diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs index df905c6..b5e051c 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs @@ -7,10 +7,6 @@ using System.Diagnostics; using System.Reflection; using MQContract.Interfaces.Encoding; -using System.Linq; -using MQContract.Messages; -using System.Linq.Expressions; -using System; namespace AutomatedTesting.ContractConnectionTests { @@ -29,7 +25,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() var groups = new List(); var serviceMessages = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(recievedActions), Capture.In>(errorActions), @@ -109,7 +105,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() var channels = new List(); var channelName = "TestSubscribeQueryResponseWithSpecificChannel"; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -160,7 +156,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() var groups = new List(); var groupName = "TestSubscribeQueryResponseWithSpecificGroup"; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -209,7 +205,7 @@ public async Task TestSubscribeQueryResponseAsyncWithServiceChannelOptions() var serviceChannelOptions = new TestServiceChannelOptions("TestSubscribeQueryResponseWithServiceChannelOptions"); List options = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -249,7 +245,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() #region Arrange var serviceSubscription = new Mock(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -286,7 +282,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() { #region Arrange - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -325,7 +321,7 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() serviceSubscription.Setup(x => x.EndAsync()) .Returns(ValueTask.CompletedTask); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -370,7 +366,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() var groups = new List(); var serviceMessages = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(recievedActions), Capture.In>(errorActions), @@ -462,7 +458,7 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() var groups = new List(); var serviceMessages = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(recievedActions), Capture.In>(errorActions), @@ -530,7 +526,7 @@ public async Task TestSubscribeQueryResponseAsyncEndAsync() serviceSubscription.Setup(x => x.EndAsync()) .Returns(ValueTask.CompletedTask); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -565,7 +561,7 @@ public async Task TestSubscribeQueryResponseAsyncAsyncCleanup() serviceSubscription.Setup(x => x.DisposeAsync()) .Returns(ValueTask.CompletedTask); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -597,7 +593,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNonAsyncCleanup() { #region Arrange var serviceSubscription = new Mock(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -630,7 +626,7 @@ public async Task TestSubscribeQueryResponseAsyncSubscriptionsCleanup() #region Arrange var serviceSubscription = new Mock(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -676,7 +672,7 @@ public async Task TestSubscribeQueryResponseAsyncWithThrowsConversionError() var recievedActions = new List>>(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(recievedActions), It.IsAny>(), diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs new file mode 100644 index 0000000..4f27d3a --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs @@ -0,0 +1,94 @@ +using AutomatedTesting.Messages; +using Moq; +using MQContract.Attributes; +using MQContract.Interfaces.Service; +using MQContract.Interfaces; +using MQContract; +using System.Diagnostics; +using System.Reflection; + +namespace AutomatedTesting.ContractConnectionTests +{ + [TestClass] + public class SubscribeQueryResponseWithoutQueryResponseTest + { + [TestMethod] + public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() + { + #region Arrange + var serviceSubscription = new Mock(); + var serviceSubObject = serviceSubscription.Object; + + var channels = new List(); + var groups = new List(); + List messages = []; + List> messageActions = []; + var defaultTimeout = TimeSpan.FromMinutes(1); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + Capture.In(channels), Capture.In(groups), + It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubObject); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + { + messages.Add(message); + foreach (var action in messageActions) + action(new RecievedServiceMessage(message.ID,message.MessageTypeID,message.Channel,message.Header,message.Data)); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimout) + .Returns(defaultTimeout); + + var contractConnection = new ContractConnection(serviceConnection.Object); + + var message = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); + var responseMessage = new BasicResponseMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); + #endregion + + #region Act + var recievedMessages = new List>(); + var exceptions = new List(); + var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + recievedMessages.Add(msg); + return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); + }, (error) => exceptions.Add(error)); + var stopwatch = Stopwatch.StartNew(); + var result = await contractConnection.QueryAsync(message); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + + await subscription.EndAsync(); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount>(recievedMessages, 1, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount(messages, 2, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(subscription); + Assert.IsNotNull(result); + Assert.AreEqual(2, channels.Count); + Assert.AreEqual(2, groups.Count); + Assert.AreEqual(2, messages.Count); + Assert.AreEqual(1, exceptions.Count); + Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, channels[0]); + Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.AreEqual(recievedMessages[0].ID, messages[0].ID); + Assert.AreEqual(0,recievedMessages[0].Headers.Keys.Count()); + Assert.AreEqual(3, messages[0].Header.Keys.Count()); + Assert.AreEqual(message, recievedMessages[0].Message); + Assert.IsFalse(result.IsError); + Assert.IsNull(result.Error); + Assert.AreEqual(result.Result, responseMessage); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); + #endregion + } + } +} diff --git a/AutomatedTesting/Messages/BasicQueryMessage.cs b/AutomatedTesting/Messages/BasicQueryMessage.cs index 33e5a3c..e49cf6d 100644 --- a/AutomatedTesting/Messages/BasicQueryMessage.cs +++ b/AutomatedTesting/Messages/BasicQueryMessage.cs @@ -4,5 +4,6 @@ namespace AutomatedTesting.Messages { [MessageChannel("BasicQueryMessage")] [QueryResponseType(typeof(BasicResponseMessage))] + [QueryResponseChannel("BasicQueryResponse")] public record BasicQueryMessage(string TypeName) { } } diff --git a/AutomatedTesting/Messages/MessageHeaderTests.cs b/AutomatedTesting/Messages/MessageHeaderTests.cs index ea8a01d..584438f 100644 --- a/AutomatedTesting/Messages/MessageHeaderTests.cs +++ b/AutomatedTesting/Messages/MessageHeaderTests.cs @@ -1,13 +1,4 @@ -using Moq; -using MQContract.Interfaces.Service; -using MQContract; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AutomatedTesting.Messages +namespace AutomatedTesting.Messages { [TestClass] public class MessageHeaderTests @@ -39,9 +30,9 @@ public void TestMessageHeaderPrimaryConstructor() public void TestMessageHeaderDictionaryConstructor() { #region Arrange - var data = new Dictionary([ - new KeyValuePair("key1","value1"), - new KeyValuePair("key2","value2") + var data = new Dictionary([ + new KeyValuePair("key1","value1"), + new KeyValuePair("key2","value2") ]); #endregion diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index 5fe1fd2..cbd6b49 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -1,19 +1,14 @@ using Apache.NMS; using Apache.NMS.Util; +using MQContract.ActiveMQ.Subscriptions; using MQContract.Interfaces.Service; using MQContract.Messages; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.ActiveMQ { - internal class Connection : IMessageServiceConnection + internal class Connection : IMessageServiceConnection,IAsyncDisposable,IDisposable { - private const string MESSAGE_TYPE_HEADER_ID = "_MessageType"; - + private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; private bool disposedValue; private readonly IConnectionFactory connectionFactory; @@ -37,32 +32,34 @@ public Connection(Uri ConnectUri){ /// public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); - public ValueTask PingAsync() - => throw new NotImplementedException(); - private async ValueTask ProduceMessage(ServiceMessage message) { var msg = await session.CreateBytesMessageAsync(message.Data.ToArray()); msg.NMSMessageId=message.ID; - msg.Properties[MESSAGE_TYPE_HEADER_ID] = message.MessageTypeID; + msg.Properties[MESSAGE_TYPE_HEADER] = message.MessageTypeID; foreach (var key in message.Header.Keys) msg.Properties[key] = message.Header[key]; return msg; } + private static MessageHeader ExtractHeaders(IPrimitiveMap properties, out string? messageTypeID) + { + var result = new Dictionary(); + messageTypeID = (string?)(properties.Contains(MESSAGE_TYPE_HEADER) ? properties[MESSAGE_TYPE_HEADER] : null); + foreach (var key in properties.Keys.OfType() + .Where(h =>!Equals(h, MESSAGE_TYPE_HEADER))) + result.Add(key, (string)properties[key]); + return new(result); + } + internal static RecievedServiceMessage ProduceMessage(string channel, IMessage message) { - var headers = new Dictionary(); - foreach(var key in message.Properties.Keys.OfType()) - { - if (!Equals(key, MESSAGE_TYPE_HEADER_ID)) - headers.Add(key, (string)message.Properties[key]); - } + var headers = ExtractHeaders(message.Properties, out var messageTypeID); return new( message.NMSMessageId, - (string)message.Properties[MESSAGE_TYPE_HEADER_ID], + messageTypeID!, channel, - new(headers), + headers, message.Body() ); } @@ -80,19 +77,11 @@ public async ValueTask PublishAsync(ServiceMessage message, } } - public ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + var result = new SubscriptionBase((msg)=>messageRecieved(ProduceMessage(channel,msg)), errorRecieved,session, channel, group); + await result.StartAsync(); + return result; } public async ValueTask CloseAsync() @@ -100,7 +89,7 @@ public async ValueTask CloseAsync() public async ValueTask DisposeAsync() { - await connection.StopAsync().ConfigureAwait(false); + await connection.StopAsync().ConfigureAwait(true); Dispose(disposing: false); GC.SuppressFinalize(this); diff --git a/Connectors/ActiveMQ/Exceptions.cs b/Connectors/ActiveMQ/Exceptions.cs new file mode 100644 index 0000000..ccbd183 --- /dev/null +++ b/Connectors/ActiveMQ/Exceptions.cs @@ -0,0 +1,18 @@ +namespace MQContract.ActiveMQ +{ + internal class QueryAsyncReponseException : Exception + { + internal QueryAsyncReponseException(string error) + : base(error) { } + } + internal class QueryExecutionFailedException : Exception + { + internal QueryExecutionFailedException() + : base("Failed to execute query") { } + } + internal class QueryResultMissingException : Exception + { + internal QueryResultMissingException() + : base("Query result not found") { } + } +} diff --git a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs b/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs deleted file mode 100644 index e4a2ceb..0000000 --- a/Connectors/ActiveMQ/Subscriptions/PublishSubscription.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Apache.NMS; -using Apache.NMS.Util; -using MQContract.Interfaces.Service; -using MQContract.Messages; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MQContract.ActiveMQ.Subscriptions -{ - internal class PublishSubscription(ISession session, string channel, Action messageRecieved, Action errorRecieved) : IServiceSubscription - { - private bool disposedValue; - private IMessageConsumer? consumer; - - internal async ValueTask StartAsync(CancellationToken cancellationToken) - { - consumer = await session.CreateConsumerAsync(SessionUtil.GetTopic(session, channel)); - consumer.Listener+=ConsumeMessage; - cancellationToken.Register(async () => await EndAsync()); - } - - private void ConsumeMessage(IMessage message) - { - try - { - messageRecieved(Connection.ProduceMessage(channel, message)); - }catch(Exception e) - { - errorRecieved(e); - } - } - - public async ValueTask EndAsync() - { - if (consumer!=null) - await consumer.CloseAsync(); - } - - public async ValueTask DisposeAsync() - { - if (!disposedValue) - { - disposedValue=true; - await EndAsync(); - consumer?.Dispose(); - } - } - } -} diff --git a/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs b/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs new file mode 100644 index 0000000..661bf2a --- /dev/null +++ b/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs @@ -0,0 +1,53 @@ +using Apache.NMS; +using Apache.NMS.Util; +using MQContract.Interfaces.Service; + +namespace MQContract.ActiveMQ.Subscriptions +{ + internal class SubscriptionBase(Action messageRecieved,Action errorRecieved,ISession session, string channel,string group) : IServiceSubscription + { + private bool disposedValue; + private IMessageConsumer? consumer; + protected readonly CancellationTokenSource cancelToken = new(); + protected string Channel => channel; + + internal async ValueTask StartAsync() + { + consumer = await session.CreateSharedConsumerAsync(SessionUtil.GetTopic(session, channel),group); + _=Task.Run(async () => + { + while (!cancelToken.IsCancellationRequested) + { + try + { + var msg = await consumer.ReceiveAsync(); + if (msg!=null) + messageRecieved(msg); + } + catch (Exception ex) + { + errorRecieved(ex); + } + } + }); + } + + public async ValueTask EndAsync() + { + if (!cancelToken.IsCancellationRequested) + await cancelToken.CancelAsync(); + if (consumer!=null) + await consumer.CloseAsync(); + } + + public async ValueTask DisposeAsync() + { + if (!disposedValue) + { + disposedValue=true; + await EndAsync(); + consumer?.Dispose(); + } + } + } +} diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 3d854fd..8f4d9cb 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -1,9 +1,7 @@ using Confluent.Kafka; using MQContract.Interfaces.Service; -using MQContract.Kafka.Options; using MQContract.Kafka.Subscriptions; using MQContract.Messages; -using MQContract.NATS.Subscriptions; using System.Text; namespace MQContract.Kafka @@ -15,10 +13,6 @@ namespace MQContract.Kafka public sealed class Connection(ClientConfig clientConfig) : IMessageServiceConnection { private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; - private const string QUERY_IDENTIFIER_HEADER = "_QueryClientID"; - private const string REPLY_ID = "_QueryReplyID"; - private const string REPLY_CHANNEL_HEADER = "_QueryReplyChannel"; - private const string ERROR_MESSAGE_TYPE_ID = "KafkaQueryError"; private readonly IProducer producer = new ProducerBuilder(clientConfig).Build(); private readonly ClientConfig clientConfig = clientConfig; @@ -42,30 +36,19 @@ internal static byte[] EncodeHeaderValue(string value) internal static string DecodeHeaderValue(byte[] value) => UTF8Encoding.UTF8.GetString(value); - internal static Headers ExtractHeaders(ServiceMessage message, Guid? queryClientID = null, Guid? replyID = null,string? replyChannel=null) + internal static Headers ExtractHeaders(ServiceMessage message) { var result = new Headers(); foreach (var key in message.Header.Keys) result.Add(key, EncodeHeaderValue(message.Header[key]!)); result.Add(MESSAGE_TYPE_HEADER, EncodeHeaderValue(message.MessageTypeID)); - if (queryClientID!=null) - result.Add(QUERY_IDENTIFIER_HEADER, queryClientID.Value.ToByteArray()); - if (replyID!=null) - result.Add(REPLY_ID,replyID.Value.ToByteArray()); - if (replyChannel!=null) - result.Add(REPLY_CHANNEL_HEADER,EncodeHeaderValue(replyChannel!)); return result; } private static MessageHeader ExtractHeaders(Headers header) => new( header - .Where(h => - !Equals(h.Key, REPLY_ID) - &&!Equals(h.Key, REPLY_CHANNEL_HEADER) - &&!Equals(h.Key, MESSAGE_TYPE_HEADER) - &&!Equals(h.Key,QUERY_IDENTIFIER_HEADER) - ) + .Where(h => !Equals(h.Key, MESSAGE_TYPE_HEADER)) .Select(h => new KeyValuePair(h.Key, DecodeHeaderValue(h.GetValueBytes()))) ); @@ -75,23 +58,6 @@ internal static MessageHeader ExtractHeaders(Headers header,out string? messageT return ExtractHeaders(header); } - internal static MessageHeader ExtractHeaders(Headers header, out string? messageTypeID,out Guid? queryClient,out Guid? replyID,out string? replyChannel) - { - messageTypeID = DecodeHeaderValue(header.FirstOrDefault(pair => Equals(pair.Key, MESSAGE_TYPE_HEADER))?.GetValueBytes()?? []); - queryClient = new Guid(header.FirstOrDefault(pair => Equals(pair.Key, QUERY_IDENTIFIER_HEADER))?.GetValueBytes()?? Guid.Empty.ToByteArray()); - replyID = new Guid(header.FirstOrDefault(pair => Equals(pair.Key, REPLY_ID))?.GetValueBytes()?? Guid.Empty.ToByteArray()); - replyChannel = DecodeHeaderValue(header.FirstOrDefault(pair => Equals(pair.Key, REPLY_CHANNEL_HEADER))?.GetValueBytes()?? []); - return ExtractHeaders(header); - } - - /// - /// Not implemented as Kafka does not support this particular action - /// - /// Throws NotImplementedException - /// Thrown because Kafka does not support this particular action - public ValueTask PingAsync() - => throw new NotImplementedException(); - /// /// Called to publish a message into the Kafka server /// @@ -119,98 +85,6 @@ public async ValueTask PublishAsync(ServiceMessage message, } } - /// - /// Called to publish a query into the Kafka server - /// - /// The service message being sent - /// The timeout supplied for the query to response - /// The options specifically for this call and must be supplied. Must be instance of QueryChannelOptions. - /// A cancellation token - /// The resulting response - /// Thrown if options is null - /// Thrown if the options that was supplied is not an instance of QueryChannelOptions - /// Thrown if the ReplyChannel is blank or null as it needs to be set - /// Thrown when the query fails to execute - /// Thrown when the responding instance has provided an error - /// Thrown when there is no response to be found for the query - public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(options); - InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); - var queryChannelOptions = (QueryChannelOptions)options; - ArgumentNullException.ThrowIfNullOrWhiteSpace(queryChannelOptions.ReplyChannel); - var callID = Guid.NewGuid(); - var headers = ExtractHeaders(message, Identifier, callID, queryChannelOptions.ReplyChannel); - var tcs = StartResponseListener(clientConfig,Identifier,callID,queryChannelOptions.ReplyChannel,cancellationToken); - await producer.ProduceAsync(message.Channel, new Message() - { - Key=message.ID, - Headers=headers, - Value=message.Data.ToArray() - },cancellationToken); - try - { - await tcs.Task.WaitAsync(timeout, cancellationToken); - } - catch (Exception) - { - throw new QueryExecutionFailedException(); - } - if (tcs.Task.IsCompleted) - { - var result = tcs.Task.Result; - if (Equals(result?.MessageTypeID, ERROR_MESSAGE_TYPE_ID)) - throw new QueryAsyncReponseException(DecodeHeaderValue(result.Data.ToArray())); - else if (result!=null) - return result; - } - throw new QueryResultMissingException(); - } - - private static TaskCompletionSource StartResponseListener(ClientConfig configuration,Guid identifier,Guid callID, string replyChannel,CancellationToken cancellationToken) - { - var result = new TaskCompletionSource(); - using var queryLock = new ManualResetEventSlim(false); - Task.Run(() => - { - using var consumer = new ConsumerBuilder(new ConsumerConfig(configuration) - { - AutoOffsetReset=AutoOffsetReset.Earliest - }).Build(); - consumer.Subscribe(replyChannel); - queryLock.Set(); - while (!cancellationToken.IsCancellationRequested) - { - try - { - var msg = consumer.Consume(cancellationToken); - var headers = ExtractHeaders(msg.Message.Headers, out var messageTypeID, out var queryClient, out var replyID, out var replyChannel); - if (Equals(queryClient, identifier) && Equals(replyID, callID)) - { - Console.WriteLine(result.TrySetResult(new( - msg.Message.Key, - headers, - messageTypeID!, - msg.Message.Value - ))); - consumer.Unassign(); - break; - } - } - catch (Exception ex) { - Console.WriteLine(ex.Message); - } - } - try - { - consumer.Close(); - } - catch (Exception) { } - },cancellationToken); - queryLock.Wait(cancellationToken); - return result; - } - /// /// Called to create a subscription to the underlying Kafka server /// @@ -232,71 +106,7 @@ private static TaskCompletionSource StartResponseListener(Cl }).Build(), messageRecieved, errorRecieved, - channel, - cancellationToken); - await subscription.Run(); - return subscription; - } - - /// - /// Called to create a subscription for queries to the underlying Kafka server - /// - /// Callback for when a query is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The group to subscribe as part of - /// Optional QueryChannelOptions to be supplied that will specify the ReplyChannel if not supplied by query message - /// A cancellation token - /// A subscription instance - /// Thrown when options is not null and is not an instance of the type QueryChannelOptions - public async ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) - { - InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); - var subscription = new QuerySubscription( - new ConsumerBuilder(new ConsumerConfig(clientConfig) - { - GroupId=(!string.IsNullOrWhiteSpace(group) ? group : null) - }).Build(), - async (recievedMessage) => - { - var headers = ExtractHeaders(recievedMessage.Headers,out var messageTypeID,out var queryClient,out var replyID,out var replyChannel); - var recievedServiceMessage = new RecievedServiceMessage( - recievedMessage.Key, - messageTypeID??string.Empty, - channel, - headers, - recievedMessage.Value - ); - try - { - var result = await messageRecieved(recievedServiceMessage); - await producer.ProduceAsync( - replyChannel??((QueryChannelOptions?)options)?.ReplyChannel??string.Empty, - new Message() - { - Key=result.ID, - Headers=ExtractHeaders(result,queryClient,replyID,replyChannel), - Value=result.Data.ToArray() - } - ); - } - catch(Exception e) - { - var respMessage = new ServiceMessage(recievedMessage.Key, ERROR_MESSAGE_TYPE_ID,replyChannel??string.Empty, new MessageHeader([]), EncodeHeaderValue(e.Message)); - await producer.ProduceAsync( - replyChannel??((QueryChannelOptions?)options)?.ReplyChannel??string.Empty, - new Message() - { - Key=recievedMessage.Key, - Headers=ExtractHeaders(respMessage,queryClient,replyID,replyChannel), - Value=respMessage.Data.ToArray() - } - ); - } - }, - errorRecieved, - channel, - cancellationToken); + channel); await subscription.Run(); return subscription; } diff --git a/Connectors/Kafka/Options/QueryChannelOptions.cs b/Connectors/Kafka/Options/QueryChannelOptions.cs deleted file mode 100644 index 435e181..0000000 --- a/Connectors/Kafka/Options/QueryChannelOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQContract.Interfaces.Service; - -namespace MQContract.Kafka.Options -{ - /// - /// Houses the QueryChannelOptions used for performing Query Response style messaging in Kafka - /// - /// The reply channel to use. This channel should be setup with a short retention policy, no longer than 5 minutes. - public record QueryChannelOptions(string ReplyChannel) : IServiceChannelOptions - { - } -} diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index 8688bf8..70cef84 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -10,14 +10,8 @@ - [CloseAsync()](#M-MQContract-Kafka-Connection-CloseAsync 'MQContract.Kafka.Connection.CloseAsync') - [Dispose()](#M-MQContract-Kafka-Connection-Dispose 'MQContract.Kafka.Connection.Dispose') - [DisposeAsync()](#M-MQContract-Kafka-Connection-DisposeAsync 'MQContract.Kafka.Connection.DisposeAsync') - - [PingAsync()](#M-MQContract-Kafka-Connection-PingAsync 'MQContract.Kafka.Connection.PingAsync') - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Kafka-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') -- [QueryChannelOptions](#T-MQContract-Kafka-Options-QueryChannelOptions 'MQContract.Kafka.Options.QueryChannelOptions') - - [#ctor(ReplyChannel)](#M-MQContract-Kafka-Options-QueryChannelOptions-#ctor-System-String- 'MQContract.Kafka.Options.QueryChannelOptions.#ctor(System.String)') - - [ReplyChannel](#P-MQContract-Kafka-Options-QueryChannelOptions-ReplyChannel 'MQContract.Kafka.Options.QueryChannelOptions.ReplyChannel') ## Connection `type` @@ -105,27 +99,6 @@ A task required for disposal This method has no parameters. - -### PingAsync() `method` - -##### Summary - -Not implemented as Kafka does not support this particular action - -##### Returns - -Throws NotImplementedException - -##### Parameters - -This method has no parameters. - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [System.NotImplementedException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.NotImplementedException 'System.NotImplementedException') | Thrown because Kafka does not support this particular action | - ### PublishAsync(message,options,cancellationToken) `method` @@ -151,37 +124,6 @@ Transmition result identifying if it worked or not | ---- | ----------- | | [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | - -### QueryAsync(message,timeout,options,cancellationToken) `method` - -##### Summary - -Called to publish a query into the Kafka server - -##### Returns - -The resulting response - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The options specifically for this call and must be supplied. Must be instance of QueryChannelOptions. | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [System.ArgumentNullException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.ArgumentNullException 'System.ArgumentNullException') | Thrown if options is null | -| [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown if the options that was supplied is not an instance of QueryChannelOptions | -| [System.ArgumentNullException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.ArgumentNullException 'System.ArgumentNullException') | Thrown if the ReplyChannel is blank or null as it needs to be set | -| [MQContract.Kafka.QueryExecutionFailedException](#T-MQContract-Kafka-QueryExecutionFailedException 'MQContract.Kafka.QueryExecutionFailedException') | Thrown when the query fails to execute | -| [MQContract.Kafka.QueryAsyncReponseException](#T-MQContract-Kafka-QueryAsyncReponseException 'MQContract.Kafka.QueryAsyncReponseException') | Thrown when the responding instance has provided an error | -| [MQContract.Kafka.QueryResultMissingException](#T-MQContract-Kafka-QueryResultMissingException 'MQContract.Kafka.QueryResultMissingException') | Thrown when there is no response to be found for the query | - ### SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` @@ -209,68 +151,3 @@ Called to create a subscription to the underlying Kafka server | Name | Description | | ---- | ----------- | | [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | - - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` - -##### Summary - -Called to create a subscription for queries to the underlying Kafka server - -##### Returns - -A subscription instance - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Optional QueryChannelOptions to be supplied that will specify the ReplyChannel if not supplied by query message | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when options is not null and is not an instance of the type QueryChannelOptions | - - -## QueryChannelOptions `type` - -##### Namespace - -MQContract.Kafka.Options - -##### Summary - -Houses the QueryChannelOptions used for performing Query Response style messaging in Kafka - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| ReplyChannel | [T:MQContract.Kafka.Options.QueryChannelOptions](#T-T-MQContract-Kafka-Options-QueryChannelOptions 'T:MQContract.Kafka.Options.QueryChannelOptions') | The reply channel to use. This channel should be setup with a short retention policy, no longer than 5 minutes. | - - -### #ctor(ReplyChannel) `constructor` - -##### Summary - -Houses the QueryChannelOptions used for performing Query Response style messaging in Kafka - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| ReplyChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The reply channel to use. This channel should be setup with a short retention policy, no longer than 5 minutes. | - - -### ReplyChannel `property` - -##### Summary - -The reply channel to use. This channel should be setup with a short retention policy, no longer than 5 minutes. diff --git a/Connectors/Kafka/Subscriptions/PublishSubscription.cs b/Connectors/Kafka/Subscriptions/PublishSubscription.cs index 159a2dd..7d7c25b 100644 --- a/Connectors/Kafka/Subscriptions/PublishSubscription.cs +++ b/Connectors/Kafka/Subscriptions/PublishSubscription.cs @@ -1,10 +1,9 @@ using MQContract.Messages; -using MQContract.NATS.Subscriptions; namespace MQContract.Kafka.Subscriptions { - internal class PublishSubscription(Confluent.Kafka.IConsumer consumer, Action messageRecieved, Action errorRecieved, string channel, CancellationToken cancellationToken) - : SubscriptionBase(consumer,channel,cancellationToken) + internal class PublishSubscription(Confluent.Kafka.IConsumer consumer, Action messageRecieved, Action errorRecieved, string channel) + : SubscriptionBase(consumer,channel) { protected override ValueTask RunAction() { diff --git a/Connectors/Kafka/Subscriptions/QuerySubscription.cs b/Connectors/Kafka/Subscriptions/QuerySubscription.cs deleted file mode 100644 index 1aeed1b..0000000 --- a/Connectors/Kafka/Subscriptions/QuerySubscription.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Confluent.Kafka; -using MQContract.NATS.Subscriptions; - -namespace MQContract.Kafka.Subscriptions -{ - internal class QuerySubscription(Confluent.Kafka.IConsumer consumer, Func, ValueTask> messageRecieved, Action errorRecieved, string channel, CancellationToken cancellationToken) - : SubscriptionBase(consumer,channel,cancellationToken) - { - protected override async ValueTask RunAction() - { - while (!cancelToken.IsCancellationRequested) - { - try - { - var msg = Consumer.Consume(); - await messageRecieved(msg.Message); - } - catch (OperationCanceledException) { } - catch (Exception ex) - { - errorRecieved(ex); - } - } - } - } -} diff --git a/Connectors/Kafka/Subscriptions/SubscriptionBase.cs b/Connectors/Kafka/Subscriptions/SubscriptionBase.cs index fe5b2d9..726c598 100644 --- a/Connectors/Kafka/Subscriptions/SubscriptionBase.cs +++ b/Connectors/Kafka/Subscriptions/SubscriptionBase.cs @@ -1,8 +1,8 @@ using MQContract.Interfaces.Service; -namespace MQContract.NATS.Subscriptions +namespace MQContract.Kafka.Subscriptions { - internal abstract class SubscriptionBase(Confluent.Kafka.IConsumer consumer,string channel,CancellationToken cancellationToken) : IServiceSubscription + internal abstract class SubscriptionBase(Confluent.Kafka.IConsumer consumer,string channel) : IServiceSubscription { protected readonly Confluent.Kafka.IConsumer Consumer = consumer; protected readonly string Channel = channel; @@ -12,10 +12,6 @@ internal abstract class SubscriptionBase(Confluent.Kafka.IConsumer - { - cancelToken.Cancel(); - }); var resultSource = new TaskCompletionSource(); Task.Run(async () => { diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 6abebf0..385fd72 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -17,7 +17,7 @@ namespace MQContract.KubeMQ /// /// This is the MessageServiceConnection implementation for using KubeMQ /// - public sealed class Connection : IMessageServiceConnection,IDisposable,IAsyncDisposable + public sealed class Connection : IQueryableMessageServiceConnection,IPingableMessageServiceConnection, IDisposable,IAsyncDisposable { private static readonly Regex regURL = new("^http(s)?://(.+)$", RegexOptions.Compiled|RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500)); diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index 256d130..4c95097 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -13,7 +13,7 @@ namespace MQContract.NATS /// /// This is the MessageServiceConnection implementation for using NATS.io /// - public sealed class Connection : IMessageServiceConnection, IAsyncDisposable,IDisposable + public sealed class Connection : IQueryableMessageServiceConnection,IPingableMessageServiceConnection, IAsyncDisposable,IDisposable { private const string MESSAGE_IDENTIFIER_HEADER = "_MessageID"; private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; diff --git a/Core/ChannelMapper.cs b/Core/ChannelMapper.cs index a60b94b..b871605 100644 --- a/Core/ChannelMapper.cs +++ b/Core/ChannelMapper.cs @@ -10,7 +10,8 @@ internal enum MapTypes Publish, PublishSubscription, Query, - QuerySubscription + QuerySubscription, + QueryResponse } private sealed record ChannelMap(MapTypes Type,Func IsMatch,Func> Change); @@ -158,11 +159,43 @@ public ChannelMapper AddQuerySubscriptionMap(Func isMatch, FuncThe current instance of the Channel Mapper public ChannelMapper AddDefaultQuerySubscriptionMap(Func> mapFunction) => Append(MapTypes.QuerySubscription, mapFunction); + /// + /// Add a direct map for query/response response calls + /// + /// The original channel that is being used in the connection + /// The channel to map it to + /// The current instance of the Channel Mapper + public ChannelMapper AddQueryResponseMap(string originalChannel, string newChannel) + => Append(MapTypes.QueryResponse, originalChannel, newChannel); + /// + /// Add a map function for query/response response calls + /// + /// The original channel that is being used in the connection + /// A function to be called with the channel supplied expecting a mapped channel name + /// The current instance of the Channel Mapper + public ChannelMapper AddQueryResponseMap(string originalChannel, Func> mapFunction) + => Append(MapTypes.QueryResponse, originalChannel, mapFunction); + /// + /// Add a map function call pair for query/response response calls + /// + /// A callback that will return true if the supplied function will mape that channel + /// A function to be called with the channel supplied expecting a mapped channel name + /// The current instance of the Channel Mapper + public ChannelMapper AddQueryResponseMap(Func isMatch, Func> mapFunction) + => Append(MapTypes.QueryResponse, isMatch, mapFunction); + /// + /// Add a default map function to call for query/response response calls + /// + /// A function to be called with the channel supplied expecting a mapped channel name + /// The current instance of the Channel Mapper + public ChannelMapper AddDefaultQueryResponseMap(Func> mapFunction) + => Append(MapTypes.QueryResponse, mapFunction); - internal ValueTask MapChannel(MapTypes mapType,string originalChannel) + internal async ValueTask MapChannel(MapTypes mapType,string originalChannel) { var map = channelMaps.Find(m=>Equals(m.Type,mapType) && m.IsMatch(originalChannel)); - return map?.Change(originalChannel)??ValueTask.FromResult(originalChannel); + if (map == null) return originalChannel; + return await map.Change(originalChannel); } } } diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index cdd8639..0ac4747 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -31,6 +31,7 @@ public sealed class ContractConnection(IMessageServiceConnection serviceConnecti ChannelMapper? channelMapper = null) : IContractConnection { + private readonly Guid Indentifier = Guid.NewGuid(); private readonly SemaphoreSlim dataLock = new(1, 1); private IEnumerable typeFactories = []; private bool disposedValue; @@ -59,7 +60,7 @@ private ValueTask MapChannel(ChannelMapper.MapTypes mapType, string orig /// /// The ping result from the service layer, if supported public ValueTask PingAsync() - => serviceConnection.PingAsync(); + => (serviceConnection is IPingableMessageServiceConnection pingableService ? pingableService.PingAsync() : throw new NotSupportedException("The underlying service does not support Ping")); /// /// Called to publish a message out into the service layer in the Pub/Sub style @@ -80,7 +81,7 @@ await ProduceServiceMessage(ChannelMapper.MapTypes.Publish,message, channel: ); private async ValueTask ProduceServiceMessage(ChannelMapper.MapTypes mapType,T message, string? channel = null, MessageHeader? messageHeader = null) where T : class - => await GetMessageFactory().ConvertMessageAsync(message, channel, messageHeader,(originalChannel)=>MapChannel(mapType,originalChannel)); + => await GetMessageFactory().ConvertMessageAsync(message, channel, messageHeader,(originalChannel)=>MapChannel(mapType,originalChannel)!); /// /// Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages asynchronously @@ -125,7 +126,7 @@ private async ValueTask SubscribeAsync(Func(GetMessageFactory(ignoreMessageHeader), messageRecieved, errorRecieved, - (originalChannel) => MapChannel(ChannelMapper.MapTypes.PublishSubscription, originalChannel), + (originalChannel) => MapChannel(ChannelMapper.MapTypes.PublishSubscription, originalChannel)!, channel: channel, group: group, synchronous: synchronous, @@ -136,15 +137,49 @@ private async ValueTask SubscribeAsync(Func> ExecuteQueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + private async ValueTask> ExecuteQueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class - => await ProduceResultAsync(await serviceConnection.QueryAsync( - await ProduceServiceMessage(ChannelMapper.MapTypes.Query,message, channel: channel, messageHeader: messageHeader), - timeout??TimeSpan.FromMilliseconds(typeof(Q).GetCustomAttribute()?.Value??serviceConnection.DefaultTimout.TotalMilliseconds), - options, + { + var realTimeout = timeout??TimeSpan.FromMilliseconds(typeof(Q).GetCustomAttribute()?.Value??serviceConnection.DefaultTimout.TotalMilliseconds); + var serviceMessage = await ProduceServiceMessage(ChannelMapper.MapTypes.Query, message, channel: channel, messageHeader: messageHeader); + if (serviceConnection is IQueryableMessageServiceConnection queryableMessageServiceConnection) + return await ProduceResultAsync(await queryableMessageServiceConnection.QueryAsync( + serviceMessage, + realTimeout, + options, + cancellationToken + )); + responseChannel ??=typeof(Q).GetCustomAttribute()?.Name; + ArgumentNullException.ThrowIfNullOrWhiteSpace(responseChannel); + var replyChannel = await MapChannel(ChannelMapper.MapTypes.QueryResponse, responseChannel!); + var callID = Guid.NewGuid(); + var (tcs,token) = await QueryResponseHelper.StartResponseListenerAsync( + serviceConnection, + realTimeout, + Indentifier, + callID, + replyChannel, cancellationToken - )); + ); + var msg = QueryResponseHelper.EncodeMessage( + serviceMessage, + Indentifier, + callID, + replyChannel, + null + ); + await serviceConnection.PublishAsync(msg, cancellationToken: cancellationToken); + try + { + await tcs.Task.WaitAsync(cancellationToken); + }finally + { + if (!token.IsCancellationRequested) + await token.CancelAsync(); + } + return await ProduceResultAsync(tcs.Task.Result); + } /// /// Called to publish a message in the Query/Response style @@ -154,14 +189,16 @@ await ProduceServiceMessage(ChannelMapper.MapTypes.Query,message, channel: ch /// The message to transmit for the query /// The timeout to allow for waiting for a response /// Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on + /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is + /// only used when the underlying connection does not support a QueryResponse style messaging. /// A message header to be sent across with the message /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token /// A QueryResult that will contain the response message and or an error - public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class - => await ExecuteQueryAsync(message, timeout: timeout, channel: channel, messageHeader: messageHeader, options: options, cancellationToken: cancellationToken); + => await ExecuteQueryAsync(message, timeout: timeout, channel: channel,responseChannel:responseChannel, messageHeader: messageHeader, options: options, cancellationToken: cancellationToken); /// /// Called to publish a message in the Query/Response style except the response Type is gathered from the QueryResponseTypeAttribute @@ -170,15 +207,19 @@ await ProduceServiceMessage(ChannelMapper.MapTypes.Query,message, channel: ch /// The message to transmit for the query /// The timeout to allow for waiting for a response /// Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on + /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is + /// only used when the underlying connection does not support a QueryResponse style messaging. /// A message header to be sent across with the message /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token /// A QueryResult that will contain the response message and or an error /// Thrown when the supplied Query type does not have a QueryResponseTypeAttribute and therefore a response type cannot be determined - public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, MessageHeader? messageHeader = null, + public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where Q : class { +#pragma warning disable CA2208 // Instantiate argument exceptions correctly var responseType = (typeof(Q).GetCustomAttribute(false)?.ResponseType)??throw new UnknownResponseTypeException("ResponseType", typeof(Q)); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly #pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields var methodInfo = typeof(ContractConnection).GetMethod(nameof(ContractConnection.ExecuteQueryAsync), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(typeof(Q), responseType!); #pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields @@ -188,6 +229,7 @@ public async ValueTask> QueryAsync(Q message, TimeSpan? t message, timeout, channel, + responseChannel, messageHeader, options, cancellationToken diff --git a/Core/Exceptions.cs b/Core/Exceptions.cs index 72fb207..dc2b245 100644 --- a/Core/Exceptions.cs +++ b/Core/Exceptions.cs @@ -45,4 +45,31 @@ public class QueryResponseException : Exception internal QueryResponseException(string message) : base(message) { } } + + /// + /// Thrown when a query call is being made to a service that does not support query response and the listener cannot be created + /// + public class QueryExecutionFailedException : Exception + { + internal QueryExecutionFailedException() + : base("Failed to execute query") { } + } + + /// + /// Thrown when a query call times out waiting for the response + /// + public class QueryTimeoutException : Exception + { + internal QueryTimeoutException() + : base("Query Response request timed out") { } + } + + /// + /// Thrown when a query call message is recieved without proper data + /// + public class InvalidQueryResponseMessageRecieved : Exception + { + internal InvalidQueryResponseMessageRecieved() + : base("A service message was recieved on a query response channel without the proper data") { } + } } diff --git a/Core/Readme.md b/Core/Readme.md index 15fb2fd..bccc90c 100644 --- a/Core/Readme.md +++ b/Core/Readme.md @@ -7,6 +7,7 @@ - [AddDefaultPublishMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultPublishMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultPublishMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddDefaultPublishSubscriptionMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultPublishSubscriptionMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultPublishSubscriptionMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddDefaultQueryMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultQueryMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultQueryMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddDefaultQueryResponseMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultQueryResponseMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultQueryResponseMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddDefaultQuerySubscriptionMap(mapFunction)](#M-MQContract-ChannelMapper-AddDefaultQuerySubscriptionMap-System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddDefaultQuerySubscriptionMap(System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddPublishMap(originalChannel,newChannel)](#M-MQContract-ChannelMapper-AddPublishMap-System-String,System-String- 'MQContract.ChannelMapper.AddPublishMap(System.String,System.String)') - [AddPublishMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddPublishMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddPublishMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') @@ -17,6 +18,9 @@ - [AddQueryMap(originalChannel,newChannel)](#M-MQContract-ChannelMapper-AddQueryMap-System-String,System-String- 'MQContract.ChannelMapper.AddQueryMap(System.String,System.String)') - [AddQueryMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddQueryMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQueryMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddQueryMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQueryMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQueryMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddQueryResponseMap(originalChannel,newChannel)](#M-MQContract-ChannelMapper-AddQueryResponseMap-System-String,System-String- 'MQContract.ChannelMapper.AddQueryResponseMap(System.String,System.String)') + - [AddQueryResponseMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddQueryResponseMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQueryResponseMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') + - [AddQueryResponseMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQueryResponseMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQueryResponseMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddQuerySubscriptionMap(originalChannel,newChannel)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-String,System-String- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.String,System.String)') - [AddQuerySubscriptionMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddQuerySubscriptionMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') @@ -27,15 +31,18 @@ - [DisposeAsync()](#M-MQContract-ContractConnection-DisposeAsync 'MQContract.ContractConnection.DisposeAsync') - [PingAsync()](#M-MQContract-ContractConnection-PingAsync 'MQContract.ContractConnection.PingAsync') - [PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync\`\`1(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync\`\`2(message,timeout,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') +- [InvalidQueryResponseMessageRecieved](#T-MQContract-InvalidQueryResponseMessageRecieved 'MQContract.InvalidQueryResponseMessageRecieved') - [MessageChannelNullException](#T-MQContract-MessageChannelNullException 'MQContract.MessageChannelNullException') - [MessageConversionException](#T-MQContract-MessageConversionException 'MQContract.MessageConversionException') +- [QueryExecutionFailedException](#T-MQContract-QueryExecutionFailedException 'MQContract.QueryExecutionFailedException') - [QueryResponseException](#T-MQContract-QueryResponseException 'MQContract.QueryResponseException') +- [QueryTimeoutException](#T-MQContract-QueryTimeoutException 'MQContract.QueryTimeoutException') - [SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') - [UnknownResponseTypeException](#T-MQContract-UnknownResponseTypeException 'MQContract.UnknownResponseTypeException') @@ -101,6 +108,23 @@ The current instance of the Channel Mapper | ---- | ---- | ----------- | | mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | + +### AddDefaultQueryResponseMap(mapFunction) `method` + +##### Summary + +Add a default map function to call for query/response response calls + +##### Returns + +The current instance of the Channel Mapper + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | + ### AddDefaultQuerySubscriptionMap(mapFunction) `method` @@ -280,6 +304,60 @@ The current instance of the Channel Mapper | isMatch | [System.Func{System.String,System.Boolean}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Boolean}') | A callback that will return true if the supplied function will mape that channel | | mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | + +### AddQueryResponseMap(originalChannel,newChannel) `method` + +##### Summary + +Add a direct map for query/response response calls + +##### Returns + +The current instance of the Channel Mapper + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | +| newChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel to map it to | + + +### AddQueryResponseMap(originalChannel,mapFunction) `method` + +##### Summary + +Add a map function for query/response response calls + +##### Returns + +The current instance of the Channel Mapper + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| originalChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The original channel that is being used in the connection | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | + + +### AddQueryResponseMap(isMatch,mapFunction) `method` + +##### Summary + +Add a map function call pair for query/response response calls + +##### Returns + +The current instance of the Channel Mapper + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| isMatch | [System.Func{System.String,System.Boolean}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Boolean}') | A callback that will return true if the supplied function will mape that channel | +| mapFunction | [System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}}') | A function to be called with the channel supplied expecting a mapped channel name | + ### AddQuerySubscriptionMap(originalChannel,newChannel) `method` @@ -453,8 +531,8 @@ An instance of the TransmissionResult record to indicate success or failure and | ---- | ----------- | | T | The type of message to publish | - -### QueryAsync\`\`1(message,timeout,channel,messageHeader,options,cancellationToken) `method` + +### QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken) `method` ##### Summary @@ -471,6 +549,8 @@ A QueryResult that will contain the response message and or an error | message | [\`\`0](#T-``0 '``0') | The message to transmit for the query | | timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The timeout to allow for waiting for a response | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on | +| responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is +only used when the underlying connection does not support a QueryResponse style messaging. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | A message header to be sent across with the message | | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -487,8 +567,8 @@ A QueryResult that will contain the response message and or an error | ---- | ----------- | | [MQContract.UnknownResponseTypeException](#T-MQContract-UnknownResponseTypeException 'MQContract.UnknownResponseTypeException') | Thrown when the supplied Query type does not have a QueryResponseTypeAttribute and therefore a response type cannot be determined | - -### QueryAsync\`\`2(message,timeout,channel,messageHeader,options,cancellationToken) `method` + +### QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken) `method` ##### Summary @@ -505,6 +585,8 @@ A QueryResult that will contain the response message and or an error | message | [\`\`0](#T-``0 '``0') | The message to transmit for the query | | timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The timeout to allow for waiting for a response | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on | +| responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is +only used when the underlying connection does not support a QueryResponse style messaging. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | A message header to be sent across with the message | | options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -658,6 +740,17 @@ An instance of the Subscription that can be held or called to end | ---- | ----------- | | [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | + +## InvalidQueryResponseMessageRecieved `type` + +##### Namespace + +MQContract + +##### Summary + +Thrown when a query call message is recieved without proper data + ## MessageChannelNullException `type` @@ -680,6 +773,17 @@ MQContract Thrown when an incoming data message causes a null object return from a converter + +## QueryExecutionFailedException `type` + +##### Namespace + +MQContract + +##### Summary + +Thrown when a query call is being made to a service that does not support query response and the listener cannot be created + ## QueryResponseException `type` @@ -691,6 +795,17 @@ MQContract Thrown when a Query call is made and there is an error in the response + +## QueryTimeoutException `type` + +##### Namespace + +MQContract + +##### Summary + +Thrown when a query call times out waiting for the response + ## SubscriptionFailedException `type` diff --git a/Core/Subscriptions/PubSubSubscription.cs b/Core/Subscriptions/PubSubSubscription.cs index 57199f7..cb40cce 100644 --- a/Core/Subscriptions/PubSubSubscription.cs +++ b/Core/Subscriptions/PubSubSubscription.cs @@ -3,7 +3,6 @@ using MQContract.Interfaces.Factories; using MQContract.Interfaces.Service; using MQContract.Messages; -using System.Threading.Channels; namespace MQContract.Subscriptions { diff --git a/Core/Subscriptions/QueryResponseHelper.cs b/Core/Subscriptions/QueryResponseHelper.cs new file mode 100644 index 0000000..b6f521e --- /dev/null +++ b/Core/Subscriptions/QueryResponseHelper.cs @@ -0,0 +1,78 @@ +using MQContract.Interfaces.Service; +using MQContract.Messages; + +namespace MQContract.Subscriptions +{ + internal static class QueryResponseHelper + { + private const string QUERY_IDENTIFIER_HEADER = "_QueryClientID"; + private const string REPLY_ID = "_QueryReplyID"; + private const string REPLY_CHANNEL_HEADER = "_QueryReplyChannel"; + private static readonly List REQUIRED_HEADERS = [QUERY_IDENTIFIER_HEADER, REPLY_ID, REPLY_CHANNEL_HEADER]; + + public static MessageHeader StripHeaders(ServiceMessage originalMessage,out Guid queryClientID,out Guid replyID,out string? replyChannel) + { + queryClientID = new(originalMessage.Header[QUERY_IDENTIFIER_HEADER]!); + replyID = new(originalMessage.Header[REPLY_ID]!); + replyChannel = originalMessage.Header[REPLY_CHANNEL_HEADER]; + return new(originalMessage.Header.Keys + .Where(key=>!Equals(key,QUERY_IDENTIFIER_HEADER) + && !Equals(key,REPLY_ID) + && !Equals(key,REPLY_CHANNEL_HEADER) + ).Select(key =>new KeyValuePair(key,originalMessage.Header[key]!))); + } + + public static ServiceMessage EncodeMessage(ServiceMessage originalMessage, Guid queryClientID, Guid replyID,string? replyChannel,string? channel) + => new( + originalMessage.ID, + originalMessage.MessageTypeID, + channel??originalMessage.Channel, + new(originalMessage.Header,new Dictionary([ + new KeyValuePair(QUERY_IDENTIFIER_HEADER,queryClientID.ToString()), + new KeyValuePair(REPLY_ID,replyID.ToString()), + new KeyValuePair(REPLY_CHANNEL_HEADER,replyChannel) + ])), + originalMessage.Data + ); + + public static bool IsValidMessage(RecievedServiceMessage serviceMessage) + => REQUIRED_HEADERS.All(key=>serviceMessage.Header.Keys.Contains(key)); + + public static async Task, CancellationTokenSource>> StartResponseListenerAsync(IMessageServiceConnection connection,TimeSpan timeout,Guid identifier,Guid callID,string replyChannel,CancellationToken cancellationToken) + { + var token = new CancellationTokenSource(); + var reg = cancellationToken.Register(() => token.Cancel()); + var result = new TaskCompletionSource(); + var consumer = await connection.SubscribeAsync( + (message) => + { + if (!result.Task.IsCompleted) + { + var headers = StripHeaders(message, out var queryClientID, out var replyID, out _); + if (Equals(queryClientID, identifier) && Equals(replyID, callID)) + { + result.TrySetResult(new( + message.ID, + headers, + message.MessageTypeID, + message.Data + )); + } + } + }, + error => { }, + replyChannel, + Guid.NewGuid().ToString(), + cancellationToken: token.Token + )??throw new QueryExecutionFailedException(); + token.Token.Register(async () => { + await consumer.EndAsync(); + await reg.DisposeAsync(); + if (!result.Task.IsCompleted) + result.TrySetException(new QueryTimeoutException()); + }); + token.CancelAfter(timeout); + return new Tuple, CancellationTokenSource>(result,token); + } + } +} diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index d6b81ac..2c1ed85 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -20,14 +20,43 @@ internal sealed class QueryResponseSubscription(IMessageFactory queryMes public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnection connection, CancellationToken cancellationToken) { - serviceSubscription = await connection.SubscribeQueryAsync( - serviceMessage => ProcessServiceMessageAsync(serviceMessage), - error => errorRecieved(error), - MessageChannel, - group??Guid.NewGuid().ToString(), - options: options, - cancellationToken: cancellationToken - ); + if (connection is IQueryableMessageServiceConnection queryableMessageServiceConnection) + serviceSubscription = await queryableMessageServiceConnection.SubscribeQueryAsync( + serviceMessage => ProcessServiceMessageAsync(serviceMessage), + error => errorRecieved(error), + MessageChannel, + group??Guid.NewGuid().ToString(), + options: options, + cancellationToken: cancellationToken + ); + else + { + serviceSubscription = await connection.SubscribeAsync( + async (serviceMessage) => + { + if (!QueryResponseHelper.IsValidMessage(serviceMessage)) + errorRecieved(new InvalidQueryResponseMessageRecieved()); + else + { + var result = await ProcessServiceMessageAsync( + new( + serviceMessage.ID, + serviceMessage.MessageTypeID, + serviceMessage.Channel, + QueryResponseHelper.StripHeaders(serviceMessage, out var queryClientID, out var replyID, out var replyChannel), + serviceMessage.Data + ) + ); + await connection.PublishAsync(QueryResponseHelper.EncodeMessage(result, queryClientID, replyID, null,replyChannel), null, cancellationToken); + } + }, + error=> errorRecieved(error), + MessageChannel, + group??Guid.NewGuid().ToString(), + options:options, + cancellationToken:cancellationToken + ); + } return serviceSubscription!=null; } diff --git a/Samples/KafkaSample/Program.cs b/Samples/KafkaSample/Program.cs index 8085a9e..89c35e0 100644 --- a/Samples/KafkaSample/Program.cs +++ b/Samples/KafkaSample/Program.cs @@ -1,8 +1,6 @@ using Messages; using MQContract; using MQContract.Kafka; -using MQContract.Kafka.Options; -using MQContract.Messages; using var sourceCancel = new CancellationTokenSource(); @@ -66,9 +64,9 @@ await Task.WhenAll( result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); -var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), options:new QueryChannelOptions("Greeting.Response"), cancellationToken: sourceCancel.Token); +var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), responseChannel: "Greeting.Response", cancellationToken: sourceCancel.Token); Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); -response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), options: new QueryChannelOptions("Greeting.Response"), cancellationToken: sourceCancel.Token); +response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), responseChannel: "Greeting.Response", cancellationToken: sourceCancel.Token); Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); diff --git a/Samples/KubeMQSample/Program.cs b/Samples/KubeMQSample/Program.cs index 18f6a05..49c02b6 100644 --- a/Samples/KubeMQSample/Program.cs +++ b/Samples/KubeMQSample/Program.cs @@ -2,7 +2,6 @@ using MQContract; using MQContract.KubeMQ; using MQContract.KubeMQ.Options; -using MQContract.Messages; using var sourceCancel = new CancellationTokenSource(); diff --git a/Samples/NATSSample/Program.cs b/Samples/NATSSample/Program.cs index 375bf90..d3519fc 100644 --- a/Samples/NATSSample/Program.cs +++ b/Samples/NATSSample/Program.cs @@ -1,6 +1,5 @@ using Messages; using MQContract; -using MQContract.Messages; using MQContract.NATS; using MQContract.NATS.Options; using NATS.Client.JetStream.Models; From 548080d3b80dba02d392546d9870cce53765235c Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Wed, 4 Sep 2024 21:38:58 -0400 Subject: [PATCH 08/22] moved packaging info to shared moved all the packaging info to a shared props to make changing version numbers easier --- Abstractions/Abstractions.csproj | 16 ++-------------- Connectors/ActiveMQ/ActiveMQ.csproj | 13 ++++++++++--- Connectors/Kafka/Kafka.csproj | 26 +++++++------------------- Connectors/KubeMQ/KubeMQ.csproj | 25 +++++++------------------ Connectors/NATS/NATS.csproj | 25 +++++++------------------ Core/Core.csproj | 15 ++------------- Shared.props | 21 +++++++++++++++++++++ 7 files changed, 56 insertions(+), 85 deletions(-) create mode 100644 Shared.props diff --git a/Abstractions/Abstractions.csproj b/Abstractions/Abstractions.csproj index 184e465..db88fe8 100644 --- a/Abstractions/Abstractions.csproj +++ b/Abstractions/Abstractions.csproj @@ -1,26 +1,14 @@  + + net8.0 enable enable MQContract.$(MSBuildProjectName) MQContract - true - $(MSBuildProjectDirectory)\Readme.md - True - roger-castaldo Abstractions for MQContract - $(AssemblyName) - https://github.com/roger-castaldo/MQContract - Readme.md - https://github.com/roger-castaldo/MQContract - Message Queue MQ Contract - MIT - True - True - True - snupkg diff --git a/Connectors/ActiveMQ/ActiveMQ.csproj b/Connectors/ActiveMQ/ActiveMQ.csproj index 21a7d28..8688663 100644 --- a/Connectors/ActiveMQ/ActiveMQ.csproj +++ b/Connectors/ActiveMQ/ActiveMQ.csproj @@ -1,13 +1,13 @@  + - net8.0 + net8.0 enable enable MQContract.$(MSBuildProjectName) MQContract.$(MSBuildProjectName) - true - $(MSBuildProjectDirectory)\Readme.md + ActiveMQ Connector for MQContract @@ -21,4 +21,11 @@ + + + + True + \ + + diff --git a/Connectors/Kafka/Kafka.csproj b/Connectors/Kafka/Kafka.csproj index f23a1b2..fde9302 100644 --- a/Connectors/Kafka/Kafka.csproj +++ b/Connectors/Kafka/Kafka.csproj @@ -1,26 +1,14 @@  + + - net8.0 - enable - enable - MQContract.$(MSBuildProjectName) - MQContract.$(MSBuildProjectName) - true - $(MSBuildProjectDirectory)\Readme.md - True - roger-castaldo + net8.0 + enable + enable + MQContract.$(MSBuildProjectName) + MQContract.$(MSBuildProjectName) Kafka Connector for MQContract - $(AssemblyName) - https://github.com/roger-castaldo/MQContract - Readme.md - https://github.com/roger-castaldo/MQContract - Message Queue MQ Contract Kafka - MIT - True - True - True - snupkg diff --git a/Connectors/KubeMQ/KubeMQ.csproj b/Connectors/KubeMQ/KubeMQ.csproj index cacaa90..12d1be1 100644 --- a/Connectors/KubeMQ/KubeMQ.csproj +++ b/Connectors/KubeMQ/KubeMQ.csproj @@ -1,25 +1,14 @@  + + - net8.0 - enable - enable - MQContract.$(MSBuildProjectName) - MQContract.$(MSBuildProjectName) - true - $(MSBuildProjectDirectory)\Readme.md - roger-castaldo + net8.0 + enable + enable + MQContract.$(MSBuildProjectName) + MQContract.$(MSBuildProjectName) KubeMQ Connector for MQContract - $(AssemblyName) - https://github.com/roger-castaldo/MQContract - Readme.md - https://github.com/roger-castaldo/MQContract - Message Queue MQ Contract KubeMQ - MIT - True - True - True - snupkg diff --git a/Connectors/NATS/NATS.csproj b/Connectors/NATS/NATS.csproj index 40022d1..172f204 100644 --- a/Connectors/NATS/NATS.csproj +++ b/Connectors/NATS/NATS.csproj @@ -1,25 +1,14 @@  + + - net8.0 - enable - enable - MQContract.$(MSBuildProjectName) - MQContract.$(MSBuildProjectName) - true - $(MSBuildProjectDirectory)\Readme.md - roger-castaldo + net8.0 + enable + enable + MQContract.$(MSBuildProjectName) + MQContract.$(MSBuildProjectName) NATS.io Connector for MQContract - $(AssemblyName) - https://github.com/roger-castaldo/MQContract - Readme.md - https://github.com/roger-castaldo/MQContract - Message Queue MQ Contract NATS - MIT - True - True - True - snupkg diff --git a/Core/Core.csproj b/Core/Core.csproj index 68d6ef1..1da2aea 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -1,26 +1,15 @@  + + net8.0 enable enable MQContract MQContract - true - $(MSBuildProjectDirectory)\Readme.md - True - roger-castaldo Core for MQContract $(AssemblyName) - https://github.com/roger-castaldo/MQContract - Readme.md - https://github.com/roger-castaldo/MQContract - Message Queue MQ Contract - MIT - True - True - True - snupkg diff --git a/Shared.props b/Shared.props new file mode 100644 index 0000000..b6b6a2a --- /dev/null +++ b/Shared.props @@ -0,0 +1,21 @@ + + + 1.1 + https://github.com/roger-castaldo/MQContract + Readme.md + https://github.com/roger-castaldo/MQContract + $(VersionPrefix) + $(VersionPrefix) + Message Queue MQ Contract + MIT + True + True + True + snupkg + true + $(MSBuildProjectDirectory)\Readme.md + True + roger-castaldo + $(AssemblyName) + + \ No newline at end of file From a781c137df3e332e43b4861091fe14f4db771a5c Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Fri, 6 Sep 2024 14:19:20 -0400 Subject: [PATCH 09/22] Corrected ActiveMQ changed underlying package used for ActiveMQ as well as produced a sample application using it and tested functionality. --- Connectors/ActiveMQ/ActiveMQ.csproj | 4 +- Connectors/ActiveMQ/Connection.cs | 53 +++++++- Connectors/ActiveMQ/Readme.md | 127 +++++++++++++++++++ Connectors/Kafka/Connection.cs | 7 +- Connectors/Kafka/Readme.md | 8 +- MQContract.sln | 9 +- Samples/ActiveMQSample/ActiveMQSample.csproj | 16 +++ Samples/ActiveMQSample/Program.cs | 76 +++++++++++ Samples/KafkaSample/Program.cs | 4 +- Samples/Messages/Greeting.cs | 1 + 10 files changed, 287 insertions(+), 18 deletions(-) create mode 100644 Samples/ActiveMQSample/ActiveMQSample.csproj create mode 100644 Samples/ActiveMQSample/Program.cs diff --git a/Connectors/ActiveMQ/ActiveMQ.csproj b/Connectors/ActiveMQ/ActiveMQ.csproj index 8688663..1bab3fd 100644 --- a/Connectors/ActiveMQ/ActiveMQ.csproj +++ b/Connectors/ActiveMQ/ActiveMQ.csproj @@ -1,6 +1,6 @@  - + net8.0 enable @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index cbd6b49..9479883 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -6,24 +6,35 @@ namespace MQContract.ActiveMQ { - internal class Connection : IMessageServiceConnection,IAsyncDisposable,IDisposable + /// + /// This is the MessageServiceConnection implemenation for using ActiveMQ + /// + public class Connection : IMessageServiceConnection,IAsyncDisposable,IDisposable { private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; private bool disposedValue; - private readonly IConnectionFactory connectionFactory; private readonly IConnection connection; private readonly ISession session; private readonly IMessageProducer producer; - public Connection(Uri ConnectUri){ - connectionFactory = new NMSConnectionFactory(ConnectUri); - connection = connectionFactory.CreateConnection(); + /// + /// Default constructor for creating instance + /// + /// The connection url to use + /// The username to use + /// The password to use + public Connection(Uri ConnectUri,string username,string password){ + var connectionFactory = new NMSConnectionFactory(ConnectUri); + connection = connectionFactory.CreateConnection(username,password); connection.Start(); session = connection.CreateSession(); producer = session.CreateProducer(); } + /// + /// The maximum message body size allowed + /// public int? MaxMessageBodySize => 4*1024*1024; /// @@ -64,8 +75,17 @@ internal static RecievedServiceMessage ProduceMessage(string channel, IMessage m ); } + /// + /// Called to publish a message into the ActiveMQ server + /// + /// The service message being sent + /// The service channel options which should be null as there is no implementations for ActiveMQ + /// A cancellation token + /// Transmition result identifying if it worked or not + /// Thrown if options was supplied because there are no implemented options for this call public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { + NoChannelOptionsAvailableException.ThrowIfNotNull(options); try { await producer.SendAsync(SessionUtil.GetTopic(session, message.Channel), await ProduceMessage(message)); @@ -77,16 +97,36 @@ public async ValueTask PublishAsync(ServiceMessage message, } } + /// + /// Called to create a subscription to the underlying ActiveMQ server + /// + /// Callback for when a message is recieved + /// Callback for when an error occurs + /// The name of the channel to bind to + /// The group to subscribe as part of + /// should be null + /// + /// + /// Thrown if options was supplied because there are no implemented options for this call public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) { + NoChannelOptionsAvailableException.ThrowIfNotNull(options); var result = new SubscriptionBase((msg)=>messageRecieved(ProduceMessage(channel,msg)), errorRecieved,session, channel, group); await result.StartAsync(); return result; } + /// + /// Called to close off the underlying ActiveMQ Connection + /// + /// public async ValueTask CloseAsync() => await connection.StopAsync(); + /// + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// + /// A task required for disposal public async ValueTask DisposeAsync() { await connection.StopAsync().ConfigureAwait(true); @@ -109,6 +149,9 @@ protected virtual void Dispose(bool disposing) } } + /// + /// Called to dispose of the required resources + /// public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method diff --git a/Connectors/ActiveMQ/Readme.md b/Connectors/ActiveMQ/Readme.md index a9144dd..e6cd7a5 100644 --- a/Connectors/ActiveMQ/Readme.md +++ b/Connectors/ActiveMQ/Readme.md @@ -4,7 +4,14 @@ ## Contents - [Connection](#T-MQContract-ActiveMQ-Connection 'MQContract.ActiveMQ.Connection') + - [#ctor(ConnectUri,username,password)](#M-MQContract-ActiveMQ-Connection-#ctor-System-Uri,System-String,System-String- 'MQContract.ActiveMQ.Connection.#ctor(System.Uri,System.String,System.String)') - [DefaultTimout](#P-MQContract-ActiveMQ-Connection-DefaultTimout 'MQContract.ActiveMQ.Connection.DefaultTimout') + - [MaxMessageBodySize](#P-MQContract-ActiveMQ-Connection-MaxMessageBodySize 'MQContract.ActiveMQ.Connection.MaxMessageBodySize') + - [CloseAsync()](#M-MQContract-ActiveMQ-Connection-CloseAsync 'MQContract.ActiveMQ.Connection.CloseAsync') + - [Dispose()](#M-MQContract-ActiveMQ-Connection-Dispose 'MQContract.ActiveMQ.Connection.Dispose') + - [DisposeAsync()](#M-MQContract-ActiveMQ-Connection-DisposeAsync 'MQContract.ActiveMQ.Connection.DisposeAsync') + - [PublishAsync(message,options,cancellationToken)](#M-MQContract-ActiveMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ActiveMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-ActiveMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ActiveMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') ## Connection `type` @@ -13,6 +20,25 @@ MQContract.ActiveMQ +##### Summary + +This is the MessageServiceConnection implemenation for using ActiveMQ + + +### #ctor(ConnectUri,username,password) `constructor` + +##### Summary + +Default constructor for creating instance + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| ConnectUri | [System.Uri](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Uri 'System.Uri') | The connection url to use | +| username | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The username to use | +| password | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The password to use | + ### DefaultTimout `property` @@ -20,3 +46,104 @@ MQContract.ActiveMQ The default timeout to use for RPC calls when not specified by the class or in the call. DEFAULT:1 minute if not specified inside the connection options + + +### MaxMessageBodySize `property` + +##### Summary + +The maximum message body size allowed + + +### CloseAsync() `method` + +##### Summary + +Called to close off the underlying ActiveMQ Connection + +##### Returns + + + +##### Parameters + +This method has no parameters. + + +### Dispose() `method` + +##### Summary + +Called to dispose of the required resources + +##### Parameters + +This method has no parameters. + + +### DisposeAsync() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Returns + +A task required for disposal + +##### Parameters + +This method has no parameters. + + +### PublishAsync(message,options,cancellationToken) `method` + +##### Summary + +Called to publish a message into the ActiveMQ server + +##### Returns + +Transmition result identifying if it worked or not + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options which should be null as there is no implementations for ActiveMQ | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +##### Exceptions + +| Name | Description | +| ---- | ----------- | +| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | + + +### SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +##### Summary + +Called to create a subscription to the underlying ActiveMQ server + +##### Returns + + + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | should be null | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | | + +##### Exceptions + +| Name | Description | +| ---- | ----------- | +| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 8f4d9cb..6abf9d7 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -9,14 +9,13 @@ namespace MQContract.Kafka /// /// This is the MessageServiceConnection implementation for using Kafka /// - /// + /// The Kafka Client Configuration to provide public sealed class Connection(ClientConfig clientConfig) : IMessageServiceConnection { private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; private readonly IProducer producer = new ProducerBuilder(clientConfig).Build(); private readonly ClientConfig clientConfig = clientConfig; - private readonly Guid Identifier = Guid.NewGuid(); private bool disposedValue; /// @@ -62,7 +61,7 @@ internal static MessageHeader ExtractHeaders(Headers header,out string? messageT /// Called to publish a message into the Kafka server /// /// The service message being sent - /// The service channel options, if desired, specifically the PublishChannelOptions which is used to access the storage capabilities of KubeMQ + /// The service channel options which should be null as there is no implementations for Kafka /// A cancellation token /// Transmition result identifying if it worked or not /// Thrown if options was supplied because there are no implemented options for this call @@ -92,7 +91,7 @@ public async ValueTask PublishAsync(ServiceMessage message, /// Callback for when an error occurs /// The name of the channel to bind to /// The group to subscribe as part of - /// + /// should be null /// /// /// Thrown if options was supplied because there are no implemented options for this call diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index 70cef84..5807912 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -28,7 +28,7 @@ This is the MessageServiceConnection implementation for using Kafka | Name | Type | Description | | ---- | ---- | ----------- | -| clientConfig | [T:MQContract.Kafka.Connection](#T-T-MQContract-Kafka-Connection 'T:MQContract.Kafka.Connection') | | +| clientConfig | [T:MQContract.Kafka.Connection](#T-T-MQContract-Kafka-Connection 'T:MQContract.Kafka.Connection') | The Kafka Client Configuration to provide | ### #ctor(clientConfig) `constructor` @@ -41,7 +41,7 @@ This is the MessageServiceConnection implementation for using Kafka | Name | Type | Description | | ---- | ---- | ----------- | -| clientConfig | [Confluent.Kafka.ClientConfig](#T-Confluent-Kafka-ClientConfig 'Confluent.Kafka.ClientConfig') | | +| clientConfig | [Confluent.Kafka.ClientConfig](#T-Confluent-Kafka-ClientConfig 'Confluent.Kafka.ClientConfig') | The Kafka Client Configuration to provide | ### DefaultTimout `property` @@ -115,7 +115,7 @@ Transmition result identifying if it worked or not | Name | Type | Description | | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options, if desired, specifically the PublishChannelOptions which is used to access the storage capabilities of KubeMQ | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options which should be null as there is no implementations for Kafka | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Exceptions @@ -143,7 +143,7 @@ Called to create a subscription to the underlying Kafka server | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | | +| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | should be null | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | | ##### Exceptions diff --git a/MQContract.sln b/MQContract.sln index 9885199..85857ed 100644 --- a/MQContract.sln +++ b/MQContract.sln @@ -27,7 +27,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kafka", "Connectors\Kafka\K EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KafkaSample", "Samples\KafkaSample\KafkaSample.csproj", "{76FFF0EF-C7F4-4D07-9CE1-CA695037CC11}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActiveMQ", "Connectors\ActiveMQ\ActiveMQ.csproj", "{3DF8097C-D24F-4AB9-98E3-A17607ECCE25}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActiveMQ", "Connectors\ActiveMQ\ActiveMQ.csproj", "{3DF8097C-D24F-4AB9-98E3-A17607ECCE25}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActiveMQSample", "Samples\ActiveMQSample\ActiveMQSample.csproj", "{F734932E-2624-4ADC-8EBE-FCE579AF09D9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -79,6 +81,10 @@ Global {3DF8097C-D24F-4AB9-98E3-A17607ECCE25}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DF8097C-D24F-4AB9-98E3-A17607ECCE25}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DF8097C-D24F-4AB9-98E3-A17607ECCE25}.Release|Any CPU.Build.0 = Release|Any CPU + {F734932E-2624-4ADC-8EBE-FCE579AF09D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F734932E-2624-4ADC-8EBE-FCE579AF09D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F734932E-2624-4ADC-8EBE-FCE579AF09D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F734932E-2624-4ADC-8EBE-FCE579AF09D9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -92,6 +98,7 @@ Global {E3CE4E3B-8500-4888-BBA5-0256A129C9F5} = {FCAD12F9-6992-44D7-8E78-464181584E06} {76FFF0EF-C7F4-4D07-9CE1-CA695037CC11} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} {3DF8097C-D24F-4AB9-98E3-A17607ECCE25} = {FCAD12F9-6992-44D7-8E78-464181584E06} + {F734932E-2624-4ADC-8EBE-FCE579AF09D9} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F4431E3A-7CBE-469C-A7FE-0A48F6768E02} diff --git a/Samples/ActiveMQSample/ActiveMQSample.csproj b/Samples/ActiveMQSample/ActiveMQSample.csproj new file mode 100644 index 0000000..3b86aa8 --- /dev/null +++ b/Samples/ActiveMQSample/ActiveMQSample.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/Samples/ActiveMQSample/Program.cs b/Samples/ActiveMQSample/Program.cs new file mode 100644 index 0000000..269dbe2 --- /dev/null +++ b/Samples/ActiveMQSample/Program.cs @@ -0,0 +1,76 @@ +using Messages; +using MQContract; +using MQContract.ActiveMQ; + +using var sourceCancel = new CancellationTokenSource(); + +Console.CancelKeyPress += delegate { + sourceCancel.Cancel(); +}; + +var serviceConnection = new Connection(new Uri("amqp:tcp://localhost:5672"),"artemis","artemis"); + +var contractConnection = new ContractConnection(serviceConnection); + +var announcementSubscription = await contractConnection.SubscribeAsync( + (announcement) => + { + Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); + return ValueTask.CompletedTask; + }, + (error) => Console.WriteLine($"Announcement error: {error.Message}"), + cancellationToken: sourceCancel.Token +); + +var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( + (greeting) => + { + Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); + System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); + return new( + $"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the Kafka sample" + ); + }, + (error) => Console.WriteLine($"Greeting error: {error.Message}"), + cancellationToken: sourceCancel.Token +); + +var storedArrivalSubscription = await contractConnection.SubscribeAsync( + (announcement) => + { + Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); + return ValueTask.CompletedTask; + }, + (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), + cancellationToken: sourceCancel.Token +); + +sourceCancel.Token.Register(async () => +{ + await Task.WhenAll( + announcementSubscription.EndAsync().AsTask(), + greetingSubscription.EndAsync().AsTask(), + storedArrivalSubscription.EndAsync().AsTask() + ).ConfigureAwait(true); + await contractConnection.CloseAsync().ConfigureAwait(true); +}, true); + +var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); +Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); +result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); +Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); + +var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); +Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); +response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); +Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); + +var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); +Console.WriteLine($"Stored Result 1 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); +storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); +Console.WriteLine($"Stored Result 2 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); + +Console.WriteLine("Press Ctrl+C to close"); + +sourceCancel.Token.WaitHandle.WaitOne(); +Console.WriteLine("System completed operation"); diff --git a/Samples/KafkaSample/Program.cs b/Samples/KafkaSample/Program.cs index 89c35e0..900e466 100644 --- a/Samples/KafkaSample/Program.cs +++ b/Samples/KafkaSample/Program.cs @@ -64,9 +64,9 @@ await Task.WhenAll( result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); -var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), responseChannel: "Greeting.Response", cancellationToken: sourceCancel.Token); +var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); -response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), responseChannel: "Greeting.Response", cancellationToken: sourceCancel.Token); +response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); diff --git a/Samples/Messages/Greeting.cs b/Samples/Messages/Greeting.cs index a8c6947..49f1b66 100644 --- a/Samples/Messages/Greeting.cs +++ b/Samples/Messages/Greeting.cs @@ -6,5 +6,6 @@ namespace Messages [MessageName("Nametag")] [MessageVersion("1.0.0.0")] [QueryResponseType(typeof(string))] + [QueryResponseChannel("Greeting.Response")] public record Greeting(string FirstName,string LastName){} } From 521f72ae250a9f66e5549e300163fd89394ea317 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Tue, 10 Sep 2024 12:08:17 -0400 Subject: [PATCH 10/22] changed service channel options Removed the Channel Service Options concept form the higher levels and allowed for it to be specified through the underlying connector to clean up and simplify the Contract Connection concepts and usage. --- .../MessageResponseTimeoutAttribute.cs | 5 + Abstractions/Exceptions.cs | 70 ----- .../Interfaces/IContractConnection.cs | 28 +- .../Service/IMessageServiceConnection.cs | 15 +- .../IQueryableMessageServiceConnection.cs | 12 +- .../Service/IServiceChannelOptions.cs | 9 - Abstractions/Readme.md | 208 ++++----------- AutomatedTesting/ChannelMapperTests.cs | 246 ++++++++---------- .../ContractConnectionTests/PublishTests.cs | 138 +++------- .../ContractConnectionTests/QueryTests.cs | 132 +++------- .../QueryWithoutQueryResponseTests.cs | 113 +++----- .../SubscribeQueryResponseTests.cs | 186 +++++-------- ...beQueryResponseWithoutQueryResponseTest.cs | 21 +- .../ContractConnectionTests/SubscribeTests.cs | 205 ++++++--------- AutomatedTesting/TestServiceChannelOptions.cs | 6 - Connectors/ActiveMQ/Connection.cs | 24 +- Connectors/ActiveMQ/Readme.md | 39 +-- Connectors/Kafka/Connection.cs | 22 +- Connectors/Kafka/Readme.md | 39 +-- Connectors/KubeMQ/Connection.cs | 104 ++++++-- .../KubeMQ/Options/PublishChannelOptions.cs | 12 - .../KubeMQ/Options/StoredChannelOptions.cs | 12 + .../StoredEventsSubscriptionOptions.cs | 45 ---- Connectors/KubeMQ/Readme.md | 227 ++++++---------- .../KubeMQ/Subscriptions/PubSubscription.cs | 2 +- Connectors/NATS/Connection.cs | 88 ++++--- .../Options/StreamPublishChannelOptions.cs | 13 - .../Options/StreamPublishSubscriberOptions.cs | 14 - .../Options/SubscriptionConsumerConfig.cs | 13 + Connectors/NATS/Readme.md | 154 +++-------- Core/ContractConnection.cs | 61 ++--- Core/Factories/MessageTypeFactory.cs | 4 +- Core/Readme.md | 49 ++-- Core/Subscriptions/PubSubSubscription.cs | 11 +- Core/Subscriptions/QueryResponseHelper.cs | 1 - .../QueryResponseSubscription.cs | 13 +- Samples/KubeMQSample/Program.cs | 12 +- Samples/NATSSample/Program.cs | 1 - Shared.props | 2 +- 39 files changed, 801 insertions(+), 1555 deletions(-) delete mode 100644 Abstractions/Exceptions.cs delete mode 100644 Abstractions/Interfaces/Service/IServiceChannelOptions.cs delete mode 100644 AutomatedTesting/TestServiceChannelOptions.cs delete mode 100644 Connectors/KubeMQ/Options/PublishChannelOptions.cs create mode 100644 Connectors/KubeMQ/Options/StoredChannelOptions.cs delete mode 100644 Connectors/KubeMQ/Options/StoredEventsSubscriptionOptions.cs delete mode 100644 Connectors/NATS/Options/StreamPublishChannelOptions.cs delete mode 100644 Connectors/NATS/Options/StreamPublishSubscriberOptions.cs create mode 100644 Connectors/NATS/Options/SubscriptionConsumerConfig.cs diff --git a/Abstractions/Attributes/MessageResponseTimeoutAttribute.cs b/Abstractions/Attributes/MessageResponseTimeoutAttribute.cs index a56c79b..015f9ad 100644 --- a/Abstractions/Attributes/MessageResponseTimeoutAttribute.cs +++ b/Abstractions/Attributes/MessageResponseTimeoutAttribute.cs @@ -16,5 +16,10 @@ public class MessageResponseTimeoutAttribute(int value) : Attribute /// The number of milliseconds for the timeout to trigger for this RPC call class /// public int Value => value; + + /// + /// The converted TimeSpan value from the supplied milliseconds value in the constructor + /// + public TimeSpan TimeSpanValue => TimeSpan.FromMilliseconds(value); } } diff --git a/Abstractions/Exceptions.cs b/Abstractions/Exceptions.cs deleted file mode 100644 index 5733908..0000000 --- a/Abstractions/Exceptions.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MQContract.Interfaces.Service; -using System.Diagnostics.CodeAnalysis; - -namespace MQContract -{ - /// - /// An exception thrown when the options supplied to an underlying system connection are not of an expected type. - /// - [ExcludeFromCodeCoverage(Justification ="This exception is only really thrown from underlying service connection implementations")] - public sealed class InvalidChannelOptionsTypeException - : InvalidCastException - { - internal InvalidChannelOptionsTypeException(IEnumerable expectedTypes,Type recievedType) : - base($"Expected Channel Options of Types[{string.Join(',',expectedTypes.Select(t=>t.FullName))}] but recieved {recievedType.FullName}") - { - } - - - internal InvalidChannelOptionsTypeException(Type expectedType, Type recievedType) : - base($"Expected Channel Options of Type {expectedType.FullName} but recieved {recievedType.FullName}") - { - } - - /// - /// Called to check if the options is of a given type - /// - /// The expected type for the ServiceChannelOptions - /// The supplied service channel options - /// Thrown when the options value is not null and not of type T - public static void ThrowIfNotNullAndNotOfType(IServiceChannelOptions? options) - { - if (options!=null && options is not T) - throw new InvalidChannelOptionsTypeException(typeof(T), options.GetType()); - } - - /// - /// Called to check if the options is one of the given types - /// - /// The supplied service channel options - /// The possible types it can be - /// Thrown when the options value is not null and not of any of the expected Types - public static void ThrowIfNotNullAndNotOfType(IServiceChannelOptions? options,IEnumerable expectedTypes) - { - if (options!=null && !expectedTypes.Contains(options.GetType())) - throw new InvalidChannelOptionsTypeException(expectedTypes, options.GetType()); - } - } - - /// - /// An exception thrown when there are options supplied to an underlying system connection that does not support options for that particular instance - /// - [ExcludeFromCodeCoverage(Justification = "This exception is only really thrown from underlying service connection implementations")] - public sealed class NoChannelOptionsAvailableException - : Exception - { - internal NoChannelOptionsAvailableException() - : base("There are no service channel options available for this action") { } - - /// - /// Called to throw if options is not null - /// - /// The service channel options that were supplied - /// Thrown when the options is not null - public static void ThrowIfNotNull(IServiceChannelOptions? options) - { - if (options!=null) - throw new NoChannelOptionsAvailableException(); - } - } -} diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index e828bde..21f97ba 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -20,10 +20,10 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// The message to send /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The headers to pass along with the message - /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token + /// /// A result indicating the tranmission results - ValueTask PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to create a subscription into the underlying service Pub/Sub style and have the messages processed asynchronously @@ -34,10 +34,10 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class - /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token + /// /// A subscription instance that can be ended when desired - ValueTask SubscribeAsync(Func,ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to create a subscription into the underlying service Pub/Sub style and have the messages processed syncrhonously @@ -48,10 +48,10 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class - /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token + /// /// A subscription instance that can be ended when desired - ValueTask SubscribeAsync(Action> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeAsync(Action> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to send a message into the underlying service in the Query/Response style @@ -64,10 +64,10 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is /// only used when the underlying connection does not support a QueryResponse style messaging. /// The headers to pass along with the message - /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token + /// /// A result indicating the success or failure as well as the returned message - ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel=null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; /// @@ -81,10 +81,10 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is /// only used when the underlying connection does not support a QueryResponse style messaging. /// The headers to pass along with the message - /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token + /// /// A result indicating the success or failure as well as the returned message - ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null,string? responseChannel=null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class; /// /// Called to create a subscription into the underlying service Query/Reponse style and have the messages processed asynchronously @@ -96,10 +96,10 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class - /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token + /// /// A subscription instance that can be ended when desired - ValueTask SubscribeQueryAsyncResponseAsync(Func,ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; /// @@ -112,10 +112,10 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class - /// Any required Service Channel Options that will be passed down to the service Connection /// A cancellation token + /// /// A subscription instance that can be ended when desired - ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; /// diff --git a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs index 01e97d9..d3a590a 100644 --- a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs @@ -11,30 +11,25 @@ public interface IMessageServiceConnection /// /// Maximum supported message body size in bytes /// - int? MaxMessageBodySize { get; } - /// - /// The default timeout to use for RPC calls when it's not specified - /// - TimeSpan DefaultTimout { get; } + uint? MaxMessageBodySize { get; } /// /// Implements a publish call to publish the given message /// /// The message to publish - /// The Service Channel Options instance that was supplied at the Contract Connection level /// A cancellation token + /// /// A transmission result instance indicating the result - ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = new CancellationToken()); /// /// Implements a call to create a subscription to a given channel as a member of a given group /// /// The callback to invoke when a message is recieved /// The callback to invoke when an exception occurs /// The name of the channel to subscribe to - /// The subscription groupt to subscribe as - /// The Service Channel Options instance that was supplied at the Contract Connection level + /// The consumer group to register as /// A cancellation token /// A service subscription object - ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = new CancellationToken()); /// /// Implements a call to close off the connection when the ContractConnection is closed /// diff --git a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs index 925c081..e4fd28f 100644 --- a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs @@ -7,25 +7,27 @@ namespace MQContract.Interfaces.Service /// public interface IQueryableMessageServiceConnection : IMessageServiceConnection { + /// + /// The default timeout to use for RPC calls when it's not specified + /// + TimeSpan DefaultTimout { get; } /// /// Implements a call to submit a response query request into the underlying service /// /// The message to query with /// The timeout for recieving a response - /// The Service Channel Options instance that was supplied at the Contract Connection level /// A cancellation token /// A Query Result instance based on what happened - ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = new CancellationToken()); /// /// Implements a call to create a subscription to a given channel as a member of a given group for responding to queries /// /// The callback to be invoked when a message is recieved, returning the response message /// The callback to invoke when an exception occurs /// The name of the channel to subscribe to - /// The subscription groupt to subscribe as - /// The Service Channel Options instance that was supplied at the Contract Connection level + /// The group to bind a consumer to /// A cancellation token /// A service subscription object - ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = new CancellationToken()); } } diff --git a/Abstractions/Interfaces/Service/IServiceChannelOptions.cs b/Abstractions/Interfaces/Service/IServiceChannelOptions.cs deleted file mode 100644 index fb93d4f..0000000 --- a/Abstractions/Interfaces/Service/IServiceChannelOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MQContract.Interfaces.Service -{ - /// - /// Used to pass service channel options to the underlying service connection. There are no implemented values this is simply mean to be a class marker. - /// - public interface IServiceChannelOptions - { - } -} diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index 2d35140..4c02cdf 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -6,13 +6,13 @@ - [IContractConnection](#T-MQContract-Interfaces-IContractConnection 'MQContract.Interfaces.IContractConnection') - [CloseAsync()](#M-MQContract-Interfaces-IContractConnection-CloseAsync 'MQContract.Interfaces.IContractConnection.CloseAsync') - [PingAsync()](#M-MQContract-Interfaces-IContractConnection-PingAsync 'MQContract.Interfaces.IContractConnection.PingAsync') - - [PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [PublishAsync\`\`1(message,channel,messageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') + - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') + - [QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') - [IEncodedMessage](#T-MQContract-Interfaces-Messages-IEncodedMessage 'MQContract.Interfaces.Messages.IEncodedMessage') - [Data](#P-MQContract-Interfaces-Messages-IEncodedMessage-Data 'MQContract.Interfaces.Messages.IEncodedMessage.Data') - [Header](#P-MQContract-Interfaces-Messages-IEncodedMessage-Header 'MQContract.Interfaces.Messages.IEncodedMessage.Header') @@ -26,11 +26,10 @@ - [DecryptAsync(stream,headers)](#M-MQContract-Interfaces-Encrypting-IMessageEncryptor-DecryptAsync-System-IO-Stream,MQContract-Messages-MessageHeader- 'MQContract.Interfaces.Encrypting.IMessageEncryptor.DecryptAsync(System.IO.Stream,MQContract.Messages.MessageHeader)') - [EncryptAsync(data,headers)](#M-MQContract-Interfaces-Encrypting-IMessageEncryptor-EncryptAsync-System-Byte[],System-Collections-Generic-Dictionary{System-String,System-String}@- 'MQContract.Interfaces.Encrypting.IMessageEncryptor.EncryptAsync(System.Byte[],System.Collections.Generic.Dictionary{System.String,System.String}@)') - [IMessageServiceConnection](#T-MQContract-Interfaces-Service-IMessageServiceConnection 'MQContract.Interfaces.Service.IMessageServiceConnection') - - [DefaultTimout](#P-MQContract-Interfaces-Service-IMessageServiceConnection-DefaultTimout 'MQContract.Interfaces.Service.IMessageServiceConnection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Interfaces-Service-IMessageServiceConnection-MaxMessageBodySize 'MQContract.Interfaces.Service.IMessageServiceConnection.MaxMessageBodySize') - [CloseAsync()](#M-MQContract-Interfaces-Service-IMessageServiceConnection-CloseAsync 'MQContract.Interfaces.Service.IMessageServiceConnection.CloseAsync') - - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [PublishAsync(message,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') + - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - [IMessageTypeEncoder\`1](#T-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1') - [DecodeAsync(stream)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-DecodeAsync-System-IO-Stream- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.DecodeAsync(System.IO.Stream)') - [EncodeAsync(message)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-EncodeAsync-`0- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.EncodeAsync(`0)') @@ -38,22 +37,19 @@ - [IPingableMessageServiceConnection](#T-MQContract-Interfaces-Service-IPingableMessageServiceConnection 'MQContract.Interfaces.Service.IPingableMessageServiceConnection') - [PingAsync()](#M-MQContract-Interfaces-Service-IPingableMessageServiceConnection-PingAsync 'MQContract.Interfaces.Service.IPingableMessageServiceConnection.PingAsync') - [IQueryableMessageServiceConnection](#T-MQContract-Interfaces-Service-IQueryableMessageServiceConnection 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection') - - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [DefaultTimout](#P-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-DefaultTimout 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.DefaultTimout') + - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - [IRecievedMessage\`1](#T-MQContract-Interfaces-IRecievedMessage`1 'MQContract.Interfaces.IRecievedMessage`1') - [Headers](#P-MQContract-Interfaces-IRecievedMessage`1-Headers 'MQContract.Interfaces.IRecievedMessage`1.Headers') - [ID](#P-MQContract-Interfaces-IRecievedMessage`1-ID 'MQContract.Interfaces.IRecievedMessage`1.ID') - [Message](#P-MQContract-Interfaces-IRecievedMessage`1-Message 'MQContract.Interfaces.IRecievedMessage`1.Message') - [ProcessedTimestamp](#P-MQContract-Interfaces-IRecievedMessage`1-ProcessedTimestamp 'MQContract.Interfaces.IRecievedMessage`1.ProcessedTimestamp') - [RecievedTimestamp](#P-MQContract-Interfaces-IRecievedMessage`1-RecievedTimestamp 'MQContract.Interfaces.IRecievedMessage`1.RecievedTimestamp') -- [IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') - [IServiceSubscription](#T-MQContract-Interfaces-Service-IServiceSubscription 'MQContract.Interfaces.Service.IServiceSubscription') - [EndAsync()](#M-MQContract-Interfaces-Service-IServiceSubscription-EndAsync 'MQContract.Interfaces.Service.IServiceSubscription.EndAsync') - [ISubscription](#T-MQContract-Interfaces-ISubscription 'MQContract.Interfaces.ISubscription') - [EndAsync()](#M-MQContract-Interfaces-ISubscription-EndAsync 'MQContract.Interfaces.ISubscription.EndAsync') -- [InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') - - [ThrowIfNotNullAndNotOfType(options,expectedTypes)](#M-MQContract-InvalidChannelOptionsTypeException-ThrowIfNotNullAndNotOfType-MQContract-Interfaces-Service-IServiceChannelOptions,System-Collections-Generic-IEnumerable{System-Type}- 'MQContract.InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(MQContract.Interfaces.Service.IServiceChannelOptions,System.Collections.Generic.IEnumerable{System.Type})') - - [ThrowIfNotNullAndNotOfType\`\`1(options)](#M-MQContract-InvalidChannelOptionsTypeException-ThrowIfNotNullAndNotOfType``1-MQContract-Interfaces-Service-IServiceChannelOptions- 'MQContract.InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType``1(MQContract.Interfaces.Service.IServiceChannelOptions)') - [MessageChannelAttribute](#T-MQContract-Attributes-MessageChannelAttribute 'MQContract.Attributes.MessageChannelAttribute') - [#ctor(name)](#M-MQContract-Attributes-MessageChannelAttribute-#ctor-System-String- 'MQContract.Attributes.MessageChannelAttribute.#ctor(System.String)') - [Name](#P-MQContract-Attributes-MessageChannelAttribute-Name 'MQContract.Attributes.MessageChannelAttribute.Name') @@ -68,12 +64,11 @@ - [Value](#P-MQContract-Attributes-MessageNameAttribute-Value 'MQContract.Attributes.MessageNameAttribute.Value') - [MessageResponseTimeoutAttribute](#T-MQContract-Attributes-MessageResponseTimeoutAttribute 'MQContract.Attributes.MessageResponseTimeoutAttribute') - [#ctor(value)](#M-MQContract-Attributes-MessageResponseTimeoutAttribute-#ctor-System-Int32- 'MQContract.Attributes.MessageResponseTimeoutAttribute.#ctor(System.Int32)') + - [TimeSpanValue](#P-MQContract-Attributes-MessageResponseTimeoutAttribute-TimeSpanValue 'MQContract.Attributes.MessageResponseTimeoutAttribute.TimeSpanValue') - [Value](#P-MQContract-Attributes-MessageResponseTimeoutAttribute-Value 'MQContract.Attributes.MessageResponseTimeoutAttribute.Value') - [MessageVersionAttribute](#T-MQContract-Attributes-MessageVersionAttribute 'MQContract.Attributes.MessageVersionAttribute') - [#ctor(version)](#M-MQContract-Attributes-MessageVersionAttribute-#ctor-System-String- 'MQContract.Attributes.MessageVersionAttribute.#ctor(System.String)') - [Version](#P-MQContract-Attributes-MessageVersionAttribute-Version 'MQContract.Attributes.MessageVersionAttribute.Version') -- [NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') - - [ThrowIfNotNull(options)](#M-MQContract-NoChannelOptionsAvailableException-ThrowIfNotNull-MQContract-Interfaces-Service-IServiceChannelOptions- 'MQContract.NoChannelOptionsAvailableException.ThrowIfNotNull(MQContract.Interfaces.Service.IServiceChannelOptions)') - [PingResult](#T-MQContract-Messages-PingResult 'MQContract.Messages.PingResult') - [#ctor(Host,Version,ResponseTime)](#M-MQContract-Messages-PingResult-#ctor-System-String,System-String,System-TimeSpan- 'MQContract.Messages.PingResult.#ctor(System.String,System.String,System.TimeSpan)') - [Host](#P-MQContract-Messages-PingResult-Host 'MQContract.Messages.PingResult.Host') @@ -156,8 +151,8 @@ Called to Ping the underlying system to obtain both information and ensure it is This method has no parameters. - -### PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken) `method` + +### PublishAsync\`\`1(message,channel,messageHeader,cancellationToken) `method` ##### Summary @@ -174,7 +169,6 @@ A result indicating the tranmission results | message | [\`\`0](#T-``0 '``0') | The message to send | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The headers to pass along with the message | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -183,8 +177,8 @@ A result indicating the tranmission results | ---- | ----------- | | T | The type of message to send | - -### QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken) `method` + +### QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,cancellationToken) `method` ##### Summary @@ -205,7 +199,6 @@ A result indicating the success or failure as well as the returned message | responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is only used when the underlying connection does not support a QueryResponse style messaging. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The headers to pass along with the message | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -214,8 +207,8 @@ only used when the underlying connection does not support a QueryResponse style | ---- | ----------- | | Q | The type of message to send for the query | - -### QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken) `method` + +### QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,cancellationToken) `method` ##### Summary @@ -235,7 +228,6 @@ A result indicating the success or failure as well as the returned message | responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is only used when the underlying connection does not support a QueryResponse style messaging. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The headers to pass along with the message | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -245,8 +237,8 @@ only used when the underlying connection does not support a QueryResponse style | Q | The type of message to send for the query | | R | The type of message to expect back for the response | - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -265,7 +257,6 @@ A subscription instance that can be ended when desired | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -274,8 +265,8 @@ A subscription instance that can be ended when desired | ---- | ----------- | | T | The type of message to listen for | - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -294,7 +285,6 @@ A subscription instance that can be ended when desired | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -303,8 +293,8 @@ A subscription instance that can be ended when desired | ---- | ----------- | | T | The type of message to listen for | - -### SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +### SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -323,7 +313,6 @@ A subscription instance that can be ended when desired | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -333,8 +322,8 @@ A subscription instance that can be ended when desired | Q | The type of message to listen for | | R | The type of message to respond with | - -### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -353,7 +342,6 @@ A subscription instance that can be ended when desired | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Any required Service Channel Options that will be passed down to the service Connection | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -552,13 +540,6 @@ MQContract.Interfaces.Service Defines an underlying service connection. This interface is used to allow for the creation of multiple underlying connection types to support the ability to use common code while being able to run against 1 or more Message services. - -### DefaultTimout `property` - -##### Summary - -The default timeout to use for RPC calls when it's not specified - ### MaxMessageBodySize `property` @@ -581,8 +562,8 @@ A task that the close is running in This method has no parameters. - -### PublishAsync(message,options,cancellationToken) `method` + +### PublishAsync(message,cancellationToken) `method` ##### Summary @@ -597,11 +578,10 @@ A transmission result instance indicating the result | Name | Type | Description | | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The message to publish | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` ##### Summary @@ -618,8 +598,7 @@ A service subscription object | messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | The callback to invoke when a message is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to subscribe to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription groupt to subscribe as | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The consumer group to register as | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -730,8 +709,15 @@ MQContract.Interfaces.Service Extends the base MessageServiceConnection Interface to Response Query messaging methodology if the underlying service supports it - -### QueryAsync(message,timeout,options,cancellationToken) `method` + +### DefaultTimout `property` + +##### Summary + +The default timeout to use for RPC calls when it's not specified + + +### QueryAsync(message,timeout,cancellationToken) `method` ##### Summary @@ -747,11 +733,10 @@ A Query Result instance based on what happened | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The message to query with | | timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout for recieving a response | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` ##### Summary @@ -768,8 +753,7 @@ A service subscription object | messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | The callback to be invoked when a message is recieved, returning the response message | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to subscribe to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription groupt to subscribe as | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The Service Channel Options instance that was supplied at the Contract Connection level | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind a consumer to | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -824,17 +808,6 @@ The timestamp of when the recieved message was converted into the actual class p The timestamp of when the message was recieved by the underlying service connection - -## IServiceChannelOptions `type` - -##### Namespace - -MQContract.Interfaces.Service - -##### Summary - -Used to pass service channel options to the underlying service connection. There are no implemented values this is simply mean to be a class marker. - ## IServiceSubscription `type` @@ -887,62 +860,6 @@ A task that is ending the subscription and closing off the resources for it This method has no parameters. - -## InvalidChannelOptionsTypeException `type` - -##### Namespace - -MQContract - -##### Summary - -An exception thrown when the options supplied to an underlying system connection are not of an expected type. - - -### ThrowIfNotNullAndNotOfType(options,expectedTypes) `method` - -##### Summary - -Called to check if the options is one of the given types - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The supplied service channel options | -| expectedTypes | [System.Collections.Generic.IEnumerable{System.Type}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Collections.Generic.IEnumerable 'System.Collections.Generic.IEnumerable{System.Type}') | The possible types it can be | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when the options value is not null and not of any of the expected Types | - - -### ThrowIfNotNullAndNotOfType\`\`1(options) `method` - -##### Summary - -Called to check if the options is of a given type - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The supplied service channel options | - -##### Generic Types - -| Name | Description | -| ---- | ----------- | -| T | The expected type for the ServiceChannelOptions | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when the options value is not null and not of type T | - ## MessageChannelAttribute `type` @@ -1166,6 +1083,13 @@ be overridden by supplying a timeout value when making an RPC call. + +### TimeSpanValue `property` + +##### Summary + +The converted TimeSpan value from the supplied milliseconds value in the constructor + ### Value `property` @@ -1226,36 +1150,6 @@ it allows you to not necessarily update code for call handling immediately. The version number to tag this class with during transmission - -## NoChannelOptionsAvailableException `type` - -##### Namespace - -MQContract - -##### Summary - -An exception thrown when there are options supplied to an underlying system connection that does not support options for that particular instance - - -### ThrowIfNotNull(options) `method` - -##### Summary - -Called to throw if options is not null - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options that were supplied | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown when the options is not null | - ## PingResult `type` diff --git a/AutomatedTesting/ChannelMapperTests.cs b/AutomatedTesting/ChannelMapperTests.cs index 9776b0e..3c10615 100644 --- a/AutomatedTesting/ChannelMapperTests.cs +++ b/AutomatedTesting/ChannelMapperTests.cs @@ -19,15 +19,12 @@ public async Task TestPublishMapWithStringToString() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new BasicMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; var mapper = new ChannelMapper() @@ -51,7 +48,7 @@ public async Task TestPublishMapWithStringToString() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -62,15 +59,12 @@ public async Task TestPublishMapWithStringToFunction() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new BasicMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; var otherChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-OtherChannel"; @@ -104,7 +98,7 @@ public async Task TestPublishMapWithStringToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -115,16 +109,13 @@ public async Task TestPublishMapWithMatchToFunction() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new BasicMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); - + List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); - + var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; var otherChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-OtherChannel"; var mapper = new ChannelMapper() @@ -157,7 +148,7 @@ public async Task TestPublishMapWithMatchToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -168,16 +159,13 @@ public async Task TestPublishMapWithDefaultFunction() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new BasicMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); - + List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); - + var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; var otherChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-OtherChannel"; var mapper = new ChannelMapper() @@ -210,7 +198,7 @@ public async Task TestPublishMapWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -221,16 +209,13 @@ public async Task TestPublishMapWithSingleMapAndWithDefaultFunction() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new BasicMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); - + List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); - + var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; var otherChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-OtherChannel"; var mapper = new ChannelMapper() @@ -262,7 +247,7 @@ public async Task TestPublishMapWithSingleMapAndWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -275,10 +260,11 @@ public async Task TestPublishSubscribeMapWithStringToString() var channels = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), - It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), + It.IsAny>(), Capture.In(channels), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -290,7 +276,7 @@ public async Task TestPublishSubscribeMapWithStringToString() #region Act var subscription = await contractConnection.SubscribeAsync( - (msg) => ValueTask.CompletedTask, + (msg) => ValueTask.CompletedTask, (error) => { }); #endregion @@ -301,8 +287,7 @@ public async Task TestPublishSubscribeMapWithStringToString() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -318,7 +303,8 @@ public async Task TestPublishSubscribeMapWithStringToFunction() serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -343,7 +329,7 @@ public async Task TestPublishSubscribeMapWithStringToFunction() var subscription2 = await contractConnection.SubscribeAsync( (msg) => ValueTask.CompletedTask, (error) => { }, - channel:otherChannel); + channel: otherChannel); #endregion #region Assert @@ -355,8 +341,7 @@ public async Task TestPublishSubscribeMapWithStringToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -372,7 +357,8 @@ public async Task TestPublishSubscribeMapWithMatchToFunction() serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -407,8 +393,7 @@ public async Task TestPublishSubscribeMapWithMatchToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -424,7 +409,8 @@ public async Task TestPublishSubscribeMapWithDefaultFunction() serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -460,8 +446,7 @@ public async Task TestPublishSubscribeMapWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -477,7 +462,8 @@ public async Task TestPublishSubscribeMapWithSingleMapAndWithDefaultFunction() serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var newChannel = $"{typeof(BasicMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -512,8 +498,7 @@ public async Task TestPublishSubscribeMapWithSingleMapAndWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -535,7 +520,7 @@ public async Task TestQueryMapWithStringToString() List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -564,7 +549,7 @@ public async Task TestQueryMapWithStringToString() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(),It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -586,7 +571,7 @@ public async Task TestQueryMapWithStringToFunction() List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -622,7 +607,7 @@ public async Task TestQueryMapWithStringToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -644,7 +629,7 @@ public async Task TestQueryMapWithMatchToFunction() List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -678,7 +663,7 @@ public async Task TestQueryMapWithMatchToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -700,7 +685,7 @@ public async Task TestQueryMapWithDefaultFunction() List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -735,7 +720,7 @@ public async Task TestQueryMapWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -757,7 +742,7 @@ public async Task TestQueryMapWithSingleMapAndWithDefaultFunction() List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -791,7 +776,7 @@ public async Task TestQueryMapWithSingleMapAndWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -809,7 +794,6 @@ public async Task TestQuerySubscribeMapWithStringToString() It.IsAny>(), Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); @@ -821,9 +805,10 @@ public async Task TestQuerySubscribeMapWithStringToString() #endregion #region Act - var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); - }, (error) => {}); + }, (error) => { }); #endregion #region Assert @@ -834,8 +819,7 @@ public async Task TestQuerySubscribeMapWithStringToString() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -853,7 +837,6 @@ public async Task TestQuerySubscribeMapWithStringToFunction() It.IsAny>(), Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); @@ -873,13 +856,15 @@ public async Task TestQuerySubscribeMapWithStringToFunction() #endregion #region Act - var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }); - var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }, - channel:otherChannel); + channel: otherChannel); #endregion #region Assert @@ -892,8 +877,7 @@ public async Task TestQuerySubscribeMapWithStringToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -911,7 +895,6 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() It.IsAny>(), Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); @@ -929,10 +912,12 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() #endregion #region Act - var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }); - var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }, channel: otherChannel); @@ -948,8 +933,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -967,7 +951,6 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() It.IsAny>(), Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); @@ -986,10 +969,12 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() #endregion #region Act - var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }); - var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }, channel: otherChannel); @@ -1005,8 +990,7 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -1024,7 +1008,6 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() It.IsAny>(), Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); @@ -1042,10 +1025,12 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() #endregion #region Act - var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }); - var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }, channel: otherChannel); @@ -1061,8 +1046,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -1081,18 +1065,17 @@ public async Task TestQueryResponseMapWithStringToString() var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); var mockSubscription = new Mock(); - var defaultTimeout = TimeSpan.FromMinutes(1); - + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) + It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); @@ -1101,9 +1084,7 @@ public async Task TestQueryResponseMapWithStringToString() action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); - + var responseChannel = "Gretting.Response"; var newChannel = $"{responseChannel}-Modded"; @@ -1115,7 +1096,7 @@ public async Task TestQueryResponseMapWithStringToString() #region Act var stopwatch = Stopwatch.StartNew(); - var result = await contractConnection.QueryAsync(testMessage,responseChannel:responseChannel); + var result = await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); #endregion @@ -1129,10 +1110,9 @@ public async Task TestQueryResponseMapWithStringToString() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -1150,18 +1130,17 @@ public async Task TestQueryResponseMapWithStringToFunction() var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); var mockSubscription = new Mock(); - var defaultTimeout = TimeSpan.FromMinutes(1); - + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) + It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); @@ -1170,8 +1149,6 @@ public async Task TestQueryResponseMapWithStringToFunction() action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var responseChannel = "Gretting.Response"; @@ -1191,7 +1168,7 @@ public async Task TestQueryResponseMapWithStringToFunction() #region Act var stopwatch = Stopwatch.StartNew(); - var result1 = await contractConnection.QueryAsync(testMessage,responseChannel: responseChannel); + var result1 = await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); var result2 = await contractConnection.QueryAsync(testMessage, responseChannel: otherChannel); @@ -1206,10 +1183,9 @@ public async Task TestQueryResponseMapWithStringToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion } @@ -1227,18 +1203,17 @@ public async Task TestQueryResponseMapWithMatchToFunction() var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); var mockSubscription = new Mock(); - var defaultTimeout = TimeSpan.FromMinutes(1); - + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) + It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); @@ -1247,8 +1222,6 @@ public async Task TestQueryResponseMapWithMatchToFunction() action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var responseChannel = "Gretting.Response"; @@ -1266,7 +1239,7 @@ public async Task TestQueryResponseMapWithMatchToFunction() #region Act var stopwatch = Stopwatch.StartNew(); - var result1 = await contractConnection.QueryAsync(testMessage,responseChannel: responseChannel); + var result1 = await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); var result2 = await contractConnection.QueryAsync(testMessage, responseChannel: otherChannel); @@ -1281,10 +1254,9 @@ public async Task TestQueryResponseMapWithMatchToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion } @@ -1302,18 +1274,16 @@ public async Task TestQueryResponseMapWithDefaultFunction() var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); var mockSubscription = new Mock(); - var defaultTimeout = TimeSpan.FromMinutes(1); - List> messageActions = []; List channels = []; var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) + It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); @@ -1322,8 +1292,6 @@ public async Task TestQueryResponseMapWithDefaultFunction() action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var responseChannel = "Gretting.Response"; @@ -1342,7 +1310,7 @@ public async Task TestQueryResponseMapWithDefaultFunction() #region Act var stopwatch = Stopwatch.StartNew(); - var result1 = await contractConnection.QueryAsync(testMessage,responseChannel: responseChannel); + var result1 = await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); var result2 = await contractConnection.QueryAsync(testMessage, responseChannel: otherChannel); @@ -1357,10 +1325,9 @@ public async Task TestQueryResponseMapWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion } @@ -1378,18 +1345,16 @@ public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); var mockSubscription = new Mock(); - var defaultTimeout = TimeSpan.FromMinutes(1); - List> messageActions = []; List channels = []; var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) + It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); @@ -1398,8 +1363,6 @@ public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var responseChannel = "Gretting.Response"; @@ -1417,7 +1380,7 @@ public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() #region Act var stopwatch = Stopwatch.StartNew(); - var result1 = await contractConnection.QueryAsync(testMessage,responseChannel: responseChannel); + var result1 = await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); var result2 = await contractConnection.QueryAsync(testMessage, responseChannel: otherChannel); @@ -1432,10 +1395,9 @@ public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion } diff --git a/AutomatedTesting/ContractConnectionTests/PublishTests.cs b/AutomatedTesting/ContractConnectionTests/PublishTests.cs index 1deb667..1178d21 100644 --- a/AutomatedTesting/ContractConnectionTests/PublishTests.cs +++ b/AutomatedTesting/ContractConnectionTests/PublishTests.cs @@ -27,16 +27,13 @@ public async Task TestPublishAsyncWithNoExtendedAspects() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new BasicMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); - + List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); - + var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -59,7 +56,7 @@ public async Task TestPublishAsyncWithNoExtendedAspects() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -74,7 +71,7 @@ public async Task TestPublishAsyncWithDifferentChannelName() List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -99,7 +96,7 @@ public async Task TestPublishAsyncWithDifferentChannelName() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -114,7 +111,7 @@ public async Task TestPublishAsyncWithMessageHeaders() List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); var messageHeader = new MessageHeader([new("testing", "testing")]); @@ -143,7 +140,7 @@ public async Task TestPublishAsyncWithMessageHeaders() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -154,15 +151,12 @@ public async Task TestPublishAsyncWithCompressionDueToMessageSize() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new BasicMessage("AAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaaaa"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); serviceConnection.Setup(x => x.MaxMessageBodySize) .Returns(35); @@ -190,7 +184,7 @@ public async Task TestPublishAsyncWithCompressionDueToMessageSize() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -206,7 +200,7 @@ public async Task TestPublishAsyncWithGlobalEncoder() List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); var globalEncoder = new Mock(); @@ -235,7 +229,7 @@ public async Task TestPublishAsyncWithGlobalEncoder() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); globalEncoder.Verify(x => x.EncodeAsync(It.IsAny()), Times.Once); #endregion } @@ -255,7 +249,7 @@ public async Task TestPublishAsyncWithGlobalEncryptor() ]); var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); var globalEncryptor = new Mock(); @@ -286,59 +280,11 @@ public async Task TestPublishAsyncWithGlobalEncryptor() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); globalEncryptor.Verify(x => x.EncryptAsync(It.IsAny(), out headers), Times.Once); #endregion } - [TestMethod] - public async Task TestPublishAsyncWithServiceChannelOptions() - { - #region Arrange - var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); - - var testMessage = new BasicMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); - var serviceChannelOptions = new TestServiceChannelOptions("PublishAsync"); - - List messages = []; - List options = []; - - var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), Capture.In(options), It.IsAny())) - .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); - - var contractConnection = new ContractConnection(serviceConnection.Object); - #endregion - - #region Act - var stopwatch = Stopwatch.StartNew(); - var result = await contractConnection.PublishAsync(testMessage, options: serviceChannelOptions); - stopwatch.Stop(); - System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); - #endregion - - #region Assert - Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); - Assert.IsNotNull(result); - Assert.AreEqual(transmissionResult, result); - Assert.AreEqual(typeof(BasicMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); - Assert.AreEqual(0, messages[0].Header.Keys.Count()); - Assert.AreEqual("U-BasicMessage-0.0.0.0", messages[0].MessageTypeID); - Assert.IsTrue(messages[0].Data.Length>0); - Assert.AreEqual(testMessage, JsonSerializer.Deserialize(new MemoryStream(messages[0].Data.ToArray()))); - Assert.AreEqual(1, options.Count); - Assert.IsInstanceOfType(options[0]); - Assert.AreEqual(serviceChannelOptions, options[0]); - #endregion - - #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - #endregion - } - [TestMethod] public async Task TestPublishAsyncWithNamedAndVersionedMessage() { @@ -346,15 +292,12 @@ public async Task TestPublishAsyncWithNamedAndVersionedMessage() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new NamedAndVersionedMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -378,7 +321,7 @@ public async Task TestPublishAsyncWithNamedAndVersionedMessage() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -389,15 +332,12 @@ public async Task TestPublishAsyncWithMessageWithDefinedEncoder() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new CustomEncoderMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages),It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -421,7 +361,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedEncoder() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -433,17 +373,14 @@ public async Task TestPublishAsyncWithMessageWithDefinedServiceInjectableEncoder var testMessage = new CustomEncoderWithInjectionMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); var serviceName = "TestPublishAsyncWithMessageWithDefinedServiceInjectableEncoder"; var services = Helper.ProduceServiceProvider(serviceName); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object, serviceProvider: services); #endregion @@ -469,7 +406,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedServiceInjectableEncoder #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -480,15 +417,12 @@ public async Task TestPublishAsyncWithMessageWithDefinedEncryptor() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new CustomEncryptorMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -512,7 +446,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedEncryptor() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -523,17 +457,14 @@ public async Task TestPublishAsyncWithMessageWithDefinedServiceInjectableEncrypt var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new CustomEncryptorWithInjectionMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); var serviceName = "TestPublishAsyncWithMessageWithDefinedServiceInjectableEncryptor"; var services = Helper.ProduceServiceProvider(serviceName); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object, serviceProvider: services); #endregion @@ -557,7 +488,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedServiceInjectableEncrypt #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -568,15 +499,12 @@ public async Task TestPublishAsyncWithNoMessageChannelThrowsError() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new NoChannelMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -595,7 +523,7 @@ public async Task TestPublishAsyncWithNoMessageChannelThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -606,15 +534,12 @@ public async Task TestPublishAsyncWithToLargeAMessageThrowsError() var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); var testMessage = new BasicMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); serviceConnection.Setup(x => x.MaxMessageBodySize) .Returns(1); @@ -635,7 +560,7 @@ public async Task TestPublishAsyncWithToLargeAMessageThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -647,15 +572,12 @@ public async Task TestPublishAsyncWithTwoDifferentMessageTypes() var testMessage1 = new BasicMessage("testMessage1"); var testMessage2 = new NoChannelMessage("testMessage2"); - var defaultTimeout = TimeSpan.FromMinutes(1); List messages = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -691,7 +613,7 @@ public async Task TestPublishAsyncWithTwoDifferentMessageTypes() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } } diff --git a/AutomatedTesting/ContractConnectionTests/QueryTests.cs b/AutomatedTesting/ContractConnectionTests/QueryTests.cs index 1e13caa..71e0e47 100644 --- a/AutomatedTesting/ContractConnectionTests/QueryTests.cs +++ b/AutomatedTesting/ContractConnectionTests/QueryTests.cs @@ -39,7 +39,7 @@ public async Task TestQueryAsyncWithNoExtendedAspects() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -71,7 +71,7 @@ public async Task TestQueryAsyncWithNoExtendedAspects() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -94,7 +94,7 @@ public async Task TestQueryAsyncWithDifferentChannelName() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -126,7 +126,7 @@ public async Task TestQueryAsyncWithDifferentChannelName() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -149,7 +149,7 @@ public async Task TestQueryAsyncWithMessageHeaders() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -185,7 +185,7 @@ public async Task TestQueryAsyncWithMessageHeaders() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -209,7 +209,7 @@ public async Task TestQueryAsyncWithTimeout() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -241,7 +241,7 @@ public async Task TestQueryAsyncWithTimeout() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -264,7 +264,7 @@ public async Task TestQueryAsyncWithCompressionDueToMessageSize() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -300,7 +300,7 @@ public async Task TestQueryAsyncWithCompressionDueToMessageSize() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -322,7 +322,7 @@ public async Task TestQueryAsyncWithGlobalEncoder() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -365,7 +365,7 @@ public async Task TestQueryAsyncWithGlobalEncoder() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -392,7 +392,7 @@ public async Task TestQueryAsyncWithGlobalEncryptor() ]); var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -437,7 +437,7 @@ public async Task TestQueryAsyncWithGlobalEncryptor() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -460,7 +460,7 @@ public async Task TestQueryAsyncWithTimeoutAttribute() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -492,67 +492,7 @@ public async Task TestQueryAsyncWithTimeoutAttribute() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - #endregion - } - - [TestMethod] - public async Task TestQueryAsyncWithServiceChannelOptions() - { - #region Arrange - var testMessage = new BasicQueryMessage("testMessage"); - var responseMessage = new BasicResponseMessage("testResponse"); - using var ms = new MemoryStream(); - await JsonSerializer.SerializeAsync(ms, responseMessage); - var responseData = (ReadOnlyMemory)ms.ToArray(); - - var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); - - - var defaultTimeout = TimeSpan.FromMinutes(1); - - List messages = []; - List timeouts = []; - List options = []; - var serviceChannelOptions = new TestServiceChannelOptions("QWueryAsync"); - - var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), Capture.In(options), It.IsAny())) - .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); - - var contractConnection = new ContractConnection(serviceConnection.Object); - #endregion - - #region Act - var stopwatch = Stopwatch.StartNew(); - var result = await contractConnection.QueryAsync(testMessage, options: serviceChannelOptions); - stopwatch.Stop(); - System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); - #endregion - - #region Assert - Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); - Assert.IsNotNull(result); - Assert.AreEqual(queryResult.ID, result.ID); - Assert.IsNull(result.Error); - Assert.IsFalse(result.IsError); - Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); - Assert.AreEqual(1, timeouts.Count); - Assert.AreEqual(defaultTimeout, timeouts[0]); - Assert.AreEqual(0, messages[0].Header.Keys.Count()); - Assert.AreEqual("U-BasicQueryMessage-0.0.0.0", messages[0].MessageTypeID); - Assert.IsTrue(messages[0].Data.Length>0); - Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(new MemoryStream(messages[0].Data.ToArray()))); - Assert.AreEqual(responseMessage, result.Result); - Assert.AreEqual(1, options.Count); - Assert.IsInstanceOfType(options[0]); - Assert.AreEqual(serviceChannelOptions, options[0]); - #endregion - - #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -575,7 +515,7 @@ public async Task TestQueryAsyncWithNamedAndVersionedMessage() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -607,7 +547,7 @@ public async Task TestQueryAsyncWithNamedAndVersionedMessage() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -630,7 +570,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncoder() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -662,7 +602,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncoder() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -687,7 +627,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncoder() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -721,7 +661,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncoder() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -744,7 +684,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncryptor() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -776,7 +716,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncryptor() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -801,7 +741,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncryptor List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -833,7 +773,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncryptor #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -856,7 +796,7 @@ public async Task TestQueryAsyncWithNoMessageChannelThrowsError() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -878,7 +818,7 @@ public async Task TestQueryAsyncWithNoMessageChannelThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -901,7 +841,7 @@ public async Task TestQueryAsyncWithToLargeAMessageThrowsError() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -925,7 +865,7 @@ public async Task TestQueryAsyncWithToLargeAMessageThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -949,7 +889,7 @@ public async Task TestQueryAsyncWithTwoDifferentMessageTypes() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -997,7 +937,7 @@ public async Task TestQueryAsyncWithTwoDifferentMessageTypes() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -1020,7 +960,7 @@ public async Task TestQueryAsyncWithAttributeReturnType() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -1052,7 +992,7 @@ public async Task TestQueryAsyncWithAttributeReturnType() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -1075,7 +1015,7 @@ public async Task TestQueryAsyncWithNoReturnType() List timeouts = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); @@ -1097,7 +1037,7 @@ public async Task TestQueryAsyncWithNoReturnType() #endregion #region Verify - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } } diff --git a/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs index 5b89071..711ca4e 100644 --- a/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs @@ -29,8 +29,6 @@ public async Task TestQueryAsyncWithNoExtendedAspects() var responseData = (ReadOnlyMemory)ms.ToArray(); - var defaultTimeout = TimeSpan.FromMinutes(1); - var mockSubscription = new Mock(); List messages = []; @@ -39,11 +37,10 @@ public async Task TestQueryAsyncWithNoExtendedAspects() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), - Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) + Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); @@ -51,15 +48,13 @@ public async Task TestQueryAsyncWithNoExtendedAspects() action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act var stopwatch = Stopwatch.StartNew(); - var result = await contractConnection.QueryAsync(testMessage,responseChannel:responseChannel); + var result = await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); #endregion @@ -81,10 +76,9 @@ public async Task TestQueryAsyncWithNoExtendedAspects() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(),It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -102,9 +96,6 @@ public async Task TestQueryAsyncWithAHeaderValue() await JsonSerializer.SerializeAsync(ms, responseMessage); var responseData = (ReadOnlyMemory)ms.ToArray(); - - var defaultTimeout = TimeSpan.FromMinutes(1); - var mockSubscription = new Mock(); List messages = []; @@ -113,11 +104,10 @@ public async Task TestQueryAsyncWithAHeaderValue() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), - Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) + Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); @@ -125,15 +115,13 @@ public async Task TestQueryAsyncWithAHeaderValue() action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act var stopwatch = Stopwatch.StartNew(); - var result = await contractConnection.QueryAsync(testMessage, messageHeader: new([new KeyValuePair(headerKey,headerValue)]), responseChannel: responseChannel); + var result = await contractConnection.QueryAsync(testMessage, messageHeader: new([new KeyValuePair(headerKey, headerValue)]), responseChannel: responseChannel); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); #endregion @@ -157,10 +145,9 @@ public async Task TestQueryAsyncWithAHeaderValue() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -176,9 +163,6 @@ public async Task TestQueryAsyncWithASecondMessageNotMatchingTheRequestBeingSent await JsonSerializer.SerializeAsync(ms, responseMessage); var responseData = (ReadOnlyMemory)ms.ToArray(); - - var defaultTimeout = TimeSpan.FromMinutes(1); - var mockSubscription = new Mock(); List messages = []; @@ -187,11 +171,10 @@ public async Task TestQueryAsyncWithASecondMessageNotMatchingTheRequestBeingSent var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), - Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) + Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, new([ @@ -206,8 +189,6 @@ public async Task TestQueryAsyncWithASecondMessageNotMatchingTheRequestBeingSent action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -236,10 +217,9 @@ public async Task TestQueryAsyncWithASecondMessageNotMatchingTheRequestBeingSent #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -255,9 +235,6 @@ public async Task TestQueryAsyncWithTheAttributeChannel() await JsonSerializer.SerializeAsync(ms, responseMessage); var responseData = (ReadOnlyMemory)ms.ToArray(); - - var defaultTimeout = TimeSpan.FromMinutes(1); - var mockSubscription = new Mock(); List messages = []; @@ -266,11 +243,10 @@ public async Task TestQueryAsyncWithTheAttributeChannel() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), - Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) + Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel!, message.Header, responseData); @@ -278,8 +254,6 @@ public async Task TestQueryAsyncWithTheAttributeChannel() action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -308,10 +282,9 @@ public async Task TestQueryAsyncWithTheAttributeChannel() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -322,15 +295,11 @@ public async Task TestQueryAsyncFailingToCreateSubscription() #region Arrange var testMessage = new BasicQueryMessage("testMessage"); var responseChannel = "BasicQuery.Response"; - var defaultTimeout = TimeSpan.FromMinutes(1); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .Returns(ValueTask.FromResult(null)); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -344,10 +313,9 @@ public async Task TestQueryAsyncFailingToCreateSubscription() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Never); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -356,21 +324,17 @@ public async Task TestQueryAsyncFailingWithNoResponseChannel() { #region Arrange var testMessage = new BasicResponseMessage("testMessage"); - var defaultTimeout = TimeSpan.FromMinutes(1); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .Returns(ValueTask.FromResult(null)); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act - var error = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage,channel:"Test")); + var error = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage, channel: "Test")); #endregion #region Assert @@ -379,10 +343,9 @@ public async Task TestQueryAsyncFailingWithNoResponseChannel() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Never); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Never); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -392,8 +355,6 @@ public async Task TestQueryAsyncWithTimeoutException() #region Arrange var testMessage = new BasicQueryMessage("testMessage"); var responseChannel = "BasicQuery.Response"; - - var defaultTimeout = TimeSpan.FromSeconds(5); var mockSubscription = new Mock(); @@ -404,23 +365,20 @@ public async Task TestQueryAsyncWithTimeoutException() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), - Capture.In(channels), It.IsAny(), - It.IsAny(), It.IsAny())) + Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act - var error = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel,timeout:defaultTimeout)); + var error = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage, responseChannel: responseChannel, timeout: defaultTimeout)); #endregion #region Assert @@ -428,10 +386,9 @@ public async Task TestQueryAsyncWithTimeoutException() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs index b5e051c..a21b9d9 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs @@ -30,12 +30,10 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() Capture.In>>(recievedActions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), - It.IsAny(), - It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(async (ServiceMessage message, TimeSpan timeout, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -53,9 +51,10 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { messages.Add(msg); - return ValueTask.FromResult(new QueryResponseMessage(responseMessage,null)); + return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.QueryAsync(message); @@ -77,7 +76,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(1, exceptions.Count); Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); @@ -90,9 +89,8 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -110,9 +108,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() It.IsAny>>(), It.IsAny>(), Capture.In(channels), - It.IsAny(), - It.IsAny(), - It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -121,9 +117,9 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() #region Act #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var subscription1 = await contractConnection.SubscribeQueryAsyncResponseAsync( - (msg) => ValueTask.FromResult>(null), + (msg) => ValueTask.FromResult>(null), (error) => { }, - channel:channelName); + channel: channelName); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. var subscription2 = await contractConnection.SubscribeQueryAsyncResponseAsync( @@ -142,8 +138,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -161,9 +156,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() It.IsAny>>(), It.IsAny>(), It.IsAny(), - Capture.In(groups), - It.IsAny(), - It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -191,51 +184,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); - #endregion - } - - [TestMethod] - public async Task TestSubscribeQueryResponseAsyncWithServiceChannelOptions() - { - #region Arrange - var serviceSubscription = new Mock(); - - var serviceChannelOptions = new TestServiceChannelOptions("TestSubscribeQueryResponseWithServiceChannelOptions"); - List options = []; - - var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), - It.IsAny>(), - It.IsAny(), - It.IsAny(), - Capture.In(options), - It.IsAny())) - .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); - #endregion - - #region Act -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync( - (msg) => ValueTask.FromResult>(null), - (error) => { }, - options:serviceChannelOptions); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - #endregion - - #region Assert - Assert.IsNotNull(subscription); - Assert.AreEqual(1, options.Count); - Assert.IsInstanceOfType(options[0]); - Assert.AreEqual(serviceChannelOptions, options[0]); - #endregion - - #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -250,9 +199,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -273,8 +220,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -287,9 +233,7 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync((IServiceSubscription?)null); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -308,8 +252,7 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -326,9 +269,7 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion @@ -348,8 +289,7 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -371,12 +311,10 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() Capture.In>>(recievedActions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), - It.IsAny(), - It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(async (ServiceMessage message, TimeSpan timeout, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -395,7 +333,8 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => + { messages.Add(msg); return new(responseMessage, null); }, (error) => exceptions.Add(error)); @@ -423,7 +362,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(1, exceptions.Count); Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); @@ -440,9 +379,8 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -463,12 +401,10 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() Capture.In>>(recievedActions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), - It.IsAny(), - It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(async (ServiceMessage message, TimeSpan timeout, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -485,7 +421,8 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => + { throw exception; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); @@ -505,16 +442,15 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() Assert.AreEqual(0, messages.Count); Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(exception, exceptions[0]); Assert.IsTrue(result.IsError); Assert.AreEqual(exception.Message, result.Error); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -528,15 +464,16 @@ public async Task TestSubscribeQueryResponseAsyncEndAsync() var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }); await subscription.EndAsync(); @@ -548,7 +485,7 @@ public async Task TestSubscribeQueryResponseAsyncEndAsync() #region Verify serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -564,14 +501,15 @@ public async Task TestSubscribeQueryResponseAsyncAsyncCleanup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }); await subscription.DisposeAsync(); @@ -583,7 +521,7 @@ public async Task TestSubscribeQueryResponseAsyncAsyncCleanup() #region Verify serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.DisposeAsync(), Times.Once); #endregion } @@ -596,14 +534,15 @@ public async Task TestSubscribeQueryResponseAsyncWithNonAsyncCleanup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }); await subscription.DisposeAsync(); @@ -615,7 +554,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNonAsyncCleanup() #region Verify serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion } @@ -629,14 +568,15 @@ public async Task TestSubscribeQueryResponseAsyncSubscriptionsCleanup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act - var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => + { throw new NotImplementedException(); }, (error) => { }); subscription.Dispose(); @@ -648,7 +588,7 @@ public async Task TestSubscribeQueryResponseAsyncSubscriptionsCleanup() #region Verify serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion } @@ -677,12 +617,10 @@ public async Task TestSubscribeQueryResponseAsyncWithThrowsConversionError() Capture.In>>(recievedActions), It.IsAny>(), It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(async (ServiceMessage message, TimeSpan timeout, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); var result = await recievedActions[0](rmessage); @@ -698,10 +636,11 @@ public async Task TestSubscribeQueryResponseAsyncWithThrowsConversionError() #region Act var messages = new List>(); var exceptions = new List(); - await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { messages.Add(msg); return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); - }, (error) => exceptions.Add(error),ignoreMessageHeader:true); + }, (error) => exceptions.Add(error), ignoreMessageHeader: true); var stopwatch = Stopwatch.StartNew(); var result = await contractConnection.QueryAsync(message); stopwatch.Stop(); @@ -716,9 +655,8 @@ await contractConnection.SubscribeQueryAsyncResponseAsync x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs index 4f27d3a..3825899 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs @@ -23,23 +23,19 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() var groups = new List(); List messages = []; List> messageActions = []; - var defaultTimeout = TimeSpan.FromMinutes(1); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), - Capture.In(channels), Capture.In(groups), - It.IsAny(), It.IsAny())) + Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubObject); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); foreach (var action in messageActions) action(new RecievedServiceMessage(message.ID,message.MessageTypeID,message.Channel,message.Header,message.Data)); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - serviceConnection.Setup(x => x.DefaultTimout) - .Returns(defaultTimeout); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -50,7 +46,8 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #region Act var recievedMessages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { + var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { recievedMessages.Add(msg); return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); }, (error) => exceptions.Add(error)); @@ -72,7 +69,8 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() Assert.AreEqual(2, messages.Count); Assert.AreEqual(1, exceptions.Count); Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); + Assert.IsNull(groups[1]); Assert.AreEqual(recievedMessages[0].ID, messages[0].ID); Assert.AreEqual(0,recievedMessages[0].Headers.Keys.Count()); Assert.AreEqual(3, messages[0].Header.Keys.Count()); @@ -83,10 +81,9 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #endregion #region Verify - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); serviceSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion } diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs index 1751ca6..bcbc601 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs @@ -27,10 +27,10 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() var serviceMessages = new List(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -48,7 +48,8 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeAsync((msg) => { + var subscription = await contractConnection.SubscribeAsync((msg) => + { messages.Add(msg); return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); @@ -72,7 +73,7 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(1, exceptions.Count); Assert.AreEqual(typeof(BasicMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); @@ -82,9 +83,8 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -99,14 +99,14 @@ public async Task TestSubscribeAsyncWithSpecificChannel() var channelName = "TestSubscribeAsyncWithSpecificChannel"; serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act - var subscription1 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { },channel:channelName); + var subscription1 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }, channel: channelName); var subscription2 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }, channel: channelName); #endregion @@ -119,8 +119,7 @@ public async Task TestSubscribeAsyncWithSpecificChannel() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -135,14 +134,14 @@ public async Task TestSubscribeAsyncWithSpecificGroup() var groupName = "TestSubscribeAsyncWithSpecificGroup"; serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); #endregion #region Act - var subscription1 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }, group:groupName); + var subscription1 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }, group: groupName); var subscription2 = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }); #endregion @@ -155,42 +154,7 @@ public async Task TestSubscribeAsyncWithSpecificGroup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Exactly(2)); - #endregion - } - - [TestMethod] - public async Task TestSubscribeAsyncWithServiceChannelOptions() - { - #region Arrange - var serviceSubscription = new Mock(); - var serviceConnection = new Mock(); - - var serviceChannelOptions = new TestServiceChannelOptions("TestSubscribeAsyncWithServiceChannelOptions"); - List options = []; - - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), - It.IsAny(), Capture.In(options), It.IsAny())) - .ReturnsAsync(serviceSubscription.Object); - - var contractConnection = new ContractConnection(serviceConnection.Object); - #endregion - - #region Act - var subscription = await contractConnection.SubscribeAsync((msg) => ValueTask.CompletedTask, (error) => { }, options:serviceChannelOptions); - #endregion - - #region Assert - Assert.IsNotNull(subscription); - Assert.AreEqual(1, options.Count); - Assert.IsInstanceOfType(options[0]); - Assert.AreEqual(serviceChannelOptions, options[0]); - #endregion - - #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -202,7 +166,7 @@ public async Task TestSubscribeAsyncNoMessageChannelThrowsError() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -219,8 +183,7 @@ public async Task TestSubscribeAsyncNoMessageChannelThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -231,7 +194,7 @@ public async Task TestSubscribeAsyncReturnFailedSubscription() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync((IServiceSubscription?)null); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -246,8 +209,7 @@ public async Task TestSubscribeAsyncReturnFailedSubscription() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -263,7 +225,7 @@ public async Task TestSubscriptionsEndAsync() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -279,8 +241,7 @@ public async Task TestSubscriptionsEndAsync() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -296,7 +257,7 @@ public async Task TestSubscribeAsyncCleanup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -312,8 +273,7 @@ public async Task TestSubscribeAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.DisposeAsync(), Times.Once); #endregion } @@ -326,7 +286,7 @@ public async Task TestSubscribeAsyncWithNonAsyncCleanup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -342,8 +302,7 @@ public async Task TestSubscribeAsyncWithNonAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion } @@ -357,7 +316,7 @@ public async Task TestSubscriptionsCleanup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); var contractConnection = new ContractConnection(serviceConnection.Object); @@ -373,8 +332,7 @@ public async Task TestSubscriptionsCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion } @@ -394,10 +352,10 @@ public async Task TestSubscribeAsyncWithSynchronousActions() var serviceMessages = new List(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -416,7 +374,8 @@ public async Task TestSubscribeAsyncWithSynchronousActions() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeAsync((msg) => { + var subscription = await contractConnection.SubscribeAsync((msg) => + { messages.Add(msg); }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); @@ -444,7 +403,7 @@ public async Task TestSubscribeAsyncWithSynchronousActions() Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(1, exceptions.Count); Assert.AreEqual(typeof(BasicMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); @@ -455,9 +414,8 @@ public async Task TestSubscribeAsyncWithSynchronousActions() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -476,10 +434,10 @@ public async Task TestSubscribeAsyncWithErrorTriggeringInOurAction() var serviceMessages = new List(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -497,7 +455,8 @@ public async Task TestSubscribeAsyncWithErrorTriggeringInOurAction() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeAsync((msg) => { + var subscription = await contractConnection.SubscribeAsync((msg) => + { throw exception; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); @@ -517,14 +476,13 @@ public async Task TestSubscribeAsyncWithErrorTriggeringInOurAction() Assert.AreEqual(0, messages.Count); Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(typeof(BasicMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(exception, exceptions[0]); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -543,10 +501,10 @@ public async Task TestSubscribeAsyncWithCorruptMetaDataHeaderException() var serviceMessages = new List(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message,$"{message.MessageTypeID}:XXXX"); serviceMessages.Add(rmessage); @@ -581,15 +539,14 @@ public async Task TestSubscribeAsyncWithCorruptMetaDataHeaderException() Assert.AreEqual(0, messages.Count); Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(typeof(BasicMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.IsInstanceOfType(exceptions[0]); Assert.AreEqual("MetaData is not valid", exceptions[0].Message); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -608,10 +565,10 @@ public async Task TestSubscribeAsyncWithDisposal() var serviceMessages = new List(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -629,7 +586,8 @@ public async Task TestSubscribeAsyncWithDisposal() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeAsync((msg) => { + var subscription = await contractConnection.SubscribeAsync((msg) => + { messages.Add(msg); return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); @@ -653,7 +611,7 @@ public async Task TestSubscribeAsyncWithDisposal() Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(1, exceptions.Count); Assert.AreEqual(typeof(BasicMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); @@ -672,9 +630,8 @@ public async Task TestSubscribeAsyncWithDisposal() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -693,10 +650,10 @@ public async Task TestSubscribeAsyncWithSingleConversion() var serviceMessages = new List(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -714,12 +671,13 @@ public async Task TestSubscribeAsyncWithSingleConversion() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeAsync((msg) => { + var subscription = await contractConnection.SubscribeAsync((msg) => + { messages.Add(msg); return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); - var result = await contractConnection.PublishAsync(message,channel:typeof(NamedAndVersionedMessage).GetCustomAttribute(false)?.Name); + var result = await contractConnection.PublishAsync(message, channel: typeof(NamedAndVersionedMessage).GetCustomAttribute(false)?.Name); stopwatch.Stop(); System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); @@ -738,7 +696,7 @@ public async Task TestSubscribeAsyncWithSingleConversion() Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(1, exceptions.Count); Assert.AreEqual(typeof(NamedAndVersionedMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); @@ -748,9 +706,8 @@ public async Task TestSubscribeAsyncWithSingleConversion() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -769,10 +726,10 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() var serviceMessages = new List(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -790,7 +747,8 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeAsync((msg) => { + var subscription = await contractConnection.SubscribeAsync((msg) => + { messages.Add(msg); return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); @@ -814,7 +772,7 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(1, exceptions.Count); Assert.AreEqual(typeof(NamedAndVersionedMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); @@ -824,9 +782,8 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -845,10 +802,10 @@ public async Task TestSubscribeAsyncWithNoConversionPath() var serviceMessages = new List(); serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), - Capture.In(groups), It.IsAny(), It.IsAny())) + Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ServiceMessage message, IServiceChannelOptions options, CancellationToken cancellationToken) => + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => { var rmessage = Helper.ProduceRecievedServiceMessage(message); serviceMessages.Add(rmessage); @@ -866,7 +823,8 @@ public async Task TestSubscribeAsyncWithNoConversionPath() #region Act var messages = new List>(); var exceptions = new List(); - var subscription = await contractConnection.SubscribeAsync((msg) => { + var subscription = await contractConnection.SubscribeAsync((msg) => + { messages.Add(msg); return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); @@ -889,15 +847,14 @@ public async Task TestSubscribeAsyncWithNoConversionPath() Assert.AreEqual(0, messages.Count); Assert.AreEqual(1, errorActions.Count); Assert.AreEqual(typeof(NamedAndVersionedMessage).GetCustomAttribute(false)?.Name, channels[0]); - Assert.IsFalse(string.IsNullOrWhiteSpace(groups[0])); + Assert.IsNull(groups[0]); Assert.AreEqual(1, exceptions.OfType().Count()); Assert.IsTrue(exceptions.Contains(exception)); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } } diff --git a/AutomatedTesting/TestServiceChannelOptions.cs b/AutomatedTesting/TestServiceChannelOptions.cs deleted file mode 100644 index 9e92dfd..0000000 --- a/AutomatedTesting/TestServiceChannelOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -using MQContract.Interfaces.Service; - -namespace AutomatedTesting -{ - internal record TestServiceChannelOptions(string TestName) : IServiceChannelOptions { } -} diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index 9479883..da4863b 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -35,13 +35,7 @@ public Connection(Uri ConnectUri,string username,string password){ /// /// The maximum message body size allowed /// - public int? MaxMessageBodySize => 4*1024*1024; - - /// - /// The default timeout to use for RPC calls when not specified by the class or in the call. - /// DEFAULT:1 minute if not specified inside the connection options - /// - public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + public uint? MaxMessageBodySize => 4*1024*1024; private async ValueTask ProduceMessage(ServiceMessage message) { @@ -79,13 +73,10 @@ internal static RecievedServiceMessage ProduceMessage(string channel, IMessage m /// Called to publish a message into the ActiveMQ server /// /// The service message being sent - /// The service channel options which should be null as there is no implementations for ActiveMQ /// A cancellation token /// Transmition result identifying if it worked or not - /// Thrown if options was supplied because there are no implemented options for this call - public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) { - NoChannelOptionsAvailableException.ThrowIfNotNull(options); try { await producer.SendAsync(SessionUtil.GetTopic(session, message.Channel), await ProduceMessage(message)); @@ -103,15 +94,12 @@ public async ValueTask PublishAsync(ServiceMessage message, /// Callback for when a message is recieved /// Callback for when an error occurs /// The name of the channel to bind to - /// The group to subscribe as part of - /// should be null - /// + /// The group to bind the consumer to + /// A cancellation token /// - /// Thrown if options was supplied because there are no implemented options for this call - public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { - NoChannelOptionsAvailableException.ThrowIfNotNull(options); - var result = new SubscriptionBase((msg)=>messageRecieved(ProduceMessage(channel,msg)), errorRecieved,session, channel, group); + var result = new SubscriptionBase((msg)=>messageRecieved(ProduceMessage(channel,msg)), errorRecieved,session, channel, group??Guid.NewGuid().ToString()); await result.StartAsync(); return result; } diff --git a/Connectors/ActiveMQ/Readme.md b/Connectors/ActiveMQ/Readme.md index e6cd7a5..d21e1d9 100644 --- a/Connectors/ActiveMQ/Readme.md +++ b/Connectors/ActiveMQ/Readme.md @@ -5,13 +5,12 @@ - [Connection](#T-MQContract-ActiveMQ-Connection 'MQContract.ActiveMQ.Connection') - [#ctor(ConnectUri,username,password)](#M-MQContract-ActiveMQ-Connection-#ctor-System-Uri,System-String,System-String- 'MQContract.ActiveMQ.Connection.#ctor(System.Uri,System.String,System.String)') - - [DefaultTimout](#P-MQContract-ActiveMQ-Connection-DefaultTimout 'MQContract.ActiveMQ.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-ActiveMQ-Connection-MaxMessageBodySize 'MQContract.ActiveMQ.Connection.MaxMessageBodySize') - [CloseAsync()](#M-MQContract-ActiveMQ-Connection-CloseAsync 'MQContract.ActiveMQ.Connection.CloseAsync') - [Dispose()](#M-MQContract-ActiveMQ-Connection-Dispose 'MQContract.ActiveMQ.Connection.Dispose') - [DisposeAsync()](#M-MQContract-ActiveMQ-Connection-DisposeAsync 'MQContract.ActiveMQ.Connection.DisposeAsync') - - [PublishAsync(message,options,cancellationToken)](#M-MQContract-ActiveMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ActiveMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-ActiveMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ActiveMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [PublishAsync(message,cancellationToken)](#M-MQContract-ActiveMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.ActiveMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') + - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-ActiveMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.ActiveMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') ## Connection `type` @@ -39,14 +38,6 @@ Default constructor for creating instance | username | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The username to use | | password | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The password to use | - -### DefaultTimout `property` - -##### Summary - -The default timeout to use for RPC calls when not specified by the class or in the call. -DEFAULT:1 minute if not specified inside the connection options - ### MaxMessageBodySize `property` @@ -95,8 +86,8 @@ A task required for disposal This method has no parameters. - -### PublishAsync(message,options,cancellationToken) `method` + +### PublishAsync(message,cancellationToken) `method` ##### Summary @@ -111,17 +102,10 @@ Transmition result identifying if it worked or not | Name | Type | Description | | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options which should be null as there is no implementations for ActiveMQ | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | - - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` ##### Summary @@ -138,12 +122,5 @@ Called to create a subscription to the underlying ActiveMQ server | messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | should be null | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind the consumer to | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 6abf9d7..383009b 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -21,13 +21,7 @@ public sealed class Connection(ClientConfig clientConfig) : IMessageServiceConne /// /// The maximum message body size allowed /// - public int? MaxMessageBodySize => clientConfig.MessageMaxBytes; - - /// - /// The default timeout to use for RPC calls when not specified by the class or in the call. - /// DEFAULT:1 minute if not specified inside the connection options - /// - public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + public uint? MaxMessageBodySize => (uint)Math.Abs(clientConfig.MessageMaxBytes??(1024*1024)); internal static byte[] EncodeHeaderValue(string value) => UTF8Encoding.UTF8.GetBytes(value); @@ -61,13 +55,10 @@ internal static MessageHeader ExtractHeaders(Headers header,out string? messageT /// Called to publish a message into the Kafka server /// /// The service message being sent - /// The service channel options which should be null as there is no implementations for Kafka /// A cancellation token /// Transmition result identifying if it worked or not - /// Thrown if options was supplied because there are no implemented options for this call - public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) { - NoChannelOptionsAvailableException.ThrowIfNotNull(options); try { var result = await producer.ProduceAsync(message.Channel, new Message() @@ -90,14 +81,11 @@ public async ValueTask PublishAsync(ServiceMessage message, /// Callback for when a message is recieved /// Callback for when an error occurs /// The name of the channel to bind to - /// The group to subscribe as part of - /// should be null - /// + /// The name of the group to bind the consumer to + /// A cancellation token /// - /// Thrown if options was supplied because there are no implemented options for this call - public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { - NoChannelOptionsAvailableException.ThrowIfNotNull(options); var subscription = new PublishSubscription( new ConsumerBuilder(new ConsumerConfig(clientConfig) { diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index 5807912..6120425 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -5,13 +5,12 @@ - [Connection](#T-MQContract-Kafka-Connection 'MQContract.Kafka.Connection') - [#ctor(clientConfig)](#M-MQContract-Kafka-Connection-#ctor-Confluent-Kafka-ClientConfig- 'MQContract.Kafka.Connection.#ctor(Confluent.Kafka.ClientConfig)') - - [DefaultTimout](#P-MQContract-Kafka-Connection-DefaultTimout 'MQContract.Kafka.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Kafka-Connection-MaxMessageBodySize 'MQContract.Kafka.Connection.MaxMessageBodySize') - [CloseAsync()](#M-MQContract-Kafka-Connection-CloseAsync 'MQContract.Kafka.Connection.CloseAsync') - [Dispose()](#M-MQContract-Kafka-Connection-Dispose 'MQContract.Kafka.Connection.Dispose') - [DisposeAsync()](#M-MQContract-Kafka-Connection-DisposeAsync 'MQContract.Kafka.Connection.DisposeAsync') - - [PublishAsync(message,options,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [PublishAsync(message,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') + - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') ## Connection `type` @@ -43,14 +42,6 @@ This is the MessageServiceConnection implementation for using Kafka | ---- | ---- | ----------- | | clientConfig | [Confluent.Kafka.ClientConfig](#T-Confluent-Kafka-ClientConfig 'Confluent.Kafka.ClientConfig') | The Kafka Client Configuration to provide | - -### DefaultTimout `property` - -##### Summary - -The default timeout to use for RPC calls when not specified by the class or in the call. -DEFAULT:1 minute if not specified inside the connection options - ### MaxMessageBodySize `property` @@ -99,8 +90,8 @@ A task required for disposal This method has no parameters. - -### PublishAsync(message,options,cancellationToken) `method` + +### PublishAsync(message,cancellationToken) `method` ##### Summary @@ -115,17 +106,10 @@ Transmition result identifying if it worked or not | Name | Type | Description | | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options which should be null as there is no implementations for Kafka | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | - - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` ##### Summary @@ -142,12 +126,5 @@ Called to create a subscription to the underlying Kafka server | messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | should be null | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the group to bind the consumer to | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 385fd72..9469137 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -19,10 +19,42 @@ namespace MQContract.KubeMQ /// public sealed class Connection : IQueryableMessageServiceConnection,IPingableMessageServiceConnection, IDisposable,IAsyncDisposable { + /// + /// These are the different read styles to use when subscribing to a stored Event PubSub + /// + public enum MessageReadStyle + { + /// + /// Start from the new ones (unread ones) only + /// + StartNewOnly = 1, + /// + /// Start at the beginning + /// + StartFromFirst = 2, + /// + /// Start at the last message + /// + StartFromLast = 3, + /// + /// Start at message number X (this value is specified when creating the listener) + /// + StartAtSequence = 4, + /// + /// Start at time X (this value is specified when creating the listener) + /// + StartAtTime = 5, + /// + /// Start at Time Delte X (this value is specified when creating the listener) + /// + StartAtTimeDelta = 6 + }; + private static readonly Regex regURL = new("^http(s)?://(.+)$", RegexOptions.Compiled|RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500)); private readonly ConnectionOptions connectionOptions; private readonly KubeClient client; + private readonly List storedChannelOptions = []; private bool disposedValue; /// @@ -55,10 +87,46 @@ public Connection(ConnectionOptions options) ); } + /// + /// Called to flag a particular channel as Stored Events when publishing or subscribing + /// + /// The name of the channel + /// The current connection to allow for chaining + public Connection RegisterStoredChannel(string channelName) + { + storedChannelOptions.Add(new(channelName)); + return this; + } + + /// + /// Called to flag a particular channel as Stored Events when publishing or subscribing + /// + /// The name of the channel + /// Set the message reading style when subscribing + /// The current connection to allow for chaining + public Connection RegisterStoredChannel(string channelName, MessageReadStyle readStyle) + { + storedChannelOptions.Add(new(channelName, readStyle)); + return this; + } + + /// + /// Called to flag a particular channel as Stored Events when publishing or subscribing + /// + /// The name of the channel + /// Set the message reading style when subscribing + /// Set the readoffset to use for the given reading style + /// The current connection to allow for chaining + public Connection RegisterStoredChannel(string channelName, MessageReadStyle readStyle, long readOffset) + { + storedChannelOptions.Add(new(channelName, readStyle, readOffset)); + return this; + } + /// /// The maximum message body size allowed /// - public int? MaxMessageBodySize => connectionOptions.MaxBodySize; + public uint? MaxMessageBodySize => (uint)Math.Abs(connectionOptions.MaxBodySize); /// /// The default timeout to use for RPC calls when not specified by the class or in the call. @@ -103,13 +171,10 @@ internal static MessageHeader ConvertMessageHeader(MapField head /// Called to publish a message into the KubeMQ server /// /// The service message being sent - /// The service channel options, if desired, specifically the PublishChannelOptions which is used to access the storage capabilities of KubeMQ /// A cancellation token /// Transmition result identifying if it worked or not - /// /// Thrown when an attempt to pass an options object that is not of the type PublishChannelOptions - public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) { - InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); try { var res = await client.SendEventAsync(new Event() { @@ -118,7 +183,7 @@ public async ValueTask PublishAsync(ServiceMessage message, Channel=message.Channel, ClientID=connectionOptions.ClientId, EventID=message.ID, - Store=(options is PublishChannelOptions pbc && pbc.Stored), + Store=storedChannelOptions.Any(sco=>Equals(message.Channel,sco.ChannelName)), Tags={ ConvertMessageHeader(message.Header) } }, connectionOptions.GrpcMetadata, cancellationToken); return new TransmissionResult(res.EventID, res.Error); @@ -140,15 +205,12 @@ public async ValueTask PublishAsync(ServiceMessage message, /// /// The service message being sent /// The timeout supplied for the query to response - /// Should be null here as there is no Service Channel Options implemented for this call /// A cancellation token /// The resulting response - /// Thrown if options was supplied because there are no implemented options for this call /// Thrown when the response from KubeMQ is null /// Thrown when there is an RPC exception from the KubeMQ server - public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) { - NoChannelOptionsAvailableException.ThrowIfNotNull(options); try { var res = await client.SendRequestAsync(new Request() @@ -188,22 +250,19 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti /// Callback for when a message is recieved /// Callback for when an error occurs /// The name of the channel to bind to - /// The group to subscribe as part of - /// The service channel options, if desired, specifically the StoredEventsSubscriptionOptions which is used to access stored event streams - /// A cancellation token + /// /// A subscription instance - /// Thrown when options is not null and is not an instance of the type StoredEventsSubscriptionOptions - public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + /// A cancellation token + public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { - InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); var sub = new PubSubscription( connectionOptions, EstablishConnection(), messageRecieved, errorRecieved, channel, - group, - (StoredEventsSubscriptionOptions?)options, + group??Guid.NewGuid().ToString(), + storedChannelOptions.Find(sco=>Equals(sco.ChannelName,channel)), cancellationToken ); sub.Run(); @@ -216,21 +275,18 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti /// Callback for when a query is recieved /// Callback for when an error occurs /// The name of the channel to bind to - /// The group to subscribe as part of - /// Should be null here as there is no Service Channel Options implemented for this call + /// The group to bind the consumer to /// A cancellation token /// A subscription instance - /// Thrown if options was supplied because there are no implemented options for this call - public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { - NoChannelOptionsAvailableException.ThrowIfNotNull(options); var sub = new QuerySubscription( connectionOptions, EstablishConnection(), messageRecieved, errorRecieved, channel, - group, + group??Guid.NewGuid().ToString(), cancellationToken ); sub.Run(); diff --git a/Connectors/KubeMQ/Options/PublishChannelOptions.cs b/Connectors/KubeMQ/Options/PublishChannelOptions.cs deleted file mode 100644 index 8f9fd34..0000000 --- a/Connectors/KubeMQ/Options/PublishChannelOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQContract.Interfaces.Service; - -namespace MQContract.KubeMQ.Options -{ - /// - /// Houses the Publish Channel options used when calling the Publish command - /// - /// Indicates if the publish should be using storage - public record PublishChannelOptions(bool Stored) : IServiceChannelOptions - { - } -} diff --git a/Connectors/KubeMQ/Options/StoredChannelOptions.cs b/Connectors/KubeMQ/Options/StoredChannelOptions.cs new file mode 100644 index 0000000..993c54d --- /dev/null +++ b/Connectors/KubeMQ/Options/StoredChannelOptions.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static MQContract.KubeMQ.Connection; + +namespace MQContract.KubeMQ.Options +{ + internal record StoredChannelOptions(string ChannelName,MessageReadStyle ReadStyle=MessageReadStyle.StartNewOnly,long ReadOffset=0) + {} +} diff --git a/Connectors/KubeMQ/Options/StoredEventsSubscriptionOptions.cs b/Connectors/KubeMQ/Options/StoredEventsSubscriptionOptions.cs deleted file mode 100644 index bf69f9b..0000000 --- a/Connectors/KubeMQ/Options/StoredEventsSubscriptionOptions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MQContract.Interfaces.Service; -using static MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions; - -namespace MQContract.KubeMQ.Options -{ - /// - /// Houses the configuration for a subscription going to a stored message channel - /// - /// The read style to use - /// The read offset to use - public record StoredEventsSubscriptionOptions(MessageReadStyle ReadStyle=MessageReadStyle.StartNewOnly,long ReadOffset=0) : IServiceChannelOptions - { - /// - /// These are the different read styles to use when subscribing to a stored Event PubSub - /// - public enum MessageReadStyle - { - /// - /// Start from the new ones (unread ones) only - /// - StartNewOnly = 1, - /// - /// Start at the beginning - /// - StartFromFirst = 2, - /// - /// Start at the last message - /// - StartFromLast = 3, - /// - /// Start at message number X (this value is specified when creating the listener) - /// - StartAtSequence = 4, - /// - /// Start at time X (this value is specified when creating the listener) - /// - StartAtTime = 5, - /// - /// Start at Time Delte X (this value is specified when creating the listener) - /// - StartAtTimeDelta = 6 - }; - - } -} diff --git a/Connectors/KubeMQ/Readme.md b/Connectors/KubeMQ/Readme.md index 2039250..d75293b 100644 --- a/Connectors/KubeMQ/Readme.md +++ b/Connectors/KubeMQ/Readme.md @@ -22,10 +22,13 @@ - [Dispose()](#M-MQContract-KubeMQ-Connection-Dispose 'MQContract.KubeMQ.Connection.Dispose') - [DisposeAsync()](#M-MQContract-KubeMQ-Connection-DisposeAsync 'MQContract.KubeMQ.Connection.DisposeAsync') - [PingAsync()](#M-MQContract-KubeMQ-Connection-PingAsync 'MQContract.KubeMQ.Connection.PingAsync') - - [PublishAsync(message,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [PublishAsync(message,cancellationToken)](#M-MQContract-KubeMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') + - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-KubeMQ-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') + - [RegisterStoredChannel(channelName)](#M-MQContract-KubeMQ-Connection-RegisterStoredChannel-System-String- 'MQContract.KubeMQ.Connection.RegisterStoredChannel(System.String)') + - [RegisterStoredChannel(channelName,readStyle)](#M-MQContract-KubeMQ-Connection-RegisterStoredChannel-System-String,MQContract-KubeMQ-Connection-MessageReadStyle- 'MQContract.KubeMQ.Connection.RegisterStoredChannel(System.String,MQContract.KubeMQ.Connection.MessageReadStyle)') + - [RegisterStoredChannel(channelName,readStyle,readOffset)](#M-MQContract-KubeMQ-Connection-RegisterStoredChannel-System-String,MQContract-KubeMQ-Connection-MessageReadStyle,System-Int64- 'MQContract.KubeMQ.Connection.RegisterStoredChannel(System.String,MQContract.KubeMQ.Connection.MessageReadStyle,System.Int64)') + - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - [ConnectionOptions](#T-MQContract-KubeMQ-ConnectionOptions 'MQContract.KubeMQ.ConnectionOptions') - [Address](#P-MQContract-KubeMQ-ConnectionOptions-Address 'MQContract.KubeMQ.ConnectionOptions.Address') - [AuthToken](#P-MQContract-KubeMQ-ConnectionOptions-AuthToken 'MQContract.KubeMQ.ConnectionOptions.AuthToken') @@ -61,13 +64,13 @@ - [Version](#P-MQContract-KubeMQ-Interfaces-IKubeMQPingResult-Version 'MQContract.KubeMQ.Interfaces.IKubeMQPingResult.Version') - [KubemqReflection](#T-MQContract-KubeMQ-SDK-Grpc-KubemqReflection 'MQContract.KubeMQ.SDK.Grpc.KubemqReflection') - [Descriptor](#P-MQContract-KubeMQ-SDK-Grpc-KubemqReflection-Descriptor 'MQContract.KubeMQ.SDK.Grpc.KubemqReflection.Descriptor') -- [MessageReadStyle](#T-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle') - - [StartAtSequence](#F-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle-StartAtSequence 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle.StartAtSequence') - - [StartAtTime](#F-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle-StartAtTime 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle.StartAtTime') - - [StartAtTimeDelta](#F-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle-StartAtTimeDelta 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle.StartAtTimeDelta') - - [StartFromFirst](#F-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle-StartFromFirst 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle.StartFromFirst') - - [StartFromLast](#F-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle-StartFromLast 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle.StartFromLast') - - [StartNewOnly](#F-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle-StartNewOnly 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle.StartNewOnly') +- [MessageReadStyle](#T-MQContract-KubeMQ-Connection-MessageReadStyle 'MQContract.KubeMQ.Connection.MessageReadStyle') + - [StartAtSequence](#F-MQContract-KubeMQ-Connection-MessageReadStyle-StartAtSequence 'MQContract.KubeMQ.Connection.MessageReadStyle.StartAtSequence') + - [StartAtTime](#F-MQContract-KubeMQ-Connection-MessageReadStyle-StartAtTime 'MQContract.KubeMQ.Connection.MessageReadStyle.StartAtTime') + - [StartAtTimeDelta](#F-MQContract-KubeMQ-Connection-MessageReadStyle-StartAtTimeDelta 'MQContract.KubeMQ.Connection.MessageReadStyle.StartAtTimeDelta') + - [StartFromFirst](#F-MQContract-KubeMQ-Connection-MessageReadStyle-StartFromFirst 'MQContract.KubeMQ.Connection.MessageReadStyle.StartFromFirst') + - [StartFromLast](#F-MQContract-KubeMQ-Connection-MessageReadStyle-StartFromLast 'MQContract.KubeMQ.Connection.MessageReadStyle.StartFromLast') + - [StartNewOnly](#F-MQContract-KubeMQ-Connection-MessageReadStyle-StartNewOnly 'MQContract.KubeMQ.Connection.MessageReadStyle.StartNewOnly') - [MessageResponseTransmissionException](#T-MQContract-KubeMQ-MessageResponseTransmissionException 'MQContract.KubeMQ.MessageResponseTransmissionException') - [PingResult](#T-MQContract-KubeMQ-SDK-Grpc-PingResult 'MQContract.KubeMQ.SDK.Grpc.PingResult') - [HostFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-PingResult-HostFieldNumber 'MQContract.KubeMQ.SDK.Grpc.PingResult.HostFieldNumber') @@ -91,9 +94,6 @@ - [RefRequestIdFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-PollResponse-RefRequestIdFieldNumber 'MQContract.KubeMQ.SDK.Grpc.PollResponse.RefRequestIdFieldNumber') - [StreamRequestTypeDataFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-PollResponse-StreamRequestTypeDataFieldNumber 'MQContract.KubeMQ.SDK.Grpc.PollResponse.StreamRequestTypeDataFieldNumber') - [TransactionIdFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-PollResponse-TransactionIdFieldNumber 'MQContract.KubeMQ.SDK.Grpc.PollResponse.TransactionIdFieldNumber') -- [PublishChannelOptions](#T-MQContract-KubeMQ-Options-PublishChannelOptions 'MQContract.KubeMQ.Options.PublishChannelOptions') - - [#ctor(Stored)](#M-MQContract-KubeMQ-Options-PublishChannelOptions-#ctor-System-Boolean- 'MQContract.KubeMQ.Options.PublishChannelOptions.#ctor(System.Boolean)') - - [Stored](#P-MQContract-KubeMQ-Options-PublishChannelOptions-Stored 'MQContract.KubeMQ.Options.PublishChannelOptions.Stored') - [QueueMessage](#T-MQContract-KubeMQ-SDK-Grpc-QueueMessage 'MQContract.KubeMQ.SDK.Grpc.QueueMessage') - [AttributesFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-QueueMessage-AttributesFieldNumber 'MQContract.KubeMQ.SDK.Grpc.QueueMessage.AttributesFieldNumber') - [BodyFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-QueueMessage-BodyFieldNumber 'MQContract.KubeMQ.SDK.Grpc.QueueMessage.BodyFieldNumber') @@ -175,10 +175,6 @@ - [IsErrorFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-SendQueueMessageResult-IsErrorFieldNumber 'MQContract.KubeMQ.SDK.Grpc.SendQueueMessageResult.IsErrorFieldNumber') - [MessageIDFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-SendQueueMessageResult-MessageIDFieldNumber 'MQContract.KubeMQ.SDK.Grpc.SendQueueMessageResult.MessageIDFieldNumber') - [SentAtFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-SendQueueMessageResult-SentAtFieldNumber 'MQContract.KubeMQ.SDK.Grpc.SendQueueMessageResult.SentAtFieldNumber') -- [StoredEventsSubscriptionOptions](#T-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions') - - [#ctor(ReadStyle,ReadOffset)](#M-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-#ctor-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle,System-Int64- 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.#ctor(MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle,System.Int64)') - - [ReadOffset](#P-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-ReadOffset 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.ReadOffset') - - [ReadStyle](#P-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-ReadStyle 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.ReadStyle') - [StreamQueueMessagesRequest](#T-MQContract-KubeMQ-SDK-Grpc-StreamQueueMessagesRequest 'MQContract.KubeMQ.SDK.Grpc.StreamQueueMessagesRequest') - [ChannelFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-StreamQueueMessagesRequest-ChannelFieldNumber 'MQContract.KubeMQ.SDK.Grpc.StreamQueueMessagesRequest.ChannelFieldNumber') - [ClientIDFieldNumber](#F-MQContract-KubeMQ-SDK-Grpc-StreamQueueMessagesRequest-ClientIDFieldNumber 'MQContract.KubeMQ.SDK.Grpc.StreamQueueMessagesRequest.ClientIDFieldNumber') @@ -401,8 +397,8 @@ This method has no parameters. | ---- | ----------- | | [MQContract.KubeMQ.UnableToConnectException](#T-MQContract-KubeMQ-UnableToConnectException 'MQContract.KubeMQ.UnableToConnectException') | Thrown when the Ping fails | - -### PublishAsync(message,options,cancellationToken) `method` + +### PublishAsync(message,cancellationToken) `method` ##### Summary @@ -417,17 +413,10 @@ Transmition result identifying if it worked or not | Name | Type | Description | | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options, if desired, specifically the PublishChannelOptions which is used to access the storage capabilities of KubeMQ | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when an attempt to pass an options object that is not of the type PublishChannelOptions | - - -### QueryAsync(message,timeout,options,cancellationToken) `method` + +### QueryAsync(message,timeout,cancellationToken) `method` ##### Summary @@ -443,19 +432,71 @@ The resulting response | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | | timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Should be null here as there is no Service Channel Options implemented for this call | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Exceptions | Name | Description | | ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | | [MQContract.KubeMQ.NullResponseException](#T-MQContract-KubeMQ-NullResponseException 'MQContract.KubeMQ.NullResponseException') | Thrown when the response from KubeMQ is null | | [MQContract.KubeMQ.RPCErrorException](#T-MQContract-KubeMQ-RPCErrorException 'MQContract.KubeMQ.RPCErrorException') | Thrown when there is an RPC exception from the KubeMQ server | - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +### RegisterStoredChannel(channelName) `method` + +##### Summary + +Called to flag a particular channel as Stored Events when publishing or subscribing + +##### Returns + +The current connection to allow for chaining + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| channelName | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel | + + +### RegisterStoredChannel(channelName,readStyle) `method` + +##### Summary + +Called to flag a particular channel as Stored Events when publishing or subscribing + +##### Returns + +The current connection to allow for chaining + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| channelName | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel | +| readStyle | [MQContract.KubeMQ.Connection.MessageReadStyle](#T-MQContract-KubeMQ-Connection-MessageReadStyle 'MQContract.KubeMQ.Connection.MessageReadStyle') | Set the message reading style when subscribing | + + +### RegisterStoredChannel(channelName,readStyle,readOffset) `method` + +##### Summary + +Called to flag a particular channel as Stored Events when publishing or subscribing + +##### Returns + +The current connection to allow for chaining + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| channelName | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel | +| readStyle | [MQContract.KubeMQ.Connection.MessageReadStyle](#T-MQContract-KubeMQ-Connection-MessageReadStyle 'MQContract.KubeMQ.Connection.MessageReadStyle') | Set the message reading style when subscribing | +| readOffset | [System.Int64](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Int64 'System.Int64') | Set the readoffset to use for the given reading style | + + +### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` ##### Summary @@ -472,18 +513,11 @@ A subscription instance | messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options, if desired, specifically the StoredEventsSubscriptionOptions which is used to access stored event streams | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when options is not null and is not an instance of the type StoredEventsSubscriptionOptions | - - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` ##### Summary @@ -500,16 +534,9 @@ A subscription instance | messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to subscribe as part of | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Should be null here as there is no Service Channel Options implemented for this call | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind the consumer to | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | - ## ConnectionOptions `type` @@ -772,53 +799,53 @@ Holder for reflection information generated from SDK/Grpc/kubemq.proto File descriptor for SDK/Grpc/kubemq.proto - + ## MessageReadStyle `type` ##### Namespace -MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions +MQContract.KubeMQ.Connection ##### Summary These are the different read styles to use when subscribing to a stored Event PubSub - + ### StartAtSequence `constants` ##### Summary Start at message number X (this value is specified when creating the listener) - + ### StartAtTime `constants` ##### Summary Start at time X (this value is specified when creating the listener) - + ### StartAtTimeDelta `constants` ##### Summary Start at Time Delte X (this value is specified when creating the listener) - + ### StartFromFirst `constants` ##### Summary Start at the beginning - + ### StartFromLast `constants` ##### Summary Start at the last message - + ### StartNewOnly `constants` ##### Summary @@ -990,43 +1017,6 @@ Field number for the "StreamRequestTypeData" field. Field number for the "TransactionId" field. - -## PublishChannelOptions `type` - -##### Namespace - -MQContract.KubeMQ.Options - -##### Summary - -Houses the Publish Channel options used when calling the Publish command - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| Stored | [T:MQContract.KubeMQ.Options.PublishChannelOptions](#T-T-MQContract-KubeMQ-Options-PublishChannelOptions 'T:MQContract.KubeMQ.Options.PublishChannelOptions') | Indicates if the publish should be using storage | - - -### #ctor(Stored) `constructor` - -##### Summary - -Houses the Publish Channel options used when calling the Publish command - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| Stored | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if the publish should be using storage | - - -### Stored `property` - -##### Summary - -Indicates if the publish should be using storage - ## QueueMessage `type` @@ -1594,51 +1584,6 @@ Field number for the "MessageID" field. Field number for the "SentAt" field. - -## StoredEventsSubscriptionOptions `type` - -##### Namespace - -MQContract.KubeMQ.Options - -##### Summary - -Houses the configuration for a subscription going to a stored message channel - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| ReadStyle | [T:MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions](#T-T-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions 'T:MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions') | The read style to use | - - -### #ctor(ReadStyle,ReadOffset) `constructor` - -##### Summary - -Houses the configuration for a subscription going to a stored message channel - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| ReadStyle | [MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle](#T-MQContract-KubeMQ-Options-StoredEventsSubscriptionOptions-MessageReadStyle 'MQContract.KubeMQ.Options.StoredEventsSubscriptionOptions.MessageReadStyle') | The read style to use | -| ReadOffset | [System.Int64](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Int64 'System.Int64') | The read offset to use | - - -### ReadOffset `property` - -##### Summary - -The read offset to use - - -### ReadStyle `property` - -##### Summary - -The read style to use - ## StreamQueueMessagesRequest `type` diff --git a/Connectors/KubeMQ/Subscriptions/PubSubscription.cs b/Connectors/KubeMQ/Subscriptions/PubSubscription.cs index 71f992d..66cb7ed 100644 --- a/Connectors/KubeMQ/Subscriptions/PubSubscription.cs +++ b/Connectors/KubeMQ/Subscriptions/PubSubscription.cs @@ -10,7 +10,7 @@ namespace MQContract.KubeMQ.Subscriptions { internal class PubSubscription(ConnectionOptions options, KubeClient client, Action messageRecieved, Action errorRecieved, string channel, string group, - StoredEventsSubscriptionOptions? storageOptions, CancellationToken cancellationToken) : + StoredChannelOptions? storageOptions, CancellationToken cancellationToken) : SubscriptionBase(options.Logger,options.ReconnectInterval,client,errorRecieved,cancellationToken) { private readonly KubeClient Client = client; diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index 4c95097..c6422b4 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -21,6 +21,7 @@ public sealed class Connection : IQueryableMessageServiceConnection,IPingableMes private readonly NatsConnection natsConnection; private readonly NatsJSContext natsJSContext; + private readonly List subscriptionConsumerConfigs = []; private readonly ILogger? logger; private bool disposedValue; @@ -56,7 +57,7 @@ private async Task ProcessConnection() /// The maximum message body size allowed. /// DEFAULT: 1MB /// - public int? MaxMessageBodySize { get; init; } = 1024*1024*1; //1MB default + public uint? MaxMessageBodySize { get; init; } = 1024*1024*1; //1MB default /// /// The default timeout to use for RPC calls when not specified by class or in the call. @@ -73,6 +74,19 @@ private async Task ProcessConnection() public ValueTask CreateStreamAsync(StreamConfig streamConfig,CancellationToken cancellationToken = default) => natsJSContext.CreateStreamAsync(streamConfig, cancellationToken); + /// + /// Called to register a consumer configuration for a given channel. This is only used for stream channels and allows for configuring + /// storing and reading patterns + /// + /// The underlying stream name that this configuration applies to + /// The consumer configuration to use for that stream + /// The underlying connection to allow for chaining + public Connection RegisterConsumerConfig(string channelName, ConsumerConfig consumerConfig) + { + subscriptionConsumerConfigs.Add(new(channelName, consumerConfig)); + return this; + } + /// /// Called to ping the NATS.io service /// @@ -125,38 +139,21 @@ internal static NatsHeaders ProduceQueryError(Exception exception,string message /// Called to publish a message into the NATS io server /// /// The service message being sent - /// The service channel options, if desired, specifically the StreamPublishChannelOptions which is used to access streams vs standard publish method /// A cancellation token /// Transmition result identifying if it worked or not - /// Thrown when an attempt to pass an options object that is not of the type StreamPublishChannelOptions - public async ValueTask PublishAsync(ServiceMessage message, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) { - InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); try { - if (options is StreamPublishChannelOptions publishChannelOptions) - { - if (publishChannelOptions.Config!=null) - await CreateStreamAsync(publishChannelOptions.Config, cancellationToken); - var ack = await natsJSContext.PublishAsync( - message.Channel, - message.Data.ToArray(), - headers: ExtractHeader(message), - cancellationToken: cancellationToken - ); - return new TransmissionResult(message.ID, (ack.Error!=null ? $"{ack.Error.Code}:{ack.Error.Description}" : null)); - } - else - { - await natsConnection.PublishAsync( + await natsConnection.PublishAsync( message.Channel, message.Data.ToArray(), headers: ExtractHeader(message), cancellationToken: cancellationToken ); - return new TransmissionResult(message.ID); - } - }catch(Exception ex) + return new TransmissionResult(message.ID); + } + catch(Exception ex) { return new TransmissionResult(message.ID, ex.Message); } @@ -167,14 +164,11 @@ await natsConnection.PublishAsync( /// /// The service message being sent /// The timeout supplied for the query to response - /// Should be null here as there is no Service Channel Options implemented for this call /// A cancellation token /// The resulting response - /// Thrown if options was supplied because there are no implemented options for this call /// Thrown when an error comes from the responding service - public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) { - NoChannelOptionsAvailableException.ThrowIfNotNull(options); var result = await natsConnection.RequestAsync( message.Channel, message.Data.ToArray(), @@ -199,20 +193,31 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti /// Callback for when a message is recieved /// Callback for when an error occurs /// The name of the channel to bind to - /// The queueGroup to use for the subscription - /// The service channel options, if desired, specifically the StreamPublishSubscriberOptions which is used to access streams vs standard subscription + /// The name of the group to bind the consumer to /// A cancellation token /// A subscription instance - /// Thrown when options is not null and is not an instance of the type StreamPublishSubscriberOptions - public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + + public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { - InvalidChannelOptionsTypeException.ThrowIfNotNullAndNotOfType(options); - IInternalServiceSubscription? subscription = null; - if (options is StreamPublishSubscriberOptions subscriberOptions) + SubscriptionBase subscription; + var isStream = false; + await foreach(var name in natsJSContext.ListStreamNamesAsync(cancellationToken: cancellationToken)) { - if (subscriberOptions.StreamConfig!=null) - await CreateStreamAsync(subscriberOptions.StreamConfig, cancellationToken); - var consumer = await natsJSContext.CreateOrUpdateConsumerAsync(subscriberOptions.StreamConfig?.Name??channel, subscriberOptions.ConsumerConfig??new ConsumerConfig(group) { AckPolicy = ConsumerConfigAckPolicy.Explicit }, cancellationToken); + if (Equals(channel, name)) + { + isStream=true; + break; + } + } + if (isStream) + { + var config = subscriptionConsumerConfigs.Find(scc => Equals(scc.Channel, channel) + &&( + (group==null && string.IsNullOrWhiteSpace(scc.Configuration.Name) && string.IsNullOrWhiteSpace(scc.Configuration.DurableName)) + ||Equals(group, scc.Configuration.Name) + ||Equals(group, scc.Configuration.DurableName) + )); + var consumer = await natsJSContext.CreateOrUpdateConsumerAsync(channel, config?.Configuration??new ConsumerConfig(group??Guid.NewGuid().ToString()) { AckPolicy = ConsumerConfigAckPolicy.Explicit }, cancellationToken); subscription = new StreamSubscription(consumer, messageRecieved, errorRecieved); } else @@ -235,14 +240,11 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti /// Callback for when a query is recieved /// Callback for when an error occurs /// The name of the channel to bind to - /// The queueGroup to use for the subscription - /// Should be null here as there is no Service Channel Options implemented for this call - /// A cancellation token + /// /// A subscription instance - /// /// Thrown if options was supplied because there are no implemented options for this call - public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string group, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + /// A cancellation token + public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { - NoChannelOptionsAvailableException.ThrowIfNotNull(options); var sub = new QuerySubscription( natsConnection.SubscribeAsync( channel, diff --git a/Connectors/NATS/Options/StreamPublishChannelOptions.cs b/Connectors/NATS/Options/StreamPublishChannelOptions.cs deleted file mode 100644 index da3e13d..0000000 --- a/Connectors/NATS/Options/StreamPublishChannelOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using MQContract.Interfaces.Service; -using NATS.Client.JetStream.Models; - -namespace MQContract.NATS.Options -{ - /// - /// Used to specify when a publish call is publishing to a JetStream - /// - /// The StreamConfig to use if not already defined - public record StreamPublishChannelOptions(StreamConfig? Config=null) : IServiceChannelOptions - { - } -} diff --git a/Connectors/NATS/Options/StreamPublishSubscriberOptions.cs b/Connectors/NATS/Options/StreamPublishSubscriberOptions.cs deleted file mode 100644 index e07e6f2..0000000 --- a/Connectors/NATS/Options/StreamPublishSubscriberOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MQContract.Interfaces.Service; -using NATS.Client.JetStream.Models; - -namespace MQContract.NATS.Options -{ - /// - /// Used to specify when a subscription call is subscribing to a JetStream and not the standard subscription - /// - /// The StreamConfig to use if not already defined - /// The ConsumerCondig to use if specific settings are required - public record StreamPublishSubscriberOptions(StreamConfig? StreamConfig=null,ConsumerConfig? ConsumerConfig=null) : IServiceChannelOptions - { - } -} diff --git a/Connectors/NATS/Options/SubscriptionConsumerConfig.cs b/Connectors/NATS/Options/SubscriptionConsumerConfig.cs new file mode 100644 index 0000000..1023b30 --- /dev/null +++ b/Connectors/NATS/Options/SubscriptionConsumerConfig.cs @@ -0,0 +1,13 @@ +using NATS.Client.JetStream.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.NATS.Options +{ + internal record SubscriptionConsumerConfig(string Channel,ConsumerConfig Configuration) + { + } +} diff --git a/Connectors/NATS/Readme.md b/Connectors/NATS/Readme.md index ef7f638..ccd6652 100644 --- a/Connectors/NATS/Readme.md +++ b/Connectors/NATS/Readme.md @@ -12,17 +12,11 @@ - [Dispose()](#M-MQContract-NATS-Connection-Dispose 'MQContract.NATS.Connection.Dispose') - [DisposeAsync()](#M-MQContract-NATS-Connection-DisposeAsync 'MQContract.NATS.Connection.DisposeAsync') - [PingAsync()](#M-MQContract-NATS-Connection-PingAsync 'MQContract.NATS.Connection.PingAsync') - - [PublishAsync(message,options,cancellationToken)](#M-MQContract-NATS-Connection-PublishAsync-MQContract-Messages-ServiceMessage,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.PublishAsync(MQContract.Messages.ServiceMessage,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync(message,timeout,options,cancellationToken)](#M-MQContract-NATS-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') -- [StreamPublishChannelOptions](#T-MQContract-NATS-Options-StreamPublishChannelOptions 'MQContract.NATS.Options.StreamPublishChannelOptions') - - [#ctor(Config)](#M-MQContract-NATS-Options-StreamPublishChannelOptions-#ctor-NATS-Client-JetStream-Models-StreamConfig- 'MQContract.NATS.Options.StreamPublishChannelOptions.#ctor(NATS.Client.JetStream.Models.StreamConfig)') - - [Config](#P-MQContract-NATS-Options-StreamPublishChannelOptions-Config 'MQContract.NATS.Options.StreamPublishChannelOptions.Config') -- [StreamPublishSubscriberOptions](#T-MQContract-NATS-Options-StreamPublishSubscriberOptions 'MQContract.NATS.Options.StreamPublishSubscriberOptions') - - [#ctor(StreamConfig,ConsumerConfig)](#M-MQContract-NATS-Options-StreamPublishSubscriberOptions-#ctor-NATS-Client-JetStream-Models-StreamConfig,NATS-Client-JetStream-Models-ConsumerConfig- 'MQContract.NATS.Options.StreamPublishSubscriberOptions.#ctor(NATS.Client.JetStream.Models.StreamConfig,NATS.Client.JetStream.Models.ConsumerConfig)') - - [ConsumerConfig](#P-MQContract-NATS-Options-StreamPublishSubscriberOptions-ConsumerConfig 'MQContract.NATS.Options.StreamPublishSubscriberOptions.ConsumerConfig') - - [StreamConfig](#P-MQContract-NATS-Options-StreamPublishSubscriberOptions-StreamConfig 'MQContract.NATS.Options.StreamPublishSubscriberOptions.StreamConfig') + - [PublishAsync(message,cancellationToken)](#M-MQContract-NATS-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.NATS.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') + - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-NATS-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.NATS.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') + - [RegisterConsumerConfig(channelName,consumerConfig)](#M-MQContract-NATS-Connection-RegisterConsumerConfig-System-String,NATS-Client-JetStream-Models-ConsumerConfig- 'MQContract.NATS.Connection.RegisterConsumerConfig(System.String,NATS.Client.JetStream.Models.ConsumerConfig)') + - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - [UnableToConnectException](#T-MQContract-NATS-UnableToConnectException 'MQContract.NATS.UnableToConnectException') @@ -139,8 +133,8 @@ The Ping Result including service information This method has no parameters. - -### PublishAsync(message,options,cancellationToken) `method` + +### PublishAsync(message,cancellationToken) `method` ##### Summary @@ -155,17 +149,10 @@ Transmition result identifying if it worked or not | Name | Type | Description | | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options, if desired, specifically the StreamPublishChannelOptions which is used to access streams vs standard publish method | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when an attempt to pass an options object that is not of the type StreamPublishChannelOptions | - - -### QueryAsync(message,timeout,options,cancellationToken) `method` + +### QueryAsync(message,timeout,cancellationToken) `method` ##### Summary @@ -181,50 +168,39 @@ The resulting response | ---- | ---- | ----------- | | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | | timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Should be null here as there is no Service Channel Options implemented for this call | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Exceptions | Name | Description | | ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | | [MQContract.NATS.QueryAsyncReponseException](#T-MQContract-NATS-QueryAsyncReponseException 'MQContract.NATS.QueryAsyncReponseException') | Thrown when an error comes from the responding service | - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +### RegisterConsumerConfig(channelName,consumerConfig) `method` ##### Summary -Called to create a subscription to the underlying nats server +Called to register a consumer configuration for a given channel. This is only used for stream channels and allows for configuring +storing and reading patterns ##### Returns -A subscription instance +The underlying connection to allow for chaining ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The queueGroup to use for the subscription | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | The service channel options, if desired, specifically the StreamPublishSubscriberOptions which is used to access streams vs standard subscription | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Exceptions +| channelName | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The underlying stream name that this configuration applies to | +| consumerConfig | [NATS.Client.JetStream.Models.ConsumerConfig](#T-NATS-Client-JetStream-Models-ConsumerConfig 'NATS.Client.JetStream.Models.ConsumerConfig') | The consumer configuration to use for that stream | -| Name | Description | -| ---- | ----------- | -| [MQContract.InvalidChannelOptionsTypeException](#T-MQContract-InvalidChannelOptionsTypeException 'MQContract.InvalidChannelOptionsTypeException') | Thrown when options is not null and is not an instance of the type StreamPublishSubscriberOptions | - - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,options,cancellationToken) `method` + +### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` ##### Summary -Called to create a subscription for queries to the underlying NATS server +Called to create a subscription to the underlying nats server ##### Returns @@ -234,100 +210,32 @@ A subscription instance | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | +| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The queueGroup to use for the subscription | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | Should be null here as there is no Service Channel Options implemented for this call | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the group to bind the consumer to | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.NoChannelOptionsAvailableException](#T-MQContract-NoChannelOptionsAvailableException 'MQContract.NoChannelOptionsAvailableException') | Thrown if options was supplied because there are no implemented options for this call | - - -## StreamPublishChannelOptions `type` - -##### Namespace - -MQContract.NATS.Options + +### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` ##### Summary -Used to specify when a publish call is publishing to a JetStream - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| Config | [T:MQContract.NATS.Options.StreamPublishChannelOptions](#T-T-MQContract-NATS-Options-StreamPublishChannelOptions 'T:MQContract.NATS.Options.StreamPublishChannelOptions') | The StreamConfig to use if not already defined | - - -### #ctor(Config) `constructor` - -##### Summary - -Used to specify when a publish call is publishing to a JetStream - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| Config | [NATS.Client.JetStream.Models.StreamConfig](#T-NATS-Client-JetStream-Models-StreamConfig 'NATS.Client.JetStream.Models.StreamConfig') | The StreamConfig to use if not already defined | - - -### Config `property` - -##### Summary - -The StreamConfig to use if not already defined - - -## StreamPublishSubscriberOptions `type` - -##### Namespace - -MQContract.NATS.Options - -##### Summary - -Used to specify when a subscription call is subscribing to a JetStream and not the standard subscription - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| StreamConfig | [T:MQContract.NATS.Options.StreamPublishSubscriberOptions](#T-T-MQContract-NATS-Options-StreamPublishSubscriberOptions 'T:MQContract.NATS.Options.StreamPublishSubscriberOptions') | The StreamConfig to use if not already defined | - - -### #ctor(StreamConfig,ConsumerConfig) `constructor` +Called to create a subscription for queries to the underlying NATS server -##### Summary +##### Returns -Used to specify when a subscription call is subscribing to a JetStream and not the standard subscription +A subscription instance ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| StreamConfig | [NATS.Client.JetStream.Models.StreamConfig](#T-NATS-Client-JetStream-Models-StreamConfig 'NATS.Client.JetStream.Models.StreamConfig') | The StreamConfig to use if not already defined | -| ConsumerConfig | [NATS.Client.JetStream.Models.ConsumerConfig](#T-NATS-Client-JetStream-Models-ConsumerConfig 'NATS.Client.JetStream.Models.ConsumerConfig') | The ConsumerCondig to use if specific settings are required | - - -### ConsumerConfig `property` - -##### Summary - -The ConsumerCondig to use if specific settings are required - - -### StreamConfig `property` - -##### Summary - -The StreamConfig to use if not already defined +| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ## UnableToConnectException `type` diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index 0ac4747..fc6ec9c 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -69,14 +69,13 @@ public ValueTask PingAsync() /// The instance of the message to publish /// Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on /// A message header to be sent across with the message - /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token + /// /// An instance of the TransmissionResult record to indicate success or failure and an ID - public async ValueTask PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + public async ValueTask PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) where T : class => await serviceConnection.PublishAsync( - await ProduceServiceMessage(ChannelMapper.MapTypes.Publish,message, channel: channel, messageHeader: messageHeader), - options, + await ProduceServiceMessage(ChannelMapper.MapTypes.Publish, message, channel: channel, messageHeader: messageHeader), cancellationToken ); @@ -92,12 +91,11 @@ private async ValueTask ProduceServiceMessage(ChannelMapper.M /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class - => SubscribeAsync(messageRecieved, errorRecieved, channel, group, ignoreMessageHeader, false, options, cancellationToken); + public ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = default) where T : class + => CreateSubscriptionAsync(messageRecieved, errorRecieved, channel, group, ignoreMessageHeader,false, cancellationToken); /// /// Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages synchronously @@ -108,19 +106,18 @@ public ValueTask SubscribeAsync(Func, Valu /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public ValueTask SubscribeAsync(Action> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where T : class - => SubscribeAsync((msg) => + public ValueTask SubscribeAsync(Action> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = default) where T : class + => CreateSubscriptionAsync((msg) => { messageRecieved(msg); return ValueTask.CompletedTask; }, - errorRecieved, channel, group, ignoreMessageHeader, true, options, cancellationToken); + errorRecieved, channel, group, ignoreMessageHeader, true, cancellationToken); - private async ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel, string? group, bool ignoreMessageHeader,bool synchronous, IServiceChannelOptions? options, CancellationToken cancellationToken) + private async ValueTask CreateSubscriptionAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel, string? group, bool ignoreMessageHeader,bool synchronous, CancellationToken cancellationToken) where T : class { var subscription = new PubSubSubscription(GetMessageFactory(ignoreMessageHeader), @@ -130,24 +127,22 @@ private async ValueTask SubscribeAsync(Func> ExecuteQueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + private async ValueTask> ExecuteQueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class { - var realTimeout = timeout??TimeSpan.FromMilliseconds(typeof(Q).GetCustomAttribute()?.Value??serviceConnection.DefaultTimout.TotalMilliseconds); + var realTimeout = timeout??typeof(Q).GetCustomAttribute()?.TimeSpanValue; var serviceMessage = await ProduceServiceMessage(ChannelMapper.MapTypes.Query, message, channel: channel, messageHeader: messageHeader); - if (serviceConnection is IQueryableMessageServiceConnection queryableMessageServiceConnection) + if (serviceConnection is IQueryableMessageServiceConnection queryableMessageServiceConnection) return await ProduceResultAsync(await queryableMessageServiceConnection.QueryAsync( serviceMessage, - realTimeout, - options, + realTimeout??queryableMessageServiceConnection.DefaultTimout, cancellationToken )); responseChannel ??=typeof(Q).GetCustomAttribute()?.Name; @@ -156,7 +151,7 @@ private async ValueTask SubscribeAsync(Func SubscribeAsync(FuncSpecifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is /// only used when the underlying connection does not support a QueryResponse style messaging. /// A message header to be sent across with the message - /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token + /// /// A QueryResult that will contain the response message and or an error - public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, IServiceChannelOptions? options = null, CancellationToken cancellationToken = new CancellationToken()) + public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class - => await ExecuteQueryAsync(message, timeout: timeout, channel: channel,responseChannel:responseChannel, messageHeader: messageHeader, options: options, cancellationToken: cancellationToken); + => await ExecuteQueryAsync(message, timeout: timeout, channel: channel,responseChannel:responseChannel, messageHeader: messageHeader, cancellationToken: cancellationToken); /// /// Called to publish a message in the Query/Response style except the response Type is gathered from the QueryResponseTypeAttribute @@ -210,12 +205,12 @@ private async ValueTask SubscribeAsync(FuncSpecifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is /// only used when the underlying connection does not support a QueryResponse style messaging. /// A message header to be sent across with the message - /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token + /// /// A QueryResult that will contain the response message and or an error /// Thrown when the supplied Query type does not have a QueryResponseTypeAttribute and therefore a response type cannot be determined public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, - IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) where Q : class + CancellationToken cancellationToken = default) where Q : class { #pragma warning disable CA2208 // Instantiate argument exceptions correctly var responseType = (typeof(Q).GetCustomAttribute(false)?.ResponseType)??throw new UnknownResponseTypeException("ResponseType", typeof(Q)); @@ -231,7 +226,6 @@ public async ValueTask> QueryAsync(Q message, TimeSpan? t channel, responseChannel, messageHeader, - options, cancellationToken ] ); @@ -277,14 +271,14 @@ private async ValueTask> ProduceResultAsync(ServiceQueryResult /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token + /// /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public ValueTask SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = default) where Q : class where R : class - => SubscribeQueryResponseAsync(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,false,options,cancellationToken); + => ProduceSubscribeQueryResponseAsync(messageRecieved, errorRecieved, channel, group, ignoreMessageHeader,false, cancellationToken); /// /// Creates a subscription with the underlying service layer for the Query/Response style processing messages synchronously @@ -296,20 +290,20 @@ public ValueTask SubscribeQueryAsyncResponseAsync(FuncUsed to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary /// A cancellation token + /// /// An instance of the Subscription that can be held or called to end /// An exception thrown when the subscription has failed to establish - public ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, IServiceChannelOptions? options = null, CancellationToken cancellationToken = default) + public ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = default) where Q : class where R : class - => SubscribeQueryResponseAsync((msg) => + => ProduceSubscribeQueryResponseAsync((msg) => { var result = messageRecieved(msg); return ValueTask.FromResult(result); - }, errorRecieved, channel, group, ignoreMessageHeader, true, options, cancellationToken); + }, errorRecieved, channel, group, ignoreMessageHeader, true, cancellationToken); - private async ValueTask SubscribeQueryResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, IServiceChannelOptions? options, CancellationToken cancellationToken) + private async ValueTask ProduceSubscribeQueryResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, CancellationToken cancellationToken) where Q : class where R : class { @@ -322,7 +316,6 @@ private async ValueTask SubscribeQueryResponseAsync(Func private readonly IMessageTypeEncoder? messageEncoder; private readonly IMessageTypeEncryptor? messageEncryptor; private readonly IEnumerable> converters; - private readonly int maxMessageSize; + private readonly uint maxMessageSize; public bool IgnoreMessageHeader { get; private init; } private readonly string messageName = typeof(T).GetCustomAttributes().Select(mn => mn.Value).FirstOrDefault(Utility.TypeName()); private readonly string messageVersion = typeof(T).GetCustomAttributes().Select(mc => mc.Version.ToString()).FirstOrDefault("0.0.0.0"); private readonly string messageChannel = typeof(T).GetCustomAttributes().Select(mc => mc.Name).FirstOrDefault(string.Empty); - public MessageTypeFactory(IMessageEncoder? globalMessageEncoder, IMessageEncryptor? globalMessageEncryptor, IServiceProvider? serviceProvider, bool ignoreMessageHeader, int? maxMessageSize) + public MessageTypeFactory(IMessageEncoder? globalMessageEncoder, IMessageEncryptor? globalMessageEncryptor, IServiceProvider? serviceProvider, bool ignoreMessageHeader, uint? maxMessageSize) { this.maxMessageSize = maxMessageSize??int.MaxValue; this.globalMessageEncryptor = globalMessageEncryptor; diff --git a/Core/Readme.md b/Core/Readme.md index bccc90c..794ed24 100644 --- a/Core/Readme.md +++ b/Core/Readme.md @@ -30,13 +30,13 @@ - [Dispose()](#M-MQContract-ContractConnection-Dispose 'MQContract.ContractConnection.Dispose') - [DisposeAsync()](#M-MQContract-ContractConnection-DisposeAsync 'MQContract.ContractConnection.DisposeAsync') - [PingAsync()](#M-MQContract-ContractConnection-PingAsync 'MQContract.ContractConnection.PingAsync') - - [PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') - - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,MQContract-Interfaces-Service-IServiceChannelOptions,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,MQContract.Interfaces.Service.IServiceChannelOptions,System.Threading.CancellationToken)') + - [PublishAsync\`\`1(message,channel,messageHeader,cancellationToken)](#M-MQContract-ContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.ContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') + - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') + - [QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') - [InvalidQueryResponseMessageRecieved](#T-MQContract-InvalidQueryResponseMessageRecieved 'MQContract.InvalidQueryResponseMessageRecieved') - [MessageChannelNullException](#T-MQContract-MessageChannelNullException 'MQContract.MessageChannelNullException') - [MessageConversionException](#T-MQContract-MessageConversionException 'MQContract.MessageConversionException') @@ -504,8 +504,8 @@ The ping result from the service layer, if supported This method has no parameters. - -### PublishAsync\`\`1(message,channel,messageHeader,options,cancellationToken) `method` + +### PublishAsync\`\`1(message,channel,messageHeader,cancellationToken) `method` ##### Summary @@ -522,7 +522,6 @@ An instance of the TransmissionResult record to indicate success or failure and | message | [\`\`0](#T-``0 '``0') | The instance of the message to publish | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | A message header to be sent across with the message | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -531,8 +530,8 @@ An instance of the TransmissionResult record to indicate success or failure and | ---- | ----------- | | T | The type of message to publish | - -### QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken) `method` + +### QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,cancellationToken) `method` ##### Summary @@ -552,7 +551,6 @@ A QueryResult that will contain the response message and or an error | responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is only used when the underlying connection does not support a QueryResponse style messaging. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | A message header to be sent across with the message | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -567,8 +565,8 @@ only used when the underlying connection does not support a QueryResponse style | ---- | ----------- | | [MQContract.UnknownResponseTypeException](#T-MQContract-UnknownResponseTypeException 'MQContract.UnknownResponseTypeException') | Thrown when the supplied Query type does not have a QueryResponseTypeAttribute and therefore a response type cannot be determined | - -### QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,options,cancellationToken) `method` + +### QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,cancellationToken) `method` ##### Summary @@ -588,7 +586,6 @@ A QueryResult that will contain the response message and or an error | responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is only used when the underlying connection does not support a QueryResponse style messaging. | | messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | A message header to be sent across with the message | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -598,8 +595,8 @@ only used when the underlying connection does not support a QueryResponse style | Q | The type of message to transmit for the Query | | R | The type of message expected as a response | - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -618,7 +615,6 @@ An instance of the Subscription that can be held or called to end | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -633,8 +629,8 @@ An instance of the Subscription that can be held or called to end | ---- | ----------- | | [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -653,7 +649,6 @@ An instance of the Subscription that can be held or called to end | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -668,8 +663,8 @@ An instance of the Subscription that can be held or called to end | ---- | ----------- | | [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - -### SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +### SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -688,7 +683,6 @@ An instance of the Subscription that can be held or called to end | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types @@ -704,8 +698,8 @@ An instance of the Subscription that can be held or called to end | ---- | ----------- | | [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - -### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,options,cancellationToken) `method` + +### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -724,7 +718,6 @@ An instance of the Subscription that can be held or called to end | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| options | [MQContract.Interfaces.Service.IServiceChannelOptions](#T-MQContract-Interfaces-Service-IServiceChannelOptions 'MQContract.Interfaces.Service.IServiceChannelOptions') | An instance of a ServiceChannelOptions to pass down to the service layer if desired and/or necessary | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | ##### Generic Types diff --git a/Core/Subscriptions/PubSubSubscription.cs b/Core/Subscriptions/PubSubSubscription.cs index cb40cce..a6909a5 100644 --- a/Core/Subscriptions/PubSubSubscription.cs +++ b/Core/Subscriptions/PubSubSubscription.cs @@ -8,19 +8,18 @@ namespace MQContract.Subscriptions { internal sealed class PubSubSubscription(IMessageFactory messageFactory, Func, ValueTask> messageRecieved, Action errorRecieved, Func> mapChannel, - string? channel = null, string? group = null, bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) + string? channel = null, string? group = null, bool synchronous=false,ILogger? logger=null) : SubscriptionBase(mapChannel,channel,synchronous) where T : class { public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnection connection,CancellationToken cancellationToken) { serviceSubscription = await connection.SubscribeAsync( - async serviceMessage=> await ProcessMessage(serviceMessage), - error=>errorRecieved(error), + async serviceMessage => await ProcessMessage(serviceMessage), + error => errorRecieved(error), MessageChannel, - group??Guid.NewGuid().ToString(), - options:options, - cancellationToken:cancellationToken + group:group, + cancellationToken: cancellationToken ); if (serviceSubscription==null) return false; diff --git a/Core/Subscriptions/QueryResponseHelper.cs b/Core/Subscriptions/QueryResponseHelper.cs index b6f521e..de339be 100644 --- a/Core/Subscriptions/QueryResponseHelper.cs +++ b/Core/Subscriptions/QueryResponseHelper.cs @@ -62,7 +62,6 @@ public static async Task, Cancell }, error => { }, replyChannel, - Guid.NewGuid().ToString(), cancellationToken: token.Token )??throw new QueryExecutionFailedException(); token.Token.Register(async () => { diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index 2c1ed85..6879a91 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -10,7 +10,7 @@ internal sealed class QueryResponseSubscription(IMessageFactory queryMes Func, ValueTask>> messageRecieved, Action errorRecieved, Func> mapChannel, string? channel = null, string? group = null, - bool synchronous=false,IServiceChannelOptions? options = null,ILogger? logger=null) + bool synchronous=false,ILogger? logger=null) : SubscriptionBase(mapChannel,channel,synchronous) where Q : class where R : class @@ -25,8 +25,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio serviceMessage => ProcessServiceMessageAsync(serviceMessage), error => errorRecieved(error), MessageChannel, - group??Guid.NewGuid().ToString(), - options: options, + group:group, cancellationToken: cancellationToken ); else @@ -47,14 +46,12 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio serviceMessage.Data ) ); - await connection.PublishAsync(QueryResponseHelper.EncodeMessage(result, queryClientID, replyID, null,replyChannel), null, cancellationToken); + await connection.PublishAsync(QueryResponseHelper.EncodeMessage(result, queryClientID, replyID, null, replyChannel), cancellationToken); } }, - error=> errorRecieved(error), + error => errorRecieved(error), MessageChannel, - group??Guid.NewGuid().ToString(), - options:options, - cancellationToken:cancellationToken + cancellationToken: cancellationToken ); } return serviceSubscription!=null; diff --git a/Samples/KubeMQSample/Program.cs b/Samples/KubeMQSample/Program.cs index 49c02b6..0f5612f 100644 --- a/Samples/KubeMQSample/Program.cs +++ b/Samples/KubeMQSample/Program.cs @@ -13,7 +13,8 @@ { Logger=new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider().CreateLogger("Messages"), ClientId="KubeMQSample" -}); +}) + .RegisterStoredChannel("StoredArrivals"); var contractConnection = new ContractConnection(serviceConnection); @@ -45,7 +46,6 @@ return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), - options:new StoredEventsSubscriptionOptions(StoredEventsSubscriptionOptions.MessageReadStyle.StartNewOnly), cancellationToken: sourceCancel.Token ); @@ -59,19 +59,19 @@ await Task.WhenAll( await contractConnection.CloseAsync().ConfigureAwait(true); }, true); -var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken:sourceCancel.Token); +var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); -var response = await contractConnection.QueryAsync(new Greeting("Bob","Loblaw"),cancellationToken:sourceCancel.Token); +var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); -var storedResult = await contractConnection.PublishAsync(new("Bob","Loblaw"),options:new PublishChannelOptions(true), cancellationToken:sourceCancel.Token); +var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Stored Result 1 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); -storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), options: new PublishChannelOptions(true), cancellationToken: sourceCancel.Token); +storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Stored Result 2 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); Console.WriteLine("Press Ctrl+C to close"); diff --git a/Samples/NATSSample/Program.cs b/Samples/NATSSample/Program.cs index d3519fc..ae4242e 100644 --- a/Samples/NATSSample/Program.cs +++ b/Samples/NATSSample/Program.cs @@ -52,7 +52,6 @@ return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), - options: new StreamPublishSubscriberOptions(), cancellationToken: sourceCancel.Token ); diff --git a/Shared.props b/Shared.props index b6b6a2a..3edeb33 100644 --- a/Shared.props +++ b/Shared.props @@ -6,7 +6,7 @@ https://github.com/roger-castaldo/MQContract $(VersionPrefix) $(VersionPrefix) - Message Queue MQ Contract + Message Queue MQ Contract ServiceBus Messaging Abstraction PubSub QueryResponse MIT True True From 4f4991f5518ea588d6e949f749484469dadc55cc Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Thu, 12 Sep 2024 14:18:06 -0400 Subject: [PATCH 11/22] added in Redis support added in full support for Redis as a MQ bus. Added in an initial attempt at RabbitMQ but requires much more thorough testing and validation. --- .../Messages/RecievedServiceMessage.cs | 3 +- Abstractions/Readme.md | 15 +- Connectors/ActiveMQ/Connection.cs | 3 +- .../Subscriptions/SubscriptionBase.cs | 2 +- Connectors/Kafka/Connection.cs | 2 +- Connectors/RabbitMQ/Connection.cs | 110 ++++++++ Connectors/RabbitMQ/RabbitMQ.csproj | 33 +++ Connectors/RabbitMQ/Readme.md | 80 ++++++ Connectors/RabbitMQ/Subscription.cs | 45 ++++ Connectors/Redis/Connection.cs | 244 ++++++++++++++++++ Connectors/Redis/Readme.md | 199 ++++++++++++++ Connectors/Redis/Redis.csproj | 33 +++ .../Redis/Subscriptions/PubSubscription.cs | 25 ++ .../QueryResponseSubscription.cs | 26 ++ .../Redis/Subscriptions/SubscriptionBase.cs | 79 ++++++ Core/ContractConnection.cs | 19 +- Core/Subscriptions/PubSubSubscription.cs | 2 + Core/Subscriptions/QueryResponseHelper.cs | 4 +- MQContract.sln | 21 ++ Samples/ActiveMQSample/Program.cs | 65 +---- Samples/KafkaSample/Program.cs | 65 +---- Samples/KubeMQSample/Program.cs | 63 +---- Samples/Messages/Messages.csproj | 1 + Samples/Messages/SampleExecution.cs | 81 ++++++ Samples/NATSSample/Program.cs | 63 +---- Samples/RedisSample/Program.cs | 17 ++ Samples/RedisSample/RedisSample.csproj | 19 ++ 27 files changed, 1053 insertions(+), 266 deletions(-) create mode 100644 Connectors/RabbitMQ/Connection.cs create mode 100644 Connectors/RabbitMQ/RabbitMQ.csproj create mode 100644 Connectors/RabbitMQ/Readme.md create mode 100644 Connectors/RabbitMQ/Subscription.cs create mode 100644 Connectors/Redis/Connection.cs create mode 100644 Connectors/Redis/Readme.md create mode 100644 Connectors/Redis/Redis.csproj create mode 100644 Connectors/Redis/Subscriptions/PubSubscription.cs create mode 100644 Connectors/Redis/Subscriptions/QueryResponseSubscription.cs create mode 100644 Connectors/Redis/Subscriptions/SubscriptionBase.cs create mode 100644 Samples/Messages/SampleExecution.cs create mode 100644 Samples/RedisSample/Program.cs create mode 100644 Samples/RedisSample/RedisSample.csproj diff --git a/Abstractions/Messages/RecievedServiceMessage.cs b/Abstractions/Messages/RecievedServiceMessage.cs index 55cb5bc..fc7faac 100644 --- a/Abstractions/Messages/RecievedServiceMessage.cs +++ b/Abstractions/Messages/RecievedServiceMessage.cs @@ -10,8 +10,9 @@ namespace MQContract.Messages /// The channel the message was recieved on /// The message headers that came through /// The binary content of the message that should be the encoded class + /// The acknowledgement callback to be called when the message is recieved if the underlying service requires it [ExcludeFromCodeCoverage(Justification ="This is a record class and has nothing to test")] - public record RecievedServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header, ReadOnlyMemory Data) + public record RecievedServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header, ReadOnlyMemory Data,Func? Acknowledge=null) : ServiceMessage(ID,MessageTypeID,Channel,Header,Data) { /// diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index 4c02cdf..0cedc5a 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -89,7 +89,8 @@ - [Header](#P-MQContract-Messages-QueryResult`1-Header 'MQContract.Messages.QueryResult`1.Header') - [Result](#P-MQContract-Messages-QueryResult`1-Result 'MQContract.Messages.QueryResult`1.Result') - [RecievedServiceMessage](#T-MQContract-Messages-RecievedServiceMessage 'MQContract.Messages.RecievedServiceMessage') - - [#ctor(ID,MessageTypeID,Channel,Header,Data)](#M-MQContract-Messages-RecievedServiceMessage-#ctor-System-String,System-String,System-String,MQContract-Messages-MessageHeader,System-ReadOnlyMemory{System-Byte}- 'MQContract.Messages.RecievedServiceMessage.#ctor(System.String,System.String,System.String,MQContract.Messages.MessageHeader,System.ReadOnlyMemory{System.Byte})') + - [#ctor(ID,MessageTypeID,Channel,Header,Data,Acknowledge)](#M-MQContract-Messages-RecievedServiceMessage-#ctor-System-String,System-String,System-String,MQContract-Messages-MessageHeader,System-ReadOnlyMemory{System-Byte},System-Func{System-Threading-Tasks-ValueTask}- 'MQContract.Messages.RecievedServiceMessage.#ctor(System.String,System.String,System.String,MQContract.Messages.MessageHeader,System.ReadOnlyMemory{System.Byte},System.Func{System.Threading.Tasks.ValueTask})') + - [Acknowledge](#P-MQContract-Messages-RecievedServiceMessage-Acknowledge 'MQContract.Messages.RecievedServiceMessage.Acknowledge') - [RecievedTimestamp](#P-MQContract-Messages-RecievedServiceMessage-RecievedTimestamp 'MQContract.Messages.RecievedServiceMessage.RecievedTimestamp') - [ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') - [#ctor(ID,MessageTypeID,Channel,Header,Data)](#M-MQContract-Messages-ServiceMessage-#ctor-System-String,System-String,System-String,MQContract-Messages-MessageHeader,System-ReadOnlyMemory{System-Byte}- 'MQContract.Messages.ServiceMessage.#ctor(System.String,System.String,System.String,MQContract.Messages.MessageHeader,System.ReadOnlyMemory{System.Byte})') @@ -1420,8 +1421,8 @@ A Recieved Service Message that gets passed back up into the Contract Connection | ---- | ---- | ----------- | | ID | [T:MQContract.Messages.RecievedServiceMessage](#T-T-MQContract-Messages-RecievedServiceMessage 'T:MQContract.Messages.RecievedServiceMessage') | The unique ID of the message | - -### #ctor(ID,MessageTypeID,Channel,Header,Data) `constructor` + +### #ctor(ID,MessageTypeID,Channel,Header,Data,Acknowledge) `constructor` ##### Summary @@ -1436,6 +1437,14 @@ A Recieved Service Message that gets passed back up into the Contract Connection | Channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel the message was recieved on | | Header | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The message headers that came through | | Data | [System.ReadOnlyMemory{System.Byte}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.ReadOnlyMemory 'System.ReadOnlyMemory{System.Byte}') | The binary content of the message that should be the encoded class | +| Acknowledge | [System.Func{System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.Threading.Tasks.ValueTask}') | The acknowledgement callback to be called when the message is recieved if the underlying service requires it | + + +### Acknowledge `property` + +##### Summary + +The acknowledgement callback to be called when the message is recieved if the underlying service requires it ### RecievedTimestamp `property` diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index da4863b..3064802 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -65,7 +65,8 @@ internal static RecievedServiceMessage ProduceMessage(string channel, IMessage m messageTypeID!, channel, headers, - message.Body() + message.Body(), + async ()=>await message.AcknowledgeAsync() ); } diff --git a/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs b/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs index 661bf2a..90d09f3 100644 --- a/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs +++ b/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs @@ -21,7 +21,7 @@ internal async ValueTask StartAsync() try { var msg = await consumer.ReceiveAsync(); - if (msg!=null) + if (msg!=null) messageRecieved(msg); } catch (Exception ex) diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 383009b..03ea5cc 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -89,7 +89,7 @@ public async ValueTask PublishAsync(ServiceMessage message, var subscription = new PublishSubscription( new ConsumerBuilder(new ConsumerConfig(clientConfig) { - GroupId=(!string.IsNullOrWhiteSpace(group) ? group : null) + GroupId=(!string.IsNullOrWhiteSpace(group) ? group : Guid.NewGuid().ToString()) }).Build(), messageRecieved, errorRecieved, diff --git a/Connectors/RabbitMQ/Connection.cs b/Connectors/RabbitMQ/Connection.cs new file mode 100644 index 0000000..7c9f34e --- /dev/null +++ b/Connectors/RabbitMQ/Connection.cs @@ -0,0 +1,110 @@ +using MQContract.Interfaces.Service; +using MQContract.Messages; +using RabbitMQ.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.RabbitMQ +{ + /// + /// This is the MessageServiceConnection implemenation for using RabbitMQ + /// + public class Connection : IMessageServiceConnection,IDisposable + { + private readonly IConnection conn; + private readonly IModel channel; + private readonly SemaphoreSlim semaphore = new(1, 1); + private bool disposedValue; + + /// + /// Default constructor for creating instance + /// + /// The connection factory to use that was built with required authentication and connection information + public Connection(ConnectionFactory factory) + { + conn = factory.CreateConnection(); + channel = conn.CreateModel(); + MaxMessageBodySize = factory.MaxMessageSize; + } + + /// + /// The maximum message body size allowed + /// + public uint? MaxMessageBodySize { get; private init; } + + public ValueTask CloseAsync() + { + Dispose(true); + return ValueTask.CompletedTask; + } + + /// + /// Called to publish a message into the ActiveMQ server + /// + /// The service message being sent + /// A cancellation token + /// Transmition result identifying if it worked or not + public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) + { + await semaphore.WaitAsync(cancellationToken); + TransmissionResult result; + try + { + var props = channel.CreateBasicProperties(); + props.MessageId = message.MessageTypeID; + props.Type = message.MessageTypeID; + foreach(var key in message.Header.Keys) + props.Headers.Add(key, message.Header[key]); + channel.BasicPublish(string.Empty, message.Channel,props, message.Data); + result = new TransmissionResult(message.ID); + }catch(Exception e) + { + result = new TransmissionResult(message.ID, e.Message); + } + semaphore.Release(); + return result; + } + + /// + /// Called to create a subscription to the underlying RabbitMQ server + /// + /// Callback for when a message is recieved + /// Callback for when an error occurs + /// The name of the channel to bind to + /// + /// + /// + public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + { + return ValueTask.FromResult(new Subscription(conn,channel,group, messageRecieved, errorRecieved)); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + semaphore.Wait(); + channel.Close(); + channel.Dispose(); + conn.Close(); + conn.Dispose(); + semaphore.Release(); + semaphore.Dispose(); + } + disposedValue=true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Connectors/RabbitMQ/RabbitMQ.csproj b/Connectors/RabbitMQ/RabbitMQ.csproj new file mode 100644 index 0000000..66bd87f --- /dev/null +++ b/Connectors/RabbitMQ/RabbitMQ.csproj @@ -0,0 +1,33 @@ + + + + + + net8.0 + enable + enable + MQContract.$(MSBuildProjectName) + MQContract.$(MSBuildProjectName) + RabbitMQ Connector for MQContract + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + True + \ + + + diff --git a/Connectors/RabbitMQ/Readme.md b/Connectors/RabbitMQ/Readme.md new file mode 100644 index 0000000..a216a22 --- /dev/null +++ b/Connectors/RabbitMQ/Readme.md @@ -0,0 +1,80 @@ + +# MQContract.RabbitMQ + +## Contents + +- [Connection](#T-MQContract-RabbitMQ-Connection 'MQContract.RabbitMQ.Connection') + - [#ctor(factory)](#M-MQContract-RabbitMQ-Connection-#ctor-RabbitMQ-Client-ConnectionFactory- 'MQContract.RabbitMQ.Connection.#ctor(RabbitMQ.Client.ConnectionFactory)') + - [MaxMessageBodySize](#P-MQContract-RabbitMQ-Connection-MaxMessageBodySize 'MQContract.RabbitMQ.Connection.MaxMessageBodySize') + - [PublishAsync(message,cancellationToken)](#M-MQContract-RabbitMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') + - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-RabbitMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') + + +## Connection `type` + +##### Namespace + +MQContract.RabbitMQ + +##### Summary + +This is the MessageServiceConnection implemenation for using RabbitMQ + + +### #ctor(factory) `constructor` + +##### Summary + +Default constructor for creating instance + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| factory | [RabbitMQ.Client.ConnectionFactory](#T-RabbitMQ-Client-ConnectionFactory 'RabbitMQ.Client.ConnectionFactory') | The connection factory to use that was built with required authentication and connection information | + + +### MaxMessageBodySize `property` + +##### Summary + +The maximum message body size allowed + + +### PublishAsync(message,cancellationToken) `method` + +##### Summary + +Called to publish a message into the ActiveMQ server + +##### Returns + +Transmition result identifying if it worked or not + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + + +### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` + +##### Summary + +Called to create a subscription to the underlying RabbitMQ server + +##### Returns + + + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | | diff --git a/Connectors/RabbitMQ/Subscription.cs b/Connectors/RabbitMQ/Subscription.cs new file mode 100644 index 0000000..10fd9fa --- /dev/null +++ b/Connectors/RabbitMQ/Subscription.cs @@ -0,0 +1,45 @@ +using MQContract.Interfaces.Service; +using MQContract.Messages; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.RabbitMQ +{ + internal class Subscription : IServiceSubscription + { + private readonly IModel channel; + + public Subscription(IConnection conn,string channel,string? group, Action messageRecieved, Action errorRecieved) + { + this.channel = conn.CreateModel(); + this.channel.QueueBind(group??string.Empty, channel, string.Empty); + var consumer = new EventingBasicConsumer(this.channel); + consumer.Received+=(sender, @event) => + { + messageRecieved(new( + @event.BasicProperties.MessageId, + @event.BasicProperties.Type, + @event.RoutingKey, + new(@event.BasicProperties.Headers.Select(pair => new KeyValuePair(pair.Key, pair.Value.ToString()!))), + @event.Body, + () => + { + this.channel.BasicAck(@event.DeliveryTag, false); + return ValueTask.CompletedTask; + } + )); + }; + } + + public ValueTask EndAsync() + { + channel.Close(); + return ValueTask.CompletedTask; + } + } +} diff --git a/Connectors/Redis/Connection.cs b/Connectors/Redis/Connection.cs new file mode 100644 index 0000000..5612a21 --- /dev/null +++ b/Connectors/Redis/Connection.cs @@ -0,0 +1,244 @@ +using MQContract.Interfaces.Service; +using MQContract.Messages; +using MQContract.Redis.Subscriptions; +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading.Tasks; + +namespace MQContract.Redis +{ + /// + /// This is the MessageServiceConnection implementation for using Redis + /// + public class Connection : IQueryableMessageServiceConnection,IAsyncDisposable,IDisposable + { + private readonly ConnectionMultiplexer connectionMultiplexer; + private readonly IDatabase database; + private readonly Guid connectionID = Guid.NewGuid(); + private bool disposedValue; + + /// + /// Default constructor that requires the Redis Configuration settings to be provided + /// + /// + public Connection(ConfigurationOptions configuration) + { + connectionMultiplexer = ConnectionMultiplexer.Connect(configuration); + database = connectionMultiplexer.GetDatabase(); + } + + /// + /// Called to define a consumer group inside redis for a given channel + /// + /// The name of the channel to use + /// The name of the group to use + /// A ValueTask while the operation executes asynchronously + public async ValueTask DefineConsumerGroupAsync(string channel,string group) + { + if (!(await database.KeyExistsAsync(channel)) || + !(await database.StreamGroupInfoAsync(channel)).Any(x => Equals(x.Name,group))) + { + await database.StreamCreateConsumerGroupAsync(channel, group, "0-0", true); + } + } + + /// + /// The maximum message body size allowed, defaults to 4MB + /// + public uint? MaxMessageBodySize { get; init; } = 1024*1024*4; + + /// + /// The default timeout to allow for a Query Response call to execute, defaults to 1 minute + /// + public TimeSpan DefaultTimout => TimeSpan.FromMinutes(1); + + /// + /// Called to close off the underlying Redis Connection + /// + /// + public async ValueTask CloseAsync() + => await connectionMultiplexer.CloseAsync(); + + private const string MESSAGE_TYPE_KEY = "_MessageTypeID"; + private const string MESSAGE_ID_KEY = "_MessageID"; + private const string MESSAGE_DATA_KEY = "_MessageData"; + private const string MESSAGE_REPLY_KEY = "_MessageReplyChannel"; + private const string MESSAGE_TIMEOUT_KEY = "_MessageTimeout"; + + internal static NameValueEntry[] ConvertMessage(ServiceMessage message, string? replyChannel=null,TimeSpan? messageTimeout=null) + => message.Header.Keys.Select(k => new NameValueEntry(k, message.Header[k])) + .Concat( + [ + new NameValueEntry(MESSAGE_ID_KEY,message.ID), + new NameValueEntry(MESSAGE_TYPE_KEY,message.MessageTypeID), + new NameValueEntry(MESSAGE_DATA_KEY,message.Data.ToArray()) + ]) + .Concat(replyChannel==null ? [] : [new NameValueEntry(MESSAGE_REPLY_KEY,replyChannel)]) + .Concat(messageTimeout==null ? [] : [new NameValueEntry(MESSAGE_TIMEOUT_KEY,messageTimeout.ToString())] ) + .ToArray(); + + internal static (RecievedServiceMessage recievedMessage,string? replyChannel,TimeSpan? messageTimeout) ConvertMessage(NameValueEntry[] data, string channel,Func? acknowledge) +#pragma warning disable S6580 // Use a format provider when parsing date and time + => ( + new( + data.First(nve=>Equals(nve.Name,MESSAGE_ID_KEY)).Value.ToString(), + data.First(nve => Equals(nve.Name, MESSAGE_TYPE_KEY)).Value.ToString(), + channel, + new(data.Where(nve=>!Equals(nve.Name,MESSAGE_ID_KEY) + && !Equals(nve.Name,MESSAGE_TYPE_KEY) + && !Equals(nve.Name,MESSAGE_DATA_KEY) + && !Equals(nve.Name,MESSAGE_REPLY_KEY) + && !Equals(nve.Name,MESSAGE_TIMEOUT_KEY) + ) + .Select(nve=>new KeyValuePair(nve.Name!,nve.Value.ToString()))), + (byte[])data.First(nve=>Equals(nve.Name,MESSAGE_DATA_KEY)).Value!, + acknowledge + ), + Array.Find(data,(nve)=>Equals(nve.Name,MESSAGE_REPLY_KEY)).Value.ToString(), + (Array.Exists(data,nve=>Equals(nve.Name,MESSAGE_TIMEOUT_KEY)) ? + TimeSpan.Parse(Array.Find(data, (nve) => Equals(nve.Name, MESSAGE_TIMEOUT_KEY)).Value.ToString()) + : null) + ); +#pragma warning restore S6580 // Use a format provider when parsing date and time + + internal static string EncodeMessage(ServiceMessage result) + => JsonSerializer.Serialize>>( + result.Header.Keys.Select(k=> new KeyValuePair(k, result.Header[k]!)) + .Concat([ + new KeyValuePair(MESSAGE_ID_KEY, result.ID), + new KeyValuePair(MESSAGE_TYPE_KEY,result.MessageTypeID), + new KeyValuePair(MESSAGE_DATA_KEY,result.Data) + ]) + ); + + internal static ServiceQueryResult DecodeMessage(string content) + { + var data = JsonSerializer.Deserialize>>(content)!; + return new( + data.First(pair=>Equals(pair.Key,MESSAGE_ID_KEY)).Value.GetValue(), + new(data.Where(pair=>!Equals(pair.Key,MESSAGE_ID_KEY) && !Equals(pair.Key,MESSAGE_TYPE_KEY) && !Equals(pair.Key,MESSAGE_DATA_KEY)) + .Select(pair=>new KeyValuePair(pair.Key,pair.Value.GetValue())) + ), + data.First(pair => Equals(pair.Key, MESSAGE_TYPE_KEY)).Value.GetValue(), + Convert.FromBase64String(data.First(pair => Equals(pair.Key, MESSAGE_DATA_KEY)).Value.GetValue()) + ); + } + + /// + /// Called to publish a message into the Redis server + /// + /// The service message being sent + /// A cancellation token + /// Transmition result identifying if it worked or not + public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) + { + try + { + _ = await database.StreamAddAsync(message.Channel, ConvertMessage(message)); + return new(message.ID); + }catch(Exception e) + { + return new(message.ID, e.Message); + } + } + + /// + /// Called to create a subscription to the underlying Redis server + /// + /// Callback for when a message is recieved + /// Callback for when an error occurs + /// The name of the channel to bind to + /// The name of the group to bind the consumer to + /// A cancellation token + /// + public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + { + if (group!=null) + await DefineConsumerGroupAsync(channel, group!); + var result = new PubSubscription(messageRecieved, errorRecieved, database, connectionID, channel, group); + await result.StartAsync(); + return result; + } + + /// + /// Called to publish a query into the Redis server + /// + /// The service message being sent + /// The timeout supplied for the query to response + /// A cancellation token + /// The resulting response + /// Thrown when the response times out + public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) + { + var replyID = $"_inbox.{Guid.NewGuid()}"; + await database.StreamAddAsync(message.Channel, ConvertMessage(message, replyID, timeout)); + using var cancellation = new CancellationTokenSource(timeout); + using var cleanupEntry = cancellationToken.Register(() => cancellation.Cancel()); + while (!cancellation.IsCancellationRequested) + { + var keyValue = await database.StringGetDeleteAsync(replyID); + if (!keyValue.IsNull) + return DecodeMessage(keyValue.ToString()); + else + await Task.Delay((int)timeout.TotalMilliseconds/500,cancellationToken); + } + throw new TimeoutException(); + } + + /// + /// Called to create a subscription for queries to the underlying Redis server + /// + /// Callback for when a query is recieved + /// Callback for when an error occurs + /// The name of the channel to bind to + /// The group to bind to + /// A cancellation token + /// A subscription instance + public async ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + { + if (group!=null) + await DefineConsumerGroupAsync(channel, group!); + var result = new QueryResponseSubscription(messageRecieved, errorRecieved, database, connectionID, channel, group); + await result.StartAsync(); + return result; + } + + /// + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// + /// A task required for disposal + public async ValueTask DisposeAsync() + { + await connectionMultiplexer.DisposeAsync(); + + Dispose(false); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + connectionMultiplexer.Dispose(); + disposedValue=true; + } + } + + /// + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Connectors/Redis/Readme.md b/Connectors/Redis/Readme.md new file mode 100644 index 0000000..e95fc51 --- /dev/null +++ b/Connectors/Redis/Readme.md @@ -0,0 +1,199 @@ + +# MQContract.Redis + +## Contents + +- [Connection](#T-MQContract-Redis-Connection 'MQContract.Redis.Connection') + - [#ctor(configuration)](#M-MQContract-Redis-Connection-#ctor-StackExchange-Redis-ConfigurationOptions- 'MQContract.Redis.Connection.#ctor(StackExchange.Redis.ConfigurationOptions)') + - [DefaultTimout](#P-MQContract-Redis-Connection-DefaultTimout 'MQContract.Redis.Connection.DefaultTimout') + - [MaxMessageBodySize](#P-MQContract-Redis-Connection-MaxMessageBodySize 'MQContract.Redis.Connection.MaxMessageBodySize') + - [CloseAsync()](#M-MQContract-Redis-Connection-CloseAsync 'MQContract.Redis.Connection.CloseAsync') + - [DefineConsumerGroupAsync(channel,group)](#M-MQContract-Redis-Connection-DefineConsumerGroupAsync-System-String,System-String- 'MQContract.Redis.Connection.DefineConsumerGroupAsync(System.String,System.String)') + - [Dispose()](#M-MQContract-Redis-Connection-Dispose 'MQContract.Redis.Connection.Dispose') + - [DisposeAsync()](#M-MQContract-Redis-Connection-DisposeAsync 'MQContract.Redis.Connection.DisposeAsync') + - [PublishAsync(message,cancellationToken)](#M-MQContract-Redis-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.Redis.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') + - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-Redis-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.Redis.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') + - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Redis-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Redis.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Redis-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Redis.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') + + +## Connection `type` + +##### Namespace + +MQContract.Redis + +##### Summary + +This is the MessageServiceConnection implementation for using Redis + + +### #ctor(configuration) `constructor` + +##### Summary + +Default constructor that requires the Redis Configuration settings to be provided + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| configuration | [StackExchange.Redis.ConfigurationOptions](#T-StackExchange-Redis-ConfigurationOptions 'StackExchange.Redis.ConfigurationOptions') | | + + +### DefaultTimout `property` + +##### Summary + +The default timeout to allow for a Query Response call to execute, defaults to 1 minute + + +### MaxMessageBodySize `property` + +##### Summary + +The maximum message body size allowed, defaults to 4MB + + +### CloseAsync() `method` + +##### Summary + +Called to close off the underlying Redis Connection + +##### Returns + + + +##### Parameters + +This method has no parameters. + + +### DefineConsumerGroupAsync(channel,group) `method` + +##### Summary + +Called to define a consumer group inside redis for a given channel + +##### Returns + +A ValueTask while the operation executes asynchronously + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to use | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the group to use | + + +### Dispose() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Parameters + +This method has no parameters. + + +### DisposeAsync() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Returns + +A task required for disposal + +##### Parameters + +This method has no parameters. + + +### PublishAsync(message,cancellationToken) `method` + +##### Summary + +Called to publish a message into the Redis server + +##### Returns + +Transmition result identifying if it worked or not + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + + +### QueryAsync(message,timeout,cancellationToken) `method` + +##### Summary + +Called to publish a query into the Redis server + +##### Returns + +The resulting response + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | +| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +##### Exceptions + +| Name | Description | +| ---- | ----------- | +| [System.TimeoutException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeoutException 'System.TimeoutException') | Thrown when the response times out | + + +### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` + +##### Summary + +Called to create a subscription to the underlying Redis server + +##### Returns + + + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the group to bind the consumer to | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + + +### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` + +##### Summary + +Called to create a subscription for queries to the underlying Redis server + +##### Returns + +A subscription instance + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind to | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/Redis/Redis.csproj b/Connectors/Redis/Redis.csproj new file mode 100644 index 0000000..baa8024 --- /dev/null +++ b/Connectors/Redis/Redis.csproj @@ -0,0 +1,33 @@ + + + + + + net8.0 + enable + enable + MQContract.$(MSBuildProjectName) + MQContract.$(MSBuildProjectName) + Redis Connector for MQContract + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + True + \ + + + diff --git a/Connectors/Redis/Subscriptions/PubSubscription.cs b/Connectors/Redis/Subscriptions/PubSubscription.cs new file mode 100644 index 0000000..214c0b0 --- /dev/null +++ b/Connectors/Redis/Subscriptions/PubSubscription.cs @@ -0,0 +1,25 @@ +using MQContract.Messages; +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.Redis.Subscriptions +{ + internal class PubSubscription(Action messageRecieved, Action errorRecieved, IDatabase database, Guid connectionID, string channel, string? group) + : SubscriptionBase(errorRecieved,database,connectionID,channel,group) + { + protected override ValueTask ProcessMessage(StreamEntry streamEntry, string channel, string? group) + { + (var message,_,_) = Connection.ConvertMessage( + streamEntry.Values, + channel, + () => Acknowledge(streamEntry.Id) + ); + messageRecieved(message); + return ValueTask.CompletedTask; + } + } +} diff --git a/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs b/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs new file mode 100644 index 0000000..b3697e6 --- /dev/null +++ b/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs @@ -0,0 +1,26 @@ +using MQContract.Messages; +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.Redis.Subscriptions +{ + internal class QueryResponseSubscription(Func> messageRecieved, Action errorRecieved, IDatabase database, Guid connectionID, string channel, string? group) + : SubscriptionBase(errorRecieved,database,connectionID,channel,group) + { + protected override async ValueTask ProcessMessage(StreamEntry streamEntry, string channel, string? group) + { + (var message,var responseChannel,var timeout) = Connection.ConvertMessage( + streamEntry.Values, + channel, + ()=> Acknowledge(streamEntry.Id) + ); + var result = await messageRecieved(message); + await Database.StreamDeleteAsync(Channel, [streamEntry.Id]); + await Database.StringSetAsync(responseChannel, Connection.EncodeMessage(result), expiry: timeout); + } + } +} diff --git a/Connectors/Redis/Subscriptions/SubscriptionBase.cs b/Connectors/Redis/Subscriptions/SubscriptionBase.cs new file mode 100644 index 0000000..7dccdc5 --- /dev/null +++ b/Connectors/Redis/Subscriptions/SubscriptionBase.cs @@ -0,0 +1,79 @@ +using MQContract.Interfaces.Service; +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.Redis.Subscriptions +{ + internal abstract class SubscriptionBase(Action errorRecieved, IDatabase database, Guid connectionID, string channel, string? group) : IServiceSubscription,IDisposable + { + private readonly CancellationTokenSource tokenSource = new(); + private bool disposedValue; + + protected CancellationToken Token=>tokenSource.Token; + protected IDatabase Database => database; + protected string Channel => channel; + protected string? Group=> group; + + public Task StartAsync() + { + var resultSource = new TaskCompletionSource(); + RedisValue minId = "-"; + Task.Run(async () => + { + resultSource.TrySetResult(); + while (!Token.IsCancellationRequested) + { + try + { + var result = await (group==null ? database.StreamRangeAsync(channel, minId, "+", 1) : database.StreamReadGroupAsync(channel, group!, connectionID.ToString(), ">", 1)); + if (result.Length!=0) + { + minId = result[0].Id+1; + await ProcessMessage(result[0], channel, group); + } + else + await Task.Delay(50); + } + catch (Exception ex) + { + errorRecieved(ex); + } + } + }); + return resultSource.Task; + } + + protected async ValueTask Acknowledge(RedisValue Id) + { + if (Group!=null) + await Database.StreamAcknowledgeAsync(channel, group, Id); + } + + protected abstract ValueTask ProcessMessage(StreamEntry streamEntry,string channel,string? group); + + public async ValueTask EndAsync() + =>await tokenSource.CancelAsync(); + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing && !tokenSource.IsCancellationRequested) + tokenSource.Cancel(); + tokenSource.Dispose(); + disposedValue=true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index fc6ec9c..9ad1fd1 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -218,17 +218,24 @@ public async ValueTask> QueryAsync(Q message, TimeSpan? t #pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields var methodInfo = typeof(ContractConnection).GetMethod(nameof(ContractConnection.ExecuteQueryAsync), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(typeof(Q), responseType!); #pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields - var queryResult = (dynamic?)await Utility.InvokeMethodAsync( - methodInfo, - this, [ - message, + dynamic? queryResult; + try + { + queryResult = (dynamic?)await Utility.InvokeMethodAsync( + methodInfo, + this, [ + message, timeout, channel, responseChannel, messageHeader, cancellationToken - ] - ); + ] + ); + }catch(TimeoutException) + { + throw new QueryTimeoutException(); + } return new QueryResult(queryResult?.ID??string.Empty, queryResult?.Header??new MessageHeader([]), queryResult?.Result, queryResult?.Error); } diff --git a/Core/Subscriptions/PubSubSubscription.cs b/Core/Subscriptions/PubSubSubscription.cs index a6909a5..ab84cd9 100644 --- a/Core/Subscriptions/PubSubSubscription.cs +++ b/Core/Subscriptions/PubSubSubscription.cs @@ -33,6 +33,8 @@ private async ValueTask ProcessMessage(RecievedServiceMessage serviceMessage) var taskMessage = await messageFactory.ConvertMessageAsync(logger, serviceMessage) ??throw new InvalidCastException($"Unable to convert incoming message {serviceMessage.MessageTypeID} to {typeof(T).FullName}"); var tsk = messageRecieved(new RecievedMessage(serviceMessage.ID, taskMessage!, serviceMessage.Header, serviceMessage.RecievedTimestamp, DateTime.Now)); + if (serviceMessage.Acknowledge!=null) + await serviceMessage.Acknowledge(); if (Synchronous) await tsk.ConfigureAwait(false); } diff --git a/Core/Subscriptions/QueryResponseHelper.cs b/Core/Subscriptions/QueryResponseHelper.cs index de339be..1999237 100644 --- a/Core/Subscriptions/QueryResponseHelper.cs +++ b/Core/Subscriptions/QueryResponseHelper.cs @@ -44,13 +44,15 @@ public static async Task, Cancell var reg = cancellationToken.Register(() => token.Cancel()); var result = new TaskCompletionSource(); var consumer = await connection.SubscribeAsync( - (message) => + async (message) => { if (!result.Task.IsCompleted) { var headers = StripHeaders(message, out var queryClientID, out var replyID, out _); if (Equals(queryClientID, identifier) && Equals(replyID, callID)) { + if (message.Acknowledge!=null) + await message.Acknowledge(); result.TrySetResult(new( message.ID, headers, diff --git a/MQContract.sln b/MQContract.sln index 85857ed..d2dc690 100644 --- a/MQContract.sln +++ b/MQContract.sln @@ -31,6 +31,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActiveMQ", "Connectors\Acti EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActiveMQSample", "Samples\ActiveMQSample\ActiveMQSample.csproj", "{F734932E-2624-4ADC-8EBE-FCE579AF09D9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ", "Connectors\RabbitMQ\RabbitMQ.csproj", "{0B5C5567-6EA2-4DA9-9DB5-E775630F5655}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redis", "Connectors\Redis\Redis.csproj", "{DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisSample", "Samples\RedisSample\RedisSample.csproj", "{EA577C5C-036F-4647-80F1-03F0EFAF6081}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -85,6 +91,18 @@ Global {F734932E-2624-4ADC-8EBE-FCE579AF09D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {F734932E-2624-4ADC-8EBE-FCE579AF09D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {F734932E-2624-4ADC-8EBE-FCE579AF09D9}.Release|Any CPU.Build.0 = Release|Any CPU + {0B5C5567-6EA2-4DA9-9DB5-E775630F5655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B5C5567-6EA2-4DA9-9DB5-E775630F5655}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B5C5567-6EA2-4DA9-9DB5-E775630F5655}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B5C5567-6EA2-4DA9-9DB5-E775630F5655}.Release|Any CPU.Build.0 = Release|Any CPU + {DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541}.Release|Any CPU.Build.0 = Release|Any CPU + {EA577C5C-036F-4647-80F1-03F0EFAF6081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA577C5C-036F-4647-80F1-03F0EFAF6081}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA577C5C-036F-4647-80F1-03F0EFAF6081}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA577C5C-036F-4647-80F1-03F0EFAF6081}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -99,6 +117,9 @@ Global {76FFF0EF-C7F4-4D07-9CE1-CA695037CC11} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} {3DF8097C-D24F-4AB9-98E3-A17607ECCE25} = {FCAD12F9-6992-44D7-8E78-464181584E06} {F734932E-2624-4ADC-8EBE-FCE579AF09D9} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} + {0B5C5567-6EA2-4DA9-9DB5-E775630F5655} = {FCAD12F9-6992-44D7-8E78-464181584E06} + {DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541} = {FCAD12F9-6992-44D7-8E78-464181584E06} + {EA577C5C-036F-4647-80F1-03F0EFAF6081} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F4431E3A-7CBE-469C-A7FE-0A48F6768E02} diff --git a/Samples/ActiveMQSample/Program.cs b/Samples/ActiveMQSample/Program.cs index 269dbe2..d3cdf55 100644 --- a/Samples/ActiveMQSample/Program.cs +++ b/Samples/ActiveMQSample/Program.cs @@ -10,67 +10,4 @@ var serviceConnection = new Connection(new Uri("amqp:tcp://localhost:5672"),"artemis","artemis"); -var contractConnection = new ContractConnection(serviceConnection); - -var announcementSubscription = await contractConnection.SubscribeAsync( - (announcement) => - { - Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return ValueTask.CompletedTask; - }, - (error) => Console.WriteLine($"Announcement error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( - (greeting) => - { - Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); - System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return new( - $"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the Kafka sample" - ); - }, - (error) => Console.WriteLine($"Greeting error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -var storedArrivalSubscription = await contractConnection.SubscribeAsync( - (announcement) => - { - Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return ValueTask.CompletedTask; - }, - (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -sourceCancel.Token.Register(async () => -{ - await Task.WhenAll( - announcementSubscription.EndAsync().AsTask(), - greetingSubscription.EndAsync().AsTask(), - storedArrivalSubscription.EndAsync().AsTask() - ).ConfigureAwait(true); - await contractConnection.CloseAsync().ConfigureAwait(true); -}, true); - -var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); -result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); - -var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); -response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); - -var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Stored Result 1 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); -storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Stored Result 2 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); - -Console.WriteLine("Press Ctrl+C to close"); - -sourceCancel.Token.WaitHandle.WaitOne(); -Console.WriteLine("System completed operation"); +await SampleExecution.ExecuteSample(serviceConnection, "ActiveMQ", sourceCancel); diff --git a/Samples/KafkaSample/Program.cs b/Samples/KafkaSample/Program.cs index 900e466..cc47c49 100644 --- a/Samples/KafkaSample/Program.cs +++ b/Samples/KafkaSample/Program.cs @@ -14,67 +14,4 @@ BootstrapServers="localhost:56497" }); -var contractConnection = new ContractConnection(serviceConnection); - -var announcementSubscription = await contractConnection.SubscribeAsync( - (announcement) => - { - Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return ValueTask.CompletedTask; - }, - (error) => Console.WriteLine($"Announcement error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( - (greeting) => - { - Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); - System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return new( - $"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the Kafka sample" - ); - }, - (error) => Console.WriteLine($"Greeting error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -var storedArrivalSubscription = await contractConnection.SubscribeAsync( - (announcement) => - { - Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return ValueTask.CompletedTask; - }, - (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -sourceCancel.Token.Register(async () => -{ - await Task.WhenAll( - announcementSubscription.EndAsync().AsTask(), - greetingSubscription.EndAsync().AsTask(), - storedArrivalSubscription.EndAsync().AsTask() - ).ConfigureAwait(true); - await contractConnection.CloseAsync().ConfigureAwait(true); -}, true); - -var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); -result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); - -var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); -response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); - -var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Stored Result 1 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); -storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Stored Result 2 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); - -Console.WriteLine("Press Ctrl+C to close"); - -sourceCancel.Token.WaitHandle.WaitOne(); -Console.WriteLine("System completed operation"); \ No newline at end of file +await SampleExecution.ExecuteSample(serviceConnection, "Kafka", sourceCancel); \ No newline at end of file diff --git a/Samples/KubeMQSample/Program.cs b/Samples/KubeMQSample/Program.cs index 0f5612f..d7715ea 100644 --- a/Samples/KubeMQSample/Program.cs +++ b/Samples/KubeMQSample/Program.cs @@ -16,65 +16,4 @@ }) .RegisterStoredChannel("StoredArrivals"); -var contractConnection = new ContractConnection(serviceConnection); - -var announcementSubscription = await contractConnection.SubscribeAsync( - (announcement) => - { - Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return ValueTask.CompletedTask; - }, - (error) => Console.WriteLine($"Announcement error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( - (greeting) => - { - Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); - System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the KubeMQ sample"); - }, - (error) => Console.WriteLine($"Greeting error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -var storedArrivalSubscription = await contractConnection.SubscribeAsync( - (announcement) => - { - Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return ValueTask.CompletedTask; - }, - (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -sourceCancel.Token.Register(async () => -{ - await Task.WhenAll( - announcementSubscription.EndAsync().AsTask(), - greetingSubscription.EndAsync().AsTask(), - storedArrivalSubscription.EndAsync().AsTask() - ).ConfigureAwait(true); - await contractConnection.CloseAsync().ConfigureAwait(true); -}, true); - -var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); -result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); - -var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); -response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); - -var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Stored Result 1 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); -storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Stored Result 2 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); - -Console.WriteLine("Press Ctrl+C to close"); - -sourceCancel.Token.WaitHandle.WaitOne(); -Console.WriteLine("System completed operation"); \ No newline at end of file +await SampleExecution.ExecuteSample(serviceConnection, "KubeMQ", sourceCancel); \ No newline at end of file diff --git a/Samples/Messages/Messages.csproj b/Samples/Messages/Messages.csproj index 7ab567c..53fab42 100644 --- a/Samples/Messages/Messages.csproj +++ b/Samples/Messages/Messages.csproj @@ -8,6 +8,7 @@ + diff --git a/Samples/Messages/SampleExecution.cs b/Samples/Messages/SampleExecution.cs new file mode 100644 index 0000000..55e4a53 --- /dev/null +++ b/Samples/Messages/SampleExecution.cs @@ -0,0 +1,81 @@ +using MQContract; +using MQContract.Interfaces.Service; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Messages +{ + public static class SampleExecution + { + public static async ValueTask ExecuteSample(IMessageServiceConnection serviceConnection,string serviceName,CancellationTokenSource sourceCancel,ChannelMapper? mapper=null) + { + var contractConnection = new ContractConnection(serviceConnection,channelMapper:mapper); + + var announcementSubscription = await contractConnection.SubscribeAsync( + (announcement) => + { + Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); + return ValueTask.CompletedTask; + }, + (error) => Console.WriteLine($"Announcement error: {error.Message}"), + cancellationToken: sourceCancel.Token + ); + + var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( + (greeting) => + { + Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); + System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); + return new( + $"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the {serviceName} sample" + ); + }, + (error) => Console.WriteLine($"Greeting error: {error.Message}"), + cancellationToken: sourceCancel.Token + ); + + var storedArrivalSubscription = await contractConnection.SubscribeAsync( + (announcement) => + { + Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); + return ValueTask.CompletedTask; + }, + (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), + cancellationToken: sourceCancel.Token + ); + + sourceCancel.Token.Register(async () => + { + await Task.WhenAll( + announcementSubscription.EndAsync().AsTask(), + greetingSubscription.EndAsync().AsTask(), + storedArrivalSubscription.EndAsync().AsTask() + ).ConfigureAwait(true); + await contractConnection.CloseAsync().ConfigureAwait(true); + }, true); + + var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); + Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); + result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); + Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); + + var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); + Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); + response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); + Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); + + var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); + Console.WriteLine($"Stored Result 1 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); + storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); + Console.WriteLine($"Stored Result 2 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); + + Console.WriteLine("Press Ctrl+C to close"); + + sourceCancel.Token.WaitHandle.WaitOne(); + Console.WriteLine("System completed operation"); + } + } +} diff --git a/Samples/NATSSample/Program.cs b/Samples/NATSSample/Program.cs index ae4242e..dd6cd3c 100644 --- a/Samples/NATSSample/Program.cs +++ b/Samples/NATSSample/Program.cs @@ -22,65 +22,4 @@ var mapper = new ChannelMapper() .AddPublishSubscriptionMap("StoredArrivals", "StoredArrivalsStream"); -var contractConnection = new ContractConnection(serviceConnection,channelMapper:mapper); - -var announcementSubscription = await contractConnection.SubscribeAsync( - (announcement) => - { - Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return ValueTask.CompletedTask; - }, - (error) => Console.WriteLine($"Announcement error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( - (greeting) => - { - Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); - System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); - return new($"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the NATSio sample"); - }, - (error) => Console.WriteLine($"Greeting error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -var storedArrivalSubscription = await contractConnection.SubscribeAsync( - (announcement) => - { - Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); - return ValueTask.CompletedTask; - }, - (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), - cancellationToken: sourceCancel.Token -); - -sourceCancel.Token.Register(async () => -{ - await Task.WhenAll( - announcementSubscription.EndAsync().AsTask(), - greetingSubscription.EndAsync().AsTask(), - storedArrivalSubscription.EndAsync().AsTask() - ).ConfigureAwait(true); - await contractConnection.CloseAsync().ConfigureAwait(true); -}, true); - -var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); -result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Result 2 [Success:{!result.IsError}, ID:{result.ID}]"); - -var response = await contractConnection.QueryAsync(new Greeting("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Response 1 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); -response = await contractConnection.QueryAsync(new Greeting("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Response 2 [Success:{!response.IsError}, ID:{response.ID}, Response: {response.Result}]"); - -var storedResult = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Stored Result 1 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); -storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); -Console.WriteLine($"Stored Result 2 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); - -Console.WriteLine("Press Ctrl+C to close"); - -sourceCancel.Token.WaitHandle.WaitOne(); -Console.WriteLine("System completed operation"); \ No newline at end of file +await SampleExecution.ExecuteSample(serviceConnection, "NatsIO", sourceCancel,mapper); \ No newline at end of file diff --git a/Samples/RedisSample/Program.cs b/Samples/RedisSample/Program.cs new file mode 100644 index 0000000..e60feab --- /dev/null +++ b/Samples/RedisSample/Program.cs @@ -0,0 +1,17 @@ +using Messages; +using MQContract.Redis; +using StackExchange.Redis; +using System.Net; + +using var sourceCancel = new CancellationTokenSource(); + +Console.CancelKeyPress += delegate { + sourceCancel.Cancel(); +}; + +var conf = new ConfigurationOptions(); +conf.EndPoints.Add("localhost"); + +var serviceConnection = new Connection(conf); + +await SampleExecution.ExecuteSample(serviceConnection, "Redis", sourceCancel); diff --git a/Samples/RedisSample/RedisSample.csproj b/Samples/RedisSample/RedisSample.csproj new file mode 100644 index 0000000..74ec212 --- /dev/null +++ b/Samples/RedisSample/RedisSample.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + From 4f92237acc7e682c945d0772b6ca2dbcb7432239 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Fri, 13 Sep 2024 22:58:05 -0400 Subject: [PATCH 12/22] added in rabbitmq support added in and tested rabbitmq support, need to explore a slight refactor to support query response natively --- Connectors/RabbitMQ/Connection.cs | 84 +++++++++++++++++--- Connectors/RabbitMQ/Subscription.cs | 32 ++++---- MQContract.sln | 7 ++ Samples/ActiveMQSample/Program.cs | 8 +- Samples/KafkaSample/Program.cs | 8 +- Samples/KubeMQSample/Program.cs | 8 +- Samples/Messages/SampleExecution.cs | 8 +- Samples/NATSSample/Program.cs | 8 +- Samples/RabbitMQSample/Program.cs | 20 +++++ Samples/RabbitMQSample/RabbitMQSample.csproj | 20 +++++ Samples/RedisSample/Program.cs | 8 +- 11 files changed, 150 insertions(+), 61 deletions(-) create mode 100644 Samples/RabbitMQSample/Program.cs create mode 100644 Samples/RabbitMQSample/RabbitMQSample.csproj diff --git a/Connectors/RabbitMQ/Connection.cs b/Connectors/RabbitMQ/Connection.cs index 7c9f34e..27867de 100644 --- a/Connectors/RabbitMQ/Connection.cs +++ b/Connectors/RabbitMQ/Connection.cs @@ -1,6 +1,7 @@ using MQContract.Interfaces.Service; using MQContract.Messages; using RabbitMQ.Client; +using RabbitMQ.Client.Events; using System; using System.Collections.Generic; using System.Linq; @@ -30,17 +31,71 @@ public Connection(ConnectionFactory factory) MaxMessageBodySize = factory.MaxMessageSize; } + public Connection QueueDeclare(string queue, bool durable = false, bool exclusive = false, + bool autoDelete = true, IDictionary? arguments = null) + { + channel.QueueDeclare(queue, durable, exclusive, autoDelete, arguments); + return this; + } + + public Connection ExchangeDeclare(string exchange, string type, bool durable = false, bool autoDelete = false, + IDictionary arguments = null) + { + channel.ExchangeDeclare(exchange,type,durable,autoDelete,arguments); + return this; + } + + public void QueueDelete(string queue, bool ifUnused, bool ifEmpty) + => channel.QueueDelete(queue,ifUnused,ifEmpty); + /// /// The maximum message body size allowed /// - public uint? MaxMessageBodySize { get; private init; } + public uint? MaxMessageBodySize { get; init; } - public ValueTask CloseAsync() + internal static (IBasicProperties props,ReadOnlyMemory) ConvertMessage(ServiceMessage message,IModel channel) { - Dispose(true); - return ValueTask.CompletedTask; + var props = channel.CreateBasicProperties(); + props.MessageId=message.ID; + props.Type = message.MessageTypeID; + using var ms = new MemoryStream(); + using var bw = new BinaryWriter(ms); + bw.Write(message.Data.Length); + bw.Write(message.Data.ToArray()); + foreach(var key in message.Header.Keys) + { + var bytes = UTF8Encoding.UTF8.GetBytes(key); + bw.Write(bytes.Length); + bw.Write(bytes); + bytes = UTF8Encoding.UTF8.GetBytes(message.Header[key]!); + bw.Write(bytes.Length); + bw.Write(bytes); + } + bw.Flush(); + return (props, ms.ToArray()); } + internal static RecievedServiceMessage ConvertMessage(BasicDeliverEventArgs eventArgs,string channel, Func acknowledge) + { + using var ms = new MemoryStream(eventArgs.Body.ToArray()); + using var br = new BinaryReader(ms); + var data = br.ReadBytes(br.ReadInt32()); + var header = new Dictionary(); + while (br.BaseStream.Position /// Called to publish a message into the ActiveMQ server /// @@ -53,12 +108,8 @@ public async ValueTask PublishAsync(ServiceMessage message, TransmissionResult result; try { - var props = channel.CreateBasicProperties(); - props.MessageId = message.MessageTypeID; - props.Type = message.MessageTypeID; - foreach(var key in message.Header.Keys) - props.Headers.Add(key, message.Header[key]); - channel.BasicPublish(string.Empty, message.Channel,props, message.Data); + (var props, var data) = ConvertMessage(message, this.channel); + channel.BasicPublish(message.Channel,string.Empty,props,data); result = new TransmissionResult(message.ID); }catch(Exception e) { @@ -79,10 +130,21 @@ public async ValueTask PublishAsync(ServiceMessage message, /// public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { + if (group==null) + { + group = Guid.NewGuid().ToString(); + this.channel.QueueDeclare(group,true,false,false); + } return ValueTask.FromResult(new Subscription(conn,channel,group, messageRecieved, errorRecieved)); } - protected virtual void Dispose(bool disposing) + public ValueTask CloseAsync() + { + Dispose(true); + return ValueTask.CompletedTask; + } + + private void Dispose(bool disposing) { if (!disposedValue) { diff --git a/Connectors/RabbitMQ/Subscription.cs b/Connectors/RabbitMQ/Subscription.cs index 10fd9fa..ab339fd 100644 --- a/Connectors/RabbitMQ/Subscription.cs +++ b/Connectors/RabbitMQ/Subscription.cs @@ -13,31 +13,35 @@ namespace MQContract.RabbitMQ internal class Subscription : IServiceSubscription { private readonly IModel channel; + private readonly Guid subscriptionID = Guid.NewGuid(); + private readonly string consumerTag; - public Subscription(IConnection conn,string channel,string? group, Action messageRecieved, Action errorRecieved) + public Subscription(IConnection conn,string channel,string group, Action messageRecieved, Action errorRecieved) { this.channel = conn.CreateModel(); - this.channel.QueueBind(group??string.Empty, channel, string.Empty); + this.channel.QueueBind(group, channel, subscriptionID.ToString()); var consumer = new EventingBasicConsumer(this.channel); consumer.Received+=(sender, @event) => { - messageRecieved(new( - @event.BasicProperties.MessageId, - @event.BasicProperties.Type, - @event.RoutingKey, - new(@event.BasicProperties.Headers.Select(pair => new KeyValuePair(pair.Key, pair.Value.ToString()!))), - @event.Body, - () => - { - this.channel.BasicAck(@event.DeliveryTag, false); - return ValueTask.CompletedTask; - } - )); + messageRecieved( + Connection.ConvertMessage( + @event, + channel, + () => + { + this.channel.BasicAck(@event.DeliveryTag, false); + return ValueTask.CompletedTask; + } + ) + ); }; + + consumerTag = this.channel.BasicConsume(group, false, consumer); } public ValueTask EndAsync() { + channel.BasicCancel(consumerTag); channel.Close(); return ValueTask.CompletedTask; } diff --git a/MQContract.sln b/MQContract.sln index d2dc690..f928f46 100644 --- a/MQContract.sln +++ b/MQContract.sln @@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redis", "Connectors\Redis\R EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisSample", "Samples\RedisSample\RedisSample.csproj", "{EA577C5C-036F-4647-80F1-03F0EFAF6081}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RabbitMQSample", "Samples\RabbitMQSample\RabbitMQSample.csproj", "{0740514D-A6AB-41CC-9820-A619125C6C90}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -103,6 +105,10 @@ Global {EA577C5C-036F-4647-80F1-03F0EFAF6081}.Debug|Any CPU.Build.0 = Debug|Any CPU {EA577C5C-036F-4647-80F1-03F0EFAF6081}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA577C5C-036F-4647-80F1-03F0EFAF6081}.Release|Any CPU.Build.0 = Release|Any CPU + {0740514D-A6AB-41CC-9820-A619125C6C90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0740514D-A6AB-41CC-9820-A619125C6C90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0740514D-A6AB-41CC-9820-A619125C6C90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0740514D-A6AB-41CC-9820-A619125C6C90}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -120,6 +126,7 @@ Global {0B5C5567-6EA2-4DA9-9DB5-E775630F5655} = {FCAD12F9-6992-44D7-8E78-464181584E06} {DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541} = {FCAD12F9-6992-44D7-8E78-464181584E06} {EA577C5C-036F-4647-80F1-03F0EFAF6081} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} + {0740514D-A6AB-41CC-9820-A619125C6C90} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F4431E3A-7CBE-469C-A7FE-0A48F6768E02} diff --git a/Samples/ActiveMQSample/Program.cs b/Samples/ActiveMQSample/Program.cs index d3cdf55..8633532 100644 --- a/Samples/ActiveMQSample/Program.cs +++ b/Samples/ActiveMQSample/Program.cs @@ -2,12 +2,6 @@ using MQContract; using MQContract.ActiveMQ; -using var sourceCancel = new CancellationTokenSource(); - -Console.CancelKeyPress += delegate { - sourceCancel.Cancel(); -}; - var serviceConnection = new Connection(new Uri("amqp:tcp://localhost:5672"),"artemis","artemis"); -await SampleExecution.ExecuteSample(serviceConnection, "ActiveMQ", sourceCancel); +await SampleExecution.ExecuteSample(serviceConnection, "ActiveMQ"); diff --git a/Samples/KafkaSample/Program.cs b/Samples/KafkaSample/Program.cs index cc47c49..5e75b94 100644 --- a/Samples/KafkaSample/Program.cs +++ b/Samples/KafkaSample/Program.cs @@ -2,16 +2,10 @@ using MQContract; using MQContract.Kafka; -using var sourceCancel = new CancellationTokenSource(); - -Console.CancelKeyPress += delegate { - sourceCancel.Cancel(); -}; - await using var serviceConnection = new Connection(new Confluent.Kafka.ClientConfig() { ClientId="KafkaSample", BootstrapServers="localhost:56497" }); -await SampleExecution.ExecuteSample(serviceConnection, "Kafka", sourceCancel); \ No newline at end of file +await SampleExecution.ExecuteSample(serviceConnection, "Kafka"); \ No newline at end of file diff --git a/Samples/KubeMQSample/Program.cs b/Samples/KubeMQSample/Program.cs index d7715ea..e459021 100644 --- a/Samples/KubeMQSample/Program.cs +++ b/Samples/KubeMQSample/Program.cs @@ -3,12 +3,6 @@ using MQContract.KubeMQ; using MQContract.KubeMQ.Options; -using var sourceCancel = new CancellationTokenSource(); - -Console.CancelKeyPress += delegate { - sourceCancel.Cancel(); -}; - await using var serviceConnection = new Connection(new ConnectionOptions() { Logger=new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider().CreateLogger("Messages"), @@ -16,4 +10,4 @@ }) .RegisterStoredChannel("StoredArrivals"); -await SampleExecution.ExecuteSample(serviceConnection, "KubeMQ", sourceCancel); \ No newline at end of file +await SampleExecution.ExecuteSample(serviceConnection, "KubeMQ"); \ No newline at end of file diff --git a/Samples/Messages/SampleExecution.cs b/Samples/Messages/SampleExecution.cs index 55e4a53..029de49 100644 --- a/Samples/Messages/SampleExecution.cs +++ b/Samples/Messages/SampleExecution.cs @@ -10,8 +10,14 @@ namespace Messages { public static class SampleExecution { - public static async ValueTask ExecuteSample(IMessageServiceConnection serviceConnection,string serviceName,CancellationTokenSource sourceCancel,ChannelMapper? mapper=null) + public static async ValueTask ExecuteSample(IMessageServiceConnection serviceConnection,string serviceName,ChannelMapper? mapper=null) { + using var sourceCancel = new CancellationTokenSource(); + + Console.CancelKeyPress += delegate { + sourceCancel.Cancel(); + }; + var contractConnection = new ContractConnection(serviceConnection,channelMapper:mapper); var announcementSubscription = await contractConnection.SubscribeAsync( diff --git a/Samples/NATSSample/Program.cs b/Samples/NATSSample/Program.cs index dd6cd3c..3575e3a 100644 --- a/Samples/NATSSample/Program.cs +++ b/Samples/NATSSample/Program.cs @@ -4,12 +4,6 @@ using MQContract.NATS.Options; using NATS.Client.JetStream.Models; -using var sourceCancel = new CancellationTokenSource(); - -Console.CancelKeyPress += delegate { - sourceCancel.Cancel(); -}; - var serviceConnection = new Connection(new NATS.Client.Core.NatsOpts() { LoggerFactory=new Microsoft.Extensions.Logging.LoggerFactory(), @@ -22,4 +16,4 @@ var mapper = new ChannelMapper() .AddPublishSubscriptionMap("StoredArrivals", "StoredArrivalsStream"); -await SampleExecution.ExecuteSample(serviceConnection, "NatsIO", sourceCancel,mapper); \ No newline at end of file +await SampleExecution.ExecuteSample(serviceConnection, "NatsIO", mapper); \ No newline at end of file diff --git a/Samples/RabbitMQSample/Program.cs b/Samples/RabbitMQSample/Program.cs new file mode 100644 index 0000000..0c6fd7b --- /dev/null +++ b/Samples/RabbitMQSample/Program.cs @@ -0,0 +1,20 @@ +using Messages; +using MQContract.RabbitMQ; +using RabbitMQ.Client; + +var factory = new ConnectionFactory() +{ + HostName = "localhost", + Port = 5672, + UserName="guest", + Password="guest", + MaxMessageSize=1024*1024*4 +}; + +var serviceConnection = new Connection(factory) + .ExchangeDeclare("Greeting", ExchangeType.Fanout) + .ExchangeDeclare("Greeting.Response", ExchangeType.Fanout) + .ExchangeDeclare("StoredArrivals", ExchangeType.Fanout,true) + .ExchangeDeclare("Arrivals", ExchangeType.Fanout); + +await SampleExecution.ExecuteSample(serviceConnection, "RabbitMQ"); \ No newline at end of file diff --git a/Samples/RabbitMQSample/RabbitMQSample.csproj b/Samples/RabbitMQSample/RabbitMQSample.csproj new file mode 100644 index 0000000..26ad5ce --- /dev/null +++ b/Samples/RabbitMQSample/RabbitMQSample.csproj @@ -0,0 +1,20 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/Samples/RedisSample/Program.cs b/Samples/RedisSample/Program.cs index e60feab..9312204 100644 --- a/Samples/RedisSample/Program.cs +++ b/Samples/RedisSample/Program.cs @@ -3,15 +3,9 @@ using StackExchange.Redis; using System.Net; -using var sourceCancel = new CancellationTokenSource(); - -Console.CancelKeyPress += delegate { - sourceCancel.Cancel(); -}; - var conf = new ConfigurationOptions(); conf.EndPoints.Add("localhost"); var serviceConnection = new Connection(conf); -await SampleExecution.ExecuteSample(serviceConnection, "Redis", sourceCancel); +await SampleExecution.ExecuteSample(serviceConnection, "Redis"); From 5738acd2e350226b66d82c11086d48a4d5567ef6 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Sat, 14 Sep 2024 00:17:39 -0400 Subject: [PATCH 13/22] code cleanup and commenting added in comments and did some additional code cleanup --- .../Interfaces/IContractConnection.cs | 3 +- Connectors/ActiveMQ/Connection.cs | 4 +- Connectors/Kafka/Connection.cs | 34 +--- Connectors/Kafka/Readme.md | 28 --- Connectors/KubeMQ/Connection.cs | 4 +- .../KubeMQ/Options/StoredChannelOptions.cs | 7 +- Connectors/NATS/Connection.cs | 10 +- .../Options/SubscriptionConsumerConfig.cs | 5 - Connectors/NATS/Readme.md | 2 +- Connectors/RabbitMQ/Connection.cs | 190 ++++++++++++++++-- Connectors/RabbitMQ/Readme.md | 146 ++++++++++++++ Connectors/RabbitMQ/Subscription.cs | 25 +-- Connectors/Redis/Connection.cs | 6 - .../Redis/Subscriptions/PubSubscription.cs | 5 - .../QueryResponseSubscription.cs | 5 - .../Redis/Subscriptions/SubscriptionBase.cs | 5 - .../QueryResponseSubscription.cs | 2 + Samples/ActiveMQSample/Program.cs | 1 - Samples/KafkaSample/Program.cs | 3 +- Samples/KubeMQSample/Program.cs | 2 - Samples/Messages/SampleExecution.cs | 5 - Samples/NATSSample/Program.cs | 1 - Samples/RabbitMQSample/Program.cs | 1 - Samples/RedisSample/Program.cs | 1 - 24 files changed, 350 insertions(+), 145 deletions(-) diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index 21f97ba..e0208a7 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -1,5 +1,4 @@ -using MQContract.Interfaces.Service; -using MQContract.Messages; +using MQContract.Messages; namespace MQContract.Interfaces { diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index 3064802..1a2ea97 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -9,7 +9,7 @@ namespace MQContract.ActiveMQ /// /// This is the MessageServiceConnection implemenation for using ActiveMQ /// - public class Connection : IMessageServiceConnection,IAsyncDisposable,IDisposable + public sealed class Connection : IMessageServiceConnection,IAsyncDisposable,IDisposable { private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; private bool disposedValue; @@ -124,7 +124,7 @@ public async ValueTask DisposeAsync() GC.SuppressFinalize(this); } - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { if (!disposedValue) { diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 03ea5cc..4854dc5 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -16,7 +16,6 @@ public sealed class Connection(ClientConfig clientConfig) : IMessageServiceConne private readonly IProducer producer = new ProducerBuilder(clientConfig).Build(); private readonly ClientConfig clientConfig = clientConfig; - private bool disposedValue; /// /// The maximum message body size allowed @@ -104,39 +103,8 @@ public async ValueTask PublishAsync(ServiceMessage message, /// public ValueTask CloseAsync() { - Dispose(true); + producer.Dispose(); return ValueTask.CompletedTask; } - - /// - /// Called to dispose of the object correctly and allow it to clean up it's resources - /// - /// A task required for disposal - public ValueTask DisposeAsync() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - return ValueTask.CompletedTask; - } - - private void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - producer.Dispose(); - disposedValue=true; - } - } - - /// - /// Called to dispose of the required resources - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index 6120425..86a5ead 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -7,8 +7,6 @@ - [#ctor(clientConfig)](#M-MQContract-Kafka-Connection-#ctor-Confluent-Kafka-ClientConfig- 'MQContract.Kafka.Connection.#ctor(Confluent.Kafka.ClientConfig)') - [MaxMessageBodySize](#P-MQContract-Kafka-Connection-MaxMessageBodySize 'MQContract.Kafka.Connection.MaxMessageBodySize') - [CloseAsync()](#M-MQContract-Kafka-Connection-CloseAsync 'MQContract.Kafka.Connection.CloseAsync') - - [Dispose()](#M-MQContract-Kafka-Connection-Dispose 'MQContract.Kafka.Connection.Dispose') - - [DisposeAsync()](#M-MQContract-Kafka-Connection-DisposeAsync 'MQContract.Kafka.Connection.DisposeAsync') - [PublishAsync(message,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') @@ -60,32 +58,6 @@ Called to close off the underlying Kafka Connection -##### Parameters - -This method has no parameters. - - -### Dispose() `method` - -##### Summary - -Called to dispose of the required resources - -##### Parameters - -This method has no parameters. - - -### DisposeAsync() `method` - -##### Summary - -Called to dispose of the object correctly and allow it to clean up it's resources - -##### Returns - -A task required for disposal - ##### Parameters This method has no parameters. diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 9469137..753e9e2 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -183,7 +183,7 @@ public async ValueTask PublishAsync(ServiceMessage message, Channel=message.Channel, ClientID=connectionOptions.ClientId, EventID=message.ID, - Store=storedChannelOptions.Any(sco=>Equals(message.Channel,sco.ChannelName)), + Store=storedChannelOptions.Exists(sco=>Equals(message.Channel,sco.ChannelName)), Tags={ ConvertMessageHeader(message.Header) } }, connectionOptions.GrpcMetadata, cancellationToken); return new TransmissionResult(res.EventID, res.Error); @@ -211,6 +211,7 @@ public async ValueTask PublishAsync(ServiceMessage message, /// Thrown when there is an RPC exception from the KubeMQ server public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) { +#pragma warning disable S2139 // Exceptions should be either logged or rethrown but not both try { var res = await client.SendRequestAsync(new Request() @@ -242,6 +243,7 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti connectionOptions.Logger?.LogError(ex, "Exception occured in Send Message:{ErrorMessage}", ex.Message); throw; } +#pragma warning restore S2139 // Exceptions should be either logged or rethrown but not both } /// diff --git a/Connectors/KubeMQ/Options/StoredChannelOptions.cs b/Connectors/KubeMQ/Options/StoredChannelOptions.cs index 993c54d..b5649c7 100644 --- a/Connectors/KubeMQ/Options/StoredChannelOptions.cs +++ b/Connectors/KubeMQ/Options/StoredChannelOptions.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static MQContract.KubeMQ.Connection; +using static MQContract.KubeMQ.Connection; namespace MQContract.KubeMQ.Options { diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index c6422b4..d24b77c 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -63,7 +63,7 @@ private async Task ProcessConnection() /// The default timeout to use for RPC calls when not specified by class or in the call. /// DEFAULT: 30 seconds /// - public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromSeconds(30); + public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); /// /// Called to define a Stream inside the underlying NATS context. This is an exposure of the NatsJSContext.CreateStreamAsync @@ -196,11 +196,11 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti /// The name of the group to bind the consumer to /// A cancellation token /// A subscription instance - public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { SubscriptionBase subscription; var isStream = false; +#pragma warning disable S3267 // Loops should be simplified with "LINQ" expressions await foreach(var name in natsJSContext.ListStreamNamesAsync(cancellationToken: cancellationToken)) { if (Equals(channel, name)) @@ -209,6 +209,7 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti break; } } +#pragma warning restore S3267 // Loops should be simplified with "LINQ" expressions if (isStream) { var config = subscriptionConsumerConfigs.Find(scc => Equals(scc.Channel, channel) @@ -240,9 +241,10 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti /// Callback for when a query is recieved /// Callback for when an error occurs /// The name of the channel to bind to - /// - /// A subscription instance + /// The group to bind to /// A cancellation token + /// A subscription instance + public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) { var sub = new QuerySubscription( diff --git a/Connectors/NATS/Options/SubscriptionConsumerConfig.cs b/Connectors/NATS/Options/SubscriptionConsumerConfig.cs index 1023b30..2ff1851 100644 --- a/Connectors/NATS/Options/SubscriptionConsumerConfig.cs +++ b/Connectors/NATS/Options/SubscriptionConsumerConfig.cs @@ -1,9 +1,4 @@ using NATS.Client.JetStream.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.NATS.Options { diff --git a/Connectors/NATS/Readme.md b/Connectors/NATS/Readme.md index ccd6652..872f217 100644 --- a/Connectors/NATS/Readme.md +++ b/Connectors/NATS/Readme.md @@ -234,7 +234,7 @@ A subscription instance | messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | | errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind to | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/RabbitMQ/Connection.cs b/Connectors/RabbitMQ/Connection.cs index 27867de..7229330 100644 --- a/Connectors/RabbitMQ/Connection.cs +++ b/Connectors/RabbitMQ/Connection.cs @@ -2,22 +2,22 @@ using MQContract.Messages; using RabbitMQ.Client; using RabbitMQ.Client.Events; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace MQContract.RabbitMQ { /// /// This is the MessageServiceConnection implemenation for using RabbitMQ /// - public class Connection : IMessageServiceConnection,IDisposable + public sealed class Connection : IQueryableMessageServiceConnection, IDisposable { + private readonly ConnectionFactory factory; private readonly IConnection conn; private readonly IModel channel; private readonly SemaphoreSlim semaphore = new(1, 1); + private readonly Dictionary> awaitingResponses = []; + private IModel? responseListener; + private string? responseListenerTag; private bool disposedValue; /// @@ -26,11 +26,23 @@ public class Connection : IMessageServiceConnection,IDisposable /// The connection factory to use that was built with required authentication and connection information public Connection(ConnectionFactory factory) { - conn = factory.CreateConnection(); + this.factory = factory; + if (string.IsNullOrWhiteSpace(this.factory.ClientProvidedName)) + this.factory.ClientProvidedName = Guid.NewGuid().ToString(); + conn = this.factory.CreateConnection(); channel = conn.CreateModel(); MaxMessageBodySize = factory.MaxMessageSize; } + /// + /// Used to declare a queue inside the RabbitMQ server + /// + /// The name of the queue + /// Is this queue durable + /// Is this queue exclusive + /// Auto Delete queue when connection closed + /// Additional arguements + /// The connection to allow for chaining calls public Connection QueueDeclare(string queue, bool durable = false, bool exclusive = false, bool autoDelete = true, IDictionary? arguments = null) { @@ -38,13 +50,28 @@ public Connection QueueDeclare(string queue, bool durable = false, bool exclusiv return this; } + /// + /// Used to decalre an exchange inside the RabbitMQ server + /// + /// The name of the exchange + /// The type of the exchange + /// Is this durable + /// Auto Delete when connection closed + /// Additional arguements + /// The connection to allow for chaining calls public Connection ExchangeDeclare(string exchange, string type, bool durable = false, bool autoDelete = false, - IDictionary arguments = null) + IDictionary? arguments = null) { channel.ExchangeDeclare(exchange,type,durable,autoDelete,arguments); return this; } + /// + /// Used to delete a queue inside the RabbitMQ server + /// + /// The name of the queue + /// Is unused + /// Is Empty public void QueueDelete(string queue, bool ifUnused, bool ifEmpty) => channel.QueueDelete(queue,ifUnused,ifEmpty); @@ -53,13 +80,26 @@ public void QueueDelete(string queue, bool ifUnused, bool ifEmpty) /// public uint? MaxMessageBodySize { get; init; } - internal static (IBasicProperties props,ReadOnlyMemory) ConvertMessage(ServiceMessage message,IModel channel) + /// + /// The default timeout to use for RPC calls when not specified by class or in the call. + /// DEFAULT: 1 minute + /// + public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + + internal static (IBasicProperties props, ReadOnlyMemory) ConvertMessage(ServiceMessage message, IModel channel, Guid? messageId = null) { var props = channel.CreateBasicProperties(); props.MessageId=message.ID; props.Type = message.MessageTypeID; using var ms = new MemoryStream(); using var bw = new BinaryWriter(ms); + if (messageId!=null) + { + bw.Write((byte)1); + bw.Write(messageId.Value.ToByteArray()); + } + else + bw.Write((byte)0); bw.Write(message.Data.Length); bw.Write(message.Data.ToArray()); foreach(var key in message.Header.Keys) @@ -75,10 +115,15 @@ internal static (IBasicProperties props,ReadOnlyMemory) ConvertMessage(Ser return (props, ms.ToArray()); } - internal static RecievedServiceMessage ConvertMessage(BasicDeliverEventArgs eventArgs,string channel, Func acknowledge) + internal static RecievedServiceMessage ConvertMessage(BasicDeliverEventArgs eventArgs,string channel, Func acknowledge,out Guid? messageId) { using var ms = new MemoryStream(eventArgs.Body.ToArray()); using var br = new BinaryReader(ms); + var flag = br.ReadByte(); + if (flag==1) + messageId = new Guid(br.ReadBytes(16)); + else + messageId=null; var data = br.ReadBytes(br.ReadInt32()); var header = new Dictionary(); while (br.BaseStream.Position PublishAsync(ServiceMessage message, return result; } + private Subscription ProduceSubscription(IConnection conn, string channel, string? group, Action> messageRecieved, Action errorRecieved) + { + if (group==null) + { + group = Guid.NewGuid().ToString(); + this.channel.QueueDeclare(queue:group, durable:false, exclusive:false, autoDelete:true); + } + return new Subscription(conn, channel, group,messageRecieved,errorRecieved); + } + /// /// Called to create a subscription to the underlying RabbitMQ server /// @@ -129,15 +184,116 @@ public async ValueTask PublishAsync(ServiceMessage message, /// /// public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + => ValueTask.FromResult(ProduceSubscription(conn, channel, group, + (@event,modelChannel, acknowledge) => + { + messageRecieved(ConvertMessage(@event, channel, acknowledge,out _)); + }, + errorRecieved + )); + + private const string InboxChannel = "_Inbox"; + + /// + /// Called to publish a query into the RabbitMQ server + /// + /// The service message being sent + /// The timeout supplied for the query to response + /// A cancellation token + /// The resulting response + /// Thrown when the query fails to send + /// Thrown when the query times out + public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) { - if (group==null) + var messageID = Guid.NewGuid(); + (var props, var data) = ConvertMessage(message, this.channel,messageID); + props.ReplyTo = $"{InboxChannel}.${factory.ClientProvidedName}"; + var success = true; + await semaphore.WaitAsync(cancellationToken); + var result = new TaskCompletionSource(); + awaitingResponses.Add(messageID, result); + if (responseListener==null) { - group = Guid.NewGuid().ToString(); - this.channel.QueueDeclare(group,true,false,false); + ExchangeDeclare(InboxChannel, ExchangeType.Direct); + QueueDeclare(props.ReplyTo); + responseListener = this.conn.CreateModel(); + responseListener.QueueBind(props.ReplyTo, InboxChannel, props.ReplyTo); + responseListener.BasicQos(0, 1, false); + var consumer = new EventingBasicConsumer(responseListener!); + consumer.Received+=(sender, @event) => + { + var responseMessage = ConvertMessage(@event, string.Empty, () => ValueTask.CompletedTask, out var messageId); + if (messageId!=null) + { + semaphore.Wait(); + if (awaitingResponses.TryGetValue(messageId.Value, out var taskCompletionSource)) + { + taskCompletionSource.TrySetResult(new( + responseMessage.ID, + responseMessage.Header, + responseMessage.MessageTypeID, + responseMessage.Data + )); + responseListener.BasicAck(@event.DeliveryTag, false); + awaitingResponses.Remove(messageId.Value); + } + semaphore.Release(); + } + }; + responseListenerTag = responseListener.BasicConsume(props.ReplyTo, false, consumer); + } + try + { + channel.BasicPublish(message.Channel, string.Empty, props, data); + } + catch (Exception) + { + success=false; } - return ValueTask.FromResult(new Subscription(conn,channel,group, messageRecieved, errorRecieved)); + if (!success) + awaitingResponses.Remove(messageID); + semaphore.Release(); + if (!success) + throw new InvalidOperationException("An error occured attempting to submit the requested query"); + await result.Task.WaitAsync(timeout, cancellationToken); + if (result.Task.IsCompleted) + return result.Task.Result; + throw new TimeoutException(); } + /// + /// Called to create a subscription for queries to the underlying RabbitMQ server + /// + /// Callback for when a query is recieved + /// Callback for when an error occurs + /// The name of the channel to bind to + /// The group to bind to + /// A cancellation token + /// A subscription instance + public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + => ValueTask.FromResult(ProduceSubscription(conn, channel, group, + async (@event,model, acknowledge) => + { + var result = await messageRecieved(ConvertMessage(@event, channel, acknowledge,out var messageID)); + await semaphore.WaitAsync(cancellationToken); + try + { + (var props, var data) = ConvertMessage(result,model,messageID); + this.channel.BasicPublish(InboxChannel, @event.BasicProperties.ReplyTo, props, data); + } + catch (Exception e) + { + errorRecieved(e); + } + semaphore.Release(); + }, + errorRecieved + )); + + /// + /// Called to close off the contract connection and close it's underlying service connection + /// + /// A task for the closure of the connection public ValueTask CloseAsync() { Dispose(true); @@ -153,6 +309,11 @@ private void Dispose(bool disposing) semaphore.Wait(); channel.Close(); channel.Dispose(); + if (responseListener!=null) + { + responseListener.BasicCancel(responseListenerTag); + responseListener.Close(); + } conn.Close(); conn.Dispose(); semaphore.Release(); @@ -162,6 +323,9 @@ private void Dispose(bool disposing) } } + /// + /// Called to dispose of the object correctly and allow it to clean up it's resources + /// public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method diff --git a/Connectors/RabbitMQ/Readme.md b/Connectors/RabbitMQ/Readme.md index a216a22..4341477 100644 --- a/Connectors/RabbitMQ/Readme.md +++ b/Connectors/RabbitMQ/Readme.md @@ -5,9 +5,17 @@ - [Connection](#T-MQContract-RabbitMQ-Connection 'MQContract.RabbitMQ.Connection') - [#ctor(factory)](#M-MQContract-RabbitMQ-Connection-#ctor-RabbitMQ-Client-ConnectionFactory- 'MQContract.RabbitMQ.Connection.#ctor(RabbitMQ.Client.ConnectionFactory)') + - [DefaultTimout](#P-MQContract-RabbitMQ-Connection-DefaultTimout 'MQContract.RabbitMQ.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-RabbitMQ-Connection-MaxMessageBodySize 'MQContract.RabbitMQ.Connection.MaxMessageBodySize') + - [CloseAsync()](#M-MQContract-RabbitMQ-Connection-CloseAsync 'MQContract.RabbitMQ.Connection.CloseAsync') + - [Dispose()](#M-MQContract-RabbitMQ-Connection-Dispose 'MQContract.RabbitMQ.Connection.Dispose') + - [ExchangeDeclare(exchange,type,durable,autoDelete,arguments)](#M-MQContract-RabbitMQ-Connection-ExchangeDeclare-System-String,System-String,System-Boolean,System-Boolean,System-Collections-Generic-IDictionary{System-String,System-Object}- 'MQContract.RabbitMQ.Connection.ExchangeDeclare(System.String,System.String,System.Boolean,System.Boolean,System.Collections.Generic.IDictionary{System.String,System.Object})') - [PublishAsync(message,cancellationToken)](#M-MQContract-RabbitMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') + - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-RabbitMQ-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') + - [QueueDeclare(queue,durable,exclusive,autoDelete,arguments)](#M-MQContract-RabbitMQ-Connection-QueueDeclare-System-String,System-Boolean,System-Boolean,System-Boolean,System-Collections-Generic-IDictionary{System-String,System-Object}- 'MQContract.RabbitMQ.Connection.QueueDeclare(System.String,System.Boolean,System.Boolean,System.Boolean,System.Collections.Generic.IDictionary{System.String,System.Object})') + - [QueueDelete(queue,ifUnused,ifEmpty)](#M-MQContract-RabbitMQ-Connection-QueueDelete-System-String,System-Boolean,System-Boolean- 'MQContract.RabbitMQ.Connection.QueueDelete(System.String,System.Boolean,System.Boolean)') - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-RabbitMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') + - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-RabbitMQ-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') ## Connection `type` @@ -33,6 +41,14 @@ Default constructor for creating instance | ---- | ---- | ----------- | | factory | [RabbitMQ.Client.ConnectionFactory](#T-RabbitMQ-Client-ConnectionFactory 'RabbitMQ.Client.ConnectionFactory') | The connection factory to use that was built with required authentication and connection information | + +### DefaultTimout `property` + +##### Summary + +The default timeout to use for RPC calls when not specified by class or in the call. +DEFAULT: 1 minute + ### MaxMessageBodySize `property` @@ -40,6 +56,53 @@ Default constructor for creating instance The maximum message body size allowed + +### CloseAsync() `method` + +##### Summary + +Called to close off the contract connection and close it's underlying service connection + +##### Returns + +A task for the closure of the connection + +##### Parameters + +This method has no parameters. + + +### Dispose() `method` + +##### Summary + +Called to dispose of the object correctly and allow it to clean up it's resources + +##### Parameters + +This method has no parameters. + + +### ExchangeDeclare(exchange,type,durable,autoDelete,arguments) `method` + +##### Summary + +Used to decalre an exchange inside the RabbitMQ server + +##### Returns + +The connection to allow for chaining calls + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| exchange | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the exchange | +| type | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The type of the exchange | +| durable | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Is this durable | +| autoDelete | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Auto Delete when connection closed | +| arguments | [System.Collections.Generic.IDictionary{System.String,System.Object}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Collections.Generic.IDictionary 'System.Collections.Generic.IDictionary{System.String,System.Object}') | Additional arguements | + ### PublishAsync(message,cancellationToken) `method` @@ -58,6 +121,68 @@ Transmition result identifying if it worked or not | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +### QueryAsync(message,timeout,cancellationToken) `method` + +##### Summary + +Called to publish a query into the RabbitMQ server + +##### Returns + +The resulting response + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | +| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +##### Exceptions + +| Name | Description | +| ---- | ----------- | +| [System.InvalidOperationException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.InvalidOperationException 'System.InvalidOperationException') | Thrown when the query fails to send | +| [System.TimeoutException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeoutException 'System.TimeoutException') | Thrown when the query times out | + + +### QueueDeclare(queue,durable,exclusive,autoDelete,arguments) `method` + +##### Summary + +Used to declare a queue inside the RabbitMQ server + +##### Returns + +The connection to allow for chaining calls + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| queue | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the queue | +| durable | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Is this queue durable | +| exclusive | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Is this queue exclusive | +| autoDelete | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Auto Delete queue when connection closed | +| arguments | [System.Collections.Generic.IDictionary{System.String,System.Object}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Collections.Generic.IDictionary 'System.Collections.Generic.IDictionary{System.String,System.Object}') | Additional arguements | + + +### QueueDelete(queue,ifUnused,ifEmpty) `method` + +##### Summary + +Used to delete a queue inside the RabbitMQ server + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| queue | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the queue | +| ifUnused | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Is unused | +| ifEmpty | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Is Empty | + ### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` @@ -78,3 +203,24 @@ Called to create a subscription to the underlying RabbitMQ server | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | | + + +### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` + +##### Summary + +Called to create a subscription for queries to the underlying RabbitMQ server + +##### Returns + +A subscription instance + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | +| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | +| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind to | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/RabbitMQ/Subscription.cs b/Connectors/RabbitMQ/Subscription.cs index ab339fd..809f5bc 100644 --- a/Connectors/RabbitMQ/Subscription.cs +++ b/Connectors/RabbitMQ/Subscription.cs @@ -1,12 +1,6 @@ using MQContract.Interfaces.Service; -using MQContract.Messages; using RabbitMQ.Client; using RabbitMQ.Client.Events; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.RabbitMQ { @@ -16,23 +10,22 @@ internal class Subscription : IServiceSubscription private readonly Guid subscriptionID = Guid.NewGuid(); private readonly string consumerTag; - public Subscription(IConnection conn,string channel,string group, Action messageRecieved, Action errorRecieved) + public Subscription(IConnection conn,string channel,string group, Action> messageRecieved, Action errorRecieved) { this.channel = conn.CreateModel(); this.channel.QueueBind(group, channel, subscriptionID.ToString()); + this.channel.BasicQos(0, 1, false); var consumer = new EventingBasicConsumer(this.channel); consumer.Received+=(sender, @event) => { messageRecieved( - Connection.ConvertMessage( - @event, - channel, - () => - { - this.channel.BasicAck(@event.DeliveryTag, false); - return ValueTask.CompletedTask; - } - ) + @event, + this.channel, + () => + { + this.channel.BasicAck(@event.DeliveryTag, false); + return ValueTask.CompletedTask; + } ); }; diff --git a/Connectors/Redis/Connection.cs b/Connectors/Redis/Connection.cs index 5612a21..a335d34 100644 --- a/Connectors/Redis/Connection.cs +++ b/Connectors/Redis/Connection.cs @@ -2,14 +2,8 @@ using MQContract.Messages; using MQContract.Redis.Subscriptions; using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; using System.Text.Json; using System.Text.Json.Nodes; -using System.Threading.Tasks; namespace MQContract.Redis { diff --git a/Connectors/Redis/Subscriptions/PubSubscription.cs b/Connectors/Redis/Subscriptions/PubSubscription.cs index 214c0b0..25c4efe 100644 --- a/Connectors/Redis/Subscriptions/PubSubscription.cs +++ b/Connectors/Redis/Subscriptions/PubSubscription.cs @@ -1,10 +1,5 @@ using MQContract.Messages; using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.Redis.Subscriptions { diff --git a/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs b/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs index b3697e6..dab302b 100644 --- a/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs +++ b/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs @@ -1,10 +1,5 @@ using MQContract.Messages; using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.Redis.Subscriptions { diff --git a/Connectors/Redis/Subscriptions/SubscriptionBase.cs b/Connectors/Redis/Subscriptions/SubscriptionBase.cs index 7dccdc5..10985c2 100644 --- a/Connectors/Redis/Subscriptions/SubscriptionBase.cs +++ b/Connectors/Redis/Subscriptions/SubscriptionBase.cs @@ -1,10 +1,5 @@ using MQContract.Interfaces.Service; using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.Redis.Subscriptions { diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index 6879a91..d131705 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -69,6 +69,8 @@ private async ValueTask ProcessServiceMessageAsync(RecievedServi ??throw new InvalidCastException($"Unable to convert incoming message {message.MessageTypeID} to {typeof(Q).FullName}"); var result = await messageRecieved(new RecievedMessage(message.ID, taskMessage,message.Header,message.RecievedTimestamp,DateTime.Now)); response = await responseMessageFactory.ConvertMessageAsync(result.Message, message.Channel, new MessageHeader(result.Headers)); + if (message.Acknowledge!=null) + await message.Acknowledge(); }catch(Exception e) { errorRecieved(e); diff --git a/Samples/ActiveMQSample/Program.cs b/Samples/ActiveMQSample/Program.cs index 8633532..a4384d8 100644 --- a/Samples/ActiveMQSample/Program.cs +++ b/Samples/ActiveMQSample/Program.cs @@ -1,5 +1,4 @@ using Messages; -using MQContract; using MQContract.ActiveMQ; var serviceConnection = new Connection(new Uri("amqp:tcp://localhost:5672"),"artemis","artemis"); diff --git a/Samples/KafkaSample/Program.cs b/Samples/KafkaSample/Program.cs index 5e75b94..7e3808d 100644 --- a/Samples/KafkaSample/Program.cs +++ b/Samples/KafkaSample/Program.cs @@ -1,8 +1,7 @@ using Messages; -using MQContract; using MQContract.Kafka; -await using var serviceConnection = new Connection(new Confluent.Kafka.ClientConfig() +var serviceConnection = new Connection(new Confluent.Kafka.ClientConfig() { ClientId="KafkaSample", BootstrapServers="localhost:56497" diff --git a/Samples/KubeMQSample/Program.cs b/Samples/KubeMQSample/Program.cs index e459021..7089f4b 100644 --- a/Samples/KubeMQSample/Program.cs +++ b/Samples/KubeMQSample/Program.cs @@ -1,7 +1,5 @@ using Messages; -using MQContract; using MQContract.KubeMQ; -using MQContract.KubeMQ.Options; await using var serviceConnection = new Connection(new ConnectionOptions() { diff --git a/Samples/Messages/SampleExecution.cs b/Samples/Messages/SampleExecution.cs index 029de49..2bf86e0 100644 --- a/Samples/Messages/SampleExecution.cs +++ b/Samples/Messages/SampleExecution.cs @@ -1,10 +1,5 @@ using MQContract; using MQContract.Interfaces.Service; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Messages { diff --git a/Samples/NATSSample/Program.cs b/Samples/NATSSample/Program.cs index 3575e3a..a569024 100644 --- a/Samples/NATSSample/Program.cs +++ b/Samples/NATSSample/Program.cs @@ -1,7 +1,6 @@ using Messages; using MQContract; using MQContract.NATS; -using MQContract.NATS.Options; using NATS.Client.JetStream.Models; var serviceConnection = new Connection(new NATS.Client.Core.NatsOpts() diff --git a/Samples/RabbitMQSample/Program.cs b/Samples/RabbitMQSample/Program.cs index 0c6fd7b..79671ea 100644 --- a/Samples/RabbitMQSample/Program.cs +++ b/Samples/RabbitMQSample/Program.cs @@ -13,7 +13,6 @@ var serviceConnection = new Connection(factory) .ExchangeDeclare("Greeting", ExchangeType.Fanout) - .ExchangeDeclare("Greeting.Response", ExchangeType.Fanout) .ExchangeDeclare("StoredArrivals", ExchangeType.Fanout,true) .ExchangeDeclare("Arrivals", ExchangeType.Fanout); diff --git a/Samples/RedisSample/Program.cs b/Samples/RedisSample/Program.cs index 9312204..e19577d 100644 --- a/Samples/RedisSample/Program.cs +++ b/Samples/RedisSample/Program.cs @@ -1,7 +1,6 @@ using Messages; using MQContract.Redis; using StackExchange.Redis; -using System.Net; var conf = new ConfigurationOptions(); conf.EndPoints.Add("localhost"); From d995ecffb5c2b884423027b261393876cf11c1b8 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Thu, 19 Sep 2024 15:29:50 -0400 Subject: [PATCH 14/22] multiple fixes Added in initial middleware concepts, need to implement testing Updated all external connections to use interface specific calls instead of public to reduce the comments needed Corrected typo for spelling of received Moved contract connection to interface specifics instead of public to reduce repeated comments --- Abstractions/Abstractions.csproj | 2 +- .../Conversion/IMessageConverter.cs | 2 +- .../Encrypting/IMessageEncryptor.cs | 2 +- .../Interfaces/IContractConnection.cs | 91 ++- Abstractions/Interfaces/IContractMetric.cs | 48 ++ Abstractions/Interfaces/IRecievedMessage.cs | 12 +- .../Middleware/IAfterDecodeMiddleware.cs | 23 + .../IAfterDecodeSpecificTypeMiddleware.cs | 23 + .../Middleware/IAfterEncodeMiddleware.cs | 19 + .../Middleware/IBeforeDecodeMiddleware.cs | 22 + .../Middleware/IBeforeEncodeMiddleware.cs | 21 + .../IBeforeEncodeSpecificTypeMiddleware.cs | 21 + .../Interfaces/Middleware/IContext.cs | 15 + .../Service/IMessageServiceConnection.cs | 6 +- .../IQueryableMessageServiceConnection.cs | 6 +- .../Messages/RecievedServiceMessage.cs | 12 +- Abstractions/Readme.md | 635 ++++++++++++++++-- AutomatedTesting/ChannelMapperTests.cs | 130 ++-- .../ContractConnectionTests/CleanupTests.cs | 12 +- .../ContractConnectionTests/PingTests.cs | 2 +- .../ContractConnectionTests/PublishTests.cs | 28 +- .../ContractConnectionTests/QueryTests.cs | 36 +- .../QueryWithoutQueryResponseTests.cs | 62 +- .../SubscribeQueryResponseTests.cs | 134 ++-- ...beQueryResponseWithoutQueryResponseTest.cs | 22 +- .../ContractConnectionTests/SubscribeTests.cs | 190 +++--- AutomatedTesting/Helper.cs | 2 +- Connectors/ActiveMQ/Connection.cs | 45 +- Connectors/ActiveMQ/Readme.md | 93 --- .../Subscriptions/SubscriptionBase.cs | 6 +- Connectors/Kafka/Connection.cs | 34 +- Connectors/Kafka/Readme.md | 65 -- .../Subscriptions/PublishSubscription.cs | 6 +- Connectors/KubeMQ/Connection.cs | 86 +-- Connectors/KubeMQ/Exceptions.cs | 2 +- Connectors/KubeMQ/Readme.md | 173 ----- .../KubeMQ/Subscriptions/PubSubscription.cs | 8 +- .../KubeMQ/Subscriptions/QuerySubscription.cs | 8 +- .../KubeMQ/Subscriptions/SubscriptionBase.cs | 16 +- Connectors/NATS/Connection.cs | 76 +-- Connectors/NATS/Readme.md | 149 ---- .../NATS/Subscriptions/PublishSubscription.cs | 6 +- .../NATS/Subscriptions/QuerySubscription.cs | 10 +- .../NATS/Subscriptions/StreamSubscription.cs | 12 +- .../NATS/Subscriptions/SubscriptionBase.cs | 10 +- Connectors/RabbitMQ/Connection.cs | 69 +- Connectors/RabbitMQ/Readme.md | 118 ---- Connectors/RabbitMQ/Subscription.cs | 4 +- Connectors/Redis/Connection.cs | 65 +- Connectors/Redis/Readme.md | 133 ---- .../Redis/Subscriptions/PubSubscription.cs | 6 +- .../QueryResponseSubscription.cs | 6 +- .../Redis/Subscriptions/SubscriptionBase.cs | 4 +- Core/ContractConnection.Metrics.cs | 36 + Core/ContractConnection.Middleware.cs | 90 +++ Core/ContractConnection.cs | 406 +++++------ Core/Core.csproj | 2 +- Core/Exceptions.cs | 8 +- Core/Factories/MessageTypeFactory.cs | 11 +- Core/Interfaces/Factories/IMessageFactory.cs | 3 +- Core/Messages/RecievedMessage.cs | 4 +- Core/Middleware/ChannelMappingMiddleware.cs | 19 + Core/Middleware/Context.cs | 29 + Core/Middleware/Metrics/ContractMetric.cs | 36 + .../Metrics/InternalMetricTracker.cs | 82 +++ Core/Middleware/Metrics/MessageMetric.cs | 24 + Core/Middleware/Metrics/MetricEntryValue.cs | 5 + .../Metrics/ReadonlyContractMetric.cs | 15 + .../Middleware/Metrics/SystemMetricTracker.cs | 63 ++ Core/Middleware/MetricsMiddleware.cs | 89 +++ Core/Readme.md | 322 +-------- Core/Subscriptions/PubSubSubscription.cs | 17 +- Core/Subscriptions/QueryResponseHelper.cs | 2 +- .../QueryResponseSubscription.cs | 22 +- Core/Utility.cs | 15 +- Samples/Messages/SampleExecution.cs | 23 +- 76 files changed, 2031 insertions(+), 2080 deletions(-) create mode 100644 Abstractions/Interfaces/IContractMetric.cs create mode 100644 Abstractions/Interfaces/Middleware/IAfterDecodeMiddleware.cs create mode 100644 Abstractions/Interfaces/Middleware/IAfterDecodeSpecificTypeMiddleware.cs create mode 100644 Abstractions/Interfaces/Middleware/IAfterEncodeMiddleware.cs create mode 100644 Abstractions/Interfaces/Middleware/IBeforeDecodeMiddleware.cs create mode 100644 Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs create mode 100644 Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs create mode 100644 Abstractions/Interfaces/Middleware/IContext.cs create mode 100644 Core/ContractConnection.Metrics.cs create mode 100644 Core/ContractConnection.Middleware.cs create mode 100644 Core/Middleware/ChannelMappingMiddleware.cs create mode 100644 Core/Middleware/Context.cs create mode 100644 Core/Middleware/Metrics/ContractMetric.cs create mode 100644 Core/Middleware/Metrics/InternalMetricTracker.cs create mode 100644 Core/Middleware/Metrics/MessageMetric.cs create mode 100644 Core/Middleware/Metrics/MetricEntryValue.cs create mode 100644 Core/Middleware/Metrics/ReadonlyContractMetric.cs create mode 100644 Core/Middleware/Metrics/SystemMetricTracker.cs create mode 100644 Core/Middleware/MetricsMiddleware.cs diff --git a/Abstractions/Abstractions.csproj b/Abstractions/Abstractions.csproj index db88fe8..f6cb5da 100644 --- a/Abstractions/Abstractions.csproj +++ b/Abstractions/Abstractions.csproj @@ -1,6 +1,6 @@  - + net8.0 diff --git a/Abstractions/Interfaces/Conversion/IMessageConverter.cs b/Abstractions/Interfaces/Conversion/IMessageConverter.cs index 1b7b29c..afd2c61 100644 --- a/Abstractions/Interfaces/Conversion/IMessageConverter.cs +++ b/Abstractions/Interfaces/Conversion/IMessageConverter.cs @@ -2,7 +2,7 @@ { /// /// Used to define a message converter. These are called upon if a - /// message is recieved on a channel of type T but it is waiting for + /// message is received on a channel of type T but it is waiting for /// message of type V /// /// The source message type diff --git a/Abstractions/Interfaces/Encrypting/IMessageEncryptor.cs b/Abstractions/Interfaces/Encrypting/IMessageEncryptor.cs index 0710e3c..bbfdc75 100644 --- a/Abstractions/Interfaces/Encrypting/IMessageEncryptor.cs +++ b/Abstractions/Interfaces/Encrypting/IMessageEncryptor.cs @@ -10,7 +10,7 @@ namespace MQContract.Interfaces.Encrypting public interface IMessageEncryptor { /// - /// Called to decrypt the message body stream recieved as a message + /// Called to decrypt the message body stream received as a message /// /// The stream representing the message body binary data /// The message headers that were provided by the message diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index e0208a7..07a8bed 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -1,4 +1,5 @@ -using MQContract.Messages; +using MQContract.Interfaces.Middleware; +using MQContract.Messages; namespace MQContract.Interfaces { @@ -7,6 +8,66 @@ namespace MQContract.Interfaces /// public interface IContractConnection : IDisposable,IAsyncDisposable { + /// + /// Register a middleware of a given type T to be used by the contract connection + /// + /// The type of middle ware to register, it must implement IBeforeDecodeMiddleware or IBeforeEncodeMiddleware or IAfterDecodeMiddleware or IAfterEncodeMiddleware + /// The Contract Connection instance to allow chaining calls + IContractConnection RegisterMiddleware() + where T : IBeforeDecodeMiddleware, IBeforeEncodeMiddleware, IAfterDecodeMiddleware, IAfterEncodeMiddleware; + /// + /// Register a middleware of a given type T to be used by the contract connection + /// + /// Callback to create the instance + /// The type of middle ware to register, it must implement IBeforeDecodeMiddleware or IBeforeEncodeMiddleware or IAfterDecodeMiddleware or IAfterEncodeMiddleware + /// The Contract Connection instance to allow chaining calls + IContractConnection RegisterMiddleware(Func constructInstance) + where T : IBeforeDecodeMiddleware, IBeforeEncodeMiddleware, IAfterDecodeMiddleware, IAfterEncodeMiddleware; + /// + /// Register a middleware of a given type T to be used by the contract connection + /// + /// The type of middle ware to register, it must implement IBeforeEncodeSpecificTypeMiddleware<M> or IAfterDecodeSpecificTypeMiddleware<M> + /// The message type that this middleware is specifically called for + /// The Contract Connection instance to allow chaining calls + IContractConnection RegisterMiddleware() + where T : IBeforeEncodeSpecificTypeMiddleware, IAfterDecodeSpecificTypeMiddleware + where M : class; + /// + /// Register a middleware of a given type T to be used by the contract connection + /// + /// Callback to create the instance + /// The type of middle ware to register, it must implement IBeforeEncodeSpecificTypeMiddleware<M> or IAfterDecodeSpecificTypeMiddleware<M> + /// The message type that this middleware is specifically called for + /// The Contract Connection instance to allow chaining calls + IContractConnection RegisterMiddleware(Func constructInstance) + where T : IBeforeEncodeSpecificTypeMiddleware, IAfterDecodeSpecificTypeMiddleware + where M : class; + /// + /// Called to activate the metrics tracking middleware for this connection instance + /// + /// Indicates if the Meter style metrics should be enabled + /// Indicates if the internal metrics collector should be used + void AddMetrics(bool useMeter, bool useInternal); + /// + /// Called to get a snapshot of the current global metrics. Will return null if internal metrics are not enabled. + /// + /// true when the sent metrics are desired, false when received are desired + /// A record of the current metric snapshot or null if not available + IContractMetric? GetSnapshot(bool sent); + /// + /// Called to get a snapshot of the metrics for a given message type. Will return null if internal metrics are not enabled. + /// + /// The type of message to look for + /// true when the sent metrics are desired, false when received are desired + /// A record of the current metric snapshot or null if not available + IContractMetric? GetSnapshot(Type messageType,bool sent); + /// + /// Called to get a snapshot of the metrics for a given message channel. Will return null if internal metrics are not enabled. + /// + /// The channel to look for + /// true when the sent metrics are desired, false when received are desired + /// A record of the current metric snapshot or null if not available + IContractMetric? GetSnapshot(string channel,bool sent); /// /// Called to Ping the underlying system to obtain both information and ensure it is up. Not all Services support this method. /// @@ -28,29 +89,29 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// Called to create a subscription into the underlying service Pub/Sub style and have the messages processed asynchronously /// /// The type of message to listen for - /// The callback invoked when a new message is recieved - /// The callback to invoke when an error occurs + /// The callback invoked when a new message is received + /// The callback to invoke when an error occurs /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class /// A cancellation token /// /// A subscription instance that can be ended when desired - ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeAsync(Func, ValueTask> messageReceived, Action errorReceived, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to create a subscription into the underlying service Pub/Sub style and have the messages processed syncrhonously /// /// The type of message to listen for - /// The callback invoked when a new message is recieved - /// The callback to invoke when an error occurs + /// The callback invoked when a new message is received + /// The callback to invoke when an error occurs /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class /// A cancellation token /// /// A subscription instance that can be ended when desired - ValueTask SubscribeAsync(Action> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeAsync(Action> messageReceived, Action errorReceived, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) where T : class; /// /// Called to send a message into the underlying service in the Query/Response style @@ -58,7 +119,7 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// The type of message to send for the query /// The type of message to expect back for the response /// The message to send - /// The allowed timeout prior to a response being recieved + /// The allowed timeout prior to a response being received /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is /// only used when the underlying connection does not support a QueryResponse style messaging. @@ -75,7 +136,7 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// /// The type of message to send for the query /// The message to send - /// The allowed timeout prior to a response being recieved + /// The allowed timeout prior to a response being received /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is /// only used when the underlying connection does not support a QueryResponse style messaging. @@ -90,15 +151,15 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// /// The type of message to listen for /// The type of message to respond with - /// The callback invoked when a new message is recieved expecting a response of the type response - /// The callback invoked when an error occurs. + /// The callback invoked when a new message is received expecting a response of the type response + /// The callback invoked when an error occurs. /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class /// A cancellation token /// /// A subscription instance that can be ended when desired - ValueTask SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageReceived, Action errorReceived, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; /// @@ -106,15 +167,15 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// /// The type of message to listen for /// The type of message to respond with - /// The callback invoked when a new message is recieved expecting a response of the type response - /// The callback invoked when an error occurs. + /// The callback invoked when a new message is received expecting a response of the type response + /// The callback invoked when an error occurs. /// Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. /// The subscription group if desired (typically used when multiple instances of the same system are running) /// If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class /// A cancellation token /// /// A subscription instance that can be ended when desired - ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) + ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageReceived, Action errorReceived, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = new CancellationToken()) where Q : class where R : class; /// diff --git a/Abstractions/Interfaces/IContractMetric.cs b/Abstractions/Interfaces/IContractMetric.cs new file mode 100644 index 0000000..f39ad25 --- /dev/null +++ b/Abstractions/Interfaces/IContractMetric.cs @@ -0,0 +1,48 @@ +namespace MQContract.Interfaces +{ + /// + /// Houses a set of metrics that were requested from the internal metric tracker. + /// All message conversion durations are calculated from the perspective: + /// - When a class is being sent from the point of starting the middleware to the point where the class has been encoded into a service message and the middleware has completed + /// - When a service message is being recieved from the point of starting the middleware to the point where the class has been built from the service message and the middleware has completed + /// + public interface IContractMetric + { + /// + /// Total number of messages + /// + ulong Messages { get; } + /// + /// Total amount of bytes from the messages + /// + ulong MessageBytes { get; } + /// + /// Average number of bytes from the messages + /// + ulong MessageBytesAverage { get; } + /// + /// Minimum number of bytes from the messages + /// + ulong MessageBytesMin { get; } + /// + /// Maximum number of bytes from the messages + /// + ulong MessageBytesMax { get; } + /// + /// Total time spent converting the messages + /// + TimeSpan MessageConversionDuration { get; } + /// + /// Average time to encode/decode the messages + /// + TimeSpan MessageConversionAverage { get; } + /// + /// Minimum time to encode/decode a message + /// + TimeSpan MessageConversionMin { get; } + /// + /// Maximum time to encode/decode a message + /// + TimeSpan MessageConversionMax { get; } + } +} diff --git a/Abstractions/Interfaces/IRecievedMessage.cs b/Abstractions/Interfaces/IRecievedMessage.cs index 5cec6f3..ef94e4e 100644 --- a/Abstractions/Interfaces/IRecievedMessage.cs +++ b/Abstractions/Interfaces/IRecievedMessage.cs @@ -3,14 +3,14 @@ namespace MQContract.Interfaces { /// - /// An interface for describing a Message recieved on a Subscription to be passed into the appropriate callback + /// An interface for describing a Message received on a Subscription to be passed into the appropriate callback /// /// The class type of the underlying message - public interface IRecievedMessage + public interface IReceivedMessage where T : class { /// - /// The unique ID of the recieved message that was specified on the transmission side + /// The unique ID of the received message that was specified on the transmission side /// string ID { get; } /// @@ -22,11 +22,11 @@ public interface IRecievedMessage /// MessageHeader Headers { get; } /// - /// The timestamp of when the message was recieved by the underlying service connection + /// The timestamp of when the message was received by the underlying service connection /// - DateTime RecievedTimestamp { get; } + DateTime ReceivedTimestamp { get; } /// - /// The timestamp of when the recieved message was converted into the actual class prior to calling the callback + /// The timestamp of when the received message was converted into the actual class prior to calling the callback /// DateTime ProcessedTimestamp { get; } } diff --git a/Abstractions/Interfaces/Middleware/IAfterDecodeMiddleware.cs b/Abstractions/Interfaces/Middleware/IAfterDecodeMiddleware.cs new file mode 100644 index 0000000..2f8d3d7 --- /dev/null +++ b/Abstractions/Interfaces/Middleware/IAfterDecodeMiddleware.cs @@ -0,0 +1,23 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Middleware +{ + /// + /// This interface represents a Middleware to execute after a Message has been decoded from a ServiceMessage to the expected Class + /// + public interface IAfterDecodeMiddleware + { + /// + /// This is the method invoked as part of the Middleware processing during message decoding + /// + /// This will be the type of the Message that was decoded + /// A shared context that exists from the start of this decode process instance + /// The class message + /// The id of the message + /// The headers from the message + /// The timestamp of when the message was recieved + /// The timestamp of when the message was decoded into a Class + /// The message and header to allow for changes if desired + ValueTask<(T message,MessageHeader messageHeader)> AfterMessageDecodeAsync(IContext context, T message, string ID,MessageHeader messageHeader,DateTime receivedTimestamp,DateTime processedTimeStamp); + } +} diff --git a/Abstractions/Interfaces/Middleware/IAfterDecodeSpecificTypeMiddleware.cs b/Abstractions/Interfaces/Middleware/IAfterDecodeSpecificTypeMiddleware.cs new file mode 100644 index 0000000..9f0567e --- /dev/null +++ b/Abstractions/Interfaces/Middleware/IAfterDecodeSpecificTypeMiddleware.cs @@ -0,0 +1,23 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Middleware +{ + /// + /// This interface represents a Middleware to execute after a Message of the given type T has been decoded from a ServiceMessage to the expected Class + /// + public interface IAfterDecodeSpecificTypeMiddleware + where T : class + { + /// + /// This is the method invoked as part of the Middleware processing during message decoding + /// + /// A shared context that exists from the start of this decode process instance + /// The class message + /// The id of the message + /// The headers from the message + /// The timestamp of when the message was recieved + /// The timestamp of when the message was decoded into a Class + /// The message and header to allow for changes if desired + ValueTask<(T message,MessageHeader messageHeader)> AfterMessageDecodeAsync(IContext context, T message, string ID,MessageHeader messageHeader,DateTime receivedTimestamp,DateTime processedTimeStamp); + } +} diff --git a/Abstractions/Interfaces/Middleware/IAfterEncodeMiddleware.cs b/Abstractions/Interfaces/Middleware/IAfterEncodeMiddleware.cs new file mode 100644 index 0000000..118e68e --- /dev/null +++ b/Abstractions/Interfaces/Middleware/IAfterEncodeMiddleware.cs @@ -0,0 +1,19 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Middleware +{ + /// + /// This interface represents a Middleware to execute after a Message has been encoded to a ServiceMessage from the supplied Class + /// + public interface IAfterEncodeMiddleware + { + /// + /// This is the method invoked as part of the Middleware processing during message encoding + /// + /// The class of the message type that was encoded + /// A shared context that exists from the start of this encode process instance + /// The resulting encoded message + /// The message to allow for changes if desired + ValueTask AfterMessageEncodeAsync(Type messageType, IContext context, ServiceMessage message); + } +} diff --git a/Abstractions/Interfaces/Middleware/IBeforeDecodeMiddleware.cs b/Abstractions/Interfaces/Middleware/IBeforeDecodeMiddleware.cs new file mode 100644 index 0000000..fc74aa7 --- /dev/null +++ b/Abstractions/Interfaces/Middleware/IBeforeDecodeMiddleware.cs @@ -0,0 +1,22 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Middleware +{ + /// + /// This interface represents a Middleware to execute before decoding a ServiceMessage + /// + public interface IBeforeDecodeMiddleware + { + /// + /// This is the method invoked as part of the Middleware processing prior to the message decoding + /// + /// A shared context that exists from the start of this decode process instance + /// The id of the message + /// The headers from the message + /// The message type id + /// The channel the message was recieved on + /// The data of the message + /// The message header and data to allow for changes if desired + ValueTask<(MessageHeader messageHeader,ReadOnlyMemory data)> BeforeMessageDecodeAsync(IContext context, string id, MessageHeader messageHeader, string messageTypeID,string messageChannel, ReadOnlyMemory data); + } +} diff --git a/Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs b/Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs new file mode 100644 index 0000000..6ef0cc6 --- /dev/null +++ b/Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs @@ -0,0 +1,21 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Middleware +{ + /// + /// This interface represents a Middleware to execute Before a message is encoded + /// + public interface IBeforeEncodeMiddleware + { + /// + /// This is the method invoked as part of the Middle Ware processing during message encoding + /// + /// The type of message being processed + /// A shared context that exists from the start of this encoding instance + /// The message being encoded + /// The channel this message was requested to transmit to + /// The message headers being supplied + /// The message, channel and header to allow for changes if desired + ValueTask<(T message,string? channel,MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context,T message, string? channel,MessageHeader messageHeader); + } +} diff --git a/Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs b/Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs new file mode 100644 index 0000000..4e293fb --- /dev/null +++ b/Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs @@ -0,0 +1,21 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Middleware +{ + /// + /// This interface represents a Middleware to execute Before a specific message type is encoded + /// + public interface IBeforeEncodeSpecificTypeMiddleware + where T : class + { + /// + /// This is the method invoked as part of the Middle Ware processing during message encoding + /// + /// A shared context that exists from the start of this encoding instance + /// The message being encoded + /// The channel this message was requested to transmit to + /// The message headers being supplied + /// The message, channel and header to allow for changes if desired + ValueTask<(T message,string? channel,MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context,T message, string? channel,MessageHeader messageHeader); + } +} diff --git a/Abstractions/Interfaces/Middleware/IContext.cs b/Abstractions/Interfaces/Middleware/IContext.cs new file mode 100644 index 0000000..db7f792 --- /dev/null +++ b/Abstractions/Interfaces/Middleware/IContext.cs @@ -0,0 +1,15 @@ +namespace MQContract.Interfaces.Middleware +{ + /// + /// This is used to represent a Context for the middleware calls to use that exists from the start to the end of the message conversion process + /// + public interface IContext + { + /// + /// Used to store and retreive values from the context during the conversion process. + /// + /// The unique key to use + /// The value if it exists in the context + object? this[string key] { get; set; } + } +} diff --git a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs index d3a590a..b1d21ad 100644 --- a/Abstractions/Interfaces/Service/IMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IMessageServiceConnection.cs @@ -23,13 +23,13 @@ public interface IMessageServiceConnection /// /// Implements a call to create a subscription to a given channel as a member of a given group /// - /// The callback to invoke when a message is recieved - /// The callback to invoke when an exception occurs + /// The callback to invoke when a message is received + /// The callback to invoke when an exception occurs /// The name of the channel to subscribe to /// The consumer group to register as /// A cancellation token /// A service subscription object - ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group = null, CancellationToken cancellationToken = new CancellationToken()); /// /// Implements a call to close off the connection when the ContractConnection is closed /// diff --git a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs index e4fd28f..564a9de 100644 --- a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs @@ -22,12 +22,12 @@ public interface IQueryableMessageServiceConnection : IMessageServiceConnection /// /// Implements a call to create a subscription to a given channel as a member of a given group for responding to queries /// - /// The callback to be invoked when a message is recieved, returning the response message - /// The callback to invoke when an exception occurs + /// The callback to be invoked when a message is received, returning the response message + /// The callback to invoke when an exception occurs /// The name of the channel to subscribe to /// The group to bind a consumer to /// A cancellation token /// A service subscription object - ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = new CancellationToken()); + ValueTask SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group = null, CancellationToken cancellationToken = new CancellationToken()); } } diff --git a/Abstractions/Messages/RecievedServiceMessage.cs b/Abstractions/Messages/RecievedServiceMessage.cs index fc7faac..d8b573a 100644 --- a/Abstractions/Messages/RecievedServiceMessage.cs +++ b/Abstractions/Messages/RecievedServiceMessage.cs @@ -3,21 +3,21 @@ namespace MQContract.Messages { /// - /// A Recieved Service Message that gets passed back up into the Contract Connection when a message is recieved from the underlying service connection + /// A Received Service Message that gets passed back up into the Contract Connection when a message is received from the underlying service connection /// /// The unique ID of the message /// The message type id which is used for decoding to a class - /// The channel the message was recieved on + /// The channel the message was received on /// The message headers that came through /// The binary content of the message that should be the encoded class - /// The acknowledgement callback to be called when the message is recieved if the underlying service requires it + /// The acknowledgement callback to be called when the message is received if the underlying service requires it [ExcludeFromCodeCoverage(Justification ="This is a record class and has nothing to test")] - public record RecievedServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header, ReadOnlyMemory Data,Func? Acknowledge=null) + public record ReceivedServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header, ReadOnlyMemory Data,Func? Acknowledge=null) : ServiceMessage(ID,MessageTypeID,Channel,Header,Data) { /// - /// A timestamp for when the message was recieved + /// A timestamp for when the message was received /// - public DateTime RecievedTimestamp { get; private init; } = DateTime.Now; + public DateTime ReceivedTimestamp { get; private init; } = DateTime.Now; } } diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index 0cedc5a..4ebd238 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -3,16 +3,48 @@ ## Contents +- [IAfterDecodeMiddleware](#T-MQContract-Interfaces-Middleware-IAfterDecodeMiddleware 'MQContract.Interfaces.Middleware.IAfterDecodeMiddleware') + - [AfterMessageDecodeAsync\`\`1(context,message,ID,messageHeader,receivedTimestamp,processedTimeStamp)](#M-MQContract-Interfaces-Middleware-IAfterDecodeMiddleware-AfterMessageDecodeAsync``1-MQContract-Interfaces-Middleware-IContext,``0,System-String,MQContract-Messages-MessageHeader,System-DateTime,System-DateTime- 'MQContract.Interfaces.Middleware.IAfterDecodeMiddleware.AfterMessageDecodeAsync``1(MQContract.Interfaces.Middleware.IContext,``0,System.String,MQContract.Messages.MessageHeader,System.DateTime,System.DateTime)') +- [IAfterDecodeSpecificTypeMiddleware\`1](#T-MQContract-Interfaces-Middleware-IAfterDecodeSpecificTypeMiddleware`1 'MQContract.Interfaces.Middleware.IAfterDecodeSpecificTypeMiddleware`1') + - [AfterMessageDecodeAsync(context,message,ID,messageHeader,receivedTimestamp,processedTimeStamp)](#M-MQContract-Interfaces-Middleware-IAfterDecodeSpecificTypeMiddleware`1-AfterMessageDecodeAsync-MQContract-Interfaces-Middleware-IContext,`0,System-String,MQContract-Messages-MessageHeader,System-DateTime,System-DateTime- 'MQContract.Interfaces.Middleware.IAfterDecodeSpecificTypeMiddleware`1.AfterMessageDecodeAsync(MQContract.Interfaces.Middleware.IContext,`0,System.String,MQContract.Messages.MessageHeader,System.DateTime,System.DateTime)') +- [IAfterEncodeMiddleware](#T-MQContract-Interfaces-Middleware-IAfterEncodeMiddleware 'MQContract.Interfaces.Middleware.IAfterEncodeMiddleware') + - [AfterMessageEncodeAsync(messageType,context,message)](#M-MQContract-Interfaces-Middleware-IAfterEncodeMiddleware-AfterMessageEncodeAsync-System-Type,MQContract-Interfaces-Middleware-IContext,MQContract-Messages-ServiceMessage- 'MQContract.Interfaces.Middleware.IAfterEncodeMiddleware.AfterMessageEncodeAsync(System.Type,MQContract.Interfaces.Middleware.IContext,MQContract.Messages.ServiceMessage)') +- [IBeforeDecodeMiddleware](#T-MQContract-Interfaces-Middleware-IBeforeDecodeMiddleware 'MQContract.Interfaces.Middleware.IBeforeDecodeMiddleware') + - [BeforeMessageDecodeAsync(context,id,messageHeader,messageTypeID,messageChannel,data)](#M-MQContract-Interfaces-Middleware-IBeforeDecodeMiddleware-BeforeMessageDecodeAsync-MQContract-Interfaces-Middleware-IContext,System-String,MQContract-Messages-MessageHeader,System-String,System-String,System-ReadOnlyMemory{System-Byte}- 'MQContract.Interfaces.Middleware.IBeforeDecodeMiddleware.BeforeMessageDecodeAsync(MQContract.Interfaces.Middleware.IContext,System.String,MQContract.Messages.MessageHeader,System.String,System.String,System.ReadOnlyMemory{System.Byte})') +- [IBeforeEncodeMiddleware](#T-MQContract-Interfaces-Middleware-IBeforeEncodeMiddleware 'MQContract.Interfaces.Middleware.IBeforeEncodeMiddleware') + - [BeforeMessageEncodeAsync\`\`1(context,message,channel,messageHeader)](#M-MQContract-Interfaces-Middleware-IBeforeEncodeMiddleware-BeforeMessageEncodeAsync``1-MQContract-Interfaces-Middleware-IContext,``0,System-String,MQContract-Messages-MessageHeader- 'MQContract.Interfaces.Middleware.IBeforeEncodeMiddleware.BeforeMessageEncodeAsync``1(MQContract.Interfaces.Middleware.IContext,``0,System.String,MQContract.Messages.MessageHeader)') +- [IBeforeEncodeSpecificTypeMiddleware\`1](#T-MQContract-Interfaces-Middleware-IBeforeEncodeSpecificTypeMiddleware`1 'MQContract.Interfaces.Middleware.IBeforeEncodeSpecificTypeMiddleware`1') + - [BeforeMessageEncodeAsync(context,message,channel,messageHeader)](#M-MQContract-Interfaces-Middleware-IBeforeEncodeSpecificTypeMiddleware`1-BeforeMessageEncodeAsync-MQContract-Interfaces-Middleware-IContext,`0,System-String,MQContract-Messages-MessageHeader- 'MQContract.Interfaces.Middleware.IBeforeEncodeSpecificTypeMiddleware`1.BeforeMessageEncodeAsync(MQContract.Interfaces.Middleware.IContext,`0,System.String,MQContract.Messages.MessageHeader)') +- [IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') + - [Item](#P-MQContract-Interfaces-Middleware-IContext-Item-System-String- 'MQContract.Interfaces.Middleware.IContext.Item(System.String)') - [IContractConnection](#T-MQContract-Interfaces-IContractConnection 'MQContract.Interfaces.IContractConnection') + - [AddMetrics(useMeter,useInternal)](#M-MQContract-Interfaces-IContractConnection-AddMetrics-System-Boolean,System-Boolean- 'MQContract.Interfaces.IContractConnection.AddMetrics(System.Boolean,System.Boolean)') - [CloseAsync()](#M-MQContract-Interfaces-IContractConnection-CloseAsync 'MQContract.Interfaces.IContractConnection.CloseAsync') + - [GetSnapshot(sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot-System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot(System.Boolean)') + - [GetSnapshot(messageType,sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot-System-Type,System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot(System.Type,System.Boolean)') + - [GetSnapshot(channel,sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot-System-String,System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot(System.String,System.Boolean)') - [PingAsync()](#M-MQContract-Interfaces-IContractConnection-PingAsync 'MQContract.Interfaces.IContractConnection.PingAsync') - [PublishAsync\`\`1(message,channel,messageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') - [QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') - - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') - - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [RegisterMiddleware\`\`1()](#M-MQContract-Interfaces-IContractConnection-RegisterMiddleware``1 'MQContract.Interfaces.IContractConnection.RegisterMiddleware``1') + - [RegisterMiddleware\`\`1(constructInstance)](#M-MQContract-Interfaces-IContractConnection-RegisterMiddleware``1-System-Func{``0}- 'MQContract.Interfaces.IContractConnection.RegisterMiddleware``1(System.Func{``0})') + - [RegisterMiddleware\`\`2()](#M-MQContract-Interfaces-IContractConnection-RegisterMiddleware``2 'MQContract.Interfaces.IContractConnection.RegisterMiddleware``2') + - [RegisterMiddleware\`\`2(constructInstance)](#M-MQContract-Interfaces-IContractConnection-RegisterMiddleware``2-System-Func{``0}- 'MQContract.Interfaces.IContractConnection.RegisterMiddleware``2(System.Func{``0})') + - [SubscribeAsync\`\`1(messageReceived,errorReceived,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IReceivedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IReceivedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeAsync\`\`1(messageReceived,errorReceived,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IReceivedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IReceivedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeQueryAsyncResponseAsync\`\`2(messageReceived,errorReceived,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IReceivedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IReceivedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') + - [SubscribeQueryResponseAsync\`\`2(messageReceived,errorReceived,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IReceivedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IReceivedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') +- [IContractMetric](#T-MQContract-Interfaces-IContractMetric 'MQContract.Interfaces.IContractMetric') + - [MessageBytes](#P-MQContract-Interfaces-IContractMetric-MessageBytes 'MQContract.Interfaces.IContractMetric.MessageBytes') + - [MessageBytesAverage](#P-MQContract-Interfaces-IContractMetric-MessageBytesAverage 'MQContract.Interfaces.IContractMetric.MessageBytesAverage') + - [MessageBytesMax](#P-MQContract-Interfaces-IContractMetric-MessageBytesMax 'MQContract.Interfaces.IContractMetric.MessageBytesMax') + - [MessageBytesMin](#P-MQContract-Interfaces-IContractMetric-MessageBytesMin 'MQContract.Interfaces.IContractMetric.MessageBytesMin') + - [MessageConversionAverage](#P-MQContract-Interfaces-IContractMetric-MessageConversionAverage 'MQContract.Interfaces.IContractMetric.MessageConversionAverage') + - [MessageConversionDuration](#P-MQContract-Interfaces-IContractMetric-MessageConversionDuration 'MQContract.Interfaces.IContractMetric.MessageConversionDuration') + - [MessageConversionMax](#P-MQContract-Interfaces-IContractMetric-MessageConversionMax 'MQContract.Interfaces.IContractMetric.MessageConversionMax') + - [MessageConversionMin](#P-MQContract-Interfaces-IContractMetric-MessageConversionMin 'MQContract.Interfaces.IContractMetric.MessageConversionMin') + - [Messages](#P-MQContract-Interfaces-IContractMetric-Messages 'MQContract.Interfaces.IContractMetric.Messages') - [IEncodedMessage](#T-MQContract-Interfaces-Messages-IEncodedMessage 'MQContract.Interfaces.Messages.IEncodedMessage') - [Data](#P-MQContract-Interfaces-Messages-IEncodedMessage-Data 'MQContract.Interfaces.Messages.IEncodedMessage.Data') - [Header](#P-MQContract-Interfaces-Messages-IEncodedMessage-Header 'MQContract.Interfaces.Messages.IEncodedMessage.Header') @@ -29,7 +61,7 @@ - [MaxMessageBodySize](#P-MQContract-Interfaces-Service-IMessageServiceConnection-MaxMessageBodySize 'MQContract.Interfaces.Service.IMessageServiceConnection.MaxMessageBodySize') - [CloseAsync()](#M-MQContract-Interfaces-Service-IMessageServiceConnection-CloseAsync 'MQContract.Interfaces.Service.IMessageServiceConnection.CloseAsync') - [PublishAsync(message,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') + - [SubscribeAsync(messageReceived,errorReceived,channel,group,cancellationToken)](#M-MQContract-Interfaces-Service-IMessageServiceConnection-SubscribeAsync-System-Action{MQContract-Messages-ReceivedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IMessageServiceConnection.SubscribeAsync(System.Action{MQContract.Messages.ReceivedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - [IMessageTypeEncoder\`1](#T-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1') - [DecodeAsync(stream)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-DecodeAsync-System-IO-Stream- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.DecodeAsync(System.IO.Stream)') - [EncodeAsync(message)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-EncodeAsync-`0- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.EncodeAsync(`0)') @@ -39,13 +71,13 @@ - [IQueryableMessageServiceConnection](#T-MQContract-Interfaces-Service-IQueryableMessageServiceConnection 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection') - [DefaultTimout](#P-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-DefaultTimout 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.DefaultTimout') - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') -- [IRecievedMessage\`1](#T-MQContract-Interfaces-IRecievedMessage`1 'MQContract.Interfaces.IRecievedMessage`1') - - [Headers](#P-MQContract-Interfaces-IRecievedMessage`1-Headers 'MQContract.Interfaces.IRecievedMessage`1.Headers') - - [ID](#P-MQContract-Interfaces-IRecievedMessage`1-ID 'MQContract.Interfaces.IRecievedMessage`1.ID') - - [Message](#P-MQContract-Interfaces-IRecievedMessage`1-Message 'MQContract.Interfaces.IRecievedMessage`1.Message') - - [ProcessedTimestamp](#P-MQContract-Interfaces-IRecievedMessage`1-ProcessedTimestamp 'MQContract.Interfaces.IRecievedMessage`1.ProcessedTimestamp') - - [RecievedTimestamp](#P-MQContract-Interfaces-IRecievedMessage`1-RecievedTimestamp 'MQContract.Interfaces.IRecievedMessage`1.RecievedTimestamp') + - [SubscribeQueryAsync(messageReceived,errorReceived,channel,group,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-ReceivedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.ReceivedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') +- [IReceivedMessage\`1](#T-MQContract-Interfaces-IReceivedMessage`1 'MQContract.Interfaces.IReceivedMessage`1') + - [Headers](#P-MQContract-Interfaces-IReceivedMessage`1-Headers 'MQContract.Interfaces.IReceivedMessage`1.Headers') + - [ID](#P-MQContract-Interfaces-IReceivedMessage`1-ID 'MQContract.Interfaces.IReceivedMessage`1.ID') + - [Message](#P-MQContract-Interfaces-IReceivedMessage`1-Message 'MQContract.Interfaces.IReceivedMessage`1.Message') + - [ProcessedTimestamp](#P-MQContract-Interfaces-IReceivedMessage`1-ProcessedTimestamp 'MQContract.Interfaces.IReceivedMessage`1.ProcessedTimestamp') + - [ReceivedTimestamp](#P-MQContract-Interfaces-IReceivedMessage`1-ReceivedTimestamp 'MQContract.Interfaces.IReceivedMessage`1.ReceivedTimestamp') - [IServiceSubscription](#T-MQContract-Interfaces-Service-IServiceSubscription 'MQContract.Interfaces.Service.IServiceSubscription') - [EndAsync()](#M-MQContract-Interfaces-Service-IServiceSubscription-EndAsync 'MQContract.Interfaces.Service.IServiceSubscription.EndAsync') - [ISubscription](#T-MQContract-Interfaces-ISubscription 'MQContract.Interfaces.ISubscription') @@ -88,10 +120,10 @@ - [#ctor(ID,Header,Result,Error)](#M-MQContract-Messages-QueryResult`1-#ctor-System-String,MQContract-Messages-MessageHeader,`0,System-String- 'MQContract.Messages.QueryResult`1.#ctor(System.String,MQContract.Messages.MessageHeader,`0,System.String)') - [Header](#P-MQContract-Messages-QueryResult`1-Header 'MQContract.Messages.QueryResult`1.Header') - [Result](#P-MQContract-Messages-QueryResult`1-Result 'MQContract.Messages.QueryResult`1.Result') -- [RecievedServiceMessage](#T-MQContract-Messages-RecievedServiceMessage 'MQContract.Messages.RecievedServiceMessage') - - [#ctor(ID,MessageTypeID,Channel,Header,Data,Acknowledge)](#M-MQContract-Messages-RecievedServiceMessage-#ctor-System-String,System-String,System-String,MQContract-Messages-MessageHeader,System-ReadOnlyMemory{System-Byte},System-Func{System-Threading-Tasks-ValueTask}- 'MQContract.Messages.RecievedServiceMessage.#ctor(System.String,System.String,System.String,MQContract.Messages.MessageHeader,System.ReadOnlyMemory{System.Byte},System.Func{System.Threading.Tasks.ValueTask})') - - [Acknowledge](#P-MQContract-Messages-RecievedServiceMessage-Acknowledge 'MQContract.Messages.RecievedServiceMessage.Acknowledge') - - [RecievedTimestamp](#P-MQContract-Messages-RecievedServiceMessage-RecievedTimestamp 'MQContract.Messages.RecievedServiceMessage.RecievedTimestamp') +- [ReceivedServiceMessage](#T-MQContract-Messages-ReceivedServiceMessage 'MQContract.Messages.ReceivedServiceMessage') + - [#ctor(ID,MessageTypeID,Channel,Header,Data,Acknowledge)](#M-MQContract-Messages-ReceivedServiceMessage-#ctor-System-String,System-String,System-String,MQContract-Messages-MessageHeader,System-ReadOnlyMemory{System-Byte},System-Func{System-Threading-Tasks-ValueTask}- 'MQContract.Messages.ReceivedServiceMessage.#ctor(System.String,System.String,System.String,MQContract.Messages.MessageHeader,System.ReadOnlyMemory{System.Byte},System.Func{System.Threading.Tasks.ValueTask})') + - [Acknowledge](#P-MQContract-Messages-ReceivedServiceMessage-Acknowledge 'MQContract.Messages.ReceivedServiceMessage.Acknowledge') + - [ReceivedTimestamp](#P-MQContract-Messages-ReceivedServiceMessage-ReceivedTimestamp 'MQContract.Messages.ReceivedServiceMessage.ReceivedTimestamp') - [ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') - [#ctor(ID,MessageTypeID,Channel,Header,Data)](#M-MQContract-Messages-ServiceMessage-#ctor-System-String,System-String,System-String,MQContract-Messages-MessageHeader,System-ReadOnlyMemory{System-Byte}- 'MQContract.Messages.ServiceMessage.#ctor(System.String,System.String,System.String,MQContract.Messages.MessageHeader,System.ReadOnlyMemory{System.Byte})') - [Channel](#P-MQContract-Messages-ServiceMessage-Channel 'MQContract.Messages.ServiceMessage.Channel') @@ -111,6 +143,237 @@ - [ID](#P-MQContract-Messages-TransmissionResult-ID 'MQContract.Messages.TransmissionResult.ID') - [IsError](#P-MQContract-Messages-TransmissionResult-IsError 'MQContract.Messages.TransmissionResult.IsError') + +## IAfterDecodeMiddleware `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +This interface represents a Middleware to execute after a Message has been decoded from a ServiceMessage to the expected Class + + +### AfterMessageDecodeAsync\`\`1(context,message,ID,messageHeader,receivedTimestamp,processedTimeStamp) `method` + +##### Summary + +This is the method invoked as part of the Middleware processing during message decoding + +##### Returns + +The message and header to allow for changes if desired + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| context | [MQContract.Interfaces.Middleware.IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') | A shared context that exists from the start of this decode process instance | +| message | [\`\`0](#T-``0 '``0') | The class message | +| ID | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The id of the message | +| messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The headers from the message | +| receivedTimestamp | [System.DateTime](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.DateTime 'System.DateTime') | The timestamp of when the message was recieved | +| processedTimeStamp | [System.DateTime](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.DateTime 'System.DateTime') | The timestamp of when the message was decoded into a Class | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | This will be the type of the Message that was decoded | + + +## IAfterDecodeSpecificTypeMiddleware\`1 `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +This interface represents a Middleware to execute after a Message of the given type T has been decoded from a ServiceMessage to the expected Class + + +### AfterMessageDecodeAsync(context,message,ID,messageHeader,receivedTimestamp,processedTimeStamp) `method` + +##### Summary + +This is the method invoked as part of the Middleware processing during message decoding + +##### Returns + +The message and header to allow for changes if desired + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| context | [MQContract.Interfaces.Middleware.IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') | A shared context that exists from the start of this decode process instance | +| message | [\`0](#T-`0 '`0') | The class message | +| ID | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The id of the message | +| messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The headers from the message | +| receivedTimestamp | [System.DateTime](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.DateTime 'System.DateTime') | The timestamp of when the message was recieved | +| processedTimeStamp | [System.DateTime](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.DateTime 'System.DateTime') | The timestamp of when the message was decoded into a Class | + + +## IAfterEncodeMiddleware `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +This interface represents a Middleware to execute after a Message has been encoded to a ServiceMessage from the supplied Class + + +### AfterMessageEncodeAsync(messageType,context,message) `method` + +##### Summary + +This is the method invoked as part of the Middleware processing during message encoding + +##### Returns + +The message to allow for changes if desired + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageType | [System.Type](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Type 'System.Type') | The class of the message type that was encoded | +| context | [MQContract.Interfaces.Middleware.IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') | A shared context that exists from the start of this encode process instance | +| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The resulting encoded message | + + +## IBeforeDecodeMiddleware `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +This interface represents a Middleware to execute before decoding a ServiceMessage + + +### BeforeMessageDecodeAsync(context,id,messageHeader,messageTypeID,messageChannel,data) `method` + +##### Summary + +This is the method invoked as part of the Middleware processing prior to the message decoding + +##### Returns + +The message header and data to allow for changes if desired + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| context | [MQContract.Interfaces.Middleware.IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') | A shared context that exists from the start of this decode process instance | +| id | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The id of the message | +| messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The headers from the message | +| messageTypeID | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The message type id | +| messageChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel the message was recieved on | +| data | [System.ReadOnlyMemory{System.Byte}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.ReadOnlyMemory 'System.ReadOnlyMemory{System.Byte}') | The data of the message | + + +## IBeforeEncodeMiddleware `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +This interface represents a Middleware to execute Before a message is encoded + + +### BeforeMessageEncodeAsync\`\`1(context,message,channel,messageHeader) `method` + +##### Summary + +This is the method invoked as part of the Middle Ware processing during message encoding + +##### Returns + +The message, channel and header to allow for changes if desired + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| context | [MQContract.Interfaces.Middleware.IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') | A shared context that exists from the start of this encoding instance | +| message | [\`\`0](#T-``0 '``0') | The message being encoded | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel this message was requested to transmit to | +| messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The message headers being supplied | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | The type of message being processed | + + +## IBeforeEncodeSpecificTypeMiddleware\`1 `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +This interface represents a Middleware to execute Before a specific message type is encoded + + +### BeforeMessageEncodeAsync(context,message,channel,messageHeader) `method` + +##### Summary + +This is the method invoked as part of the Middle Ware processing during message encoding + +##### Returns + +The message, channel and header to allow for changes if desired + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| context | [MQContract.Interfaces.Middleware.IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') | A shared context that exists from the start of this encoding instance | +| message | [\`0](#T-`0 '`0') | The message being encoded | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel this message was requested to transmit to | +| messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The message headers being supplied | + + +## IContext `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +This is used to represent a Context for the middleware calls to use that exists from the start to the end of the message conversion process + + +### Item `property` + +##### Summary + +Used to store and retreive values from the context during the conversion process. + +##### Returns + +The value if it exists in the context + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| key | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The unique key to use | + ## IContractConnection `type` @@ -122,6 +385,20 @@ MQContract.Interfaces This interface represents the Core class for the MQContract system, IE the ContractConnection + +### AddMetrics(useMeter,useInternal) `method` + +##### Summary + +Called to activate the metrics tracking middleware for this connection instance + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| useMeter | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if the Meter style metrics should be enabled | +| useInternal | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if the internal metrics collector should be used | + ### CloseAsync() `method` @@ -137,6 +414,59 @@ A task for the closure of the connection This method has no parameters. + +### GetSnapshot(sent) `method` + +##### Summary + +Called to get a snapshot of the current global metrics. Will return null if internal metrics are not enabled. + +##### Returns + +A record of the current metric snapshot or null if not available + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| sent | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | true when the sent metrics are desired, false when received are desired | + + +### GetSnapshot(messageType,sent) `method` + +##### Summary + +Called to get a snapshot of the metrics for a given message type. Will return null if internal metrics are not enabled. + +##### Returns + +A record of the current metric snapshot or null if not available + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageType | [System.Type](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Type 'System.Type') | The type of message to look for | +| sent | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | true when the sent metrics are desired, false when received are desired | + + +### GetSnapshot(channel,sent) `method` + +##### Summary + +Called to get a snapshot of the metrics for a given message channel. Will return null if internal metrics are not enabled. + +##### Returns + +A record of the current metric snapshot or null if not available + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel to look for | +| sent | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | true when the sent metrics are desired, false when received are desired | + ### PingAsync() `method` @@ -195,7 +525,7 @@ A result indicating the success or failure as well as the returned message | Name | Type | Description | | ---- | ---- | ----------- | | message | [\`\`0](#T-``0 '``0') | The message to send | -| timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The allowed timeout prior to a response being recieved | +| timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The allowed timeout prior to a response being received | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is only used when the underlying connection does not support a QueryResponse style messaging. | @@ -224,7 +554,7 @@ A result indicating the success or failure as well as the returned message | Name | Type | Description | | ---- | ---- | ----------- | | message | [\`\`0](#T-``0 '``0') | The message to send | -| timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The allowed timeout prior to a response being recieved | +| timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The allowed timeout prior to a response being received | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is only used when the underlying connection does not support a QueryResponse style messaging. | @@ -238,8 +568,98 @@ only used when the underlying connection does not support a QueryResponse style | Q | The type of message to send for the query | | R | The type of message to expect back for the response | - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` + +### RegisterMiddleware\`\`1() `method` + +##### Summary + +Register a middleware of a given type T to be used by the contract connection + +##### Returns + +The Contract Connection instance to allow chaining calls + +##### Parameters + +This method has no parameters. + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | The type of middle ware to register, it must implement IBeforeDecodeMiddleware or IBeforeEncodeMiddleware or IAfterDecodeMiddleware or IAfterEncodeMiddleware | + + +### RegisterMiddleware\`\`1(constructInstance) `method` + +##### Summary + +Register a middleware of a given type T to be used by the contract connection + +##### Returns + +The Contract Connection instance to allow chaining calls + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| constructInstance | [System.Func{\`\`0}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{``0}') | Callback to create the instance | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | The type of middle ware to register, it must implement IBeforeDecodeMiddleware or IBeforeEncodeMiddleware or IAfterDecodeMiddleware or IAfterEncodeMiddleware | + + +### RegisterMiddleware\`\`2() `method` + +##### Summary + +Register a middleware of a given type T to be used by the contract connection + +##### Returns + +The Contract Connection instance to allow chaining calls + +##### Parameters + +This method has no parameters. + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | The type of middle ware to register, it must implement IBeforeEncodeSpecificTypeMiddleware or IAfterDecodeSpecificTypeMiddleware | +| M | The message type that this middleware is specifically called for | + + +### RegisterMiddleware\`\`2(constructInstance) `method` + +##### Summary + +Register a middleware of a given type T to be used by the contract connection + +##### Returns + +The Contract Connection instance to allow chaining calls + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| constructInstance | [System.Func{\`\`0}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{``0}') | Callback to create the instance | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | The type of middle ware to register, it must implement IBeforeEncodeSpecificTypeMiddleware or IAfterDecodeSpecificTypeMiddleware | +| M | The message type that this middleware is specifically called for | + + +### SubscribeAsync\`\`1(messageReceived,errorReceived,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -253,8 +673,8 @@ A subscription instance that can be ended when desired | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask}') | The callback invoked when a new message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an error occurs | +| messageReceived | [System.Func{MQContract.Interfaces.IReceivedMessage{\`\`0},System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IReceivedMessage{``0},System.Threading.Tasks.ValueTask}') | The callback invoked when a new message is received | +| errorReceived | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | @@ -266,8 +686,8 @@ A subscription instance that can be ended when desired | ---- | ----------- | | T | The type of message to listen for | - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` + +### SubscribeAsync\`\`1(messageReceived,errorReceived,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -281,8 +701,8 @@ A subscription instance that can be ended when desired | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Interfaces.IRecievedMessage{\`\`0}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Interfaces.IRecievedMessage{``0}}') | The callback invoked when a new message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an error occurs | +| messageReceived | [System.Action{MQContract.Interfaces.IReceivedMessage{\`\`0}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Interfaces.IReceivedMessage{``0}}') | The callback invoked when a new message is received | +| errorReceived | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an error occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | @@ -294,8 +714,8 @@ A subscription instance that can be ended when desired | ---- | ----------- | | T | The type of message to listen for | - -### SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` + +### SubscribeQueryAsyncResponseAsync\`\`2(messageReceived,errorReceived,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -309,8 +729,8 @@ A subscription instance that can be ended when desired | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{\`\`1}}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}}') | The callback invoked when a new message is recieved expecting a response of the type response | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback invoked when an error occurs. | +| messageReceived | [System.Func{MQContract.Interfaces.IReceivedMessage{\`\`0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{\`\`1}}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IReceivedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}}') | The callback invoked when a new message is received expecting a response of the type response | +| errorReceived | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback invoked when an error occurs. | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | @@ -323,8 +743,8 @@ A subscription instance that can be ended when desired | Q | The type of message to listen for | | R | The type of message to respond with | - -### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` + +### SubscribeQueryResponseAsync\`\`2(messageReceived,errorReceived,channel,group,ignoreMessageHeader,cancellationToken) `method` ##### Summary @@ -338,8 +758,8 @@ A subscription instance that can be ended when desired | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},MQContract.Messages.QueryResponseMessage{\`\`1}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}}') | The callback invoked when a new message is recieved expecting a response of the type response | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback invoked when an error occurs. | +| messageReceived | [System.Func{MQContract.Interfaces.IReceivedMessage{\`\`0},MQContract.Messages.QueryResponseMessage{\`\`1}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IReceivedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}}') | The callback invoked when a new message is received expecting a response of the type response | +| errorReceived | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback invoked when an error occurs. | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use. The prefered method is using the MessageChannelAttribute on the class. | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The subscription group if desired (typically used when multiple instances of the same system are running) | | ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the message type specified will be ignored and it will automatically attempt to convert the underlying message to the given class | @@ -352,6 +772,83 @@ A subscription instance that can be ended when desired | Q | The type of message to listen for | | R | The type of message to respond with | + +## IContractMetric `type` + +##### Namespace + +MQContract.Interfaces + +##### Summary + +Houses a set of metrics that were requested from the internal metric tracker. +All message conversion durations are calculated from the perspective: + - When a class is being sent from the point of starting the middleware to the point where the class has been encoded into a service message and the middleware has completed + - When a service message is being recieved from the point of starting the middleware to the point where the class has been built from the service message and the middleware has completed + + +### MessageBytes `property` + +##### Summary + +Total amount of bytes from the messages + + +### MessageBytesAverage `property` + +##### Summary + +Average number of bytes from the messages + + +### MessageBytesMax `property` + +##### Summary + +Maximum number of bytes from the messages + + +### MessageBytesMin `property` + +##### Summary + +Minimum number of bytes from the messages + + +### MessageConversionAverage `property` + +##### Summary + +Average time to encode/decode the messages + + +### MessageConversionDuration `property` + +##### Summary + +Total time spent converting the messages + + +### MessageConversionMax `property` + +##### Summary + +Maximum time to encode/decode a message + + +### MessageConversionMin `property` + +##### Summary + +Minimum time to encode/decode a message + + +### Messages `property` + +##### Summary + +Total number of messages + ## IEncodedMessage `type` @@ -394,7 +891,7 @@ MQContract.Interfaces.Conversion ##### Summary Used to define a message converter. These are called upon if a -message is recieved on a channel of type T but it is waiting for +message is received on a channel of type T but it is waiting for message of type V ##### Generic Types @@ -498,7 +995,7 @@ if desired. ##### Summary -Called to decrypt the message body stream recieved as a message +Called to decrypt the message body stream received as a message ##### Returns @@ -581,8 +1078,8 @@ A transmission result instance indicating the result | message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The message to publish | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` + +### SubscribeAsync(messageReceived,errorReceived,channel,group,cancellationToken) `method` ##### Summary @@ -596,8 +1093,8 @@ A service subscription object | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | The callback to invoke when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | +| messageReceived | [System.Action{MQContract.Messages.ReceivedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.ReceivedServiceMessage}') | The callback to invoke when a message is received | +| errorReceived | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to subscribe to | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The consumer group to register as | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | @@ -736,8 +1233,8 @@ A Query Result instance based on what happened | timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout for recieving a response | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` + +### SubscribeQueryAsync(messageReceived,errorReceived,channel,group,cancellationToken) `method` ##### Summary @@ -751,14 +1248,14 @@ A service subscription object | Name | Type | Description | | ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | The callback to be invoked when a message is recieved, returning the response message | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | +| messageReceived | [System.Func{MQContract.Messages.ReceivedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.ReceivedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | The callback to be invoked when a message is received, returning the response message | +| errorReceived | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to invoke when an exception occurs | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to subscribe to | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind a consumer to | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -## IRecievedMessage\`1 `type` + +## IReceivedMessage\`1 `type` ##### Namespace @@ -766,7 +1263,7 @@ MQContract.Interfaces ##### Summary -An interface for describing a Message recieved on a Subscription to be passed into the appropriate callback +An interface for describing a Message received on a Subscription to be passed into the appropriate callback ##### Generic Types @@ -774,40 +1271,40 @@ An interface for describing a Message recieved on a Subscription to be passed in | ---- | ----------- | | T | The class type of the underlying message | - + ### Headers `property` ##### Summary The headers that were supplied with the message - + ### ID `property` ##### Summary -The unique ID of the recieved message that was specified on the transmission side +The unique ID of the received message that was specified on the transmission side - + ### Message `property` ##### Summary The message that was transmitted - + ### ProcessedTimestamp `property` ##### Summary -The timestamp of when the recieved message was converted into the actual class prior to calling the callback +The timestamp of when the received message was converted into the actual class prior to calling the callback - -### RecievedTimestamp `property` + +### ReceivedTimestamp `property` ##### Summary -The timestamp of when the message was recieved by the underlying service connection +The timestamp of when the message was received by the underlying service connection ## IServiceSubscription `type` @@ -1404,8 +1901,8 @@ The response headers The resulting response if there was one - -## RecievedServiceMessage `type` + +## ReceivedServiceMessage `type` ##### Namespace @@ -1413,20 +1910,20 @@ MQContract.Messages ##### Summary -A Recieved Service Message that gets passed back up into the Contract Connection when a message is recieved from the underlying service connection +A Received Service Message that gets passed back up into the Contract Connection when a message is received from the underlying service connection ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| ID | [T:MQContract.Messages.RecievedServiceMessage](#T-T-MQContract-Messages-RecievedServiceMessage 'T:MQContract.Messages.RecievedServiceMessage') | The unique ID of the message | +| ID | [T:MQContract.Messages.ReceivedServiceMessage](#T-T-MQContract-Messages-ReceivedServiceMessage 'T:MQContract.Messages.ReceivedServiceMessage') | The unique ID of the message | - + ### #ctor(ID,MessageTypeID,Channel,Header,Data,Acknowledge) `constructor` ##### Summary -A Recieved Service Message that gets passed back up into the Contract Connection when a message is recieved from the underlying service connection +A Received Service Message that gets passed back up into the Contract Connection when a message is received from the underlying service connection ##### Parameters @@ -1434,24 +1931,24 @@ A Recieved Service Message that gets passed back up into the Contract Connection | ---- | ---- | ----------- | | ID | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The unique ID of the message | | MessageTypeID | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The message type id which is used for decoding to a class | -| Channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel the message was recieved on | +| Channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel the message was received on | | Header | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The message headers that came through | | Data | [System.ReadOnlyMemory{System.Byte}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.ReadOnlyMemory 'System.ReadOnlyMemory{System.Byte}') | The binary content of the message that should be the encoded class | -| Acknowledge | [System.Func{System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.Threading.Tasks.ValueTask}') | The acknowledgement callback to be called when the message is recieved if the underlying service requires it | +| Acknowledge | [System.Func{System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.Threading.Tasks.ValueTask}') | The acknowledgement callback to be called when the message is received if the underlying service requires it | - + ### Acknowledge `property` ##### Summary -The acknowledgement callback to be called when the message is recieved if the underlying service requires it +The acknowledgement callback to be called when the message is received if the underlying service requires it - -### RecievedTimestamp `property` + +### ReceivedTimestamp `property` ##### Summary -A timestamp for when the message was recieved +A timestamp for when the message was received ## ServiceMessage `type` diff --git a/AutomatedTesting/ChannelMapperTests.cs b/AutomatedTesting/ChannelMapperTests.cs index 3c10615..3599914 100644 --- a/AutomatedTesting/ChannelMapperTests.cs +++ b/AutomatedTesting/ChannelMapperTests.cs @@ -30,7 +30,7 @@ public async Task TestPublishMapWithStringToString() var mapper = new ChannelMapper() .AddPublishMap(typeof(BasicMessage).GetCustomAttribute(false)?.Name!, newChannel); - var contractConnection = new ContractConnection(serviceConnection.Object,channelMapper:mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object,channelMapper:mapper); #endregion #region Act @@ -76,7 +76,7 @@ public async Task TestPublishMapWithStringToFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -126,7 +126,7 @@ public async Task TestPublishMapWithMatchToFunction() return ValueTask.FromResult(newChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -176,7 +176,7 @@ public async Task TestPublishMapWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -225,7 +225,7 @@ public async Task TestPublishMapWithSingleMapAndWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -260,7 +260,7 @@ public async Task TestPublishSubscribeMapWithStringToString() var channels = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -271,7 +271,7 @@ public async Task TestPublishSubscribeMapWithStringToString() var mapper = new ChannelMapper() .AddPublishSubscriptionMap(typeof(BasicMessage).GetCustomAttribute(false)?.Name!, newChannel); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -287,7 +287,7 @@ public async Task TestPublishSubscribeMapWithStringToString() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -300,7 +300,7 @@ public async Task TestPublishSubscribeMapWithStringToFunction() var channels = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -319,7 +319,7 @@ public async Task TestPublishSubscribeMapWithStringToFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -341,7 +341,7 @@ public async Task TestPublishSubscribeMapWithStringToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -354,7 +354,7 @@ public async Task TestPublishSubscribeMapWithMatchToFunction() var channels = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -371,7 +371,7 @@ public async Task TestPublishSubscribeMapWithMatchToFunction() return ValueTask.FromResult(newChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -393,7 +393,7 @@ public async Task TestPublishSubscribeMapWithMatchToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -406,7 +406,7 @@ public async Task TestPublishSubscribeMapWithDefaultFunction() var channels = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -424,7 +424,7 @@ public async Task TestPublishSubscribeMapWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -446,7 +446,7 @@ public async Task TestPublishSubscribeMapWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -459,7 +459,7 @@ public async Task TestPublishSubscribeMapWithSingleMapAndWithDefaultFunction() var channels = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -476,7 +476,7 @@ public async Task TestPublishSubscribeMapWithSingleMapAndWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -498,7 +498,7 @@ public async Task TestPublishSubscribeMapWithSingleMapAndWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -529,7 +529,7 @@ public async Task TestQueryMapWithStringToString() var mapper = new ChannelMapper() .AddQueryMap(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name!, newChannel); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -587,7 +587,7 @@ public async Task TestQueryMapWithStringToFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -643,7 +643,7 @@ public async Task TestQueryMapWithMatchToFunction() return ValueTask.FromResult(newChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -700,7 +700,7 @@ public async Task TestQueryMapWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -756,7 +756,7 @@ public async Task TestQueryMapWithSingleMapAndWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -790,7 +790,7 @@ public async Task TestQuerySubscribeMapWithStringToString() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -801,7 +801,7 @@ public async Task TestQuerySubscribeMapWithStringToString() var mapper = new ChannelMapper() .AddQuerySubscriptionMap(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name!, newChannel); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -819,7 +819,7 @@ public async Task TestQuerySubscribeMapWithStringToString() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -833,7 +833,7 @@ public async Task TestQuerySubscribeMapWithStringToFunction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -852,7 +852,7 @@ public async Task TestQuerySubscribeMapWithStringToFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -877,7 +877,7 @@ public async Task TestQuerySubscribeMapWithStringToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -891,7 +891,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -908,7 +908,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() return ValueTask.FromResult(newChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -933,7 +933,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -947,7 +947,7 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -965,7 +965,7 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -990,7 +990,7 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -1004,7 +1004,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), @@ -1021,7 +1021,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -1046,7 +1046,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(),It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -1066,11 +1066,11 @@ public async Task TestQueryResponseMapWithStringToString() var mockSubscription = new Mock(); - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); @@ -1079,7 +1079,7 @@ public async Task TestQueryResponseMapWithStringToString() { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); @@ -1091,7 +1091,7 @@ public async Task TestQueryResponseMapWithStringToString() var mapper = new ChannelMapper() .AddQueryResponseMap(responseChannel, newChannel); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -1111,7 +1111,7 @@ public async Task TestQueryResponseMapWithStringToString() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion @@ -1131,11 +1131,11 @@ public async Task TestQueryResponseMapWithStringToFunction() var mockSubscription = new Mock(); - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); @@ -1144,7 +1144,7 @@ public async Task TestQueryResponseMapWithStringToFunction() { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); @@ -1163,7 +1163,7 @@ public async Task TestQueryResponseMapWithStringToFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -1184,7 +1184,7 @@ public async Task TestQueryResponseMapWithStringToFunction() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion @@ -1204,11 +1204,11 @@ public async Task TestQueryResponseMapWithMatchToFunction() var mockSubscription = new Mock(); - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); @@ -1217,7 +1217,7 @@ public async Task TestQueryResponseMapWithMatchToFunction() { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); @@ -1234,7 +1234,7 @@ public async Task TestQueryResponseMapWithMatchToFunction() return ValueTask.FromResult(newChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -1255,7 +1255,7 @@ public async Task TestQueryResponseMapWithMatchToFunction() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion @@ -1274,11 +1274,11 @@ public async Task TestQueryResponseMapWithDefaultFunction() var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); var mockSubscription = new Mock(); - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); @@ -1287,7 +1287,7 @@ public async Task TestQueryResponseMapWithDefaultFunction() { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); @@ -1305,7 +1305,7 @@ public async Task TestQueryResponseMapWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -1326,7 +1326,7 @@ public async Task TestQueryResponseMapWithDefaultFunction() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion @@ -1345,11 +1345,11 @@ public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); var mockSubscription = new Mock(); - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); @@ -1358,7 +1358,7 @@ public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() { if (message.Header[REPLY_CHANNEL_HEADER]!=null) channels.Add(message.Header[REPLY_CHANNEL_HEADER]!); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", message.Channel, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); @@ -1375,7 +1375,7 @@ public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() return ValueTask.FromResult(originalChannel); }); - var contractConnection = new ContractConnection(serviceConnection.Object, channelMapper: mapper); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, channelMapper: mapper); #endregion #region Act @@ -1396,7 +1396,7 @@ public async Task TestQueryResponseMapWithSingleMapAndWithDefaultFunction() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); mockSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion diff --git a/AutomatedTesting/ContractConnectionTests/CleanupTests.cs b/AutomatedTesting/ContractConnectionTests/CleanupTests.cs index 4b8e986..b276f5e 100644 --- a/AutomatedTesting/ContractConnectionTests/CleanupTests.cs +++ b/AutomatedTesting/ContractConnectionTests/CleanupTests.cs @@ -15,7 +15,7 @@ public async Task TestCloseAsync() serviceConnection.Setup(x => x.CloseAsync()) .Returns(ValueTask.CompletedTask); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -36,7 +36,7 @@ public void TestDispose() #region Arrange var serviceConnection = new Mock(); - var contractConnection = new ContractConnection(serviceConnection.As().Object); + var contractConnection = ContractConnection.Instance(serviceConnection.As().Object); #endregion #region Act @@ -58,7 +58,7 @@ public void TestDisposeWithAsyncDispose() var serviceConnection = new Mock(); serviceConnection.Setup(x=>x.DisposeAsync()).Returns(ValueTask.CompletedTask); - var contractConnection = new ContractConnection(serviceConnection.As().Object); + var contractConnection = ContractConnection.Instance(serviceConnection.As().Object); #endregion #region Act @@ -79,7 +79,7 @@ public async Task TestDisposeAsyncWithDispose() #region Arrange var serviceConnection = new Mock(); - var contractConnection = new ContractConnection(serviceConnection.As().Object); + var contractConnection = ContractConnection.Instance(serviceConnection.As().Object); #endregion #region Act @@ -100,7 +100,7 @@ public async Task TestDisposeAsyncWithDisposeAsync() #region Arrange var serviceConnection = new Mock(); - var contractConnection = new ContractConnection(serviceConnection.As().Object); + var contractConnection = ContractConnection.Instance(serviceConnection.As().Object); #endregion #region Act @@ -121,7 +121,7 @@ public async Task TestDisposeAsyncWithDisposeAsyncAndDispose() #region Arrange var serviceConnection = new Mock(); - var contractConnection = new ContractConnection(serviceConnection.As().As().Object); + var contractConnection = ContractConnection.Instance(serviceConnection.As().As().Object); #endregion #region Act diff --git a/AutomatedTesting/ContractConnectionTests/PingTests.cs b/AutomatedTesting/ContractConnectionTests/PingTests.cs index c3e01cf..ae0b86b 100644 --- a/AutomatedTesting/ContractConnectionTests/PingTests.cs +++ b/AutomatedTesting/ContractConnectionTests/PingTests.cs @@ -17,7 +17,7 @@ public async Task TestPingAsync() serviceConnection.Setup(x => x.PingAsync()) .ReturnsAsync(pingResult); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act diff --git a/AutomatedTesting/ContractConnectionTests/PublishTests.cs b/AutomatedTesting/ContractConnectionTests/PublishTests.cs index 1178d21..3145e47 100644 --- a/AutomatedTesting/ContractConnectionTests/PublishTests.cs +++ b/AutomatedTesting/ContractConnectionTests/PublishTests.cs @@ -34,7 +34,7 @@ public async Task TestPublishAsyncWithNoExtendedAspects() serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -74,7 +74,7 @@ public async Task TestPublishAsyncWithDifferentChannelName() serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -116,7 +116,7 @@ public async Task TestPublishAsyncWithMessageHeaders() var messageHeader = new MessageHeader([new("testing", "testing")]); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -160,7 +160,7 @@ public async Task TestPublishAsyncWithCompressionDueToMessageSize() serviceConnection.Setup(x => x.MaxMessageBodySize) .Returns(35); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -207,7 +207,7 @@ public async Task TestPublishAsyncWithGlobalEncoder() globalEncoder.Setup(x => x.EncodeAsync(It.IsAny())) .ReturnsAsync(encodedData); - var contractConnection = new ContractConnection(serviceConnection.Object, defaultMessageEncoder: globalEncoder.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, defaultMessageEncoder: globalEncoder.Object); #endregion #region Act @@ -256,7 +256,7 @@ public async Task TestPublishAsyncWithGlobalEncryptor() globalEncryptor.Setup(x => x.EncryptAsync(Capture.In(binaries), out headers)) .ReturnsAsync((byte[] binary, Dictionary h) => binary.Reverse().ToArray()); - var contractConnection = new ContractConnection(serviceConnection.Object, defaultMessageEncryptor: globalEncryptor.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, defaultMessageEncryptor: globalEncryptor.Object); #endregion #region Act @@ -299,7 +299,7 @@ public async Task TestPublishAsyncWithNamedAndVersionedMessage() serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -339,7 +339,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedEncoder() serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -382,7 +382,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedServiceInjectableEncoder serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object, serviceProvider: services); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, serviceProvider: services); #endregion #region Act @@ -424,7 +424,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedEncryptor() serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -466,7 +466,7 @@ public async Task TestPublishAsyncWithMessageWithDefinedServiceInjectableEncrypt serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object, serviceProvider: services); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, serviceProvider: services); #endregion #region Act @@ -506,7 +506,7 @@ public async Task TestPublishAsyncWithNoMessageChannelThrowsError() serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -543,7 +543,7 @@ public async Task TestPublishAsyncWithToLargeAMessageThrowsError() serviceConnection.Setup(x => x.MaxMessageBodySize) .Returns(1); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -579,7 +579,7 @@ public async Task TestPublishAsyncWithTwoDifferentMessageTypes() serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) .ReturnsAsync(transmissionResult); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act diff --git a/AutomatedTesting/ContractConnectionTests/QueryTests.cs b/AutomatedTesting/ContractConnectionTests/QueryTests.cs index 71e0e47..0241bd6 100644 --- a/AutomatedTesting/ContractConnectionTests/QueryTests.cs +++ b/AutomatedTesting/ContractConnectionTests/QueryTests.cs @@ -44,7 +44,7 @@ public async Task TestQueryAsyncWithNoExtendedAspects() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -99,7 +99,7 @@ public async Task TestQueryAsyncWithDifferentChannelName() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -156,7 +156,7 @@ public async Task TestQueryAsyncWithMessageHeaders() var messageHeader = new MessageHeader([new("testing", "testing")]); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -214,7 +214,7 @@ public async Task TestQueryAsyncWithTimeout() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -271,7 +271,7 @@ public async Task TestQueryAsyncWithCompressionDueToMessageSize() serviceConnection.Setup(x => x.MaxMessageBodySize) .Returns(37); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -338,7 +338,7 @@ public async Task TestQueryAsyncWithGlobalEncoder() return result; }); - var contractConnection = new ContractConnection(serviceConnection.Object, defaultMessageEncoder: globalEncoder.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, defaultMessageEncoder: globalEncoder.Object); #endregion #region Act @@ -408,7 +408,7 @@ public async Task TestQueryAsyncWithGlobalEncryptor() return new MemoryStream(buff.Reverse().ToArray()); }); - var contractConnection = new ContractConnection(serviceConnection.Object, defaultMessageEncryptor: globalEncryptor.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, defaultMessageEncryptor: globalEncryptor.Object); #endregion #region Act @@ -465,7 +465,7 @@ public async Task TestQueryAsyncWithTimeoutAttribute() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -520,7 +520,7 @@ public async Task TestQueryAsyncWithNamedAndVersionedMessage() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -575,7 +575,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncoder() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -632,7 +632,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncoder() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object, serviceProvider: services); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, serviceProvider: services); #endregion #region Act @@ -689,7 +689,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncryptor() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -746,7 +746,7 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncryptor serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object, serviceProvider: services); + var contractConnection = ContractConnection.Instance(serviceConnection.Object, serviceProvider: services); #endregion #region Act @@ -801,7 +801,7 @@ public async Task TestQueryAsyncWithNoMessageChannelThrowsError() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -848,7 +848,7 @@ public async Task TestQueryAsyncWithToLargeAMessageThrowsError() serviceConnection.Setup(x => x.MaxMessageBodySize) .Returns(1); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -894,7 +894,7 @@ public async Task TestQueryAsyncWithTwoDifferentMessageTypes() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -965,7 +965,7 @@ public async Task TestQueryAsyncWithAttributeReturnType() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -1020,7 +1020,7 @@ public async Task TestQueryAsyncWithNoReturnType() serviceConnection.Setup(x => x.DefaultTimout) .Returns(defaultTimeout); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act diff --git a/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs index 711ca4e..31169a5 100644 --- a/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/QueryWithoutQueryResponseTests.cs @@ -32,24 +32,24 @@ public async Task TestQueryAsyncWithNoExtendedAspects() var mockSubscription = new Mock(); List messages = []; - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -77,7 +77,7 @@ public async Task TestQueryAsyncWithNoExtendedAspects() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion @@ -99,24 +99,24 @@ public async Task TestQueryAsyncWithAHeaderValue() var mockSubscription = new Mock(); List messages = []; - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -146,7 +146,7 @@ public async Task TestQueryAsyncWithAHeaderValue() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion @@ -166,31 +166,31 @@ public async Task TestQueryAsyncWithASecondMessageNotMatchingTheRequestBeingSent var mockSubscription = new Mock(); List messages = []; - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, new([ + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, new([ new KeyValuePair(QUERY_IDENTIFIER_HEADER,Guid.NewGuid().ToString()), new KeyValuePair(REPLY_ID,Guid.NewGuid().ToString()), new KeyValuePair(REPLY_CHANNEL_HEADER,responseChannel) ]), responseData); foreach (var action in messageActions) action(resp); - resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); + resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -218,7 +218,7 @@ public async Task TestQueryAsyncWithASecondMessageNotMatchingTheRequestBeingSent #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion @@ -238,24 +238,24 @@ public async Task TestQueryAsyncWithTheAttributeChannel() var mockSubscription = new Mock(); List messages = []; - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { messages.Add(message); - var resp = new RecievedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel!, message.Header, responseData); + var resp = new ReceivedServiceMessage(message.ID, "U-BasicResponseMessage-0.0.0.0", responseChannel!, message.Header, responseData); foreach (var action in messageActions) action(resp); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -283,7 +283,7 @@ public async Task TestQueryAsyncWithTheAttributeChannel() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion @@ -297,11 +297,11 @@ public async Task TestQueryAsyncFailingToCreateSubscription() var responseChannel = "BasicQuery.Response"; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(ValueTask.FromResult(null)); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -314,7 +314,7 @@ public async Task TestQueryAsyncFailingToCreateSubscription() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Never); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -326,11 +326,11 @@ public async Task TestQueryAsyncFailingWithNoResponseChannel() var testMessage = new BasicResponseMessage("testMessage"); var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(ValueTask.FromResult(null)); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -344,7 +344,7 @@ public async Task TestQueryAsyncFailingWithNoResponseChannel() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Never); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -360,11 +360,11 @@ public async Task TestQueryAsyncWithTimeoutException() var mockSubscription = new Mock(); List messages = []; - List> messageActions = []; + List> messageActions = []; List channels = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(mockSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) @@ -374,7 +374,7 @@ public async Task TestQueryAsyncWithTimeoutException() return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -387,7 +387,7 @@ public async Task TestQueryAsyncWithTimeoutException() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs index a21b9d9..7cd6dec 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs @@ -19,15 +19,15 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #region Arrange var serviceSubscription = new Mock(); - var recievedActions = new List>>(); + var receivedActions = new List>>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - Capture.In>>(recievedActions), + Capture.In>>(receivedActions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) @@ -35,13 +35,13 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); - var result = await recievedActions[0](rmessage); + var result = await receivedActions[0](rmessage); return Helper.ProduceQueryResult(result); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); var message = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); var responseMessage = new BasicResponseMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); @@ -49,7 +49,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { @@ -66,10 +66,10 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result); - Assert.AreEqual(1, recievedActions.Count); + Assert.AreEqual(1, receivedActions.Count); Assert.AreEqual(1, channels.Count); Assert.AreEqual(1, groups.Count); Assert.AreEqual(1, serviceMessages.Count); @@ -79,17 +79,17 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); - Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); + Assert.AreEqual(serviceMessages[0].ReceivedTimestamp, messages[0].ReceivedTimestamp); Assert.AreEqual(message, messages[0].Message); Assert.AreEqual(exception, exceptions[0]); Assert.IsFalse(result.IsError); Assert.IsNull(result.Error); Assert.AreEqual(result.Result, responseMessage); - System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].RecievedTimestamp).TotalMilliseconds}ms"); + System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].ReceivedTimestamp).TotalMilliseconds}ms"); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -105,13 +105,13 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -138,7 +138,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -153,12 +153,12 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -184,7 +184,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -196,12 +196,12 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -220,7 +220,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -230,12 +230,12 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() #region Arrange var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((IServiceSubscription?)null); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -252,7 +252,7 @@ public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -266,12 +266,12 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - It.IsAny>>(), + It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -289,7 +289,7 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -300,15 +300,15 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() #region Arrange var serviceSubscription = new Mock(); - var recievedActions = new List>>(); + var receivedActions = new List>>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - Capture.In>>(recievedActions), + Capture.In>>(receivedActions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) @@ -316,13 +316,13 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); - var result = await recievedActions[0](rmessage); + var result = await receivedActions[0](rmessage); return Helper.ProduceQueryResult(result); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); var message1 = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects1"); var message2 = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects2"); @@ -331,7 +331,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { @@ -352,10 +352,10 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(messages, 2, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount>(messages, 2, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result1); - Assert.AreEqual(1, recievedActions.Count); + Assert.AreEqual(1, receivedActions.Count); Assert.AreEqual(1, channels.Count); Assert.AreEqual(1, groups.Count); Assert.AreEqual(2, serviceMessages.Count); @@ -365,7 +365,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); - Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); + Assert.AreEqual(serviceMessages[0].ReceivedTimestamp, messages[0].ReceivedTimestamp); Assert.AreEqual(message1, messages[0].Message); Assert.AreEqual(message2, messages[1].Message); Assert.AreEqual(exception, exceptions[0]); @@ -375,11 +375,11 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() Assert.IsFalse(result2.IsError); Assert.IsNull(result2.Error); Assert.AreEqual(result2.Result, responseMessage); - System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].RecievedTimestamp).TotalMilliseconds}ms"); + System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].ReceivedTimestamp).TotalMilliseconds}ms"); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -390,15 +390,15 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() #region Arrange var serviceSubscription = new Mock(); - var recievedActions = new List>>(); + var receivedActions = new List>>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - Capture.In>>(recievedActions), + Capture.In>>(receivedActions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) @@ -406,20 +406,20 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); - var result = await recievedActions[0](rmessage); + var result = await receivedActions[0](rmessage); return Helper.ProduceQueryResult(result); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); var message = new BasicQueryMessage("TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction"); var exception = new NullReferenceException("TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction"); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeQueryResponseAsync((msg) => { @@ -435,7 +435,7 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() Assert.IsTrue(await Helper.WaitForCount(exceptions, 1, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result); - Assert.AreEqual(1, recievedActions.Count); + Assert.AreEqual(1, receivedActions.Count); Assert.AreEqual(1, channels.Count); Assert.AreEqual(1, groups.Count); Assert.AreEqual(1, serviceMessages.Count); @@ -449,7 +449,7 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -464,11 +464,11 @@ public async Task TestSubscribeQueryResponseAsyncEndAsync() var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -484,7 +484,7 @@ public async Task TestSubscribeQueryResponseAsyncEndAsync() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion @@ -500,11 +500,11 @@ public async Task TestSubscribeQueryResponseAsyncAsyncCleanup() var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -520,7 +520,7 @@ public async Task TestSubscribeQueryResponseAsyncAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.DisposeAsync(), Times.Once); #endregion @@ -533,11 +533,11 @@ public async Task TestSubscribeQueryResponseAsyncWithNonAsyncCleanup() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -553,7 +553,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNonAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion @@ -567,11 +567,11 @@ public async Task TestSubscribeQueryResponseAsyncSubscriptionsCleanup() var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -587,7 +587,7 @@ public async Task TestSubscribeQueryResponseAsyncSubscriptionsCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion @@ -610,11 +610,11 @@ public async Task TestSubscribeQueryResponseAsyncWithThrowsConversionError() globalConverter.Setup(x => x.EncodeAsync(It.IsAny())) .Returns(ValueTask.FromResult([])); - var recievedActions = new List>>(); + var receivedActions = new List>>(); var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( - Capture.In>>(recievedActions), + Capture.In>>(receivedActions), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -622,19 +622,19 @@ public async Task TestSubscribeQueryResponseAsyncWithThrowsConversionError() serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); - var result = await recievedActions[0](rmessage); + var rmessage = Helper.ProduceReceivedServiceMessage(message); + var result = await receivedActions[0](rmessage); return Helper.ProduceQueryResult(result); }); - var contractConnection = new ContractConnection(serviceConnection.Object,defaultMessageEncoder:globalConverter.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object,defaultMessageEncoder:globalConverter.Object); var responseMessage = new BasicResponseMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { @@ -655,7 +655,7 @@ await contractConnection.SubscribeQueryAsyncResponseAsync x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs index 3825899..982dcee 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseWithoutQueryResponseTest.cs @@ -22,10 +22,10 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() var channels = new List(); var groups = new List(); List messages = []; - List> messageActions = []; + List> messageActions = []; var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(messageActions), It.IsAny>(), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubObject); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) @@ -33,22 +33,22 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() { messages.Add(message); foreach (var action in messageActions) - action(new RecievedServiceMessage(message.ID,message.MessageTypeID,message.Channel,message.Header,message.Data)); + action(new ReceivedServiceMessage(message.ID,message.MessageTypeID,message.Channel,message.Header,message.Data)); return ValueTask.FromResult(new TransmissionResult(message.ID)); }); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); var message = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); var responseMessage = new BasicResponseMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); #endregion #region Act - var recievedMessages = new List>(); + var receivedMessages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => { - recievedMessages.Add(msg); + receivedMessages.Add(msg); return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); @@ -60,7 +60,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(recievedMessages, 1, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount>(receivedMessages, 1, TimeSpan.FromMinutes(1))); Assert.IsTrue(await Helper.WaitForCount(messages, 2, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result); @@ -71,10 +71,10 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, channels[0]); Assert.IsNull(groups[0]); Assert.IsNull(groups[1]); - Assert.AreEqual(recievedMessages[0].ID, messages[0].ID); - Assert.AreEqual(0,recievedMessages[0].Headers.Keys.Count()); + Assert.AreEqual(receivedMessages[0].ID, messages[0].ID); + Assert.AreEqual(0,receivedMessages[0].Headers.Keys.Count()); Assert.AreEqual(3, messages[0].Header.Keys.Count()); - Assert.AreEqual(message, recievedMessages[0].Message); + Assert.AreEqual(message, receivedMessages[0].Message); Assert.IsFalse(result.IsError); Assert.IsNull(result.Error); Assert.AreEqual(result.Result, responseMessage); @@ -82,7 +82,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() #region Verify serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); serviceSubscription.Verify(x => x.EndAsync(), Times.Exactly(2)); #endregion diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs index bcbc601..8c7aeb5 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeTests.cs @@ -20,19 +20,19 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - var actions = new List>(); + var actions = new List>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); @@ -42,11 +42,11 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); var exception = new NullReferenceException("TestSubscribeAsyncWithNoExtendedAspects"); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { @@ -63,7 +63,7 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result); Assert.AreEqual(1, actions.Count); @@ -76,14 +76,14 @@ public async Task TestSubscribeAsyncWithNoExtendedAspects() Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); - Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); + Assert.AreEqual(serviceMessages[0].ReceivedTimestamp, messages[0].ReceivedTimestamp); Assert.AreEqual(message, messages[0].Message); Assert.AreEqual(exception, exceptions[0]); - System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].RecievedTimestamp).TotalMilliseconds}ms"); + System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].ReceivedTimestamp).TotalMilliseconds}ms"); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -98,11 +98,11 @@ public async Task TestSubscribeAsyncWithSpecificChannel() var channels = new List(); var channelName = "TestSubscribeAsyncWithSpecificChannel"; - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), Capture.In(channels), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -119,7 +119,7 @@ public async Task TestSubscribeAsyncWithSpecificChannel() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -133,11 +133,11 @@ public async Task TestSubscribeAsyncWithSpecificGroup() var groups = new List(); var groupName = "TestSubscribeAsyncWithSpecificGroup"; - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -154,7 +154,7 @@ public async Task TestSubscribeAsyncWithSpecificGroup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -165,11 +165,11 @@ public async Task TestSubscribeAsyncNoMessageChannelThrowsError() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -183,7 +183,7 @@ public async Task TestSubscribeAsyncNoMessageChannelThrowsError() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); #endregion } @@ -193,11 +193,11 @@ public async Task TestSubscribeAsyncReturnFailedSubscription() #region Arrange var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((IServiceSubscription?)null); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -209,7 +209,7 @@ public async Task TestSubscribeAsyncReturnFailedSubscription() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -224,11 +224,11 @@ public async Task TestSubscriptionsEndAsync() var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -241,7 +241,7 @@ public async Task TestSubscriptionsEndAsync() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.EndAsync(), Times.Once); #endregion } @@ -256,11 +256,11 @@ public async Task TestSubscribeAsyncCleanup() var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -273,7 +273,7 @@ public async Task TestSubscribeAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.DisposeAsync(), Times.Once); #endregion } @@ -285,11 +285,11 @@ public async Task TestSubscribeAsyncWithNonAsyncCleanup() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -302,7 +302,7 @@ public async Task TestSubscribeAsyncWithNonAsyncCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion } @@ -315,11 +315,11 @@ public async Task TestSubscriptionsCleanup() var serviceConnection = new Mock(); - serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), + serviceConnection.Setup(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(serviceSubscription.As().Object); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act @@ -332,7 +332,7 @@ public async Task TestSubscriptionsCleanup() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceSubscription.Verify(x => x.Dispose(), Times.Once); #endregion } @@ -345,19 +345,19 @@ public async Task TestSubscribeAsyncWithSynchronousActions() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - var actions = new List>(); + var actions = new List>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); @@ -368,11 +368,11 @@ public async Task TestSubscribeAsyncWithSynchronousActions() var message2 = new BasicMessage("TestSubscribeAsyncWithSynchronousActions2"); var exception = new NullReferenceException("TestSubscribeAsyncWithSynchronousActions"); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { @@ -392,7 +392,7 @@ public async Task TestSubscribeAsyncWithSynchronousActions() #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(messages, 2, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount>(messages, 2, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result1); Assert.IsNotNull(result2); @@ -406,15 +406,15 @@ public async Task TestSubscribeAsyncWithSynchronousActions() Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); - Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); + Assert.AreEqual(serviceMessages[0].ReceivedTimestamp, messages[0].ReceivedTimestamp); Assert.AreEqual(message1, messages[0].Message); Assert.AreEqual(message2, messages[1].Message); Assert.AreEqual(exception, exceptions[0]); - System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].RecievedTimestamp).TotalMilliseconds}ms"); + System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].ReceivedTimestamp).TotalMilliseconds}ms"); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); #endregion } @@ -427,19 +427,19 @@ public async Task TestSubscribeAsyncWithErrorTriggeringInOurAction() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - var actions = new List>(); + var actions = new List>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); @@ -449,11 +449,11 @@ public async Task TestSubscribeAsyncWithErrorTriggeringInOurAction() var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); var exception = new NullReferenceException("TestSubscribeAsyncWithNoExtendedAspects"); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { @@ -481,7 +481,7 @@ public async Task TestSubscribeAsyncWithErrorTriggeringInOurAction() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -494,19 +494,19 @@ public async Task TestSubscribeAsyncWithCorruptMetaDataHeaderException() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - var actions = new List>(); + var actions = new List>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message,$"{message.MessageTypeID}:XXXX"); + var rmessage = Helper.ProduceReceivedServiceMessage(message,$"{message.MessageTypeID}:XXXX"); serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); @@ -515,11 +515,11 @@ public async Task TestSubscribeAsyncWithCorruptMetaDataHeaderException() var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { return ValueTask.CompletedTask; }, (error) => exceptions.Add(error)); var stopwatch = Stopwatch.StartNew(); @@ -545,7 +545,7 @@ public async Task TestSubscribeAsyncWithCorruptMetaDataHeaderException() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -558,19 +558,19 @@ public async Task TestSubscribeAsyncWithDisposal() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - var actions = new List>(); + var actions = new List>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); @@ -580,11 +580,11 @@ public async Task TestSubscribeAsyncWithDisposal() var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); var exception = new NullReferenceException("TestSubscribeAsyncWithNoExtendedAspects"); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { @@ -601,7 +601,7 @@ public async Task TestSubscribeAsyncWithDisposal() #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result); Assert.AreEqual(1, actions.Count); @@ -614,10 +614,10 @@ public async Task TestSubscribeAsyncWithDisposal() Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); - Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); + Assert.AreEqual(serviceMessages[0].ReceivedTimestamp, messages[0].ReceivedTimestamp); Assert.AreEqual(message, messages[0].Message); Assert.AreEqual(exception, exceptions[0]); - System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].RecievedTimestamp).TotalMilliseconds}ms"); + System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].ReceivedTimestamp).TotalMilliseconds}ms"); Exception? disposeError = null; try { @@ -630,7 +630,7 @@ public async Task TestSubscribeAsyncWithDisposal() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -643,19 +643,19 @@ public async Task TestSubscribeAsyncWithSingleConversion() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - var actions = new List>(); + var actions = new List>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); @@ -665,11 +665,11 @@ public async Task TestSubscribeAsyncWithSingleConversion() var message = new BasicMessage("TestSubscribeAsyncWithNoExtendedAspects"); var exception = new NullReferenceException("TestSubscribeAsyncWithNoExtendedAspects"); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { @@ -686,7 +686,7 @@ public async Task TestSubscribeAsyncWithSingleConversion() #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result); Assert.AreEqual(1, actions.Count); @@ -699,14 +699,14 @@ public async Task TestSubscribeAsyncWithSingleConversion() Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); - Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); + Assert.AreEqual(serviceMessages[0].ReceivedTimestamp, messages[0].ReceivedTimestamp); Assert.AreEqual(message.Name, messages[0].Message.TestName); Assert.AreEqual(exception, exceptions[0]); - System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].RecievedTimestamp).TotalMilliseconds}ms"); + System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].ReceivedTimestamp).TotalMilliseconds}ms"); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -719,19 +719,19 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - var actions = new List>(); + var actions = new List>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); @@ -741,11 +741,11 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() var message = new NoChannelMessage("TestSubscribeAsyncWithNoExtendedAspects"); var exception = new NullReferenceException("TestSubscribeAsyncWithNoExtendedAspects"); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { @@ -762,7 +762,7 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() #endregion #region Assert - Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); Assert.IsNotNull(subscription); Assert.IsNotNull(result); Assert.AreEqual(1, actions.Count); @@ -775,14 +775,14 @@ public async Task TestSubscribeAsyncWithMultipleStepConversion() Assert.IsNull(groups[0]); Assert.AreEqual(serviceMessages[0].ID, messages[0].ID); Assert.AreEqual(serviceMessages[0].Header.Keys.Count(), messages[0].Headers.Keys.Count()); - Assert.AreEqual(serviceMessages[0].RecievedTimestamp, messages[0].RecievedTimestamp); + Assert.AreEqual(serviceMessages[0].ReceivedTimestamp, messages[0].ReceivedTimestamp); Assert.AreEqual(message.TestName, messages[0].Message.TestName); Assert.AreEqual(exception, exceptions[0]); - System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].RecievedTimestamp).TotalMilliseconds}ms"); + System.Diagnostics.Trace.WriteLine($"Time to process message {messages[0].ProcessedTimestamp.Subtract(messages[0].ReceivedTimestamp).TotalMilliseconds}ms"); #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } @@ -795,19 +795,19 @@ public async Task TestSubscribeAsyncWithNoConversionPath() var serviceSubscription = new Mock(); var serviceConnection = new Mock(); - var actions = new List>(); + var actions = new List>(); var errorActions = new List>(); var channels = new List(); var groups = new List(); - var serviceMessages = new List(); + var serviceMessages = new List(); - serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), Capture.In>(errorActions), Capture.In(channels), Capture.In(groups), It.IsAny())) .ReturnsAsync(serviceSubscription.Object); serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns((ServiceMessage message, CancellationToken cancellationToken) => { - var rmessage = Helper.ProduceRecievedServiceMessage(message); + var rmessage = Helper.ProduceReceivedServiceMessage(message); serviceMessages.Add(rmessage); foreach (var act in actions) act(rmessage); @@ -817,11 +817,11 @@ public async Task TestSubscribeAsyncWithNoConversionPath() var message = new BasicQueryMessage("TestSubscribeAsyncWithNoExtendedAspects"); var exception = new NullReferenceException("TestSubscribeAsyncWithNoExtendedAspects"); - var contractConnection = new ContractConnection(serviceConnection.Object); + var contractConnection = ContractConnection.Instance(serviceConnection.Object); #endregion #region Act - var messages = new List>(); + var messages = new List>(); var exceptions = new List(); var subscription = await contractConnection.SubscribeAsync((msg) => { @@ -853,7 +853,7 @@ public async Task TestSubscribeAsyncWithNoConversionPath() #endregion #region Verify - serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.SubscribeAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); #endregion } diff --git a/AutomatedTesting/Helper.cs b/AutomatedTesting/Helper.cs index 6a60c08..9cd1efa 100644 --- a/AutomatedTesting/Helper.cs +++ b/AutomatedTesting/Helper.cs @@ -12,7 +12,7 @@ public static IServiceProvider ProduceServiceProvider(string serviceName) return services.BuildServiceProvider(); } - public static RecievedServiceMessage ProduceRecievedServiceMessage(ServiceMessage message, string? messageTypeID = null) + public static ReceivedServiceMessage ProduceReceivedServiceMessage(ServiceMessage message, string? messageTypeID = null) => new(message.ID, messageTypeID??message.MessageTypeID, message.Channel, message.Header, message.Data); public static ServiceQueryResult ProduceQueryResult(ServiceMessage message) diff --git a/Connectors/ActiveMQ/Connection.cs b/Connectors/ActiveMQ/Connection.cs index 1a2ea97..c887fbb 100644 --- a/Connectors/ActiveMQ/Connection.cs +++ b/Connectors/ActiveMQ/Connection.cs @@ -32,10 +32,7 @@ public Connection(Uri ConnectUri,string username,string password){ producer = session.CreateProducer(); } - /// - /// The maximum message body size allowed - /// - public uint? MaxMessageBodySize => 4*1024*1024; + uint? IMessageServiceConnection.MaxMessageBodySize => 4*1024*1024; private async ValueTask ProduceMessage(ServiceMessage message) { @@ -57,7 +54,7 @@ private static MessageHeader ExtractHeaders(IPrimitiveMap properties, out string return new(result); } - internal static RecievedServiceMessage ProduceMessage(string channel, IMessage message) + internal static ReceivedServiceMessage ProduceMessage(string channel, IMessage message) { var headers = ExtractHeaders(message.Properties, out var messageTypeID); return new( @@ -70,13 +67,7 @@ internal static RecievedServiceMessage ProduceMessage(string channel, IMessage m ); } - /// - /// Called to publish a message into the ActiveMQ server - /// - /// The service message being sent - /// A cancellation token - /// Transmition result identifying if it worked or not - public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) { try { @@ -89,34 +80,17 @@ public async ValueTask PublishAsync(ServiceMessage message, } } - /// - /// Called to create a subscription to the underlying ActiveMQ server - /// - /// Callback for when a message is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The group to bind the consumer to - /// A cancellation token - /// - public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { - var result = new SubscriptionBase((msg)=>messageRecieved(ProduceMessage(channel,msg)), errorRecieved,session, channel, group??Guid.NewGuid().ToString()); + var result = new SubscriptionBase((msg)=>messageReceived(ProduceMessage(channel,msg)), errorReceived,session, channel, group??Guid.NewGuid().ToString()); await result.StartAsync(); return result; } - /// - /// Called to close off the underlying ActiveMQ Connection - /// - /// - public async ValueTask CloseAsync() + async ValueTask IMessageServiceConnection.CloseAsync() => await connection.StopAsync(); - /// - /// Called to dispose of the object correctly and allow it to clean up it's resources - /// - /// A task required for disposal - public async ValueTask DisposeAsync() + async ValueTask IAsyncDisposable.DisposeAsync() { await connection.StopAsync().ConfigureAwait(true); @@ -138,10 +112,7 @@ private void Dispose(bool disposing) } } - /// - /// Called to dispose of the required resources - /// - public void Dispose() + void IDisposable.Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); diff --git a/Connectors/ActiveMQ/Readme.md b/Connectors/ActiveMQ/Readme.md index d21e1d9..3107ac1 100644 --- a/Connectors/ActiveMQ/Readme.md +++ b/Connectors/ActiveMQ/Readme.md @@ -5,12 +5,6 @@ - [Connection](#T-MQContract-ActiveMQ-Connection 'MQContract.ActiveMQ.Connection') - [#ctor(ConnectUri,username,password)](#M-MQContract-ActiveMQ-Connection-#ctor-System-Uri,System-String,System-String- 'MQContract.ActiveMQ.Connection.#ctor(System.Uri,System.String,System.String)') - - [MaxMessageBodySize](#P-MQContract-ActiveMQ-Connection-MaxMessageBodySize 'MQContract.ActiveMQ.Connection.MaxMessageBodySize') - - [CloseAsync()](#M-MQContract-ActiveMQ-Connection-CloseAsync 'MQContract.ActiveMQ.Connection.CloseAsync') - - [Dispose()](#M-MQContract-ActiveMQ-Connection-Dispose 'MQContract.ActiveMQ.Connection.Dispose') - - [DisposeAsync()](#M-MQContract-ActiveMQ-Connection-DisposeAsync 'MQContract.ActiveMQ.Connection.DisposeAsync') - - [PublishAsync(message,cancellationToken)](#M-MQContract-ActiveMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.ActiveMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-ActiveMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.ActiveMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') ## Connection `type` @@ -37,90 +31,3 @@ Default constructor for creating instance | ConnectUri | [System.Uri](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Uri 'System.Uri') | The connection url to use | | username | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The username to use | | password | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The password to use | - - -### MaxMessageBodySize `property` - -##### Summary - -The maximum message body size allowed - - -### CloseAsync() `method` - -##### Summary - -Called to close off the underlying ActiveMQ Connection - -##### Returns - - - -##### Parameters - -This method has no parameters. - - -### Dispose() `method` - -##### Summary - -Called to dispose of the required resources - -##### Parameters - -This method has no parameters. - - -### DisposeAsync() `method` - -##### Summary - -Called to dispose of the object correctly and allow it to clean up it's resources - -##### Returns - -A task required for disposal - -##### Parameters - -This method has no parameters. - - -### PublishAsync(message,cancellationToken) `method` - -##### Summary - -Called to publish a message into the ActiveMQ server - -##### Returns - -Transmition result identifying if it worked or not - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription to the underlying ActiveMQ server - -##### Returns - - - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind the consumer to | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs b/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs index 90d09f3..b9b5d33 100644 --- a/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs +++ b/Connectors/ActiveMQ/Subscriptions/SubscriptionBase.cs @@ -4,7 +4,7 @@ namespace MQContract.ActiveMQ.Subscriptions { - internal class SubscriptionBase(Action messageRecieved,Action errorRecieved,ISession session, string channel,string group) : IServiceSubscription + internal class SubscriptionBase(Action messageReceived,Action errorReceived,ISession session, string channel,string group) : IServiceSubscription { private bool disposedValue; private IMessageConsumer? consumer; @@ -22,11 +22,11 @@ internal async ValueTask StartAsync() { var msg = await consumer.ReceiveAsync(); if (msg!=null) - messageRecieved(msg); + messageReceived(msg); } catch (Exception ex) { - errorRecieved(ex); + errorReceived(ex); } } }); diff --git a/Connectors/Kafka/Connection.cs b/Connectors/Kafka/Connection.cs index 4854dc5..c379c4f 100644 --- a/Connectors/Kafka/Connection.cs +++ b/Connectors/Kafka/Connection.cs @@ -17,10 +17,7 @@ public sealed class Connection(ClientConfig clientConfig) : IMessageServiceConne private readonly IProducer producer = new ProducerBuilder(clientConfig).Build(); private readonly ClientConfig clientConfig = clientConfig; - /// - /// The maximum message body size allowed - /// - public uint? MaxMessageBodySize => (uint)Math.Abs(clientConfig.MessageMaxBytes??(1024*1024)); + uint? IMessageServiceConnection.MaxMessageBodySize => (uint)Math.Abs(clientConfig.MessageMaxBytes??(1024*1024)); internal static byte[] EncodeHeaderValue(string value) => UTF8Encoding.UTF8.GetBytes(value); @@ -50,13 +47,7 @@ internal static MessageHeader ExtractHeaders(Headers header,out string? messageT return ExtractHeaders(header); } - /// - /// Called to publish a message into the Kafka server - /// - /// The service message being sent - /// A cancellation token - /// Transmition result identifying if it worked or not - public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) { try { @@ -74,34 +65,21 @@ public async ValueTask PublishAsync(ServiceMessage message, } } - /// - /// Called to create a subscription to the underlying Kafka server - /// - /// Callback for when a message is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The name of the group to bind the consumer to - /// A cancellation token - /// - public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { var subscription = new PublishSubscription( new ConsumerBuilder(new ConsumerConfig(clientConfig) { GroupId=(!string.IsNullOrWhiteSpace(group) ? group : Guid.NewGuid().ToString()) }).Build(), - messageRecieved, - errorRecieved, + messageReceived, + errorReceived, channel); await subscription.Run(); return subscription; } - /// - /// Called to close off the underlying Kafka Connection - /// - /// - public ValueTask CloseAsync() + ValueTask IMessageServiceConnection.CloseAsync() { producer.Dispose(); return ValueTask.CompletedTask; diff --git a/Connectors/Kafka/Readme.md b/Connectors/Kafka/Readme.md index 86a5ead..2d1a8ce 100644 --- a/Connectors/Kafka/Readme.md +++ b/Connectors/Kafka/Readme.md @@ -5,10 +5,6 @@ - [Connection](#T-MQContract-Kafka-Connection 'MQContract.Kafka.Connection') - [#ctor(clientConfig)](#M-MQContract-Kafka-Connection-#ctor-Confluent-Kafka-ClientConfig- 'MQContract.Kafka.Connection.#ctor(Confluent.Kafka.ClientConfig)') - - [MaxMessageBodySize](#P-MQContract-Kafka-Connection-MaxMessageBodySize 'MQContract.Kafka.Connection.MaxMessageBodySize') - - [CloseAsync()](#M-MQContract-Kafka-Connection-CloseAsync 'MQContract.Kafka.Connection.CloseAsync') - - [PublishAsync(message,cancellationToken)](#M-MQContract-Kafka-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Kafka-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Kafka.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') ## Connection `type` @@ -39,64 +35,3 @@ This is the MessageServiceConnection implementation for using Kafka | Name | Type | Description | | ---- | ---- | ----------- | | clientConfig | [Confluent.Kafka.ClientConfig](#T-Confluent-Kafka-ClientConfig 'Confluent.Kafka.ClientConfig') | The Kafka Client Configuration to provide | - - -### MaxMessageBodySize `property` - -##### Summary - -The maximum message body size allowed - - -### CloseAsync() `method` - -##### Summary - -Called to close off the underlying Kafka Connection - -##### Returns - - - -##### Parameters - -This method has no parameters. - - -### PublishAsync(message,cancellationToken) `method` - -##### Summary - -Called to publish a message into the Kafka server - -##### Returns - -Transmition result identifying if it worked or not - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription to the underlying Kafka server - -##### Returns - - - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the group to bind the consumer to | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/Kafka/Subscriptions/PublishSubscription.cs b/Connectors/Kafka/Subscriptions/PublishSubscription.cs index 7d7c25b..16e1dbe 100644 --- a/Connectors/Kafka/Subscriptions/PublishSubscription.cs +++ b/Connectors/Kafka/Subscriptions/PublishSubscription.cs @@ -2,7 +2,7 @@ namespace MQContract.Kafka.Subscriptions { - internal class PublishSubscription(Confluent.Kafka.IConsumer consumer, Action messageRecieved, Action errorRecieved, string channel) + internal class PublishSubscription(Confluent.Kafka.IConsumer consumer, Action messageReceived, Action errorReceived, string channel) : SubscriptionBase(consumer,channel) { protected override ValueTask RunAction() @@ -13,7 +13,7 @@ protected override ValueTask RunAction() { var msg = Consumer.Consume(cancellationToken:cancelToken.Token); var headers = Connection.ExtractHeaders(msg.Message.Headers, out var messageTypeID); - messageRecieved(new RecievedServiceMessage( + messageReceived(new ReceivedServiceMessage( msg.Message.Key??string.Empty, messageTypeID??string.Empty, Channel, @@ -24,7 +24,7 @@ protected override ValueTask RunAction() catch (OperationCanceledException) { } catch (Exception ex) { - errorRecieved(ex); + errorReceived(ex); } finally { } } diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 753e9e2..35c3d84 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -123,16 +123,9 @@ public Connection RegisterStoredChannel(string channelName, MessageReadStyle rea return this; } - /// - /// The maximum message body size allowed - /// - public uint? MaxMessageBodySize => (uint)Math.Abs(connectionOptions.MaxBodySize); + uint? IMessageServiceConnection.MaxMessageBodySize => (uint)Math.Abs(connectionOptions.MaxBodySize); - /// - /// The default timeout to use for RPC calls when not specified by the class or in the call. - /// DEFAULT:30 seconds if not specified inside the connection options - /// - public TimeSpan DefaultTimout => TimeSpan.FromMilliseconds(connectionOptions.DefaultRPCTimeout??30000); + TimeSpan IQueryableMessageServiceConnection.DefaultTimout => TimeSpan.FromMilliseconds(connectionOptions.DefaultRPCTimeout??30000); private KubeClient EstablishConnection() { @@ -142,12 +135,7 @@ private KubeClient EstablishConnection() return result; } - /// - /// Called to ping the KubeMQ service - /// - /// The Ping result, specically a PingResponse instance - /// Thrown when the Ping fails - public ValueTask PingAsync() + ValueTask IPingableMessageServiceConnection.PingAsync() { var watch = new Stopwatch(); watch.Start(); @@ -167,13 +155,7 @@ internal static MapField ConvertMessageHeader(MessageHeader head internal static MessageHeader ConvertMessageHeader(MapField header) => new(header.AsEnumerable()); - /// - /// Called to publish a message into the KubeMQ server - /// - /// The service message being sent - /// A cancellation token - /// Transmition result identifying if it worked or not - public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) { try { var res = await client.SendEventAsync(new Event() @@ -200,16 +182,7 @@ public async ValueTask PublishAsync(ServiceMessage message, } } - /// - /// Called to publish a query into the KubeMQ server - /// - /// The service message being sent - /// The timeout supplied for the query to response - /// A cancellation token - /// The resulting response - /// Thrown when the response from KubeMQ is null - /// Thrown when there is an RPC exception from the KubeMQ server - public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) + async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) { #pragma warning disable S2139 // Exceptions should be either logged or rethrown but not both try @@ -246,22 +219,13 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti #pragma warning restore S2139 // Exceptions should be either logged or rethrown but not both } - /// - /// Called to create a subscription to the underlying KubeMQ server - /// - /// Callback for when a message is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// - /// A subscription instance - /// A cancellation token - public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { var sub = new PubSubscription( connectionOptions, EstablishConnection(), - messageRecieved, - errorRecieved, + messageReceived, + errorReceived, channel, group??Guid.NewGuid().ToString(), storedChannelOptions.Find(sco=>Equals(sco.ChannelName,channel)), @@ -271,22 +235,13 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti return ValueTask.FromResult(sub); } - /// - /// Called to create a subscription for queries to the underlying KubeMQ server - /// - /// Callback for when a query is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The group to bind the consumer to - /// A cancellation token - /// A subscription instance - public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + ValueTask IQueryableMessageServiceConnection.SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { var sub = new QuerySubscription( connectionOptions, EstablishConnection(), - messageRecieved, - errorRecieved, + messageReceived, + errorReceived, channel, group??Guid.NewGuid().ToString(), cancellationToken @@ -294,18 +249,11 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti sub.Run(); return ValueTask.FromResult(sub); } - /// - /// Called to close the underlying KubeMQ Client connection - /// - /// - public ValueTask CloseAsync() + + ValueTask IMessageServiceConnection.CloseAsync() => client.DisposeAsync(); - /// - /// Called to dispose of the object correctly and allow it to clean up it's resources - /// - /// A task required for disposal - public async ValueTask DisposeAsync() + async ValueTask IAsyncDisposable.DisposeAsync() { await client.DisposeAsync(); @@ -322,10 +270,8 @@ private void Dispose(bool disposing) disposedValue=true; } } - /// - /// Called to dispose of the underlying resources - /// - public void Dispose() + + void IDisposable.Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); diff --git a/Connectors/KubeMQ/Exceptions.cs b/Connectors/KubeMQ/Exceptions.cs index 41060b3..08ea47d 100644 --- a/Connectors/KubeMQ/Exceptions.cs +++ b/Connectors/KubeMQ/Exceptions.cs @@ -33,7 +33,7 @@ internal MessageResponseTransmissionException(Guid subscriptionID,string message internal class NullResponseException : NullReferenceException { internal NullResponseException() - : base("null response recieved from KubeMQ server") { } + : base("null response received from KubeMQ server") { } } internal class RPCErrorException : Exception diff --git a/Connectors/KubeMQ/Readme.md b/Connectors/KubeMQ/Readme.md index d75293b..b3ea5b5 100644 --- a/Connectors/KubeMQ/Readme.md +++ b/Connectors/KubeMQ/Readme.md @@ -16,19 +16,9 @@ - [ClientDisposedException](#T-MQContract-KubeMQ-ClientDisposedException 'MQContract.KubeMQ.ClientDisposedException') - [Connection](#T-MQContract-KubeMQ-Connection 'MQContract.KubeMQ.Connection') - [#ctor(options)](#M-MQContract-KubeMQ-Connection-#ctor-MQContract-KubeMQ-ConnectionOptions- 'MQContract.KubeMQ.Connection.#ctor(MQContract.KubeMQ.ConnectionOptions)') - - [DefaultTimout](#P-MQContract-KubeMQ-Connection-DefaultTimout 'MQContract.KubeMQ.Connection.DefaultTimout') - - [MaxMessageBodySize](#P-MQContract-KubeMQ-Connection-MaxMessageBodySize 'MQContract.KubeMQ.Connection.MaxMessageBodySize') - - [CloseAsync()](#M-MQContract-KubeMQ-Connection-CloseAsync 'MQContract.KubeMQ.Connection.CloseAsync') - - [Dispose()](#M-MQContract-KubeMQ-Connection-Dispose 'MQContract.KubeMQ.Connection.Dispose') - - [DisposeAsync()](#M-MQContract-KubeMQ-Connection-DisposeAsync 'MQContract.KubeMQ.Connection.DisposeAsync') - - [PingAsync()](#M-MQContract-KubeMQ-Connection-PingAsync 'MQContract.KubeMQ.Connection.PingAsync') - - [PublishAsync(message,cancellationToken)](#M-MQContract-KubeMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') - - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-KubeMQ-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') - [RegisterStoredChannel(channelName)](#M-MQContract-KubeMQ-Connection-RegisterStoredChannel-System-String- 'MQContract.KubeMQ.Connection.RegisterStoredChannel(System.String)') - [RegisterStoredChannel(channelName,readStyle)](#M-MQContract-KubeMQ-Connection-RegisterStoredChannel-System-String,MQContract-KubeMQ-Connection-MessageReadStyle- 'MQContract.KubeMQ.Connection.RegisterStoredChannel(System.String,MQContract.KubeMQ.Connection.MessageReadStyle)') - [RegisterStoredChannel(channelName,readStyle,readOffset)](#M-MQContract-KubeMQ-Connection-RegisterStoredChannel-System-String,MQContract-KubeMQ-Connection-MessageReadStyle,System-Int64- 'MQContract.KubeMQ.Connection.RegisterStoredChannel(System.String,MQContract.KubeMQ.Connection.MessageReadStyle,System.Int64)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-KubeMQ-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.KubeMQ.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - [ConnectionOptions](#T-MQContract-KubeMQ-ConnectionOptions 'MQContract.KubeMQ.ConnectionOptions') - [Address](#P-MQContract-KubeMQ-ConnectionOptions-Address 'MQContract.KubeMQ.ConnectionOptions.Address') - [AuthToken](#P-MQContract-KubeMQ-ConnectionOptions-AuthToken 'MQContract.KubeMQ.ConnectionOptions.AuthToken') @@ -320,127 +310,6 @@ Primary constructor to create an instance using the supplied configuration optio | ---- | ----------- | | [MQContract.KubeMQ.UnableToConnectException](#T-MQContract-KubeMQ-UnableToConnectException 'MQContract.KubeMQ.UnableToConnectException') | Thrown when the initial attempt to connect fails | - -### DefaultTimout `property` - -##### Summary - -The default timeout to use for RPC calls when not specified by the class or in the call. -DEFAULT:30 seconds if not specified inside the connection options - - -### MaxMessageBodySize `property` - -##### Summary - -The maximum message body size allowed - - -### CloseAsync() `method` - -##### Summary - -Called to close the underlying KubeMQ Client connection - -##### Returns - - - -##### Parameters - -This method has no parameters. - - -### Dispose() `method` - -##### Summary - -Called to dispose of the underlying resources - -##### Parameters - -This method has no parameters. - - -### DisposeAsync() `method` - -##### Summary - -Called to dispose of the object correctly and allow it to clean up it's resources - -##### Returns - -A task required for disposal - -##### Parameters - -This method has no parameters. - - -### PingAsync() `method` - -##### Summary - -Called to ping the KubeMQ service - -##### Returns - -The Ping result, specically a PingResponse instance - -##### Parameters - -This method has no parameters. - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.KubeMQ.UnableToConnectException](#T-MQContract-KubeMQ-UnableToConnectException 'MQContract.KubeMQ.UnableToConnectException') | Thrown when the Ping fails | - - -### PublishAsync(message,cancellationToken) `method` - -##### Summary - -Called to publish a message into the KubeMQ server - -##### Returns - -Transmition result identifying if it worked or not - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### QueryAsync(message,timeout,cancellationToken) `method` - -##### Summary - -Called to publish a query into the KubeMQ server - -##### Returns - -The resulting response - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.KubeMQ.NullResponseException](#T-MQContract-KubeMQ-NullResponseException 'MQContract.KubeMQ.NullResponseException') | Thrown when the response from KubeMQ is null | -| [MQContract.KubeMQ.RPCErrorException](#T-MQContract-KubeMQ-RPCErrorException 'MQContract.KubeMQ.RPCErrorException') | Thrown when there is an RPC exception from the KubeMQ server | - ### RegisterStoredChannel(channelName) `method` @@ -495,48 +364,6 @@ The current connection to allow for chaining | readStyle | [MQContract.KubeMQ.Connection.MessageReadStyle](#T-MQContract-KubeMQ-Connection-MessageReadStyle 'MQContract.KubeMQ.Connection.MessageReadStyle') | Set the message reading style when subscribing | | readOffset | [System.Int64](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Int64 'System.Int64') | Set the readoffset to use for the given reading style | - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription to the underlying KubeMQ server - -##### Returns - -A subscription instance - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription for queries to the underlying KubeMQ server - -##### Returns - -A subscription instance - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind the consumer to | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - ## ConnectionOptions `type` diff --git a/Connectors/KubeMQ/Subscriptions/PubSubscription.cs b/Connectors/KubeMQ/Subscriptions/PubSubscription.cs index 66cb7ed..ebeb248 100644 --- a/Connectors/KubeMQ/Subscriptions/PubSubscription.cs +++ b/Connectors/KubeMQ/Subscriptions/PubSubscription.cs @@ -9,9 +9,9 @@ namespace MQContract.KubeMQ.Subscriptions { internal class PubSubscription(ConnectionOptions options, KubeClient client, - Action messageRecieved, Action errorRecieved, string channel, string group, + Action messageReceived, Action errorReceived, string channel, string group, StoredChannelOptions? storageOptions, CancellationToken cancellationToken) : - SubscriptionBase(options.Logger,options.ReconnectInterval,client,errorRecieved,cancellationToken) + SubscriptionBase(options.Logger,options.ReconnectInterval,client,errorReceived,cancellationToken) { private readonly KubeClient Client = client; @@ -31,9 +31,9 @@ protected override AsyncServerStreamingCall EstablishCall() cancelToken.Token); } - protected override ValueTask MessageRecieved(EventReceive message) + protected override ValueTask MessageReceived(EventReceive message) { - messageRecieved(new(message.EventID,message.Metadata,message.Channel,Connection.ConvertMessageHeader(message.Tags),message.Body.ToArray())); + messageReceived(new(message.EventID,message.Metadata,message.Channel,Connection.ConvertMessageHeader(message.Tags),message.Body.ToArray())); return ValueTask.CompletedTask; } } diff --git a/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs b/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs index 77adfb8..e098d33 100644 --- a/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs +++ b/Connectors/KubeMQ/Subscriptions/QuerySubscription.cs @@ -7,9 +7,9 @@ namespace MQContract.KubeMQ.Subscriptions { internal class QuerySubscription(ConnectionOptions options, KubeClient client, - Func> messageRecieved, Action errorRecieved, + Func> messageReceived, Action errorReceived, string channel, string group, CancellationToken cancellationToken) - : SubscriptionBase(options.Logger,options.ReconnectInterval,client,errorRecieved,cancellationToken) + : SubscriptionBase(options.Logger,options.ReconnectInterval,client,errorReceived,cancellationToken) { private readonly KubeClient Client = client; protected override AsyncServerStreamingCall EstablishCall() @@ -26,12 +26,12 @@ protected override AsyncServerStreamingCall EstablishCall() cancelToken.Token); } - protected override async ValueTask MessageRecieved(Request message) + protected override async ValueTask MessageReceived(Request message) { ServiceMessage? result; try { - result = await messageRecieved(new(message.RequestID,message.Metadata,message.Channel,Connection.ConvertMessageHeader(message.Tags),message.Body.ToArray())); + result = await messageReceived(new(message.RequestID,message.Metadata,message.Channel,Connection.ConvertMessageHeader(message.Tags),message.Body.ToArray())); } catch (Exception ex) { diff --git a/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs b/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs index 9b580f9..180a3ad 100644 --- a/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs +++ b/Connectors/KubeMQ/Subscriptions/SubscriptionBase.cs @@ -6,7 +6,7 @@ namespace MQContract.KubeMQ.Subscriptions { internal abstract class SubscriptionBase(ILogger? logger,int reconnectInterval, KubeClient client, - Action errorRecieved, CancellationToken cancellationToken) : IServiceSubscription + Action errorReceived, CancellationToken cancellationToken) : IServiceSubscription where T : class { private bool disposedValue; @@ -15,7 +15,7 @@ internal abstract class SubscriptionBase(ILogger? logger,int reconnectInterva protected readonly CancellationTokenSource cancelToken = new(); protected abstract AsyncServerStreamingCall EstablishCall(); - protected abstract ValueTask MessageRecieved(T message); + protected abstract ValueTask MessageReceived(T message); public void Run() { @@ -40,7 +40,7 @@ public void Run() await foreach (var resp in call.ResponseStream.ReadAllAsync(cancelToken.Token)) { if (active) - await MessageRecieved(resp); + await MessageReceived(resp); else break; } @@ -60,19 +60,19 @@ public void Run() case StatusCode.Unavailable: case StatusCode.DataLoss: case StatusCode.DeadlineExceeded: - logger?.LogTrace("RPC Error recieved on subscription {SubscriptionID}, retrying connection after delay {ReconnectDelay}ms. StatusCode:{StatusCode},Message:{ErrorMessage}", ID, reconnectInterval, rpcx.StatusCode, rpcx.Message); + logger?.LogTrace("RPC Error received on subscription {SubscriptionID}, retrying connection after delay {ReconnectDelay}ms. StatusCode:{StatusCode},Message:{ErrorMessage}", ID, reconnectInterval, rpcx.StatusCode, rpcx.Message); break; default: - logger?.LogError(rpcx, "RPC Error recieved on subscription {SubscriptionID}. StatusCode:{StatusCode},Message:{ErrorMessage}", ID, rpcx.StatusCode, rpcx.Message); - errorRecieved(rpcx); + logger?.LogError(rpcx, "RPC Error received on subscription {SubscriptionID}. StatusCode:{StatusCode},Message:{ErrorMessage}", ID, rpcx.StatusCode, rpcx.Message); + errorReceived(rpcx); break; } } } catch (Exception e) { - logger?.LogError(e, "Error recieved on subscription {SubscriptionID}. Message:{ErrorMessage}", ID, e.Message); - errorRecieved(e); + logger?.LogError(e, "Error received on subscription {SubscriptionID}. Message:{ErrorMessage}", ID, e.Message); + errorReceived(e); } if (active && !cancellationToken.IsCancellationRequested) await Task.Delay(reconnectInterval); diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index d24b77c..9c50ccf 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -87,11 +87,7 @@ public Connection RegisterConsumerConfig(string channelName, ConsumerConfig cons return this; } - /// - /// Called to ping the NATS.io service - /// - /// The Ping Result including service information - public async ValueTask PingAsync() + async ValueTask IPingableMessageServiceConnection.PingAsync() => new PingResult(natsConnection.ServerInfo?.Host??string.Empty, natsConnection.ServerInfo?.Version??string.Empty, await natsConnection.PingAsync() @@ -135,13 +131,7 @@ internal static NatsHeaders ProduceQueryError(Exception exception,string message ])); } - /// - /// Called to publish a message into the NATS io server - /// - /// The service message being sent - /// A cancellation token - /// Transmition result identifying if it worked or not - public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) { try { @@ -159,15 +149,7 @@ await natsConnection.PublishAsync( } } - /// - /// Called to publish a query into the NATS io server - /// - /// The service message being sent - /// The timeout supplied for the query to response - /// A cancellation token - /// The resulting response - /// Thrown when an error comes from the responding service - public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) + async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) { var result = await natsConnection.RequestAsync( message.Channel, @@ -187,16 +169,7 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti ); } - /// - /// Called to create a subscription to the underlying nats server - /// - /// Callback for when a message is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The name of the group to bind the consumer to - /// A cancellation token - /// A subscription instance - public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { SubscriptionBase subscription; var isStream = false; @@ -219,7 +192,7 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti ||Equals(group, scc.Configuration.DurableName) )); var consumer = await natsJSContext.CreateOrUpdateConsumerAsync(channel, config?.Configuration??new ConsumerConfig(group??Guid.NewGuid().ToString()) { AckPolicy = ConsumerConfigAckPolicy.Explicit }, cancellationToken); - subscription = new StreamSubscription(consumer, messageRecieved, errorRecieved); + subscription = new StreamSubscription(consumer, messageReceived, errorReceived); } else subscription = new PublishSubscription( @@ -228,24 +201,14 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti queueGroup: group, cancellationToken: cancellationToken ), - messageRecieved, - errorRecieved + messageReceived, + errorReceived ); subscription.Run(); return subscription; } - /// - /// Called to create a subscription for queries to the underlying NATS server - /// - /// Callback for when a query is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The group to bind to - /// A cancellation token - /// A subscription instance - - public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + ValueTask IQueryableMessageServiceConnection.SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { var sub = new QuerySubscription( natsConnection.SubscribeAsync( @@ -253,24 +216,17 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti queueGroup: group, cancellationToken: cancellationToken ), - messageRecieved, - errorRecieved + messageReceived, + errorReceived ); sub.Run(); return ValueTask.FromResult(sub); } - /// - /// Called to close off the contract connection and close it's underlying service connection - /// - /// A task for the closure of the connection - public ValueTask CloseAsync() + + ValueTask IMessageServiceConnection.CloseAsync() => natsConnection.DisposeAsync(); - /// - /// Called to dispose of the object correctly and allow it to clean up it's resources - /// - /// A task required for disposal - public async ValueTask DisposeAsync() + async ValueTask IAsyncDisposable.DisposeAsync() { await natsConnection.DisposeAsync().ConfigureAwait(true); @@ -287,10 +243,8 @@ private void Dispose(bool disposing) disposedValue=true; } } - /// - /// Called to dispose of the object correctly and allow it to clean up it's resources - /// - public void Dispose() + + void IDisposable.Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); diff --git a/Connectors/NATS/Readme.md b/Connectors/NATS/Readme.md index 872f217..aa1f9cc 100644 --- a/Connectors/NATS/Readme.md +++ b/Connectors/NATS/Readme.md @@ -7,16 +7,8 @@ - [#ctor(options)](#M-MQContract-NATS-Connection-#ctor-NATS-Client-Core-NatsOpts- 'MQContract.NATS.Connection.#ctor(NATS.Client.Core.NatsOpts)') - [DefaultTimout](#P-MQContract-NATS-Connection-DefaultTimout 'MQContract.NATS.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-NATS-Connection-MaxMessageBodySize 'MQContract.NATS.Connection.MaxMessageBodySize') - - [CloseAsync()](#M-MQContract-NATS-Connection-CloseAsync 'MQContract.NATS.Connection.CloseAsync') - [CreateStreamAsync(streamConfig,cancellationToken)](#M-MQContract-NATS-Connection-CreateStreamAsync-NATS-Client-JetStream-Models-StreamConfig,System-Threading-CancellationToken- 'MQContract.NATS.Connection.CreateStreamAsync(NATS.Client.JetStream.Models.StreamConfig,System.Threading.CancellationToken)') - - [Dispose()](#M-MQContract-NATS-Connection-Dispose 'MQContract.NATS.Connection.Dispose') - - [DisposeAsync()](#M-MQContract-NATS-Connection-DisposeAsync 'MQContract.NATS.Connection.DisposeAsync') - - [PingAsync()](#M-MQContract-NATS-Connection-PingAsync 'MQContract.NATS.Connection.PingAsync') - - [PublishAsync(message,cancellationToken)](#M-MQContract-NATS-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.NATS.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') - - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-NATS-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.NATS.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') - [RegisterConsumerConfig(channelName,consumerConfig)](#M-MQContract-NATS-Connection-RegisterConsumerConfig-System-String,NATS-Client-JetStream-Models-ConsumerConfig- 'MQContract.NATS.Connection.RegisterConsumerConfig(System.String,NATS.Client.JetStream.Models.ConsumerConfig)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-NATS-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.NATS.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - [UnableToConnectException](#T-MQContract-NATS-UnableToConnectException 'MQContract.NATS.UnableToConnectException') @@ -59,21 +51,6 @@ DEFAULT: 30 seconds The maximum message body size allowed. DEFAULT: 1MB - -### CloseAsync() `method` - -##### Summary - -Called to close off the contract connection and close it's underlying service connection - -##### Returns - -A task for the closure of the connection - -##### Parameters - -This method has no parameters. - ### CreateStreamAsync(streamConfig,cancellationToken) `method` @@ -92,90 +69,6 @@ The stream creation result | streamConfig | [NATS.Client.JetStream.Models.StreamConfig](#T-NATS-Client-JetStream-Models-StreamConfig 'NATS.Client.JetStream.Models.StreamConfig') | The configuration settings for the stream | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -### Dispose() `method` - -##### Summary - -Called to dispose of the object correctly and allow it to clean up it's resources - -##### Parameters - -This method has no parameters. - - -### DisposeAsync() `method` - -##### Summary - -Called to dispose of the object correctly and allow it to clean up it's resources - -##### Returns - -A task required for disposal - -##### Parameters - -This method has no parameters. - - -### PingAsync() `method` - -##### Summary - -Called to ping the NATS.io service - -##### Returns - -The Ping Result including service information - -##### Parameters - -This method has no parameters. - - -### PublishAsync(message,cancellationToken) `method` - -##### Summary - -Called to publish a message into the NATS io server - -##### Returns - -Transmition result identifying if it worked or not - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### QueryAsync(message,timeout,cancellationToken) `method` - -##### Summary - -Called to publish a query into the NATS io server - -##### Returns - -The resulting response - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.NATS.QueryAsyncReponseException](#T-MQContract-NATS-QueryAsyncReponseException 'MQContract.NATS.QueryAsyncReponseException') | Thrown when an error comes from the responding service | - ### RegisterConsumerConfig(channelName,consumerConfig) `method` @@ -195,48 +88,6 @@ The underlying connection to allow for chaining | channelName | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The underlying stream name that this configuration applies to | | consumerConfig | [NATS.Client.JetStream.Models.ConsumerConfig](#T-NATS-Client-JetStream-Models-ConsumerConfig 'NATS.Client.JetStream.Models.ConsumerConfig') | The consumer configuration to use for that stream | - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription to the underlying nats server - -##### Returns - -A subscription instance - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the group to bind the consumer to | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription for queries to the underlying NATS server - -##### Returns - -A subscription instance - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind to | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - ## UnableToConnectException `type` diff --git a/Connectors/NATS/Subscriptions/PublishSubscription.cs b/Connectors/NATS/Subscriptions/PublishSubscription.cs index 6ea6cfb..12b595d 100644 --- a/Connectors/NATS/Subscriptions/PublishSubscription.cs +++ b/Connectors/NATS/Subscriptions/PublishSubscription.cs @@ -4,7 +4,7 @@ namespace MQContract.NATS.Subscriptions { internal class PublishSubscription(IAsyncEnumerable> asyncEnumerable, - Action messageRecieved, Action errorRecieved) + Action messageReceived, Action errorReceived) : SubscriptionBase() { protected override async Task RunAction() @@ -13,11 +13,11 @@ protected override async Task RunAction() { try { - messageRecieved(ExtractMessage(msg)); + messageReceived(ExtractMessage(msg)); } catch (Exception ex) { - errorRecieved(ex); + errorReceived(ex); } } } diff --git a/Connectors/NATS/Subscriptions/QuerySubscription.cs b/Connectors/NATS/Subscriptions/QuerySubscription.cs index 520fd82..de27852 100644 --- a/Connectors/NATS/Subscriptions/QuerySubscription.cs +++ b/Connectors/NATS/Subscriptions/QuerySubscription.cs @@ -4,17 +4,17 @@ namespace MQContract.NATS.Subscriptions { internal class QuerySubscription(IAsyncEnumerable> asyncEnumerable, - Func> messageRecieved, Action errorRecieved) + Func> messageReceived, Action errorReceived) : SubscriptionBase() { protected override async Task RunAction() { await foreach (var msg in asyncEnumerable.WithCancellation(CancelToken)) { - var recievedMessage = ExtractMessage(msg); + var receivedMessage = ExtractMessage(msg); try { - var result = await messageRecieved(recievedMessage); + var result = await messageReceived(receivedMessage); await msg.ReplyAsync( result.Data.ToArray(), headers: Connection.ExtractHeader(result), @@ -24,8 +24,8 @@ await msg.ReplyAsync( } catch (Exception ex) { - errorRecieved(ex); - var headers = Connection.ProduceQueryError(ex, recievedMessage.ID, out var responseData); + errorReceived(ex); + var headers = Connection.ProduceQueryError(ex, receivedMessage.ID, out var responseData); await msg.ReplyAsync( responseData, replyTo: msg.ReplyTo, diff --git a/Connectors/NATS/Subscriptions/StreamSubscription.cs b/Connectors/NATS/Subscriptions/StreamSubscription.cs index d92c46e..44fc725 100644 --- a/Connectors/NATS/Subscriptions/StreamSubscription.cs +++ b/Connectors/NATS/Subscriptions/StreamSubscription.cs @@ -3,8 +3,8 @@ namespace MQContract.NATS.Subscriptions { - internal class StreamSubscription(INatsJSConsumer consumer, Action messageRecieved, - Action errorRecieved) + internal class StreamSubscription(INatsJSConsumer consumer, Action messageReceived, + Action errorReceived) : SubscriptionBase() { protected override async Task RunAction() @@ -20,12 +20,12 @@ protected override async Task RunAction() var success = true; try { - messageRecieved(ExtractMessage(msg)); + messageReceived(ExtractMessage(msg)); } catch (Exception ex) { success=false; - errorRecieved(ex); + errorReceived(ex); await msg.NakAsync(cancellationToken: CancelToken); } if (success) @@ -34,11 +34,11 @@ protected override async Task RunAction() } catch (NatsJSProtocolException e) { - errorRecieved(e); + errorReceived(e); } catch (NatsJSException e) { - errorRecieved(e); + errorReceived(e); // log exception await Task.Delay(1000, CancelToken); // backoff } diff --git a/Connectors/NATS/Subscriptions/SubscriptionBase.cs b/Connectors/NATS/Subscriptions/SubscriptionBase.cs index 9d4e017..e7e3d8f 100644 --- a/Connectors/NATS/Subscriptions/SubscriptionBase.cs +++ b/Connectors/NATS/Subscriptions/SubscriptionBase.cs @@ -11,13 +11,13 @@ internal abstract class SubscriptionBase() : IInternalServiceSubscription,IDispo protected CancellationToken CancelToken => CancelTokenSource.Token; - protected static RecievedServiceMessage ExtractMessage(NatsJSMsg recievedMessage) - => ExtractMessage(recievedMessage.Headers, recievedMessage.Subject, recievedMessage.Data); + protected static ReceivedServiceMessage ExtractMessage(NatsJSMsg receivedMessage) + => ExtractMessage(receivedMessage.Headers, receivedMessage.Subject, receivedMessage.Data); - protected static RecievedServiceMessage ExtractMessage(NatsMsg recievedMessage) - => ExtractMessage(recievedMessage.Headers, recievedMessage.Subject, recievedMessage.Data); + protected static ReceivedServiceMessage ExtractMessage(NatsMsg receivedMessage) + => ExtractMessage(receivedMessage.Headers, receivedMessage.Subject, receivedMessage.Data); - private static RecievedServiceMessage ExtractMessage(NatsHeaders? headers, string subject, byte[]? data) + private static ReceivedServiceMessage ExtractMessage(NatsHeaders? headers, string subject, byte[]? data) { var convertedHeaders = Connection.ExtractHeader(headers, out var messageID, out var messageTypeID); return new( diff --git a/Connectors/RabbitMQ/Connection.cs b/Connectors/RabbitMQ/Connection.cs index 7229330..ed2b412 100644 --- a/Connectors/RabbitMQ/Connection.cs +++ b/Connectors/RabbitMQ/Connection.cs @@ -115,7 +115,7 @@ internal static (IBasicProperties props, ReadOnlyMemory) ConvertMessage(Se return (props, ms.ToArray()); } - internal static RecievedServiceMessage ConvertMessage(BasicDeliverEventArgs eventArgs,string channel, Func acknowledge,out Guid? messageId) + internal static ReceivedServiceMessage ConvertMessage(BasicDeliverEventArgs eventArgs,string channel, Func acknowledge,out Guid? messageId) { using var ms = new MemoryStream(eventArgs.Body.ToArray()); using var br = new BinaryReader(ms); @@ -141,13 +141,8 @@ internal static RecievedServiceMessage ConvertMessage(BasicDeliverEventArgs even acknowledge ); } - /// - /// Called to publish a message into the ActiveMQ server - /// - /// The service message being sent - /// A cancellation token - /// Transmition result identifying if it worked or not - public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) + + async ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) { await semaphore.WaitAsync(cancellationToken); TransmissionResult result; @@ -164,46 +159,28 @@ public async ValueTask PublishAsync(ServiceMessage message, return result; } - private Subscription ProduceSubscription(IConnection conn, string channel, string? group, Action> messageRecieved, Action errorRecieved) + private Subscription ProduceSubscription(IConnection conn, string channel, string? group, Action> messageReceived, Action errorReceived) { if (group==null) { group = Guid.NewGuid().ToString(); this.channel.QueueDeclare(queue:group, durable:false, exclusive:false, autoDelete:true); } - return new Subscription(conn, channel, group,messageRecieved,errorRecieved); + return new Subscription(conn, channel, group,messageReceived,errorReceived); } - /// - /// Called to create a subscription to the underlying RabbitMQ server - /// - /// Callback for when a message is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// - /// - /// - public ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) => ValueTask.FromResult(ProduceSubscription(conn, channel, group, (@event,modelChannel, acknowledge) => { - messageRecieved(ConvertMessage(@event, channel, acknowledge,out _)); + messageReceived(ConvertMessage(@event, channel, acknowledge,out _)); }, - errorRecieved + errorReceived )); private const string InboxChannel = "_Inbox"; - /// - /// Called to publish a query into the RabbitMQ server - /// - /// The service message being sent - /// The timeout supplied for the query to response - /// A cancellation token - /// The resulting response - /// Thrown when the query fails to send - /// Thrown when the query times out - public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) + async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) { var messageID = Guid.NewGuid(); (var props, var data) = ConvertMessage(message, this.channel,messageID); @@ -261,20 +238,11 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti throw new TimeoutException(); } - /// - /// Called to create a subscription for queries to the underlying RabbitMQ server - /// - /// Callback for when a query is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The group to bind to - /// A cancellation token - /// A subscription instance - public ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + ValueTask IQueryableMessageServiceConnection.SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) => ValueTask.FromResult(ProduceSubscription(conn, channel, group, async (@event,model, acknowledge) => { - var result = await messageRecieved(ConvertMessage(@event, channel, acknowledge,out var messageID)); + var result = await messageReceived(ConvertMessage(@event, channel, acknowledge,out var messageID)); await semaphore.WaitAsync(cancellationToken); try { @@ -283,18 +251,14 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti } catch (Exception e) { - errorRecieved(e); + errorReceived(e); } semaphore.Release(); }, - errorRecieved + errorReceived )); - /// - /// Called to close off the contract connection and close it's underlying service connection - /// - /// A task for the closure of the connection - public ValueTask CloseAsync() + ValueTask IMessageServiceConnection.CloseAsync() { Dispose(true); return ValueTask.CompletedTask; @@ -323,10 +287,7 @@ private void Dispose(bool disposing) } } - /// - /// Called to dispose of the object correctly and allow it to clean up it's resources - /// - public void Dispose() + void IDisposable.Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); diff --git a/Connectors/RabbitMQ/Readme.md b/Connectors/RabbitMQ/Readme.md index 4341477..1cb9c6b 100644 --- a/Connectors/RabbitMQ/Readme.md +++ b/Connectors/RabbitMQ/Readme.md @@ -7,15 +7,9 @@ - [#ctor(factory)](#M-MQContract-RabbitMQ-Connection-#ctor-RabbitMQ-Client-ConnectionFactory- 'MQContract.RabbitMQ.Connection.#ctor(RabbitMQ.Client.ConnectionFactory)') - [DefaultTimout](#P-MQContract-RabbitMQ-Connection-DefaultTimout 'MQContract.RabbitMQ.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-RabbitMQ-Connection-MaxMessageBodySize 'MQContract.RabbitMQ.Connection.MaxMessageBodySize') - - [CloseAsync()](#M-MQContract-RabbitMQ-Connection-CloseAsync 'MQContract.RabbitMQ.Connection.CloseAsync') - - [Dispose()](#M-MQContract-RabbitMQ-Connection-Dispose 'MQContract.RabbitMQ.Connection.Dispose') - [ExchangeDeclare(exchange,type,durable,autoDelete,arguments)](#M-MQContract-RabbitMQ-Connection-ExchangeDeclare-System-String,System-String,System-Boolean,System-Boolean,System-Collections-Generic-IDictionary{System-String,System-Object}- 'MQContract.RabbitMQ.Connection.ExchangeDeclare(System.String,System.String,System.Boolean,System.Boolean,System.Collections.Generic.IDictionary{System.String,System.Object})') - - [PublishAsync(message,cancellationToken)](#M-MQContract-RabbitMQ-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') - - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-RabbitMQ-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') - [QueueDeclare(queue,durable,exclusive,autoDelete,arguments)](#M-MQContract-RabbitMQ-Connection-QueueDeclare-System-String,System-Boolean,System-Boolean,System-Boolean,System-Collections-Generic-IDictionary{System-String,System-Object}- 'MQContract.RabbitMQ.Connection.QueueDeclare(System.String,System.Boolean,System.Boolean,System.Boolean,System.Collections.Generic.IDictionary{System.String,System.Object})') - [QueueDelete(queue,ifUnused,ifEmpty)](#M-MQContract-RabbitMQ-Connection-QueueDelete-System-String,System-Boolean,System-Boolean- 'MQContract.RabbitMQ.Connection.QueueDelete(System.String,System.Boolean,System.Boolean)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-RabbitMQ-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-RabbitMQ-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.RabbitMQ.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') ## Connection `type` @@ -56,32 +50,6 @@ DEFAULT: 1 minute The maximum message body size allowed - -### CloseAsync() `method` - -##### Summary - -Called to close off the contract connection and close it's underlying service connection - -##### Returns - -A task for the closure of the connection - -##### Parameters - -This method has no parameters. - - -### Dispose() `method` - -##### Summary - -Called to dispose of the object correctly and allow it to clean up it's resources - -##### Parameters - -This method has no parameters. - ### ExchangeDeclare(exchange,type,durable,autoDelete,arguments) `method` @@ -103,50 +71,6 @@ The connection to allow for chaining calls | autoDelete | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Auto Delete when connection closed | | arguments | [System.Collections.Generic.IDictionary{System.String,System.Object}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Collections.Generic.IDictionary 'System.Collections.Generic.IDictionary{System.String,System.Object}') | Additional arguements | - -### PublishAsync(message,cancellationToken) `method` - -##### Summary - -Called to publish a message into the ActiveMQ server - -##### Returns - -Transmition result identifying if it worked or not - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### QueryAsync(message,timeout,cancellationToken) `method` - -##### Summary - -Called to publish a query into the RabbitMQ server - -##### Returns - -The resulting response - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [System.InvalidOperationException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.InvalidOperationException 'System.InvalidOperationException') | Thrown when the query fails to send | -| [System.TimeoutException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeoutException 'System.TimeoutException') | Thrown when the query times out | - ### QueueDeclare(queue,durable,exclusive,autoDelete,arguments) `method` @@ -182,45 +106,3 @@ Used to delete a queue inside the RabbitMQ server | queue | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the queue | | ifUnused | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Is unused | | ifEmpty | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Is Empty | - - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription to the underlying RabbitMQ server - -##### Returns - - - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | | - - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription for queries to the underlying RabbitMQ server - -##### Returns - -A subscription instance - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind to | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/RabbitMQ/Subscription.cs b/Connectors/RabbitMQ/Subscription.cs index 809f5bc..2b77190 100644 --- a/Connectors/RabbitMQ/Subscription.cs +++ b/Connectors/RabbitMQ/Subscription.cs @@ -10,7 +10,7 @@ internal class Subscription : IServiceSubscription private readonly Guid subscriptionID = Guid.NewGuid(); private readonly string consumerTag; - public Subscription(IConnection conn,string channel,string group, Action> messageRecieved, Action errorRecieved) + public Subscription(IConnection conn,string channel,string group, Action> messageReceived, Action errorReceived) { this.channel = conn.CreateModel(); this.channel.QueueBind(group, channel, subscriptionID.ToString()); @@ -18,7 +18,7 @@ public Subscription(IConnection conn,string channel,string group, Action { - messageRecieved( + messageReceived( @event, this.channel, () => diff --git a/Connectors/Redis/Connection.cs b/Connectors/Redis/Connection.cs index a335d34..a6183fc 100644 --- a/Connectors/Redis/Connection.cs +++ b/Connectors/Redis/Connection.cs @@ -50,13 +50,9 @@ public async ValueTask DefineConsumerGroupAsync(string channel,string group) /// /// The default timeout to allow for a Query Response call to execute, defaults to 1 minute /// - public TimeSpan DefaultTimout => TimeSpan.FromMinutes(1); + public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); - /// - /// Called to close off the underlying Redis Connection - /// - /// - public async ValueTask CloseAsync() + async ValueTask IMessageServiceConnection.CloseAsync() => await connectionMultiplexer.CloseAsync(); private const string MESSAGE_TYPE_KEY = "_MessageTypeID"; @@ -77,7 +73,7 @@ internal static NameValueEntry[] ConvertMessage(ServiceMessage message, string? .Concat(messageTimeout==null ? [] : [new NameValueEntry(MESSAGE_TIMEOUT_KEY,messageTimeout.ToString())] ) .ToArray(); - internal static (RecievedServiceMessage recievedMessage,string? replyChannel,TimeSpan? messageTimeout) ConvertMessage(NameValueEntry[] data, string channel,Func? acknowledge) + internal static (ReceivedServiceMessage receivedMessage,string? replyChannel,TimeSpan? messageTimeout) ConvertMessage(NameValueEntry[] data, string channel,Func? acknowledge) #pragma warning disable S6580 // Use a format provider when parsing date and time => ( new( @@ -124,13 +120,7 @@ internal static ServiceQueryResult DecodeMessage(string content) ); } - /// - /// Called to publish a message into the Redis server - /// - /// The service message being sent - /// A cancellation token - /// Transmition result identifying if it worked or not - public async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) { try { @@ -142,33 +132,16 @@ public async ValueTask PublishAsync(ServiceMessage message, } } - /// - /// Called to create a subscription to the underlying Redis server - /// - /// Callback for when a message is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The name of the group to bind the consumer to - /// A cancellation token - /// - public async ValueTask SubscribeAsync(Action messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + async ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { if (group!=null) await DefineConsumerGroupAsync(channel, group!); - var result = new PubSubscription(messageRecieved, errorRecieved, database, connectionID, channel, group); + var result = new PubSubscription(messageReceived, errorReceived, database, connectionID, channel, group); await result.StartAsync(); return result; } - /// - /// Called to publish a query into the Redis server - /// - /// The service message being sent - /// The timeout supplied for the query to response - /// A cancellation token - /// The resulting response - /// Thrown when the response times out - public async ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) + async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) { var replyID = $"_inbox.{Guid.NewGuid()}"; await database.StreamAddAsync(message.Channel, ConvertMessage(message, replyID, timeout)); @@ -185,29 +158,16 @@ public async ValueTask QueryAsync(ServiceMessage message, Ti throw new TimeoutException(); } - /// - /// Called to create a subscription for queries to the underlying Redis server - /// - /// Callback for when a query is recieved - /// Callback for when an error occurs - /// The name of the channel to bind to - /// The group to bind to - /// A cancellation token - /// A subscription instance - public async ValueTask SubscribeQueryAsync(Func> messageRecieved, Action errorRecieved, string channel, string? group = null, CancellationToken cancellationToken = default) + async ValueTask IQueryableMessageServiceConnection.SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { if (group!=null) await DefineConsumerGroupAsync(channel, group!); - var result = new QueryResponseSubscription(messageRecieved, errorRecieved, database, connectionID, channel, group); + var result = new QueryResponseSubscription(messageReceived, errorReceived, database, connectionID, channel, group); await result.StartAsync(); return result; } - /// - /// Called to dispose of the object correctly and allow it to clean up it's resources - /// - /// A task required for disposal - public async ValueTask DisposeAsync() + async ValueTask IAsyncDisposable.DisposeAsync() { await connectionMultiplexer.DisposeAsync(); @@ -225,10 +185,7 @@ private void Dispose(bool disposing) } } - /// - /// Called to dispose of the object correctly and allow it to clean up it's resources - /// - public void Dispose() + void IDisposable.Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); diff --git a/Connectors/Redis/Readme.md b/Connectors/Redis/Readme.md index e95fc51..bb2671f 100644 --- a/Connectors/Redis/Readme.md +++ b/Connectors/Redis/Readme.md @@ -7,14 +7,7 @@ - [#ctor(configuration)](#M-MQContract-Redis-Connection-#ctor-StackExchange-Redis-ConfigurationOptions- 'MQContract.Redis.Connection.#ctor(StackExchange.Redis.ConfigurationOptions)') - [DefaultTimout](#P-MQContract-Redis-Connection-DefaultTimout 'MQContract.Redis.Connection.DefaultTimout') - [MaxMessageBodySize](#P-MQContract-Redis-Connection-MaxMessageBodySize 'MQContract.Redis.Connection.MaxMessageBodySize') - - [CloseAsync()](#M-MQContract-Redis-Connection-CloseAsync 'MQContract.Redis.Connection.CloseAsync') - [DefineConsumerGroupAsync(channel,group)](#M-MQContract-Redis-Connection-DefineConsumerGroupAsync-System-String,System-String- 'MQContract.Redis.Connection.DefineConsumerGroupAsync(System.String,System.String)') - - [Dispose()](#M-MQContract-Redis-Connection-Dispose 'MQContract.Redis.Connection.Dispose') - - [DisposeAsync()](#M-MQContract-Redis-Connection-DisposeAsync 'MQContract.Redis.Connection.DisposeAsync') - - [PublishAsync(message,cancellationToken)](#M-MQContract-Redis-Connection-PublishAsync-MQContract-Messages-ServiceMessage,System-Threading-CancellationToken- 'MQContract.Redis.Connection.PublishAsync(MQContract.Messages.ServiceMessage,System.Threading.CancellationToken)') - - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-Redis-Connection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.Redis.Connection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') - - [SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Redis-Connection-SubscribeAsync-System-Action{MQContract-Messages-RecievedServiceMessage},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Redis.Connection.SubscribeAsync(System.Action{MQContract.Messages.RecievedServiceMessage},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - - [SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken)](#M-MQContract-Redis-Connection-SubscribeQueryAsync-System-Func{MQContract-Messages-RecievedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Redis.Connection.SubscribeQueryAsync(System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') ## Connection `type` @@ -54,21 +47,6 @@ The default timeout to allow for a Query Response call to execute, defaults to 1 The maximum message body size allowed, defaults to 4MB - -### CloseAsync() `method` - -##### Summary - -Called to close off the underlying Redis Connection - -##### Returns - - - -##### Parameters - -This method has no parameters. - ### DefineConsumerGroupAsync(channel,group) `method` @@ -86,114 +64,3 @@ A ValueTask while the operation executes asynchronously | ---- | ---- | ----------- | | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to use | | group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the group to use | - - -### Dispose() `method` - -##### Summary - -Called to dispose of the object correctly and allow it to clean up it's resources - -##### Parameters - -This method has no parameters. - - -### DisposeAsync() `method` - -##### Summary - -Called to dispose of the object correctly and allow it to clean up it's resources - -##### Returns - -A task required for disposal - -##### Parameters - -This method has no parameters. - - -### PublishAsync(message,cancellationToken) `method` - -##### Summary - -Called to publish a message into the Redis server - -##### Returns - -Transmition result identifying if it worked or not - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### QueryAsync(message,timeout,cancellationToken) `method` - -##### Summary - -Called to publish a query into the Redis server - -##### Returns - -The resulting response - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message being sent | -| timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout supplied for the query to response | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [System.TimeoutException](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeoutException 'System.TimeoutException') | Thrown when the response times out | - - -### SubscribeAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription to the underlying Redis server - -##### Returns - - - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Messages.RecievedServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.RecievedServiceMessage}') | Callback for when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the group to bind the consumer to | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - - -### SubscribeQueryAsync(messageRecieved,errorRecieved,channel,group,cancellationToken) `method` - -##### Summary - -Called to create a subscription for queries to the underlying Redis server - -##### Returns - -A subscription instance - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Messages.RecievedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}}') | Callback for when a query is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | Callback for when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The name of the channel to bind to | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The group to bind to | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | diff --git a/Connectors/Redis/Subscriptions/PubSubscription.cs b/Connectors/Redis/Subscriptions/PubSubscription.cs index 25c4efe..ea551b7 100644 --- a/Connectors/Redis/Subscriptions/PubSubscription.cs +++ b/Connectors/Redis/Subscriptions/PubSubscription.cs @@ -3,8 +3,8 @@ namespace MQContract.Redis.Subscriptions { - internal class PubSubscription(Action messageRecieved, Action errorRecieved, IDatabase database, Guid connectionID, string channel, string? group) - : SubscriptionBase(errorRecieved,database,connectionID,channel,group) + internal class PubSubscription(Action messageReceived, Action errorReceived, IDatabase database, Guid connectionID, string channel, string? group) + : SubscriptionBase(errorReceived,database,connectionID,channel,group) { protected override ValueTask ProcessMessage(StreamEntry streamEntry, string channel, string? group) { @@ -13,7 +13,7 @@ protected override ValueTask ProcessMessage(StreamEntry streamEntry, string chan channel, () => Acknowledge(streamEntry.Id) ); - messageRecieved(message); + messageReceived(message); return ValueTask.CompletedTask; } } diff --git a/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs b/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs index dab302b..6cb9294 100644 --- a/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs +++ b/Connectors/Redis/Subscriptions/QueryResponseSubscription.cs @@ -3,8 +3,8 @@ namespace MQContract.Redis.Subscriptions { - internal class QueryResponseSubscription(Func> messageRecieved, Action errorRecieved, IDatabase database, Guid connectionID, string channel, string? group) - : SubscriptionBase(errorRecieved,database,connectionID,channel,group) + internal class QueryResponseSubscription(Func> messageReceived, Action errorReceived, IDatabase database, Guid connectionID, string channel, string? group) + : SubscriptionBase(errorReceived,database,connectionID,channel,group) { protected override async ValueTask ProcessMessage(StreamEntry streamEntry, string channel, string? group) { @@ -13,7 +13,7 @@ protected override async ValueTask ProcessMessage(StreamEntry streamEntry, strin channel, ()=> Acknowledge(streamEntry.Id) ); - var result = await messageRecieved(message); + var result = await messageReceived(message); await Database.StreamDeleteAsync(Channel, [streamEntry.Id]); await Database.StringSetAsync(responseChannel, Connection.EncodeMessage(result), expiry: timeout); } diff --git a/Connectors/Redis/Subscriptions/SubscriptionBase.cs b/Connectors/Redis/Subscriptions/SubscriptionBase.cs index 10985c2..29bd699 100644 --- a/Connectors/Redis/Subscriptions/SubscriptionBase.cs +++ b/Connectors/Redis/Subscriptions/SubscriptionBase.cs @@ -3,7 +3,7 @@ namespace MQContract.Redis.Subscriptions { - internal abstract class SubscriptionBase(Action errorRecieved, IDatabase database, Guid connectionID, string channel, string? group) : IServiceSubscription,IDisposable + internal abstract class SubscriptionBase(Action errorReceived, IDatabase database, Guid connectionID, string channel, string? group) : IServiceSubscription,IDisposable { private readonly CancellationTokenSource tokenSource = new(); private bool disposedValue; @@ -35,7 +35,7 @@ public Task StartAsync() } catch (Exception ex) { - errorRecieved(ex); + errorReceived(ex); } } }); diff --git a/Core/ContractConnection.Metrics.cs b/Core/ContractConnection.Metrics.cs new file mode 100644 index 0000000..201075e --- /dev/null +++ b/Core/ContractConnection.Metrics.cs @@ -0,0 +1,36 @@ +using MQContract.Interfaces; +using MQContract.Middleware; + +namespace MQContract +{ + public sealed partial class ContractConnection + { + void IContractConnection.AddMetrics(bool useMeter, bool useInternal) + { + lock (middleware) + { + middleware.Insert(0, new MetricsMiddleware(useMeter, useInternal)); + } + } + + private MetricsMiddleware? MetricsMiddleware + { + get + { + MetricsMiddleware? metricsMiddleware; + lock (middleware) + { + metricsMiddleware = middleware.OfType().FirstOrDefault(); + } + return metricsMiddleware; + } + } + + IContractMetric? IContractConnection.GetSnapshot(bool sent) + => MetricsMiddleware?.GetSnapshot(sent); + IContractMetric? IContractConnection.GetSnapshot(Type messageType, bool sent) + => MetricsMiddleware?.GetSnapshot(messageType, sent); + IContractMetric? IContractConnection.GetSnapshot(string channel, bool sent) + => MetricsMiddleware?.GetSnapshot(channel, sent); + } +} diff --git a/Core/ContractConnection.Middleware.cs b/Core/ContractConnection.Middleware.cs new file mode 100644 index 0000000..b24fab0 --- /dev/null +++ b/Core/ContractConnection.Middleware.cs @@ -0,0 +1,90 @@ +using Microsoft.Extensions.DependencyInjection; +using MQContract.Interfaces; +using MQContract.Interfaces.Middleware; +using MQContract.Messages; +using MQContract.Middleware; + +namespace MQContract +{ + public sealed partial class ContractConnection + { + private ContractConnection RegisterMiddleware(object element) + { + lock (middleware) + { + middleware.Add(element); + } + return this; + } + + private ContractConnection RegisterMiddleware(Type type) + => RegisterMiddleware((serviceProvider == null ? Activator.CreateInstance(type) : ActivatorUtilities.CreateInstance(serviceProvider, type))!); + + IContractConnection IContractConnection.RegisterMiddleware() + => RegisterMiddleware(typeof(T)); + IContractConnection IContractConnection.RegisterMiddleware(Func constructInstance) + => RegisterMiddleware(constructInstance()); + IContractConnection IContractConnection.RegisterMiddleware() + => RegisterMiddleware(typeof(T)); + IContractConnection IContractConnection.RegisterMiddleware(Func constructInstance) + => RegisterMiddleware(constructInstance()); + + private async ValueTask<(T message,string? channel,MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, T message, string? channel, MessageHeader messageHeader) + where T : class + { + IBeforeEncodeMiddleware[] genericHandlers; + IBeforeEncodeSpecificTypeMiddleware[] specificHandlers; + lock (middleware) + { + genericHandlers = middleware.OfType().ToArray(); + specificHandlers = middleware.OfType>().ToArray(); + } + foreach (var handler in genericHandlers) + (message,channel,messageHeader) = await handler.BeforeMessageEncodeAsync(context,message, channel, messageHeader); + foreach(var handler in specificHandlers) + (message,channel,messageHeader) = await handler.BeforeMessageEncodeAsync(context,message, channel, messageHeader); + return (message, channel, messageHeader); + } + + private async ValueTask AfterMessageEncodeAsync(IContext context,ServiceMessage message) + { + IAfterEncodeMiddleware[] genericHandlers; + lock (middleware) + { + genericHandlers = middleware.OfType().ToArray(); + } + foreach(var handler in genericHandlers) + message = await handler.AfterMessageEncodeAsync(typeof(T),context,message); + return message; + } + + private async ValueTask<(MessageHeader messageHeader, ReadOnlyMemory data)> BeforeMessageDecodeAsync(IContext context, string id, MessageHeader messageHeader, string messageTypeID,string messageChannel, ReadOnlyMemory data) + { + IBeforeDecodeMiddleware[] genericHandlers; + lock (middleware) + { + genericHandlers = middleware.OfType().ToArray(); + } + foreach (var handler in genericHandlers) + (messageHeader,data) = await handler.BeforeMessageDecodeAsync(context,id,messageHeader,messageTypeID,messageChannel,data); + return (messageHeader,data); + } + + private async ValueTask<(T message, MessageHeader messageHeader)> AfterMessageDecodeAsync(IContext context, T message, string ID, MessageHeader messageHeader, DateTime receivedTimestamp, DateTime processedTimeStamp) + where T : class + { + IAfterDecodeMiddleware[] genericHandlers; + IAfterDecodeSpecificTypeMiddleware[] specificHandlers; + lock (middleware) + { + genericHandlers = middleware.OfType().ToArray(); + specificHandlers = middleware.OfType>().ToArray(); + } + foreach (var handler in genericHandlers) + (message, messageHeader) = await handler.AfterMessageDecodeAsync(context, message, ID, messageHeader, receivedTimestamp, processedTimeStamp); + foreach (var handler in specificHandlers) + (message, messageHeader) = await handler.AfterMessageDecodeAsync(context, message, ID, messageHeader, receivedTimestamp, processedTimeStamp); + return (message, messageHeader); + } + } +} diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index 9ad1fd1..7daaac5 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -5,37 +5,69 @@ using MQContract.Interfaces.Encoding; using MQContract.Interfaces.Encrypting; using MQContract.Interfaces.Factories; +using MQContract.Interfaces.Middleware; using MQContract.Interfaces.Service; using MQContract.Messages; +using MQContract.Middleware; using MQContract.Subscriptions; using System.Reflection; namespace MQContract { /// - /// This is the primary class for this library and is used to create a Contract style connection between systems using the underlying service connection layer. + /// The primary ContractConnection item which implements IContractConnection /// - /// The service connection implementation to use for the underlying message requests. - /// A default message encoder implementation if desired. If there is no specific encoder for a given type, this encoder would be called. The built in default being used dotnet Json serializer. - /// A default message encryptor implementation if desired. If there is no specific encryptor - /// A service prodivder instance supplied in the case that dependency injection might be necessary - /// An instance of a logger if logging is desired - /// An instance of a ChannelMapper used to translate channels from one instance to another based on class channel attributes or supplied channels if necessary. - /// For example, it might be necessary for a Nats.IO instance when you are trying to read from a stored message stream that is comprised of another channel or set of channels - /// - public sealed class ContractConnection(IMessageServiceConnection serviceConnection, - IMessageEncoder? defaultMessageEncoder = null, - IMessageEncryptor? defaultMessageEncryptor = null, - IServiceProvider? serviceProvider = null, - ILogger? logger = null, - ChannelMapper? channelMapper = null) + public sealed partial class ContractConnection : IContractConnection { - private readonly Guid Indentifier = Guid.NewGuid(); + private readonly Guid indentifier = Guid.NewGuid(); private readonly SemaphoreSlim dataLock = new(1, 1); + private readonly IMessageServiceConnection serviceConnection; + private readonly IMessageEncoder? defaultMessageEncoder; + private readonly IMessageEncryptor? defaultMessageEncryptor; + private readonly IServiceProvider? serviceProvider; + private readonly ILogger? logger; + private readonly ChannelMapper? channelMapper; + private readonly List middleware; private IEnumerable typeFactories = []; private bool disposedValue; + private ContractConnection(IMessageServiceConnection serviceConnection, + IMessageEncoder? defaultMessageEncoder = null, + IMessageEncryptor? defaultMessageEncryptor = null, + IServiceProvider? serviceProvider = null, + ILogger? logger = null, + ChannelMapper? channelMapper = null) + { + this.serviceConnection = serviceConnection; + this.defaultMessageEncoder = defaultMessageEncoder; + this.defaultMessageEncryptor= defaultMessageEncryptor; + this.serviceProvider = serviceProvider; + this.logger=logger; + this.channelMapper=channelMapper; + this.middleware= [new ChannelMappingMiddleware(this.channelMapper)]; + } + + /// + /// This is the call used to create an instance of a Contract Connection which will return the Interface + /// + /// The service connection implementation to use for the underlying message requests. + /// A default message encoder implementation if desired. If there is no specific encoder for a given type, this encoder would be called. The built in default being used dotnet Json serializer. + /// A default message encryptor implementation if desired. If there is no specific encryptor + /// A service prodivder instance supplied in the case that dependency injection might be necessary + /// An instance of a logger if logging is desired + /// An instance of a ChannelMapper used to translate channels from one instance to another based on class channel attributes or supplied channels if necessary. + /// For example, it might be necessary for a Nats.IO instance when you are trying to read from a stored message stream that is comprised of another channel or set of channels + /// + /// An instance of IContractConnection + public static IContractConnection Instance(IMessageServiceConnection serviceConnection, + IMessageEncoder? defaultMessageEncoder = null, + IMessageEncryptor? defaultMessageEncryptor = null, + IServiceProvider? serviceProvider = null, + ILogger? logger = null, + ChannelMapper? channelMapper = null) + => new ContractConnection(serviceConnection,defaultMessageEncoder,defaultMessageEncryptor,serviceProvider,logger, channelMapper); + private IMessageFactory GetMessageFactory(bool ignoreMessageHeader = false) where T : class { dataLock.Wait(); @@ -55,74 +87,32 @@ private IMessageFactory GetMessageFactory(bool ignoreMessageHeader = false private ValueTask MapChannel(ChannelMapper.MapTypes mapType, string originalChannel) => channelMapper?.MapChannel(mapType, originalChannel)??ValueTask.FromResult(originalChannel); - /// - /// Called to execute a ping against the service layer - /// - /// The ping result from the service layer, if supported - public ValueTask PingAsync() - => (serviceConnection is IPingableMessageServiceConnection pingableService ? pingableService.PingAsync() : throw new NotSupportedException("The underlying service does not support Ping")); - - /// - /// Called to publish a message out into the service layer in the Pub/Sub style - /// - /// The type of message to publish - /// The instance of the message to publish - /// Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on - /// A message header to be sent across with the message - /// A cancellation token - /// - /// An instance of the TransmissionResult record to indicate success or failure and an ID - public async ValueTask PublishAsync(T message, string? channel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) - where T : class - => await serviceConnection.PublishAsync( - await ProduceServiceMessage(ChannelMapper.MapTypes.Publish, message, channel: channel, messageHeader: messageHeader), - cancellationToken + private async ValueTask<(IContext context, ServiceMessage)> ProduceServiceMessage(ChannelMapper.MapTypes mapType, T message, string? channel = null, MessageHeader? messageHeader = null) where T : class + { + var factory = GetMessageFactory(); + var context = new Context(mapType); + (message, channel, messageHeader) = await BeforeMessageEncodeAsync(context, message, channel??factory.MessageChannel, messageHeader??new([])); + var serviceMessage = await AfterMessageEncodeAsync(context, + await factory.ConvertMessageAsync(message, channel, messageHeader) ); + return (context, serviceMessage); + } - private async ValueTask ProduceServiceMessage(ChannelMapper.MapTypes mapType,T message, string? channel = null, MessageHeader? messageHeader = null) where T : class - => await GetMessageFactory().ConvertMessageAsync(message, channel, messageHeader,(originalChannel)=>MapChannel(mapType,originalChannel)!); - - /// - /// Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages asynchronously - /// - /// The type of message to listen for - /// The callback to be executed when a message is recieved - /// The callback to be executed when an error occurs - /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on - /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) - /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// A cancellation token - /// An instance of the Subscription that can be held or called to end - /// An exception thrown when the subscription has failed to establish - public ValueTask SubscribeAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = default) where T : class - => CreateSubscriptionAsync(messageRecieved, errorRecieved, channel, group, ignoreMessageHeader,false, cancellationToken); - - /// - /// Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages synchronously - /// - /// The type of message to listen for - /// The callback to be executed when a message is recieved - /// The callback to be executed when an error occurs - /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on - /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) - /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// A cancellation token - /// An instance of the Subscription that can be held or called to end - /// An exception thrown when the subscription has failed to establish - public ValueTask SubscribeAsync(Action> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = default) where T : class - => CreateSubscriptionAsync((msg) => - { - messageRecieved(msg); - return ValueTask.CompletedTask; - }, - errorRecieved, channel, group, ignoreMessageHeader, true, cancellationToken); - - private async ValueTask CreateSubscriptionAsync(Func, ValueTask> messageRecieved, Action errorRecieved, string? channel, string? group, bool ignoreMessageHeader,bool synchronous, CancellationToken cancellationToken) + private async ValueTask CreateSubscriptionAsync(Func, ValueTask> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, CancellationToken cancellationToken) where T : class { - var subscription = new PubSubSubscription(GetMessageFactory(ignoreMessageHeader), - messageRecieved, - errorRecieved, + var messageFactory = GetMessageFactory(ignoreMessageHeader); + var subscription = new PubSubSubscription( + async (serviceMessage) => + { + var context = new Context(ChannelMapper.MapTypes.PublishSubscription); + (MessageHeader messageHeader, ReadOnlyMemory data) = await BeforeMessageDecodeAsync(context, serviceMessage.ID, serviceMessage.Header, serviceMessage.MessageTypeID, serviceMessage.Channel, serviceMessage.Data); + var taskMessage = await messageFactory.ConvertMessageAsync(logger, new ServiceMessage(serviceMessage.ID, serviceMessage.MessageTypeID, serviceMessage.Channel, messageHeader, data)) + ??throw new InvalidCastException($"Unable to convert incoming message {serviceMessage.MessageTypeID} to {typeof(T).FullName}"); + (taskMessage, messageHeader) = await AfterMessageDecodeAsync(context, taskMessage, serviceMessage.ID, messageHeader, serviceMessage.ReceivedTimestamp, DateTime.Now); + await messageReceived(new ReceivedMessage(serviceMessage.ID, taskMessage!, messageHeader, serviceMessage.ReceivedTimestamp, DateTime.Now)); + }, + errorReceived, (originalChannel) => MapChannel(ChannelMapper.MapTypes.PublishSubscription, originalChannel)!, channel: channel, group: group, @@ -138,28 +128,31 @@ private async ValueTask CreateSubscriptionAsync(Func()?.TimeSpanValue; - var serviceMessage = await ProduceServiceMessage(ChannelMapper.MapTypes.Query, message, channel: channel, messageHeader: messageHeader); - if (serviceConnection is IQueryableMessageServiceConnection queryableMessageServiceConnection) - return await ProduceResultAsync(await queryableMessageServiceConnection.QueryAsync( - serviceMessage, - realTimeout??queryableMessageServiceConnection.DefaultTimout, - cancellationToken - )); + (var context, var serviceMessage) = await ProduceServiceMessage(ChannelMapper.MapTypes.Query, message, channel: channel, messageHeader: messageHeader); + if (serviceConnection is IQueryableMessageServiceConnection queryableMessageServiceConnection) + return await ProduceResultAsync( + context, + await queryableMessageServiceConnection.QueryAsync( + serviceMessage, + realTimeout??queryableMessageServiceConnection.DefaultTimout, + cancellationToken + ) + ); responseChannel ??=typeof(Q).GetCustomAttribute()?.Name; ArgumentNullException.ThrowIfNullOrWhiteSpace(responseChannel); var replyChannel = await MapChannel(ChannelMapper.MapTypes.QueryResponse, responseChannel!); var callID = Guid.NewGuid(); - var (tcs,token) = await QueryResponseHelper.StartResponseListenerAsync( + var (tcs, token) = await QueryResponseHelper.StartResponseListenerAsync( serviceConnection, realTimeout??TimeSpan.FromMinutes(1), - Indentifier, + indentifier, callID, replyChannel, cancellationToken ); var msg = QueryResponseHelper.EncodeMessage( serviceMessage, - Indentifier, + indentifier, callID, replyChannel, null @@ -168,87 +161,28 @@ private async ValueTask CreateSubscriptionAsync(Func(tcs.Task.Result); + return await ProduceResultAsync(context, tcs.Task.Result); } - - /// - /// Called to publish a message in the Query/Response style - /// - /// The type of message to transmit for the Query - /// The type of message expected as a response - /// The message to transmit for the query - /// The timeout to allow for waiting for a response - /// Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on - /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is - /// only used when the underlying connection does not support a QueryResponse style messaging. - /// A message header to be sent across with the message - /// A cancellation token - /// - /// A QueryResult that will contain the response message and or an error - public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) - where Q : class - where R : class - => await ExecuteQueryAsync(message, timeout: timeout, channel: channel,responseChannel:responseChannel, messageHeader: messageHeader, cancellationToken: cancellationToken); - - /// - /// Called to publish a message in the Query/Response style except the response Type is gathered from the QueryResponseTypeAttribute - /// - /// The type of message to transmit for the Query - /// The message to transmit for the query - /// The timeout to allow for waiting for a response - /// Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on - /// Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is - /// only used when the underlying connection does not support a QueryResponse style messaging. - /// A message header to be sent across with the message - /// A cancellation token - /// - /// A QueryResult that will contain the response message and or an error - /// Thrown when the supplied Query type does not have a QueryResponseTypeAttribute and therefore a response type cannot be determined - public async ValueTask> QueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, - CancellationToken cancellationToken = default) where Q : class + private async ValueTask> ProduceResultAsync(IContext context, ServiceQueryResult queryResult) where R : class { -#pragma warning disable CA2208 // Instantiate argument exceptions correctly - var responseType = (typeof(Q).GetCustomAttribute(false)?.ResponseType)??throw new UnknownResponseTypeException("ResponseType", typeof(Q)); -#pragma warning restore CA2208 // Instantiate argument exceptions correctly -#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields - var methodInfo = typeof(ContractConnection).GetMethod(nameof(ContractConnection.ExecuteQueryAsync), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(typeof(Q), responseType!); -#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields - dynamic? queryResult; + var receivedTime = DateTime.Now; + (var messageHeader, var data) = await BeforeMessageDecodeAsync(context, queryResult.ID, queryResult.Header, queryResult.MessageTypeID, string.Empty, queryResult.Data); + QueryResult result; try { - queryResult = (dynamic?)await Utility.InvokeMethodAsync( - methodInfo, - this, [ - message, - timeout, - channel, - responseChannel, + result = new QueryResult( + queryResult.ID, messageHeader, - cancellationToken - ] + Result: await GetMessageFactory().ConvertMessageAsync(logger, new ServiceQueryResult(queryResult.ID, messageHeader, queryResult.MessageTypeID, data)) ); - }catch(TimeoutException) - { - throw new QueryTimeoutException(); } - return new QueryResult(queryResult?.ID??string.Empty, queryResult?.Header??new MessageHeader([]), queryResult?.Result, queryResult?.Error); - } - - private async ValueTask> ProduceResultAsync(ServiceQueryResult queryResult) where R : class - { - try - { - return new( - queryResult.ID, - queryResult.Header, - Result: await GetMessageFactory().ConvertMessageAsync(logger, queryResult) - ); - }catch(QueryResponseException qre) + catch (QueryResponseException qre) { return new( queryResult.ID, @@ -257,7 +191,7 @@ private async ValueTask> ProduceResultAsync(ServiceQueryResult Error: qre.Message ); } - catch(Exception ex) + catch (Exception ex) { return new( queryResult.ID, @@ -266,59 +200,30 @@ private async ValueTask> ProduceResultAsync(ServiceQueryResult Error: ex.Message ); } + (var decodedResult, var decodedHeader) = await AfterMessageDecodeAsync(context, result.Result!, queryResult.ID, result.Header, receivedTime, DateTime.Now); + return new QueryResult(result.ID, decodedHeader, decodedResult); } - - /// - /// Creates a subscription with the underlying service layer for the Query/Response style processing messages asynchronously - /// - /// The expected message type for the Query - /// The expected message type for the Response - /// The callback to be executed when a message is recieved and expects a returned response - /// The callback to be executed when an error occurs - /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on - /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) - /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// A cancellation token - /// - /// An instance of the Subscription that can be held or called to end - /// An exception thrown when the subscription has failed to establish - public ValueTask SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = default) - where Q : class - where R : class - => ProduceSubscribeQueryResponseAsync(messageRecieved, errorRecieved, channel, group, ignoreMessageHeader,false, cancellationToken); - - /// - /// Creates a subscription with the underlying service layer for the Query/Response style processing messages synchronously - /// - /// The expected message type for the Query - /// The expected message type for the Response - /// The callback to be executed when a message is recieved and expects a returned response - /// The callback to be executed when an error occurs - /// Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on - /// Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) - /// If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T - /// A cancellation token - /// - /// An instance of the Subscription that can be held or called to end - /// An exception thrown when the subscription has failed to establish - public ValueTask SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageRecieved, Action errorRecieved, string? channel = null, string? group = null, bool ignoreMessageHeader = false, CancellationToken cancellationToken = default) - where Q : class - where R : class - => ProduceSubscribeQueryResponseAsync((msg) => - { - var result = messageRecieved(msg); - return ValueTask.FromResult(result); - }, errorRecieved, channel, group, ignoreMessageHeader, true, cancellationToken); - - private async ValueTask ProduceSubscribeQueryResponseAsync(Func, ValueTask>> messageRecieved, Action errorRecieved, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, CancellationToken cancellationToken) + private async ValueTask ProduceSubscribeQueryResponseAsync(Func, ValueTask>> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, CancellationToken cancellationToken) where Q : class where R : class { + var queryMessageFactory = GetMessageFactory(ignoreMessageHeader); + var responseMessageFactory = GetMessageFactory(); var subscription = new QueryResponseSubscription( - GetMessageFactory(ignoreMessageHeader), - GetMessageFactory(), - messageRecieved, - errorRecieved, + async (message) => + { + var context = new Context(ChannelMapper.MapTypes.QuerySubscription); + (var messageHeader, var data) = await BeforeMessageDecodeAsync(context, message.ID, message.Header, message.MessageTypeID, message.Channel, message.Data); + var taskMessage = await queryMessageFactory.ConvertMessageAsync(logger, new ReceivedServiceMessage(message.ID, message.MessageTypeID, message.Channel, messageHeader, data, message.Acknowledge)) + ??throw new InvalidCastException($"Unable to convert incoming message {message.MessageTypeID} to {typeof(Q).FullName}"); + (taskMessage, messageHeader) = await AfterMessageDecodeAsync(context, taskMessage!, message.ID, messageHeader, message.ReceivedTimestamp, DateTime.Now); + var result = await messageReceived(new ReceivedMessage(message.ID, taskMessage, messageHeader, message.ReceivedTimestamp, DateTime.Now)); + context = new Context(ChannelMapper.MapTypes.QueryResponse); + (var resultMessage, var resultChannel, var resultHeader) = await BeforeMessageEncodeAsync(context, result.Message, message.Channel, message.Header); + var encodedMessage = await responseMessageFactory.ConvertMessageAsync(resultMessage, resultChannel, resultHeader); + return await AfterMessageEncodeAsync(context, encodedMessage); + }, + errorReceived, (originalChannel) => MapChannel(ChannelMapper.MapTypes.QuerySubscription, originalChannel), channel: channel, group: group, @@ -329,11 +234,73 @@ private async ValueTask ProduceSubscribeQueryResponseAsync( throw new SubscriptionFailedException(); } - /// - /// Called to close off this connection and it's underlying service connection - /// - /// - public ValueTask CloseAsync() + ValueTask IContractConnection.PingAsync() + => (serviceConnection is IPingableMessageServiceConnection pingableService ? pingableService.PingAsync() : throw new NotSupportedException("The underlying service does not support Ping")); + + async ValueTask IContractConnection.PublishAsync(T message, string? channel, MessageHeader? messageHeader, CancellationToken cancellationToken) + { + (_, var serviceMessage) = await ProduceServiceMessage(ChannelMapper.MapTypes.Publish, message, channel: channel, messageHeader: messageHeader); + return await serviceConnection.PublishAsync( + serviceMessage, + cancellationToken + ); + } + + ValueTask IContractConnection.SubscribeAsync(Func, ValueTask> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) where T : class + => CreateSubscriptionAsync(messageReceived, errorReceived, channel, group, ignoreMessageHeader,false, cancellationToken); + + ValueTask IContractConnection.SubscribeAsync(Action> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) where T : class + => CreateSubscriptionAsync((msg) => + { + messageReceived(msg); + return ValueTask.CompletedTask; + }, + errorReceived, channel, group, ignoreMessageHeader, true, cancellationToken); + + async ValueTask> IContractConnection.QueryAsync(Q message, TimeSpan? timeout, string? channel, string? responseChannel, MessageHeader? messageHeader, CancellationToken cancellationToken) + => await ExecuteQueryAsync(message, timeout: timeout, channel: channel,responseChannel:responseChannel, messageHeader: messageHeader, cancellationToken: cancellationToken); + + async ValueTask> IContractConnection.QueryAsync(Q message, TimeSpan? timeout, string? channel, string? responseChannel, MessageHeader? messageHeader, + CancellationToken cancellationToken) + { +#pragma warning disable CA2208 // Instantiate argument exceptions correctly + var responseType = (typeof(Q).GetCustomAttribute(false)?.ResponseType)??throw new UnknownResponseTypeException("ResponseType", typeof(Q)); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly +#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + var methodInfo = typeof(ContractConnection).GetMethod(nameof(ContractConnection.ExecuteQueryAsync), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(typeof(Q), responseType!); +#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + dynamic? queryResult; + try + { + queryResult = (dynamic?)await Utility.InvokeMethodAsync( + methodInfo, + this, [ + message, + timeout, + channel, + responseChannel, + messageHeader, + cancellationToken + ] + ); + }catch(TimeoutException) + { + throw new QueryTimeoutException(); + } + return new QueryResult(queryResult?.ID??string.Empty, queryResult?.Header??new MessageHeader([]), queryResult?.Result, queryResult?.Error); + } + + ValueTask IContractConnection.SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) + => ProduceSubscribeQueryResponseAsync(messageReceived, errorReceived, channel, group, ignoreMessageHeader,false, cancellationToken); + + ValueTask IContractConnection.SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) + => ProduceSubscribeQueryResponseAsync((msg) => + { + var result = messageReceived(msg); + return ValueTask.FromResult(result); + }, errorReceived, channel, group, ignoreMessageHeader, true, cancellationToken); + + ValueTask IContractConnection.CloseAsync() => serviceConnection?.CloseAsync()??ValueTask.CompletedTask; private void Dispose(bool disposing) @@ -351,21 +318,14 @@ private void Dispose(bool disposing) } } - /// - /// Called to dispose of the resources contained within - /// - public void Dispose() + void IDisposable.Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } - /// - /// Called to dispose of the resources contained within - /// - /// A task of the underlying resources being disposed - public async ValueTask DisposeAsync() + async ValueTask IAsyncDisposable.DisposeAsync() { if (serviceConnection is IAsyncDisposable asyncDisposable) await asyncDisposable.DisposeAsync().ConfigureAwait(true); diff --git a/Core/Core.csproj b/Core/Core.csproj index 1da2aea..d88ece6 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -1,6 +1,6 @@  - + net8.0 diff --git a/Core/Exceptions.cs b/Core/Exceptions.cs index dc2b245..6c258c1 100644 --- a/Core/Exceptions.cs +++ b/Core/Exceptions.cs @@ -65,11 +65,11 @@ internal QueryTimeoutException() } /// - /// Thrown when a query call message is recieved without proper data + /// Thrown when a query call message is received without proper data /// - public class InvalidQueryResponseMessageRecieved : Exception + public class InvalidQueryResponseMessageReceived : Exception { - internal InvalidQueryResponseMessageRecieved() - : base("A service message was recieved on a query response channel without the proper data") { } + internal InvalidQueryResponseMessageReceived() + : base("A service message was received on a query response channel without the proper data") { } } } diff --git a/Core/Factories/MessageTypeFactory.cs b/Core/Factories/MessageTypeFactory.cs index 33a125a..3fe6f83 100644 --- a/Core/Factories/MessageTypeFactory.cs +++ b/Core/Factories/MessageTypeFactory.cs @@ -28,9 +28,9 @@ internal class MessageTypeFactory private readonly uint maxMessageSize; public bool IgnoreMessageHeader { get; private init; } - private readonly string messageName = typeof(T).GetCustomAttributes().Select(mn => mn.Value).FirstOrDefault(Utility.TypeName()); - private readonly string messageVersion = typeof(T).GetCustomAttributes().Select(mc => mc.Version.ToString()).FirstOrDefault("0.0.0.0"); - private readonly string messageChannel = typeof(T).GetCustomAttributes().Select(mc => mc.Name).FirstOrDefault(string.Empty); + private readonly string messageName = Utility.MessageTypeName(); + private readonly string messageVersion = Utility.MessageVersionString(); + public string? MessageChannel => typeof(T).GetCustomAttributes().Select(mc => mc.Name).FirstOrDefault(); public MessageTypeFactory(IMessageEncoder? globalMessageEncoder, IMessageEncryptor? globalMessageEncryptor, IServiceProvider? serviceProvider, bool ignoreMessageHeader, uint? maxMessageSize) { @@ -146,9 +146,8 @@ private static bool IsMessageTypeMatch(string metaData, Type t, out bool isCompr return false; } - public async ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader, Func>? mapChannel=null) + public async ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader messageHeader) { - channel ??= messageChannel; if (string.IsNullOrWhiteSpace(channel)) throw new MessageChannelNullException(); @@ -171,8 +170,6 @@ public async ValueTask ConvertMessageAsync(T message, string? ch else metaData="U"; metaData+=$"-{messageName}-{messageVersion}"; - if (mapChannel!=null) - channel=await mapChannel(channel); return new ServiceMessage(Guid.NewGuid().ToString(), metaData, channel, new MessageHeader(messageHeader, messageHeaders), body); } diff --git a/Core/Interfaces/Factories/IMessageFactory.cs b/Core/Interfaces/Factories/IMessageFactory.cs index 6726f9a..74313e3 100644 --- a/Core/Interfaces/Factories/IMessageFactory.cs +++ b/Core/Interfaces/Factories/IMessageFactory.cs @@ -5,6 +5,7 @@ namespace MQContract.Interfaces.Factories { internal interface IMessageFactory : IMessageTypeFactory, IConversionPath where T : class { - ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader? messageHeader,Func>? mapChannel=null); + string? MessageChannel { get; } + ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader messageHeader); } } diff --git a/Core/Messages/RecievedMessage.cs b/Core/Messages/RecievedMessage.cs index 3ecb28f..6c779bf 100644 --- a/Core/Messages/RecievedMessage.cs +++ b/Core/Messages/RecievedMessage.cs @@ -2,8 +2,8 @@ namespace MQContract.Messages { - internal record RecievedMessage(string ID,T Message,MessageHeader Headers,DateTime RecievedTimestamp,DateTime ProcessedTimestamp) - : IRecievedMessage + internal record ReceivedMessage(string ID,T Message,MessageHeader Headers,DateTime ReceivedTimestamp,DateTime ProcessedTimestamp) + : IReceivedMessage where T : class {} } diff --git a/Core/Middleware/ChannelMappingMiddleware.cs b/Core/Middleware/ChannelMappingMiddleware.cs new file mode 100644 index 0000000..1dd6e50 --- /dev/null +++ b/Core/Middleware/ChannelMappingMiddleware.cs @@ -0,0 +1,19 @@ +using MQContract.Interfaces.Middleware; +using MQContract.Messages; + +namespace MQContract.Middleware +{ + internal class ChannelMappingMiddleware(ChannelMapper? channelMapper) + : IBeforeEncodeMiddleware + { + private async ValueTask MapChannel(Context context,string? channel) + { + if (channelMapper==null || channel==null) + return channel; + return await channelMapper.MapChannel(context.MapDirection, channel); + } + + public async ValueTask<(T message, string? channel, MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, T message, string? channel, MessageHeader messageHeader) + => (message, await MapChannel((Context)context,channel), messageHeader); + } +} diff --git a/Core/Middleware/Context.cs b/Core/Middleware/Context.cs new file mode 100644 index 0000000..46464d9 --- /dev/null +++ b/Core/Middleware/Context.cs @@ -0,0 +1,29 @@ +using MQContract.Interfaces.Middleware; + +namespace MQContract.Middleware +{ + internal class Context : IContext + { + private const string MapTypeKey = "_MapType"; + private readonly Dictionary values = []; + + public Context(ChannelMapper.MapTypes mapDirection) + { + this[MapTypeKey] = mapDirection; + } + + public object? this[string key] { + get => values.TryGetValue(key, out var value) ? value : null; + set + { + if (value==null) + values.Remove(key); + else + values.TryAdd(key, value); + } + } + + public ChannelMapper.MapTypes MapDirection + => (ChannelMapper.MapTypes)this[MapTypeKey]!; + } +} diff --git a/Core/Middleware/Metrics/ContractMetric.cs b/Core/Middleware/Metrics/ContractMetric.cs new file mode 100644 index 0000000..e89a327 --- /dev/null +++ b/Core/Middleware/Metrics/ContractMetric.cs @@ -0,0 +1,36 @@ +using MQContract.Interfaces; + +namespace MQContract.Middleware.Metrics +{ + internal record ContractMetric : IContractMetric + { + public ulong Messages { get; private set; } = 0; + + public ulong MessageBytes { get; private set; } = 0; + + public ulong MessageBytesAverage => MessageBytes / Messages; + + public ulong MessageBytesMin { get; private set; } = ulong.MaxValue; + + public ulong MessageBytesMax { get; private set; } = ulong.MinValue; + + public TimeSpan MessageConversionDuration { get; private set; } = TimeSpan.Zero; + + public TimeSpan MessageConversionAverage => MessageConversionDuration / Messages; + + public TimeSpan MessageConversionMin { get; private set; } = TimeSpan.MaxValue; + + public TimeSpan MessageConversionMax { get; private set; } = TimeSpan.MinValue; + + public void AddMessageRecord(int messageSize, TimeSpan encodingDuration) + { + Messages++; + MessageBytes += (ulong)messageSize; + MessageBytesMin = Math.Min(MessageBytesMin, (ulong)messageSize); + MessageBytesMax = Math.Max(MessageBytesMax, (ulong)messageSize); + MessageConversionDuration += encodingDuration; + MessageConversionMin = TimeSpan.FromTicks(Math.Min(MessageConversionMin.Ticks, encodingDuration.Ticks)); + MessageConversionMax = TimeSpan.FromTicks(Math.Max(MessageConversionMin.Ticks, encodingDuration.Ticks)); + } + } +} diff --git a/Core/Middleware/Metrics/InternalMetricTracker.cs b/Core/Middleware/Metrics/InternalMetricTracker.cs new file mode 100644 index 0000000..dcaf3a7 --- /dev/null +++ b/Core/Middleware/Metrics/InternalMetricTracker.cs @@ -0,0 +1,82 @@ +namespace MQContract.Middleware.Metrics +{ + internal class InternalMetricTracker + { + private readonly SemaphoreSlim semDataLock = new(1, 1); + private readonly ContractMetric sentGlobalMetric = new(); + private readonly ContractMetric receivedGlobalMetric = new(); + private readonly Dictionary sentTypeMetrics = []; + private readonly Dictionary receivedTypeMetrics = []; + private readonly Dictionary sentChannelMetrics = []; + private readonly Dictionary receivedChannelMetrics = []; + + public void AppendEntry(MetricEntryValue entry) { + semDataLock.Wait(); + ContractMetric? channelMetric = null; + ContractMetric? typeMetric = null; + if (entry.Sent) + { + sentGlobalMetric.AddMessageRecord(entry.MessageSize, entry.Duration); + if (!sentTypeMetrics.TryGetValue(entry.Type, out typeMetric)) + { + typeMetric = new(); + sentTypeMetrics.Add(entry.Type, typeMetric); + } + if (!string.IsNullOrWhiteSpace(entry.Channel) && !sentChannelMetrics.TryGetValue(entry.Channel, out channelMetric)) + { + channelMetric = new(); + sentChannelMetrics.Add(entry.Channel, channelMetric); + } + } + else + { + receivedGlobalMetric.AddMessageRecord(entry.MessageSize, entry.Duration); + if (!receivedTypeMetrics.TryGetValue(entry.Type, out typeMetric)) + { + typeMetric = new(); + receivedTypeMetrics.Add(entry.Type, typeMetric); + } + if (!string.IsNullOrWhiteSpace(entry.Channel) && !receivedChannelMetrics.TryGetValue(entry.Channel, out channelMetric)) + { + channelMetric = new(); + receivedChannelMetrics.Add(entry.Channel, channelMetric); + } + } + typeMetric?.AddMessageRecord(entry.MessageSize,entry.Duration); + channelMetric?.AddMessageRecord(entry.MessageSize, entry.Duration); + semDataLock.Release(); + } + + public ReadonlyContractMetric GetSnapshot(bool sent) + { + semDataLock.Wait(); + var result = new ReadonlyContractMetric((sent ? sentGlobalMetric : receivedGlobalMetric)); + semDataLock.Release(); + return result; + } + + public ReadonlyContractMetric? GetSnapshot(Type messageType,bool sent) + { + ReadonlyContractMetric? result = null; + semDataLock.Wait(); + if (sent && sentTypeMetrics.TryGetValue(messageType, out var sentValue)) + result = new ReadonlyContractMetric(sentValue!); + else if (!sent && receivedTypeMetrics.TryGetValue(messageType, out var receivedValue)) + result = new ReadonlyContractMetric(receivedValue!); + semDataLock.Release(); + return result; + } + + public ReadonlyContractMetric? GetSnapshot(string channel, bool sent) + { + ReadonlyContractMetric? result = null; + semDataLock.Wait(); + if (sent && sentChannelMetrics.TryGetValue(channel, out var sentValue)) + result = new ReadonlyContractMetric(sentValue!); + else if (!sent && receivedChannelMetrics.TryGetValue(channel, out var receivedValue)) + result = new ReadonlyContractMetric(receivedValue!); + semDataLock.Release(); + return result; + } + } +} diff --git a/Core/Middleware/Metrics/MessageMetric.cs b/Core/Middleware/Metrics/MessageMetric.cs new file mode 100644 index 0000000..4b61516 --- /dev/null +++ b/Core/Middleware/Metrics/MessageMetric.cs @@ -0,0 +1,24 @@ +using System.Diagnostics.Metrics; + +namespace MQContract.Middleware.Metrics +{ + internal record MessageMetric(Counter Sent,Counter SentBytes, Counter Received,Counter ReceivedBytes, + Histogram EncodingDuration, Histogram DecodingDuration) + { + public void AddEntry(MetricEntryValue entry) + { + if (entry.Sent) + { + Sent.Add(1); + SentBytes.Add(entry.MessageSize); + EncodingDuration.Record(entry.Duration.TotalMilliseconds); + } + else + { + Received.Add(1); + ReceivedBytes.Add(entry.MessageSize); + DecodingDuration.Record(entry.Duration.TotalMilliseconds); + } + } + } +} diff --git a/Core/Middleware/Metrics/MetricEntryValue.cs b/Core/Middleware/Metrics/MetricEntryValue.cs new file mode 100644 index 0000000..0f1eee0 --- /dev/null +++ b/Core/Middleware/Metrics/MetricEntryValue.cs @@ -0,0 +1,5 @@ +namespace MQContract.Middleware.Metrics +{ + internal record MetricEntryValue(Type Type, string? Channel,bool Sent, int MessageSize, TimeSpan Duration) + { } +} diff --git a/Core/Middleware/Metrics/ReadonlyContractMetric.cs b/Core/Middleware/Metrics/ReadonlyContractMetric.cs new file mode 100644 index 0000000..f6ee94c --- /dev/null +++ b/Core/Middleware/Metrics/ReadonlyContractMetric.cs @@ -0,0 +1,15 @@ +using MQContract.Interfaces; + +namespace MQContract.Middleware.Metrics +{ + internal record ReadonlyContractMetric(ulong Messages,ulong MessageBytes,ulong MessageBytesAverage, ulong MessageBytesMin, ulong MessageBytesMax, + TimeSpan MessageConversionDuration,TimeSpan MessageConversionAverage,TimeSpan MessageConversionMin, TimeSpan MessageConversionMax + ) + : IContractMetric + { + public ReadonlyContractMetric(ContractMetric metric) + : this(metric.Messages, metric.MessageBytes, metric.MessageBytesAverage, metric.MessageBytesMin, metric.MessageBytesMax, + metric.MessageConversionDuration, metric.MessageConversionAverage, metric.MessageConversionMin, metric.MessageConversionMax) + { } + } +} diff --git a/Core/Middleware/Metrics/SystemMetricTracker.cs b/Core/Middleware/Metrics/SystemMetricTracker.cs new file mode 100644 index 0000000..2f37273 --- /dev/null +++ b/Core/Middleware/Metrics/SystemMetricTracker.cs @@ -0,0 +1,63 @@ +using System.Diagnostics.Metrics; + +namespace MQContract.Middleware.Metrics +{ + internal class SystemMetricTracker + { + private const string MeterName = "mqcontract"; + + + private readonly SemaphoreSlim semDataLock = new(1, 1); + private readonly Meter meter; + private readonly MessageMetric globalMetric; + private readonly Dictionary typeMetrics = []; + private readonly Dictionary channelMetrics = []; + + public SystemMetricTracker() + { + meter = new Meter(MeterName); + globalMetric = new( + meter.CreateCounter($"{MeterName}.messages.sent.count"), + meter.CreateCounter($"{MeterName}.messages.sent.bytes"), + meter.CreateCounter($"{MeterName}.messages.received.count"), + meter.CreateCounter($"{MeterName}.messages.received.bytes"), + meter.CreateHistogram($"{MeterName}.messages.encodingduration", unit: "ms"), + meter.CreateHistogram($"{MeterName}.messages.decodingduration", unit: "ms") + ); + } + + public void AppendEntry(MetricEntryValue entry) + { + globalMetric.AddEntry(entry); + semDataLock.Wait(); + MessageMetric? channelMetric = null; + if (!typeMetrics.TryGetValue(entry.Type, out MessageMetric? typeMetric)) + { + typeMetric = new( + meter.CreateCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.','_')}.sent.count"), + meter.CreateCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.sent.bytes"), + meter.CreateCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.received.count"), + meter.CreateCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.received.bytes"), + meter.CreateHistogram($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.encodingduration"), + meter.CreateHistogram($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.decodingduration") + ); + typeMetrics.Add(entry.Type, typeMetric!); + } + if (!string.IsNullOrWhiteSpace(entry.Channel) && !channelMetrics.TryGetValue(entry.Channel, out channelMetric)) + { + channelMetric = new( + meter.CreateCounter($"{MeterName}.channels.{entry.Channel}.sent.count"), + meter.CreateCounter($"{MeterName}.channels.{entry.Channel}.sent.bytes"), + meter.CreateCounter($"{MeterName}.channels.{entry.Channel}.received.count"), + meter.CreateCounter($"{MeterName}.channels.{entry.Channel}.received.bytes"), + meter.CreateHistogram($"{MeterName}.channels.{entry.Channel}.encodingduration"), + meter.CreateHistogram($"{MeterName}.channels.{entry.Channel}.decodingduration") + ); + channelMetrics.Add(entry.Channel!, channelMetric!); + } + typeMetric?.AddEntry(entry); + channelMetric?.AddEntry(entry); + semDataLock.Release(); + } + } +} diff --git a/Core/Middleware/MetricsMiddleware.cs b/Core/Middleware/MetricsMiddleware.cs new file mode 100644 index 0000000..f24f0c1 --- /dev/null +++ b/Core/Middleware/MetricsMiddleware.cs @@ -0,0 +1,89 @@ +using MQContract.Interfaces; +using MQContract.Interfaces.Middleware; +using MQContract.Messages; +using MQContract.Middleware.Metrics; +using System.Diagnostics; +using System.Threading.Channels; + +namespace MQContract.Middleware +{ + internal class MetricsMiddleware : IBeforeEncodeMiddleware, IAfterEncodeMiddleware,IBeforeDecodeMiddleware,IAfterDecodeMiddleware + { + private const string StopWatchKey = "_MetricStopwatch"; + private const string MessageReceivedChannelKey = "_MetricMessageReceivedChannel"; + private const string MessageRecievedSizeKey = "_MetricMessageRecievedSize"; + + private readonly SystemMetricTracker? systemTracker; + private readonly InternalMetricTracker? internalTracker; + private readonly Channel channel = Channel.CreateUnbounded(); + + public MetricsMiddleware(bool useMeter,bool useInternal) + { + if (useMeter) + systemTracker=new(); + if (useInternal) + internalTracker=new(); + Start(); + } + + private void Start() + { + Task.Run(async () => + { + while (await channel.Reader.WaitToReadAsync()) + { + var entry = await channel.Reader.ReadAsync(); + if (entry != null) + { + systemTracker?.AppendEntry(entry!); + internalTracker?.AppendEntry(entry!); + } + } + }); + } + + public IContractMetric? GetSnapshot(bool sent) + => internalTracker?.GetSnapshot(sent); + public IContractMetric? GetSnapshot(Type messageType, bool sent) + => internalTracker?.GetSnapshot(messageType, sent); + public IContractMetric? GetSnapshot(string channel, bool sent) + => internalTracker?.GetSnapshot(channel,sent); + + private async ValueTask AddStat(Type messageType, string? channel, bool sending, int messageSize, Stopwatch? stopWatch) + => await this.channel.Writer.WriteAsync(new(messageType, channel, sending, messageSize, stopWatch?.Elapsed??TimeSpan.Zero)); + + public async ValueTask<(T message, MessageHeader messageHeader)> AfterMessageDecodeAsync(IContext context, T message, string ID, MessageHeader messageHeader, DateTime receivedTimestamp, DateTime processedTimeStamp) + { + var stopWatch = (Stopwatch?)context[StopWatchKey]; + stopWatch?.Stop(); + await AddStat(typeof(T), (string?)context[MessageReceivedChannelKey]??string.Empty, false, (int?)context[MessageRecievedSizeKey]??0, stopWatch); + return (message,messageHeader); + } + + public async ValueTask AfterMessageEncodeAsync(Type messageType, IContext context, ServiceMessage message) + { + var stopWatch = (Stopwatch?)context[StopWatchKey]; + stopWatch?.Stop(); + await AddStat(messageType, message.Channel,true,message.Data.Length,stopWatch); + return message; + } + + public ValueTask<(MessageHeader messageHeader, ReadOnlyMemory data)> BeforeMessageDecodeAsync(IContext context, string id, MessageHeader messageHeader, string messageTypeID,string messageChannel, ReadOnlyMemory data) + { + context[MessageReceivedChannelKey] = messageChannel; + context[MessageRecievedSizeKey] = data.Length; + var stopwatch = new Stopwatch(); + context[StopWatchKey] = stopwatch; + stopwatch.Start(); + return ValueTask.FromResult((messageHeader, data)); + } + + public ValueTask<(T message, string? channel, MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, T message, string? channel, MessageHeader messageHeader) + { + var stopwatch = new Stopwatch(); + context[StopWatchKey] = stopwatch; + stopwatch.Start(); + return ValueTask.FromResult((message, channel, messageHeader)); + } + } +} diff --git a/Core/Readme.md b/Core/Readme.md index 794ed24..1325dd1 100644 --- a/Core/Readme.md +++ b/Core/Readme.md @@ -25,19 +25,8 @@ - [AddQuerySubscriptionMap(originalChannel,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-String,System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.String,System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [AddQuerySubscriptionMap(isMatch,mapFunction)](#M-MQContract-ChannelMapper-AddQuerySubscriptionMap-System-Func{System-String,System-Boolean},System-Func{System-String,System-Threading-Tasks-ValueTask{System-String}}- 'MQContract.ChannelMapper.AddQuerySubscriptionMap(System.Func{System.String,System.Boolean},System.Func{System.String,System.Threading.Tasks.ValueTask{System.String}})') - [ContractConnection](#T-MQContract-ContractConnection 'MQContract.ContractConnection') - - [#ctor(serviceConnection,defaultMessageEncoder,defaultMessageEncryptor,serviceProvider,logger,channelMapper)](#M-MQContract-ContractConnection-#ctor-MQContract-Interfaces-Service-IMessageServiceConnection,MQContract-Interfaces-Encoding-IMessageEncoder,MQContract-Interfaces-Encrypting-IMessageEncryptor,System-IServiceProvider,Microsoft-Extensions-Logging-ILogger,MQContract-ChannelMapper- 'MQContract.ContractConnection.#ctor(MQContract.Interfaces.Service.IMessageServiceConnection,MQContract.Interfaces.Encoding.IMessageEncoder,MQContract.Interfaces.Encrypting.IMessageEncryptor,System.IServiceProvider,Microsoft.Extensions.Logging.ILogger,MQContract.ChannelMapper)') - - [CloseAsync()](#M-MQContract-ContractConnection-CloseAsync 'MQContract.ContractConnection.CloseAsync') - - [Dispose()](#M-MQContract-ContractConnection-Dispose 'MQContract.ContractConnection.Dispose') - - [DisposeAsync()](#M-MQContract-ContractConnection-DisposeAsync 'MQContract.ContractConnection.DisposeAsync') - - [PingAsync()](#M-MQContract-ContractConnection-PingAsync 'MQContract.ContractConnection.PingAsync') - - [PublishAsync\`\`1(message,channel,messageHeader,cancellationToken)](#M-MQContract-ContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.ContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') - - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') - - [QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-ContractConnection-QueryAsync``2-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.ContractConnection.QueryAsync``2(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') - - [SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-ContractConnection-SubscribeAsync``1-System-Action{MQContract-Interfaces-IRecievedMessage{``0}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeAsync``1(System.Action{MQContract.Interfaces.IRecievedMessage{``0}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') - - [SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryAsyncResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},System-Threading-Tasks-ValueTask{MQContract-Messages-QueryResponseMessage{``1}}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryAsyncResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') - - [SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken)](#M-MQContract-ContractConnection-SubscribeQueryResponseAsync``2-System-Func{MQContract-Interfaces-IRecievedMessage{``0},MQContract-Messages-QueryResponseMessage{``1}},System-Action{System-Exception},System-String,System-String,System-Boolean,System-Threading-CancellationToken- 'MQContract.ContractConnection.SubscribeQueryResponseAsync``2(System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}},System.Action{System.Exception},System.String,System.String,System.Boolean,System.Threading.CancellationToken)') -- [InvalidQueryResponseMessageRecieved](#T-MQContract-InvalidQueryResponseMessageRecieved 'MQContract.InvalidQueryResponseMessageRecieved') + - [Instance(serviceConnection,defaultMessageEncoder,defaultMessageEncryptor,serviceProvider,logger,channelMapper)](#M-MQContract-ContractConnection-Instance-MQContract-Interfaces-Service-IMessageServiceConnection,MQContract-Interfaces-Encoding-IMessageEncoder,MQContract-Interfaces-Encrypting-IMessageEncryptor,System-IServiceProvider,Microsoft-Extensions-Logging-ILogger,MQContract-ChannelMapper- 'MQContract.ContractConnection.Instance(MQContract.Interfaces.Service.IMessageServiceConnection,MQContract.Interfaces.Encoding.IMessageEncoder,MQContract.Interfaces.Encrypting.IMessageEncryptor,System.IServiceProvider,Microsoft.Extensions.Logging.ILogger,MQContract.ChannelMapper)') +- [InvalidQueryResponseMessageReceived](#T-MQContract-InvalidQueryResponseMessageReceived 'MQContract.InvalidQueryResponseMessageReceived') - [MessageChannelNullException](#T-MQContract-MessageChannelNullException 'MQContract.MessageChannelNullException') - [MessageConversionException](#T-MQContract-MessageConversionException 'MQContract.MessageConversionException') - [QueryExecutionFailedException](#T-MQContract-QueryExecutionFailedException 'MQContract.QueryExecutionFailedException') @@ -421,20 +410,18 @@ MQContract ##### Summary -This is the primary class for this library and is used to create a Contract style connection between systems using the underlying service connection layer. +The primary ContractConnection item which implements IContractConnection -##### Parameters + +### Instance(serviceConnection,defaultMessageEncoder,defaultMessageEncryptor,serviceProvider,logger,channelMapper) `method` -| Name | Type | Description | -| ---- | ---- | ----------- | -| serviceConnection | [T:MQContract.ContractConnection](#T-T-MQContract-ContractConnection 'T:MQContract.ContractConnection') | The service connection implementation to use for the underlying message requests. | +##### Summary - -### #ctor(serviceConnection,defaultMessageEncoder,defaultMessageEncryptor,serviceProvider,logger,channelMapper) `constructor` +This is the call used to create an instance of a Contract Connection which will return the Interface -##### Summary +##### Returns -This is the primary class for this library and is used to create a Contract style connection between systems using the underlying service connection layer. +An instance of IContractConnection ##### Parameters @@ -448,293 +435,8 @@ This is the primary class for this library and is used to create a Contract styl | channelMapper | [MQContract.ChannelMapper](#T-MQContract-ChannelMapper 'MQContract.ChannelMapper') | An instance of a ChannelMapper used to translate channels from one instance to another based on class channel attributes or supplied channels if necessary. For example, it might be necessary for a Nats.IO instance when you are trying to read from a stored message stream that is comprised of another channel or set of channels | - -### CloseAsync() `method` - -##### Summary - -Called to close off this connection and it's underlying service connection - -##### Returns - - - -##### Parameters - -This method has no parameters. - - -### Dispose() `method` - -##### Summary - -Called to dispose of the resources contained within - -##### Parameters - -This method has no parameters. - - -### DisposeAsync() `method` - -##### Summary - -Called to dispose of the resources contained within - -##### Returns - -A task of the underlying resources being disposed - -##### Parameters - -This method has no parameters. - - -### PingAsync() `method` - -##### Summary - -Called to execute a ping against the service layer - -##### Returns - -The ping result from the service layer, if supported - -##### Parameters - -This method has no parameters. - - -### PublishAsync\`\`1(message,channel,messageHeader,cancellationToken) `method` - -##### Summary - -Called to publish a message out into the service layer in the Pub/Sub style - -##### Returns - -An instance of the TransmissionResult record to indicate success or failure and an ID - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [\`\`0](#T-``0 '``0') | The instance of the message to publish | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on | -| messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | A message header to be sent across with the message | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Generic Types - -| Name | Description | -| ---- | ----------- | -| T | The type of message to publish | - - -### QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,cancellationToken) `method` - -##### Summary - -Called to publish a message in the Query/Response style except the response Type is gathered from the QueryResponseTypeAttribute - -##### Returns - -A QueryResult that will contain the response message and or an error - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [\`\`0](#T-``0 '``0') | The message to transmit for the query | -| timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The timeout to allow for waiting for a response | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on | -| responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is -only used when the underlying connection does not support a QueryResponse style messaging. | -| messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | A message header to be sent across with the message | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Generic Types - -| Name | Description | -| ---- | ----------- | -| Q | The type of message to transmit for the Query | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.UnknownResponseTypeException](#T-MQContract-UnknownResponseTypeException 'MQContract.UnknownResponseTypeException') | Thrown when the supplied Query type does not have a QueryResponseTypeAttribute and therefore a response type cannot be determined | - - -### QueryAsync\`\`2(message,timeout,channel,responseChannel,messageHeader,cancellationToken) `method` - -##### Summary - -Called to publish a message in the Query/Response style - -##### Returns - -A QueryResult that will contain the response message and or an error - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| message | [\`\`0](#T-``0 '``0') | The message to transmit for the query | -| timeout | [System.Nullable{System.TimeSpan}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Nullable 'System.Nullable{System.TimeSpan}') | The timeout to allow for waiting for a response | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to transmit the message on | -| responseChannel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Specifies the message channel to use for the response. The preferred method is using the QueryResponseChannelAttribute on the class. This is -only used when the underlying connection does not support a QueryResponse style messaging. | -| messageHeader | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | A message header to be sent across with the message | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Generic Types - -| Name | Description | -| ---- | ----------- | -| Q | The type of message to transmit for the Query | -| R | The type of message expected as a response | - - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` - -##### Summary - -Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages asynchronously - -##### Returns - -An instance of the Subscription that can be held or called to end - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask}') | The callback to be executed when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to be executed when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | -| ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Generic Types - -| Name | Description | -| ---- | ----------- | -| T | The type of message to listen for | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - - -### SubscribeAsync\`\`1(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` - -##### Summary - -Called to establish a Subscription in the sevice layer for the Pub/Sub style messaging processing messages synchronously - -##### Returns - -An instance of the Subscription that can be held or called to end - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Action{MQContract.Interfaces.IRecievedMessage{\`\`0}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Interfaces.IRecievedMessage{``0}}') | The callback to be executed when a message is recieved | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to be executed when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | -| ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Generic Types - -| Name | Description | -| ---- | ----------- | -| T | The type of message to listen for | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - - -### SubscribeQueryAsyncResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` - -##### Summary - -Creates a subscription with the underlying service layer for the Query/Response style processing messages asynchronously - -##### Returns - -An instance of the Subscription that can be held or called to end - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{\`\`1}}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},System.Threading.Tasks.ValueTask{MQContract.Messages.QueryResponseMessage{``1}}}') | The callback to be executed when a message is recieved and expects a returned response | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to be executed when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | -| ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Generic Types - -| Name | Description | -| ---- | ----------- | -| Q | The expected message type for the Query | -| R | The expected message type for the Response | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - - -### SubscribeQueryResponseAsync\`\`2(messageRecieved,errorRecieved,channel,group,ignoreMessageHeader,cancellationToken) `method` - -##### Summary - -Creates a subscription with the underlying service layer for the Query/Response style processing messages synchronously - -##### Returns - -An instance of the Subscription that can be held or called to end - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| messageRecieved | [System.Func{MQContract.Interfaces.IRecievedMessage{\`\`0},MQContract.Messages.QueryResponseMessage{\`\`1}}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{MQContract.Interfaces.IRecievedMessage{``0},MQContract.Messages.QueryResponseMessage{``1}}') | The callback to be executed when a message is recieved and expects a returned response | -| errorRecieved | [System.Action{System.Exception}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{System.Exception}') | The callback to be executed when an error occurs | -| channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to override the MessageChannelAttribute from the class or to specify a channel to listen for messages on | -| group | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | Used to specify a group to associate to at the service layer (refer to groups in KubeMQ, Nats.IO, etc) | -| ignoreMessageHeader | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If set to true this will cause the subscription to ignore the message type specified and assume that the type of message is of type T | -| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | - -##### Generic Types - -| Name | Description | -| ---- | ----------- | -| Q | The expected message type for the Query | -| R | The expected message type for the Response | - -##### Exceptions - -| Name | Description | -| ---- | ----------- | -| [MQContract.SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') | An exception thrown when the subscription has failed to establish | - - -## InvalidQueryResponseMessageRecieved `type` + +## InvalidQueryResponseMessageReceived `type` ##### Namespace @@ -742,7 +444,7 @@ MQContract ##### Summary -Thrown when a query call message is recieved without proper data +Thrown when a query call message is received without proper data ## MessageChannelNullException `type` diff --git a/Core/Subscriptions/PubSubSubscription.cs b/Core/Subscriptions/PubSubSubscription.cs index ab84cd9..e75c172 100644 --- a/Core/Subscriptions/PubSubSubscription.cs +++ b/Core/Subscriptions/PubSubSubscription.cs @@ -1,12 +1,10 @@ using Microsoft.Extensions.Logging; -using MQContract.Interfaces; -using MQContract.Interfaces.Factories; using MQContract.Interfaces.Service; using MQContract.Messages; namespace MQContract.Subscriptions { - internal sealed class PubSubSubscription(IMessageFactory messageFactory, Func, ValueTask> messageRecieved, Action errorRecieved, + internal sealed class PubSubSubscription(Func messageReceived, Action errorReceived, Func> mapChannel, string? channel = null, string? group = null, bool synchronous=false,ILogger? logger=null) : SubscriptionBase(mapChannel,channel,synchronous) @@ -16,7 +14,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio { serviceSubscription = await connection.SubscribeAsync( async serviceMessage => await ProcessMessage(serviceMessage), - error => errorRecieved(error), + error => errorReceived(error), MessageChannel, group:group, cancellationToken: cancellationToken @@ -26,21 +24,18 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio return true; } - private async ValueTask ProcessMessage(RecievedServiceMessage serviceMessage) + private async ValueTask ProcessMessage(ReceivedServiceMessage serviceMessage) { try { - var taskMessage = await messageFactory.ConvertMessageAsync(logger, serviceMessage) - ??throw new InvalidCastException($"Unable to convert incoming message {serviceMessage.MessageTypeID} to {typeof(T).FullName}"); - var tsk = messageRecieved(new RecievedMessage(serviceMessage.ID, taskMessage!, serviceMessage.Header, serviceMessage.RecievedTimestamp, DateTime.Now)); + var tsk = messageReceived(serviceMessage); + await tsk.ConfigureAwait(!Synchronous); if (serviceMessage.Acknowledge!=null) await serviceMessage.Acknowledge(); - if (Synchronous) - await tsk.ConfigureAwait(false); } catch (Exception e) { - errorRecieved(e); + errorReceived(e); } } } diff --git a/Core/Subscriptions/QueryResponseHelper.cs b/Core/Subscriptions/QueryResponseHelper.cs index 1999237..ff5c2b3 100644 --- a/Core/Subscriptions/QueryResponseHelper.cs +++ b/Core/Subscriptions/QueryResponseHelper.cs @@ -35,7 +35,7 @@ public static ServiceMessage EncodeMessage(ServiceMessage originalMessage, Guid originalMessage.Data ); - public static bool IsValidMessage(RecievedServiceMessage serviceMessage) + public static bool IsValidMessage(ReceivedServiceMessage serviceMessage) => REQUIRED_HEADERS.All(key=>serviceMessage.Header.Keys.Contains(key)); public static async Task, CancellationTokenSource>> StartResponseListenerAsync(IMessageServiceConnection connection,TimeSpan timeout,Guid identifier,Guid callID,string replyChannel,CancellationToken cancellationToken) diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index d131705..77d091b 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -1,13 +1,12 @@ using Microsoft.Extensions.Logging; -using MQContract.Interfaces; -using MQContract.Interfaces.Factories; using MQContract.Interfaces.Service; using MQContract.Messages; namespace MQContract.Subscriptions { - internal sealed class QueryResponseSubscription(IMessageFactory queryMessageFactory,IMessageFactory responseMessageFactory, - Func, ValueTask>> messageRecieved, Action errorRecieved, + internal sealed class QueryResponseSubscription( + Func> processMessage, + Action errorReceived, Func> mapChannel, string? channel = null, string? group = null, bool synchronous=false,ILogger? logger=null) @@ -23,7 +22,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio if (connection is IQueryableMessageServiceConnection queryableMessageServiceConnection) serviceSubscription = await queryableMessageServiceConnection.SubscribeQueryAsync( serviceMessage => ProcessServiceMessageAsync(serviceMessage), - error => errorRecieved(error), + error => errorReceived(error), MessageChannel, group:group, cancellationToken: cancellationToken @@ -34,7 +33,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio async (serviceMessage) => { if (!QueryResponseHelper.IsValidMessage(serviceMessage)) - errorRecieved(new InvalidQueryResponseMessageRecieved()); + errorReceived(new InvalidQueryResponseMessageReceived()); else { var result = await ProcessServiceMessageAsync( @@ -49,7 +48,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio await connection.PublishAsync(QueryResponseHelper.EncodeMessage(result, queryClientID, replyID, null, replyChannel), cancellationToken); } }, - error => errorRecieved(error), + error => errorReceived(error), MessageChannel, cancellationToken: cancellationToken ); @@ -57,7 +56,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio return serviceSubscription!=null; } - private async ValueTask ProcessServiceMessageAsync(RecievedServiceMessage message) + private async ValueTask ProcessServiceMessageAsync(ReceivedServiceMessage message) { if (Synchronous&&!(token?.IsCancellationRequested??false)) manualResetEvent!.Wait(cancellationToken:token!.Token); @@ -65,15 +64,12 @@ private async ValueTask ProcessServiceMessageAsync(RecievedServi ServiceMessage? response = null; try { - var taskMessage = await queryMessageFactory.ConvertMessageAsync(logger, message) - ??throw new InvalidCastException($"Unable to convert incoming message {message.MessageTypeID} to {typeof(Q).FullName}"); - var result = await messageRecieved(new RecievedMessage(message.ID, taskMessage,message.Header,message.RecievedTimestamp,DateTime.Now)); - response = await responseMessageFactory.ConvertMessageAsync(result.Message, message.Channel, new MessageHeader(result.Headers)); + response = await processMessage(message); if (message.Acknowledge!=null) await message.Acknowledge(); }catch(Exception e) { - errorRecieved(e); + errorReceived(e); error=e; } if (Synchronous) diff --git a/Core/Utility.cs b/Core/Utility.cs index fcfe103..9b8f5e7 100644 --- a/Core/Utility.cs +++ b/Core/Utility.cs @@ -1,9 +1,16 @@ -using System.Reflection; +using MQContract.Attributes; +using System.Reflection; namespace MQContract { internal static class Utility { + internal static string MessageTypeName() + => MessageTypeName(typeof(T)); + + internal static string MessageTypeName(Type messageType) + => messageType.GetCustomAttributes().Select(mn => mn.Value).FirstOrDefault(TypeName(messageType)); + internal static string TypeName() => TypeName(typeof(T)); @@ -15,6 +22,12 @@ internal static string TypeName(Type type) return result; } + internal static string MessageVersionString() + => MessageVersionString(typeof(T)); + + internal static string MessageVersionString(Type messageType) + =>messageType.GetCustomAttributes().Select(mc => mc.Version.ToString()).FirstOrDefault("0.0.0.0"); + internal static async ValueTask InvokeMethodAsync(MethodInfo method,object container, object?[]? parameters) { var valueTask = method.Invoke(container, parameters)!; diff --git a/Samples/Messages/SampleExecution.cs b/Samples/Messages/SampleExecution.cs index 2bf86e0..943c854 100644 --- a/Samples/Messages/SampleExecution.cs +++ b/Samples/Messages/SampleExecution.cs @@ -1,5 +1,7 @@ using MQContract; +using MQContract.Interfaces; using MQContract.Interfaces.Service; +using System.Text.Json; namespace Messages { @@ -13,12 +15,13 @@ public static async ValueTask ExecuteSample(IMessageServiceConnection serviceCon sourceCancel.Cancel(); }; - var contractConnection = new ContractConnection(serviceConnection,channelMapper:mapper); + var contractConnection = ContractConnection.Instance(serviceConnection,channelMapper:mapper); + contractConnection.AddMetrics(false, true); var announcementSubscription = await contractConnection.SubscribeAsync( (announcement) => { - Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); + Console.WriteLine($"Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.ReceivedTimestamp}]"); return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Announcement error: {error.Message}"), @@ -28,8 +31,8 @@ public static async ValueTask ExecuteSample(IMessageServiceConnection serviceCon var greetingSubscription = await contractConnection.SubscribeQueryResponseAsync( (greeting) => { - Console.WriteLine($"Greeting recieved for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.RecievedTimestamp}]"); - System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.RecievedTimestamp).TotalMilliseconds}ms"); + Console.WriteLine($"Greeting received for {greeting.Message.LastName}, {greeting.Message.FirstName}. [{greeting.ID},{greeting.ReceivedTimestamp}]"); + System.Diagnostics.Debug.WriteLine($"Time to convert message: {greeting.ProcessedTimestamp.Subtract(greeting.ReceivedTimestamp).TotalMilliseconds}ms"); return new( $"Welcome {greeting.Message.FirstName} {greeting.Message.LastName} to the {serviceName} sample" ); @@ -41,7 +44,7 @@ public static async ValueTask ExecuteSample(IMessageServiceConnection serviceCon var storedArrivalSubscription = await contractConnection.SubscribeAsync( (announcement) => { - Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.RecievedTimestamp}]"); + Console.WriteLine($"Stored Announcing the arrival of {announcement.Message.LastName}, {announcement.Message.FirstName}. [{announcement.ID},{announcement.ReceivedTimestamp}]"); return ValueTask.CompletedTask; }, (error) => Console.WriteLine($"Stored Announcement error: {error.Message}"), @@ -77,6 +80,16 @@ await Task.WhenAll( sourceCancel.Token.WaitHandle.WaitOne(); Console.WriteLine("System completed operation"); + var jsonOptions = new JsonSerializerOptions() + { + WriteIndented = true + }; + Console.WriteLine($"Greetings Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting),true),jsonOptions)}"); + Console.WriteLine($"Greetings Recieved: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting),false), jsonOptions)}"); + Console.WriteLine($"StoredArrivals Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(ArrivalAnnouncement), true), jsonOptions)}"); + Console.WriteLine($"StoredArrivals Recieved: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(ArrivalAnnouncement), false), jsonOptions)}"); + Console.WriteLine($"Arrivals Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(StoredArrivalAnnouncement), true), jsonOptions)}"); + Console.WriteLine($"Arrivals Recieved: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(StoredArrivalAnnouncement), false), jsonOptions)}"); } } } From d0159e2a4c13f853914e66c3f742fadc5051c62f Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Mon, 23 Sep 2024 14:50:42 -0400 Subject: [PATCH 15/22] added metric tests added in unit testing for the metrics --- .../Interfaces/IContractConnection.cs | 35 ++- Abstractions/Readme.md | 39 ++- AutomatedTesting/AutomatedTesting.csproj | 1 + .../InternalMetricTests.cs | 288 ++++++++++++++++++ .../SystemMetricTests.cs | 256 ++++++++++++++++ Core/ContractConnection.Metrics.cs | 8 +- Core/ContractConnection.Middleware.cs | 1 - Core/ContractConnection.cs | 10 +- Core/Factories/MessageTypeFactory.cs | 6 +- Core/Interfaces/Factories/IMessageFactory.cs | 2 +- Core/Middleware/Metrics/ContractMetric.cs | 4 +- Core/Middleware/Metrics/MessageMetric.cs | 2 +- .../Middleware/Metrics/SystemMetricTracker.cs | 36 +-- Core/Middleware/MetricsMiddleware.cs | 11 +- .../QueryResponseSubscription.cs | 15 +- Samples/Messages/SampleExecution.cs | 2 +- 16 files changed, 655 insertions(+), 61 deletions(-) create mode 100644 AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs create mode 100644 AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index 07a8bed..aca0acd 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -1,5 +1,6 @@ using MQContract.Interfaces.Middleware; using MQContract.Messages; +using System.Diagnostics.Metrics; namespace MQContract.Interfaces { @@ -45,9 +46,31 @@ IContractConnection RegisterMiddleware(Func constructInstance) /// /// Called to activate the metrics tracking middleware for this connection instance /// - /// Indicates if the Meter style metrics should be enabled + /// The Meter item to create all system metrics against /// Indicates if the internal metrics collector should be used - void AddMetrics(bool useMeter, bool useInternal); + /// The Contract Connection instance to allow chaining calls + /// + /// For the Meter metrics, all durations are in ms and the following values and patterns will apply: + /// mqcontract.messages.sent.count = count of messages sent (Counter) + /// mqcontract.messages.sent.bytes = count of bytes sent (message data) (Counter) + /// mqcontract.messages.received.count = count of messages received (Counter) + /// mqcontract.messages.received.bytes = count of bytes received (message data) (Counter) + /// mqcontract.messages.encodingduration = milliseconds to encode messages (Histogram) + /// mqcontract.messages.decodingduration = milliseconds to decode messages (Histogram) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.sent.count = count of messages sent of a given type (Counter) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.sent.bytes = count of bytes sent (message data) of a given type (Counter) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.received.count = count of messages received of a given type (Counter) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.received.bytes = count of bytes received (message data) of a given type (Counter) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.encodingduration = milliseconds to encode messages of a given type (Histogram) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.decodingduration = milliseconds to decode messages of a given type (Histogram) + /// mqcontract.channels.{Channel}.sent.count = count of messages sent for a given channel (Counter) + /// mqcontract.channels.{Channel}.sent.bytes = count of bytes sent (message data) for a given channel (Counter) + /// mqcontract.channels.{Channel}.received.count = count of messages received for a given channel (Counter) + /// mqcontract.channels.{Channel}.received.bytes = count of bytes received (message data) for a given channel (Counter) + /// mqcontract.channels.{Channel}.encodingduration = milliseconds to encode messages for a given channel (Histogram) + /// mqcontract.channels.{Channel}.decodingduration = milliseconds to decode messages for a given channel (Histogram) + /// + IContractConnection AddMetrics(Meter? meter, bool useInternal); /// /// Called to get a snapshot of the current global metrics. Will return null if internal metrics are not enabled. /// @@ -62,6 +85,14 @@ IContractConnection RegisterMiddleware(Func constructInstance) /// A record of the current metric snapshot or null if not available IContractMetric? GetSnapshot(Type messageType,bool sent); /// + /// Called to get a snapshot of the metrics for a given message type. Will return null if internal metrics are not enabled. + /// + /// The type of message to look for + /// true when the sent metrics are desired, false when received are desired + /// A record of the current metric snapshot or null if not available + IContractMetric? GetSnapshot(bool sent) + where T : class; + /// /// Called to get a snapshot of the metrics for a given message channel. Will return null if internal metrics are not enabled. /// /// The channel to look for diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index 4ebd238..94f10e4 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -18,11 +18,11 @@ - [IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') - [Item](#P-MQContract-Interfaces-Middleware-IContext-Item-System-String- 'MQContract.Interfaces.Middleware.IContext.Item(System.String)') - [IContractConnection](#T-MQContract-Interfaces-IContractConnection 'MQContract.Interfaces.IContractConnection') - - [AddMetrics(useMeter,useInternal)](#M-MQContract-Interfaces-IContractConnection-AddMetrics-System-Boolean,System-Boolean- 'MQContract.Interfaces.IContractConnection.AddMetrics(System.Boolean,System.Boolean)') - [CloseAsync()](#M-MQContract-Interfaces-IContractConnection-CloseAsync 'MQContract.Interfaces.IContractConnection.CloseAsync') - [GetSnapshot(sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot-System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot(System.Boolean)') - [GetSnapshot(messageType,sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot-System-Type,System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot(System.Type,System.Boolean)') - [GetSnapshot(channel,sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot-System-String,System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot(System.String,System.Boolean)') + - [GetSnapshot\`\`1(sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot``1-System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot``1(System.Boolean)') - [PingAsync()](#M-MQContract-Interfaces-IContractConnection-PingAsync 'MQContract.Interfaces.IContractConnection.PingAsync') - [PublishAsync\`\`1(message,channel,messageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-PublishAsync``1-``0,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.PublishAsync``1(``0,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') - [QueryAsync\`\`1(message,timeout,channel,responseChannel,messageHeader,cancellationToken)](#M-MQContract-Interfaces-IContractConnection-QueryAsync``1-``0,System-Nullable{System-TimeSpan},System-String,System-String,MQContract-Messages-MessageHeader,System-Threading-CancellationToken- 'MQContract.Interfaces.IContractConnection.QueryAsync``1(``0,System.Nullable{System.TimeSpan},System.String,System.String,MQContract.Messages.MessageHeader,System.Threading.CancellationToken)') @@ -385,20 +385,6 @@ MQContract.Interfaces This interface represents the Core class for the MQContract system, IE the ContractConnection - -### AddMetrics(useMeter,useInternal) `method` - -##### Summary - -Called to activate the metrics tracking middleware for this connection instance - -##### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| useMeter | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if the Meter style metrics should be enabled | -| useInternal | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if the internal metrics collector should be used | - ### CloseAsync() `method` @@ -467,6 +453,29 @@ A record of the current metric snapshot or null if not available | channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel to look for | | sent | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | true when the sent metrics are desired, false when received are desired | + +### GetSnapshot\`\`1(sent) `method` + +##### Summary + +Called to get a snapshot of the metrics for a given message type. Will return null if internal metrics are not enabled. + +##### Returns + +A record of the current metric snapshot or null if not available + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| sent | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | true when the sent metrics are desired, false when received are desired | + +##### Generic Types + +| Name | Description | +| ---- | ----------- | +| T | The type of message to look for | + ### PingAsync() `method` diff --git a/AutomatedTesting/AutomatedTesting.csproj b/AutomatedTesting/AutomatedTesting.csproj index 9a85329..1689474 100644 --- a/AutomatedTesting/AutomatedTesting.csproj +++ b/AutomatedTesting/AutomatedTesting.csproj @@ -10,6 +10,7 @@ + diff --git a/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs b/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs new file mode 100644 index 0000000..4b372d2 --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs @@ -0,0 +1,288 @@ +using AutomatedTesting.Messages; +using Moq; +using MQContract.Interfaces.Service; +using MQContract; +using MQContract.Interfaces; + +namespace AutomatedTesting.ContractConnectionTests +{ + [TestClass] + public class InternalMetricTests + { + [TestMethod] + public void TestInternalMetricInitialization() + { + #region Arrange + var channel = "TestInternalMetricsChannel"; + + var serviceConnection = new Mock(); + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .AddMetrics(null, true); + #endregion + + #region Act + IContractMetric?[] sentMetrics = [ + contractConnection.GetSnapshot(true), + contractConnection.GetSnapshot(typeof(BasicMessage),true), + contractConnection.GetSnapshot(true), + contractConnection.GetSnapshot(channel,true) + ]; + IContractMetric?[] receivedMetrics = [ + contractConnection.GetSnapshot(false), + contractConnection.GetSnapshot(typeof(BasicMessage),false), + contractConnection.GetSnapshot(false), + contractConnection.GetSnapshot(channel,false) + ]; + #endregion + + #region Assert + Assert.IsNotNull(sentMetrics[0]); + Assert.IsNotNull(receivedMetrics[0]); + Assert.IsFalse(sentMetrics.Skip(1).Any(m => m!=null)); + Assert.AreEqual(ulong.MaxValue, sentMetrics[0]?.MessageBytesMin); + Assert.AreEqual(0, sentMetrics[0]?.MessageBytes); + Assert.AreEqual(ulong.MinValue, sentMetrics[0]?.MessageBytesMax); + Assert.AreEqual(0, sentMetrics[0]?.MessageBytesAverage); + Assert.AreEqual(0, sentMetrics[0]?.Messages); + Assert.AreEqual(TimeSpan.MaxValue, sentMetrics[0]?.MessageConversionMin); + Assert.AreEqual(TimeSpan.MinValue, sentMetrics[0]?.MessageConversionMax); + Assert.AreEqual(TimeSpan.Zero, sentMetrics[0]?.MessageConversionAverage); + Assert.AreEqual(TimeSpan.Zero, sentMetrics[0]?.MessageConversionDuration); + + Assert.IsFalse(receivedMetrics.Skip(1).Any(m => m!=null)); + Assert.AreEqual(ulong.MaxValue, receivedMetrics[0]?.MessageBytesMin); + Assert.AreEqual(0, receivedMetrics[0]?.MessageBytes); + Assert.AreEqual(ulong.MinValue, receivedMetrics[0]?.MessageBytesMax); + Assert.AreEqual(0, receivedMetrics[0]?.MessageBytesAverage); + Assert.AreEqual(0, receivedMetrics[0]?.Messages); + Assert.AreEqual(TimeSpan.MaxValue, receivedMetrics[0]?.MessageConversionMin); + Assert.AreEqual(TimeSpan.MinValue, receivedMetrics[0]?.MessageConversionMax); + Assert.AreEqual(TimeSpan.Zero, receivedMetrics[0]?.MessageConversionAverage); + Assert.AreEqual(TimeSpan.Zero, receivedMetrics[0]?.MessageConversionDuration); + #endregion + + #region Verify + #endregion + } + + [TestMethod] + public async Task TestPublishAsyncSubscribeInternalMetrics() + { + #region Arrange + var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); + var serviceSubscription = new Mock(); + + var testMessage = new BasicMessage("testMessage"); + var channel = "TestInternalMetricsChannel"; + + List messages = []; + var actions = new List>(); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => + { + var rmessage = Helper.ProduceReceivedServiceMessage(message); + foreach (var act in actions) + act(rmessage); + return ValueTask.FromResult(transmissionResult); + }); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .AddMetrics(null,true); + #endregion + + #region Act + var subscription = await contractConnection.SubscribeAsync( + (msg) => ValueTask.CompletedTask, + (error) => { }, + channel:channel); + var result = await contractConnection.PublishAsync(testMessage,channel:channel); + _ = await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1)); + IContractMetric?[] sentMetrics = [ + contractConnection.GetSnapshot(true), + contractConnection.GetSnapshot(typeof(BasicMessage),true), + contractConnection.GetSnapshot(true), + contractConnection.GetSnapshot(channel,true) + ]; + IContractMetric?[] receivedMetrics = [ + contractConnection.GetSnapshot(false), + contractConnection.GetSnapshot(typeof(BasicMessage),false), + contractConnection.GetSnapshot(false), + contractConnection.GetSnapshot(channel,false) + ]; + #endregion + + #region Assert + Assert.IsNotNull(result); + Assert.AreEqual(transmissionResult, result); + Assert.IsFalse(Array.Exists(sentMetrics,(m => m==null))); + Assert.AreEqual(1, sentMetrics[0]?.Messages); + Assert.IsTrue(Array.TrueForAll(sentMetrics,(m => + Equals(sentMetrics[0]?.MessageBytesMin,m?.MessageBytesMin) && + Equals(sentMetrics[0]?.MessageBytes, m?.MessageBytes) && + Equals(sentMetrics[0]?.MessageBytesMax, m?.MessageBytesMax) && + Equals(sentMetrics[0]?.MessageBytesAverage, m?.MessageBytesAverage) && + Equals(sentMetrics[0]?.Messages, m?.Messages) && + Equals(sentMetrics[0]?.MessageConversionMin, m?.MessageConversionMin) && + Equals(sentMetrics[0]?.MessageConversionMax, m?.MessageConversionMax) && + Equals(sentMetrics[0]?.MessageConversionAverage, m?.MessageConversionAverage) && + Equals(sentMetrics[0]?.MessageConversionDuration, m?.MessageConversionDuration) + ))); + + Assert.IsFalse(Array.Exists(receivedMetrics, (m => m==null))); + Assert.AreEqual(1, receivedMetrics[0]?.Messages); + Assert.IsTrue(Array.TrueForAll(receivedMetrics,(m => + Equals(receivedMetrics[0]?.MessageBytesMin, m?.MessageBytesMin) && + Equals(receivedMetrics[0]?.MessageBytes, m?.MessageBytes) && + Equals(receivedMetrics[0]?.MessageBytesMax, m?.MessageBytesMax) && + Equals(receivedMetrics[0]?.MessageBytesAverage, m?.MessageBytesAverage) && + Equals(receivedMetrics[0]?.Messages, m?.Messages) && + Equals(receivedMetrics[0]?.MessageConversionMin, m?.MessageConversionMin) && + Equals(receivedMetrics[0]?.MessageConversionMax, m?.MessageConversionMax) && + Equals(receivedMetrics[0]?.MessageConversionAverage, m?.MessageConversionAverage) && + Equals(receivedMetrics[0]?.MessageConversionDuration, m?.MessageConversionDuration) + ))); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() + { + #region Arrange + var serviceSubscription = new Mock(); + + var receivedActions = new List>>(); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeQueryAsync( + Capture.In>>(receivedActions), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(serviceSubscription.Object); + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => + { + var rmessage = Helper.ProduceReceivedServiceMessage(message); + var result = await receivedActions[0](rmessage); + return Helper.ProduceQueryResult(result); + }); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .AddMetrics(null, true); + + var message = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); + var responseMessage = new BasicResponseMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); + var channel = "TestQueryMetricChannel"; + #endregion + + #region Act + _ = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { + return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); + }, (error) => { }, + channel:channel); + _ = await contractConnection.QueryAsync(message,channel:channel); + IContractMetric?[] querySentMetrics = [ + contractConnection.GetSnapshot(typeof(BasicQueryMessage),true), + contractConnection.GetSnapshot(true) + ]; + IContractMetric?[] queryReceivedMetrics = [ + contractConnection.GetSnapshot(typeof(BasicQueryMessage),false), + contractConnection.GetSnapshot(false) + ]; + IContractMetric?[] responseSentMetrics = [ + contractConnection.GetSnapshot(typeof(BasicResponseMessage),true), + contractConnection.GetSnapshot(true) + ]; + IContractMetric?[] responseReceivedMetrics = [ + contractConnection.GetSnapshot(typeof(BasicResponseMessage),false), + contractConnection.GetSnapshot(false) + ]; + IContractMetric? globalSentMetrics = contractConnection.GetSnapshot(true); + IContractMetric? globalReceivedMetrics = contractConnection.GetSnapshot(false); + IContractMetric? channelSentMetrics = contractConnection.GetSnapshot(channel, true); + IContractMetric? channelRecievedMetrics = contractConnection.GetSnapshot(channel, false); + #endregion + + #region Assert + Assert.IsNotNull(globalSentMetrics); + Assert.AreEqual(2, globalSentMetrics.Messages); + Assert.IsNotNull(globalReceivedMetrics); + Assert.AreEqual(2, globalReceivedMetrics.Messages); + Assert.IsNotNull(channelSentMetrics); + Assert.AreEqual(1, channelSentMetrics.Messages); + Assert.IsNotNull(channelRecievedMetrics); + Assert.AreEqual(1, channelRecievedMetrics.Messages); + Assert.IsTrue(Array.TrueForAll(querySentMetrics, (q) => q!=null)); + Assert.IsTrue(Array.TrueForAll(queryReceivedMetrics, (q) => q!=null)); + Assert.IsTrue(Array.TrueForAll(responseSentMetrics, (q) => q!=null)); + Assert.IsTrue(Array.TrueForAll(responseReceivedMetrics, (q) => q!=null)); + Assert.AreEqual(1, querySentMetrics[0]?.Messages); + Assert.IsTrue(Array.TrueForAll(querySentMetrics, (m => + Equals(querySentMetrics[0]?.MessageBytesMin, m?.MessageBytesMin) && + Equals(querySentMetrics[0]?.MessageBytes, m?.MessageBytes) && + Equals(querySentMetrics[0]?.MessageBytesMax, m?.MessageBytesMax) && + Equals(querySentMetrics[0]?.MessageBytesAverage, m?.MessageBytesAverage) && + Equals(querySentMetrics[0]?.Messages, m?.Messages) && + Equals(querySentMetrics[0]?.MessageConversionMin, m?.MessageConversionMin) && + Equals(querySentMetrics[0]?.MessageConversionMax, m?.MessageConversionMax) && + Equals(querySentMetrics[0]?.MessageConversionAverage, m?.MessageConversionAverage) && + Equals(querySentMetrics[0]?.MessageConversionDuration, m?.MessageConversionDuration) + ))); + Assert.AreEqual(1, queryReceivedMetrics[0]?.Messages); + Assert.IsTrue(Array.TrueForAll(queryReceivedMetrics, (m => + Equals(queryReceivedMetrics[0]?.MessageBytesMin, m?.MessageBytesMin) && + Equals(queryReceivedMetrics[0]?.MessageBytes, m?.MessageBytes) && + Equals(queryReceivedMetrics[0]?.MessageBytesMax, m?.MessageBytesMax) && + Equals(queryReceivedMetrics[0]?.MessageBytesAverage, m?.MessageBytesAverage) && + Equals(queryReceivedMetrics[0]?.Messages, m?.Messages) && + Equals(queryReceivedMetrics[0]?.MessageConversionMin, m?.MessageConversionMin) && + Equals(queryReceivedMetrics[0]?.MessageConversionMax, m?.MessageConversionMax) && + Equals(queryReceivedMetrics[0]?.MessageConversionAverage, m?.MessageConversionAverage) && + Equals(queryReceivedMetrics[0]?.MessageConversionDuration, m?.MessageConversionDuration) + ))); + Assert.AreEqual(1, responseSentMetrics[0]?.Messages); + Assert.IsTrue(Array.TrueForAll(responseSentMetrics, (m => + Equals(responseSentMetrics[0]?.MessageBytesMin, m?.MessageBytesMin) && + Equals(responseSentMetrics[0]?.MessageBytes, m?.MessageBytes) && + Equals(responseSentMetrics[0]?.MessageBytesMax, m?.MessageBytesMax) && + Equals(responseSentMetrics[0]?.MessageBytesAverage, m?.MessageBytesAverage) && + Equals(responseSentMetrics[0]?.Messages, m?.Messages) && + Equals(responseSentMetrics[0]?.MessageConversionMin, m?.MessageConversionMin) && + Equals(responseSentMetrics[0]?.MessageConversionMax, m?.MessageConversionMax) && + Equals(responseSentMetrics[0]?.MessageConversionAverage, m?.MessageConversionAverage) && + Equals(responseSentMetrics[0]?.MessageConversionDuration, m?.MessageConversionDuration) + ))); + Assert.AreEqual(1, responseReceivedMetrics[0]?.Messages); + Assert.IsTrue(Array.TrueForAll(responseReceivedMetrics, (m => + Equals(responseReceivedMetrics[0]?.MessageBytesMin, m?.MessageBytesMin) && + Equals(responseReceivedMetrics[0]?.MessageBytes, m?.MessageBytes) && + Equals(responseReceivedMetrics[0]?.MessageBytesMax, m?.MessageBytesMax) && + Equals(responseReceivedMetrics[0]?.MessageBytesAverage, m?.MessageBytesAverage) && + Equals(responseReceivedMetrics[0]?.Messages, m?.Messages) && + Equals(responseReceivedMetrics[0]?.MessageConversionMin, m?.MessageConversionMin) && + Equals(responseReceivedMetrics[0]?.MessageConversionMax, m?.MessageConversionMax) && + Equals(responseReceivedMetrics[0]?.MessageConversionAverage, m?.MessageConversionAverage) && + Equals(responseReceivedMetrics[0]?.MessageConversionDuration, m?.MessageConversionDuration) + ))); + Assert.AreEqual(querySentMetrics[0]?.MessageBytes, queryReceivedMetrics[0]?.MessageBytes); + Assert.AreEqual(responseSentMetrics[0]?.MessageBytes, responseReceivedMetrics[0]?.MessageBytes); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + } +} diff --git a/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs b/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs new file mode 100644 index 0000000..e595477 --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs @@ -0,0 +1,256 @@ +using AutomatedTesting.Messages; +using Moq; +using MQContract.Interfaces.Service; +using MQContract; +using MQContract.Attributes; +using System.Reflection; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using System.Diagnostics.Metrics; + +namespace AutomatedTesting.ContractConnectionTests +{ + [TestClass] + public class SystemMetricTests + { + private const string MeterName = "mqcontract"; + private static (MetricCollector sent,MetricCollector sentBytes,MetricCollector receivedCount,MetricCollector receivedBytes, + MetricCollector encodingDuration,MetricCollector decodingDuration) ProduceCollectors(Meter owningMeter,Type? messageType=null,string? channel = null) + { + var template = "messages"; + if (channel!=null) + template = $"channels.{channel}"; + else if (messageType!=null) + template = $"types.{messageType.GetCustomAttributes().Select(mn => mn.Value).FirstOrDefault(messageType.Name)}.{messageType.GetCustomAttributes().Select(mc => mc.Version.ToString()).FirstOrDefault("0.0.0.0").Replace('.', '_')}"; + return ( + new MetricCollector(owningMeter,$"{MeterName}.{template}.sent.count"), + new MetricCollector(owningMeter, $"{MeterName}.{template}.sent.bytes"), + new MetricCollector(owningMeter,$"{MeterName}.{template}.received.count"), + new MetricCollector(owningMeter, $"{MeterName}.{template}.received.bytes"), + new MetricCollector(owningMeter, $"{MeterName}.{template}.encodingduration"), + new MetricCollector(owningMeter, $"{MeterName}.{template}.decodingduration") + ); + } + + private static void CheckMeasurement(IReadOnlyList> readOnlyList, int count, long value) + { + Assert.AreEqual(count, readOnlyList.Count); + if (count>0) + Assert.AreEqual(value, readOnlyList[0].Value); + } + + private static void CheckMeasurement(IReadOnlyList> readOnlyList, int count, double value) + { + Assert.AreEqual(count, readOnlyList.Count); + if (count>0) + Assert.AreEqual(value, readOnlyList[0].Value); + } + + private static void CheckMeasurementGreaterThan(IReadOnlyList> readOnlyList, int count, long value) + { + Assert.AreEqual(count, readOnlyList.Count); + Assert.IsTrue(value> readOnlyList, int count, double value) + { + Assert.AreEqual(count, readOnlyList.Count); + Assert.IsTrue(value> left, IReadOnlyList> right) + =>Assert.IsTrue(left.Select(v=>v.Value).SequenceEqual(right.Select(v=>v.Value))); + + private static void AreMeasurementsEquals(IReadOnlyList> left, IReadOnlyList> right) + => Assert.IsTrue(left.Select(v => v.Value).SequenceEqual(right.Select(v => v.Value))); + + [TestMethod] + public void TestSystemMetricInitialization() + { + #region Arrange + var testMeter = new Meter("TestSystemMetricInitialization"); + var serviceConnection = new Mock(); + _ = ContractConnection.Instance(serviceConnection.Object) + .AddMetrics(testMeter, false); + #endregion + + #region Act + (MetricCollector sent, MetricCollector sentBytes, MetricCollector receivedCount, MetricCollector recievedBytes, + MetricCollector encodingDuration, MetricCollector decodingDuration) = ProduceCollectors(testMeter); + #endregion + + #region Assert + CheckMeasurement(sent.GetMeasurementSnapshot(), 0, 0); + CheckMeasurement(sentBytes.GetMeasurementSnapshot(), 0, 0); + CheckMeasurement(receivedCount.GetMeasurementSnapshot(), 0, 0); + CheckMeasurement(recievedBytes.GetMeasurementSnapshot(), 0, 0); + CheckMeasurement(encodingDuration.GetMeasurementSnapshot(), 0, 0); + CheckMeasurement(decodingDuration.GetMeasurementSnapshot(), 0, 0); + #endregion + + #region Verify + #endregion + } + + [TestMethod] + public async Task TestPublishAsyncSubscribeSystemMetrics() + { + #region Arrange + var testMeter = new Meter("TestPublishAsyncSubscribeSystemMetrics"); + var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); + var serviceSubscription = new Mock(); + + var testMessage = new BasicMessage("testMessage"); + var channel = "TestSystemMetricsChannel"; + + List messages = []; + var actions = new List>(); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Capture.In>(actions), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => + { + var rmessage = Helper.ProduceReceivedServiceMessage(message); + foreach (var act in actions) + act(rmessage); + return ValueTask.FromResult(transmissionResult); + }); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .AddMetrics(testMeter,false); + #endregion + + #region Act + (MetricCollector sent, MetricCollector sentBytes, MetricCollector receivedCount, MetricCollector receivedBytes, + MetricCollector encodingDuration, MetricCollector decodingDuration) = ProduceCollectors(testMeter); + (MetricCollector sentType, MetricCollector sentBytesType, MetricCollector receivedCountType, MetricCollector receivedBytesType, + MetricCollector encodingDurationType, MetricCollector decodingDurationType) = ProduceCollectors(testMeter,messageType: typeof(BasicMessage)); + (MetricCollector sentChannel, MetricCollector sentBytesChannel, MetricCollector receivedCountChannel, MetricCollector receivedBytesChannel, + MetricCollector encodingDurationChannel, MetricCollector decodingDurationChannel) = ProduceCollectors(testMeter,channel: channel); + _ = await contractConnection.SubscribeAsync( + (msg) => ValueTask.CompletedTask, + (error) => { }, + channel:channel); + var result = await contractConnection.PublishAsync(testMessage,channel:channel); + _ = await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1)); + #endregion + + #region Assert + Assert.IsNotNull(result); + Assert.AreEqual(transmissionResult, result); + CheckMeasurement(sent.GetMeasurementSnapshot(), 1, 1); + CheckMeasurementGreaterThan(sentBytes.GetMeasurementSnapshot(), 1, 0); + CheckMeasurement(receivedCount.GetMeasurementSnapshot(), 1, 1); + CheckMeasurementGreaterThan(receivedBytes.GetMeasurementSnapshot(), 1, 0); + CheckMeasurementGreaterThan(encodingDuration.GetMeasurementSnapshot(), 1, 0); + CheckMeasurementGreaterThan(decodingDuration.GetMeasurementSnapshot(), 1, 0); + + AreMeasurementsEquals(sent.GetMeasurementSnapshot(), sentType.GetMeasurementSnapshot()); + AreMeasurementsEquals(sentBytes.GetMeasurementSnapshot(), sentBytesType.GetMeasurementSnapshot()); + AreMeasurementsEquals(receivedCount.GetMeasurementSnapshot(), receivedCountType.GetMeasurementSnapshot()); + AreMeasurementsEquals(receivedBytes.GetMeasurementSnapshot(), receivedBytesType.GetMeasurementSnapshot()); + AreMeasurementsEquals(encodingDuration.GetMeasurementSnapshot(),encodingDurationType.GetMeasurementSnapshot()); + AreMeasurementsEquals(decodingDuration.GetMeasurementSnapshot(),decodingDurationType.GetMeasurementSnapshot()); + + AreMeasurementsEquals(sent.GetMeasurementSnapshot(), sentChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(sentBytes.GetMeasurementSnapshot(), sentBytesChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(receivedCount.GetMeasurementSnapshot(), receivedCountChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(receivedBytes.GetMeasurementSnapshot(), receivedBytesChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(encodingDuration.GetMeasurementSnapshot(), encodingDurationChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(decodingDuration.GetMeasurementSnapshot(), decodingDurationChannel.GetMeasurementSnapshot()); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() + { + #region Arrange + var testMeter = new Meter("TestSubscribeQueryResponseAsyncWithNoExtendedAspects"); + var serviceSubscription = new Mock(); + + var receivedActions = new List>>(); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeQueryAsync( + Capture.In>>(receivedActions), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(serviceSubscription.Object); + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(async (ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) => + { + var rmessage = Helper.ProduceReceivedServiceMessage(message); + var result = await receivedActions[0](rmessage); + return Helper.ProduceQueryResult(result); + }); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .AddMetrics(testMeter, false); + + var message = new BasicQueryMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); + var responseMessage = new BasicResponseMessage("TestSubscribeQueryResponseWithNoExtendedAspects"); + var channel = "TestQueryMetricChannel"; + #endregion + + #region Act + (MetricCollector sent, MetricCollector sentBytes, MetricCollector receivedCount, MetricCollector receivedBytes, + MetricCollector encodingDuration, MetricCollector decodingDuration) = ProduceCollectors(testMeter); + (MetricCollector sentRequestType, MetricCollector sentBytesRequestType, MetricCollector receivedCountRequestType, MetricCollector receivedBytesRequestType, + MetricCollector encodingDurationRequestType, MetricCollector decodingDurationRequestType) = ProduceCollectors(testMeter,messageType: typeof(BasicQueryMessage)); + (MetricCollector sentResponseType, MetricCollector sentBytesResponseType, MetricCollector receivedCountResponseType, MetricCollector receivedBytesResponseType, + MetricCollector encodingDurationResponseType, MetricCollector decodingDurationResponseType) = ProduceCollectors(testMeter,messageType: typeof(BasicResponseMessage)); + (MetricCollector sentChannel, MetricCollector sentBytesChannel, MetricCollector receivedCountChannel, MetricCollector receivedBytesChannel, + MetricCollector encodingDurationChannel, MetricCollector decodingDurationChannel) = ProduceCollectors(testMeter,channel: channel); + _ = await contractConnection.SubscribeQueryAsyncResponseAsync((msg) => + { + return ValueTask.FromResult(new QueryResponseMessage(responseMessage, null)); + }, (error) => { }, + channel:channel); + _ = await contractConnection.QueryAsync(message,channel:channel); + #endregion + + #region Assert + CheckMeasurement(sent.GetMeasurementSnapshot(), 2, 1); + CheckMeasurementGreaterThan(sentBytes.GetMeasurementSnapshot(), 2, 0); + CheckMeasurement(receivedCount.GetMeasurementSnapshot(), 2, 1); + CheckMeasurementGreaterThan(receivedBytes.GetMeasurementSnapshot(), 2, 0); + CheckMeasurementGreaterThan(encodingDuration.GetMeasurementSnapshot(), 2, 0); + CheckMeasurementGreaterThan(decodingDuration.GetMeasurementSnapshot(), 2, 0); + + CheckMeasurement(sentRequestType.GetMeasurementSnapshot(), 1, 1); + CheckMeasurementGreaterThan(sentBytesRequestType.GetMeasurementSnapshot(), 1, 0); + CheckMeasurement(receivedCountRequestType.GetMeasurementSnapshot(), 1, 1); + CheckMeasurementGreaterThan(receivedBytesRequestType.GetMeasurementSnapshot(), 1, 0); + CheckMeasurementGreaterThan(encodingDurationRequestType.GetMeasurementSnapshot(), 1, 0); + CheckMeasurementGreaterThan(decodingDurationRequestType.GetMeasurementSnapshot(), 1, 0); + + CheckMeasurement(sentResponseType.GetMeasurementSnapshot(), 1, 1); + CheckMeasurementGreaterThan(sentBytesResponseType.GetMeasurementSnapshot(), 1, 0); + CheckMeasurement(receivedCountResponseType.GetMeasurementSnapshot(), 1, 1); + CheckMeasurementGreaterThan(receivedBytesResponseType.GetMeasurementSnapshot(), 1, 0); + CheckMeasurementGreaterThan(encodingDurationResponseType.GetMeasurementSnapshot(), 1, 0); + CheckMeasurementGreaterThan(decodingDurationResponseType.GetMeasurementSnapshot(), 1, 0); + + AreMeasurementsEquals(sentRequestType.GetMeasurementSnapshot(), sentChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(sentBytesRequestType.GetMeasurementSnapshot(), sentBytesChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(receivedCountRequestType.GetMeasurementSnapshot(), receivedCountChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(receivedBytesRequestType.GetMeasurementSnapshot(), receivedBytesChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(encodingDurationRequestType.GetMeasurementSnapshot(), encodingDurationChannel.GetMeasurementSnapshot()); + AreMeasurementsEquals(decodingDurationRequestType.GetMeasurementSnapshot(), decodingDurationChannel.GetMeasurementSnapshot()); + #endregion + + #region Verify + serviceConnection.Verify(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + } +} diff --git a/Core/ContractConnection.Metrics.cs b/Core/ContractConnection.Metrics.cs index 201075e..0f7f2a0 100644 --- a/Core/ContractConnection.Metrics.cs +++ b/Core/ContractConnection.Metrics.cs @@ -1,16 +1,18 @@ using MQContract.Interfaces; using MQContract.Middleware; +using System.Diagnostics.Metrics; namespace MQContract { public sealed partial class ContractConnection { - void IContractConnection.AddMetrics(bool useMeter, bool useInternal) + IContractConnection IContractConnection.AddMetrics(Meter? meter, bool useInternal) { lock (middleware) { - middleware.Insert(0, new MetricsMiddleware(useMeter, useInternal)); + middleware.Insert(0, new MetricsMiddleware(meter, useInternal)); } + return this; } private MetricsMiddleware? MetricsMiddleware @@ -30,6 +32,8 @@ private MetricsMiddleware? MetricsMiddleware => MetricsMiddleware?.GetSnapshot(sent); IContractMetric? IContractConnection.GetSnapshot(Type messageType, bool sent) => MetricsMiddleware?.GetSnapshot(messageType, sent); + IContractMetric? IContractConnection.GetSnapshot(bool sent) + => MetricsMiddleware?.GetSnapshot(typeof(T), sent); IContractMetric? IContractConnection.GetSnapshot(string channel, bool sent) => MetricsMiddleware?.GetSnapshot(channel, sent); } diff --git a/Core/ContractConnection.Middleware.cs b/Core/ContractConnection.Middleware.cs index b24fab0..422d87e 100644 --- a/Core/ContractConnection.Middleware.cs +++ b/Core/ContractConnection.Middleware.cs @@ -2,7 +2,6 @@ using MQContract.Interfaces; using MQContract.Interfaces.Middleware; using MQContract.Messages; -using MQContract.Middleware; namespace MQContract { diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index 7daaac5..acb64e2 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -93,7 +93,7 @@ private ValueTask MapChannel(ChannelMapper.MapTypes mapType, string orig var context = new Context(mapType); (message, channel, messageHeader) = await BeforeMessageEncodeAsync(context, message, channel??factory.MessageChannel, messageHeader??new([])); var serviceMessage = await AfterMessageEncodeAsync(context, - await factory.ConvertMessageAsync(message, channel, messageHeader) + await factory.ConvertMessageAsync(message,false, channel, messageHeader) ); return (context, serviceMessage); } @@ -179,7 +179,7 @@ private async ValueTask> ProduceResultAsync(IContext context, result = new QueryResult( queryResult.ID, messageHeader, - Result: await GetMessageFactory().ConvertMessageAsync(logger, new ServiceQueryResult(queryResult.ID, messageHeader, queryResult.MessageTypeID, data)) + Result: await GetMessageFactory(true).ConvertMessageAsync(logger, new ServiceQueryResult(queryResult.ID, messageHeader, queryResult.MessageTypeID, data)) ); } catch (QueryResponseException qre) @@ -210,7 +210,7 @@ private async ValueTask ProduceSubscribeQueryResponseAsync( var queryMessageFactory = GetMessageFactory(ignoreMessageHeader); var responseMessageFactory = GetMessageFactory(); var subscription = new QueryResponseSubscription( - async (message) => + async (message,replyChannel) => { var context = new Context(ChannelMapper.MapTypes.QuerySubscription); (var messageHeader, var data) = await BeforeMessageDecodeAsync(context, message.ID, message.Header, message.MessageTypeID, message.Channel, message.Data); @@ -219,8 +219,8 @@ private async ValueTask ProduceSubscribeQueryResponseAsync( (taskMessage, messageHeader) = await AfterMessageDecodeAsync(context, taskMessage!, message.ID, messageHeader, message.ReceivedTimestamp, DateTime.Now); var result = await messageReceived(new ReceivedMessage(message.ID, taskMessage, messageHeader, message.ReceivedTimestamp, DateTime.Now)); context = new Context(ChannelMapper.MapTypes.QueryResponse); - (var resultMessage, var resultChannel, var resultHeader) = await BeforeMessageEncodeAsync(context, result.Message, message.Channel, message.Header); - var encodedMessage = await responseMessageFactory.ConvertMessageAsync(resultMessage, resultChannel, resultHeader); + (var resultMessage, var resultChannel, var resultHeader) = await BeforeMessageEncodeAsync(context, result.Message, replyChannel, message.Header); + var encodedMessage = await responseMessageFactory.ConvertMessageAsync(resultMessage,true, resultChannel??replyChannel, resultHeader); return await AfterMessageEncodeAsync(context, encodedMessage); }, errorReceived, diff --git a/Core/Factories/MessageTypeFactory.cs b/Core/Factories/MessageTypeFactory.cs index 3fe6f83..6036951 100644 --- a/Core/Factories/MessageTypeFactory.cs +++ b/Core/Factories/MessageTypeFactory.cs @@ -146,9 +146,9 @@ private static bool IsMessageTypeMatch(string metaData, Type t, out bool isCompr return false; } - public async ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader messageHeader) + public async ValueTask ConvertMessageAsync(T message,bool ignoreChannel, string? channel, MessageHeader messageHeader) { - if (string.IsNullOrWhiteSpace(channel)) + if (string.IsNullOrWhiteSpace(channel)&&!ignoreChannel) throw new MessageChannelNullException(); var encodedData = await (messageEncoder?.EncodeAsync(message)??globalMessageEncoder!.EncodeAsync(message)); @@ -171,7 +171,7 @@ public async ValueTask ConvertMessageAsync(T message, string? ch metaData="U"; metaData+=$"-{messageName}-{messageVersion}"; - return new ServiceMessage(Guid.NewGuid().ToString(), metaData, channel, new MessageHeader(messageHeader, messageHeaders), body); + return new ServiceMessage(Guid.NewGuid().ToString(), metaData, channel??string.Empty, new MessageHeader(messageHeader, messageHeaders), body); } async ValueTask IConversionPath.ConvertMessageAsync(ILogger? logger, IEncodedMessage message, Stream? dataStream) diff --git a/Core/Interfaces/Factories/IMessageFactory.cs b/Core/Interfaces/Factories/IMessageFactory.cs index 74313e3..b3cc59b 100644 --- a/Core/Interfaces/Factories/IMessageFactory.cs +++ b/Core/Interfaces/Factories/IMessageFactory.cs @@ -6,6 +6,6 @@ namespace MQContract.Interfaces.Factories internal interface IMessageFactory : IMessageTypeFactory, IConversionPath where T : class { string? MessageChannel { get; } - ValueTask ConvertMessageAsync(T message, string? channel, MessageHeader messageHeader); + ValueTask ConvertMessageAsync(T message,bool ignoreChannel, string? channel, MessageHeader messageHeader); } } diff --git a/Core/Middleware/Metrics/ContractMetric.cs b/Core/Middleware/Metrics/ContractMetric.cs index e89a327..2c5bec1 100644 --- a/Core/Middleware/Metrics/ContractMetric.cs +++ b/Core/Middleware/Metrics/ContractMetric.cs @@ -8,7 +8,7 @@ internal record ContractMetric : IContractMetric public ulong MessageBytes { get; private set; } = 0; - public ulong MessageBytesAverage => MessageBytes / Messages; + public ulong MessageBytesAverage => (Messages==0 ? 0 : MessageBytes / Messages); public ulong MessageBytesMin { get; private set; } = ulong.MaxValue; @@ -16,7 +16,7 @@ internal record ContractMetric : IContractMetric public TimeSpan MessageConversionDuration { get; private set; } = TimeSpan.Zero; - public TimeSpan MessageConversionAverage => MessageConversionDuration / Messages; + public TimeSpan MessageConversionAverage => (Messages==0 ? TimeSpan.Zero : MessageConversionDuration / Messages); public TimeSpan MessageConversionMin { get; private set; } = TimeSpan.MaxValue; diff --git a/Core/Middleware/Metrics/MessageMetric.cs b/Core/Middleware/Metrics/MessageMetric.cs index 4b61516..89f9603 100644 --- a/Core/Middleware/Metrics/MessageMetric.cs +++ b/Core/Middleware/Metrics/MessageMetric.cs @@ -2,7 +2,7 @@ namespace MQContract.Middleware.Metrics { - internal record MessageMetric(Counter Sent,Counter SentBytes, Counter Received,Counter ReceivedBytes, + internal record MessageMetric(UpDownCounter Sent, UpDownCounter SentBytes, UpDownCounter Received, UpDownCounter ReceivedBytes, Histogram EncodingDuration, Histogram DecodingDuration) { public void AddEntry(MetricEntryValue entry) diff --git a/Core/Middleware/Metrics/SystemMetricTracker.cs b/Core/Middleware/Metrics/SystemMetricTracker.cs index 2f37273..73b07b8 100644 --- a/Core/Middleware/Metrics/SystemMetricTracker.cs +++ b/Core/Middleware/Metrics/SystemMetricTracker.cs @@ -13,14 +13,14 @@ internal class SystemMetricTracker private readonly Dictionary typeMetrics = []; private readonly Dictionary channelMetrics = []; - public SystemMetricTracker() + public SystemMetricTracker(Meter meter) { - meter = new Meter(MeterName); + this.meter = meter; globalMetric = new( - meter.CreateCounter($"{MeterName}.messages.sent.count"), - meter.CreateCounter($"{MeterName}.messages.sent.bytes"), - meter.CreateCounter($"{MeterName}.messages.received.count"), - meter.CreateCounter($"{MeterName}.messages.received.bytes"), + meter.CreateUpDownCounter($"{MeterName}.messages.sent.count"), + meter.CreateUpDownCounter($"{MeterName}.messages.sent.bytes"), + meter.CreateUpDownCounter($"{MeterName}.messages.received.count"), + meter.CreateUpDownCounter($"{MeterName}.messages.received.bytes"), meter.CreateHistogram($"{MeterName}.messages.encodingduration", unit: "ms"), meter.CreateHistogram($"{MeterName}.messages.decodingduration", unit: "ms") ); @@ -34,24 +34,24 @@ public void AppendEntry(MetricEntryValue entry) if (!typeMetrics.TryGetValue(entry.Type, out MessageMetric? typeMetric)) { typeMetric = new( - meter.CreateCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.','_')}.sent.count"), - meter.CreateCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.sent.bytes"), - meter.CreateCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.received.count"), - meter.CreateCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.received.bytes"), - meter.CreateHistogram($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.encodingduration"), - meter.CreateHistogram($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.decodingduration") + meter.CreateUpDownCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.','_')}.sent.count"), + meter.CreateUpDownCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.sent.bytes"), + meter.CreateUpDownCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.received.count"), + meter.CreateUpDownCounter($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.received.bytes"), + meter.CreateHistogram($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.encodingduration", unit: "ms"), + meter.CreateHistogram($"{MeterName}.types.{Utility.MessageTypeName(entry.Type)}.{Utility.MessageVersionString(entry.Type).Replace('.', '_')}.decodingduration", unit: "ms") ); typeMetrics.Add(entry.Type, typeMetric!); } if (!string.IsNullOrWhiteSpace(entry.Channel) && !channelMetrics.TryGetValue(entry.Channel, out channelMetric)) { channelMetric = new( - meter.CreateCounter($"{MeterName}.channels.{entry.Channel}.sent.count"), - meter.CreateCounter($"{MeterName}.channels.{entry.Channel}.sent.bytes"), - meter.CreateCounter($"{MeterName}.channels.{entry.Channel}.received.count"), - meter.CreateCounter($"{MeterName}.channels.{entry.Channel}.received.bytes"), - meter.CreateHistogram($"{MeterName}.channels.{entry.Channel}.encodingduration"), - meter.CreateHistogram($"{MeterName}.channels.{entry.Channel}.decodingduration") + meter.CreateUpDownCounter($"{MeterName}.channels.{entry.Channel}.sent.count"), + meter.CreateUpDownCounter($"{MeterName}.channels.{entry.Channel}.sent.bytes"), + meter.CreateUpDownCounter($"{MeterName}.channels.{entry.Channel}.received.count"), + meter.CreateUpDownCounter($"{MeterName}.channels.{entry.Channel}.received.bytes"), + meter.CreateHistogram($"{MeterName}.channels.{entry.Channel}.encodingduration", unit: "ms"), + meter.CreateHistogram($"{MeterName}.channels.{entry.Channel}.decodingduration", unit: "ms") ); channelMetrics.Add(entry.Channel!, channelMetric!); } diff --git a/Core/Middleware/MetricsMiddleware.cs b/Core/Middleware/MetricsMiddleware.cs index f24f0c1..56502d6 100644 --- a/Core/Middleware/MetricsMiddleware.cs +++ b/Core/Middleware/MetricsMiddleware.cs @@ -3,6 +3,7 @@ using MQContract.Messages; using MQContract.Middleware.Metrics; using System.Diagnostics; +using System.Diagnostics.Metrics; using System.Threading.Channels; namespace MQContract.Middleware @@ -17,10 +18,10 @@ internal class MetricsMiddleware : IBeforeEncodeMiddleware, IAfterEncodeMiddlewa private readonly InternalMetricTracker? internalTracker; private readonly Channel channel = Channel.CreateUnbounded(); - public MetricsMiddleware(bool useMeter,bool useInternal) + public MetricsMiddleware(Meter? meter,bool useInternal) { - if (useMeter) - systemTracker=new(); + if (meter!=null) + systemTracker=new(meter!); if (useInternal) internalTracker=new(); Start(); @@ -57,6 +58,9 @@ private async ValueTask AddStat(Type messageType, string? channel, bool sending, var stopWatch = (Stopwatch?)context[StopWatchKey]; stopWatch?.Stop(); await AddStat(typeof(T), (string?)context[MessageReceivedChannelKey]??string.Empty, false, (int?)context[MessageRecievedSizeKey]??0, stopWatch); + context[StopWatchKey]=null; + context[MessageReceivedChannelKey]=null; + context[MessageRecievedSizeKey]=null; return (message,messageHeader); } @@ -65,6 +69,7 @@ public async ValueTask AfterMessageEncodeAsync(Type messageType, var stopWatch = (Stopwatch?)context[StopWatchKey]; stopWatch?.Stop(); await AddStat(messageType, message.Channel,true,message.Data.Length,stopWatch); + context[StopWatchKey] = null; return message; } diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index 77d091b..6985533 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -5,7 +5,7 @@ namespace MQContract.Subscriptions { internal sealed class QueryResponseSubscription( - Func> processMessage, + Func> processMessage, Action errorReceived, Func> mapChannel, string? channel = null, string? group = null, @@ -21,7 +21,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio { if (connection is IQueryableMessageServiceConnection queryableMessageServiceConnection) serviceSubscription = await queryableMessageServiceConnection.SubscribeQueryAsync( - serviceMessage => ProcessServiceMessageAsync(serviceMessage), + serviceMessage => ProcessServiceMessageAsync(serviceMessage,string.Empty), error => errorReceived(error), MessageChannel, group:group, @@ -43,7 +43,8 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio serviceMessage.Channel, QueryResponseHelper.StripHeaders(serviceMessage, out var queryClientID, out var replyID, out var replyChannel), serviceMessage.Data - ) + ), + replyChannel ); await connection.PublishAsync(QueryResponseHelper.EncodeMessage(result, queryClientID, replyID, null, replyChannel), cancellationToken); } @@ -56,7 +57,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio return serviceSubscription!=null; } - private async ValueTask ProcessServiceMessageAsync(ReceivedServiceMessage message) + private async ValueTask ProcessServiceMessageAsync(ReceivedServiceMessage message,string replyChannel) { if (Synchronous&&!(token?.IsCancellationRequested??false)) manualResetEvent!.Wait(cancellationToken:token!.Token); @@ -64,7 +65,7 @@ private async ValueTask ProcessServiceMessageAsync(ReceivedServi ServiceMessage? response = null; try { - response = await processMessage(message); + response = await processMessage(message,replyChannel); if (message.Acknowledge!=null) await message.Acknowledge(); }catch(Exception e) @@ -75,8 +76,8 @@ private async ValueTask ProcessServiceMessageAsync(ReceivedServi if (Synchronous) manualResetEvent!.Set(); if (error!=null) - return ErrorServiceMessage.Produce(message.Channel,error); - return response??ErrorServiceMessage.Produce(message.Channel, new NullReferenceException()); + return ErrorServiceMessage.Produce(replyChannel,error); + return response??ErrorServiceMessage.Produce(replyChannel, new NullReferenceException()); } protected override void InternalDispose() diff --git a/Samples/Messages/SampleExecution.cs b/Samples/Messages/SampleExecution.cs index 943c854..95039ef 100644 --- a/Samples/Messages/SampleExecution.cs +++ b/Samples/Messages/SampleExecution.cs @@ -16,7 +16,7 @@ public static async ValueTask ExecuteSample(IMessageServiceConnection serviceCon }; var contractConnection = ContractConnection.Instance(serviceConnection,channelMapper:mapper); - contractConnection.AddMetrics(false, true); + contractConnection.AddMetrics(null, true); var announcementSubscription = await contractConnection.SubscribeAsync( (announcement) => From 87ef55b6aeeae9b7a89df9f530c36a2fc63c44c0 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Tue, 24 Sep 2024 14:12:20 -0400 Subject: [PATCH 16/22] adjusted testing timings Adjusted some of the unit test timings to ensure that all metric tracking has occured --- .../InternalMetricTests.cs | 37 +++++++++++++++++++ .../SystemMetricTests.cs | 2 + 2 files changed, 39 insertions(+) diff --git a/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs b/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs index 4b372d2..084240b 100644 --- a/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs +++ b/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs @@ -65,6 +65,41 @@ public void TestInternalMetricInitialization() #endregion } + [TestMethod] + public void TestInternalMetricGetSnapshotsWithoutEnabling() + { + #region Arrange + var channel = "TestInternalMetricsChannel"; + + var serviceConnection = new Mock(); + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .AddMetrics(null, false); + #endregion + + #region Act + IContractMetric?[] sentMetrics = [ + contractConnection.GetSnapshot(true), + contractConnection.GetSnapshot(typeof(BasicMessage),true), + contractConnection.GetSnapshot(true), + contractConnection.GetSnapshot(channel,true) + ]; + IContractMetric?[] receivedMetrics = [ + contractConnection.GetSnapshot(false), + contractConnection.GetSnapshot(typeof(BasicMessage),false), + contractConnection.GetSnapshot(false), + contractConnection.GetSnapshot(channel,false) + ]; + #endregion + + #region Assert + Assert.IsTrue(Array.TrueForAll(sentMetrics,m => m==null)); + Assert.IsTrue(Array.TrueForAll(receivedMetrics,m => m==null)); + #endregion + + #region Verify + #endregion + } + [TestMethod] public async Task TestPublishAsyncSubscribeInternalMetrics() { @@ -102,6 +137,7 @@ public async Task TestPublishAsyncSubscribeInternalMetrics() channel:channel); var result = await contractConnection.PublishAsync(testMessage,channel:channel); _ = await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1)); + await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(true); IContractMetric?[] sentMetrics = [ contractConnection.GetSnapshot(true), contractConnection.GetSnapshot(typeof(BasicMessage),true), @@ -192,6 +228,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() }, (error) => { }, channel:channel); _ = await contractConnection.QueryAsync(message,channel:channel); + await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(true); IContractMetric?[] querySentMetrics = [ contractConnection.GetSnapshot(typeof(BasicQueryMessage),true), contractConnection.GetSnapshot(true) diff --git a/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs b/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs index e595477..f291ff4 100644 --- a/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs @@ -135,6 +135,7 @@ public async Task TestPublishAsyncSubscribeSystemMetrics() channel:channel); var result = await contractConnection.PublishAsync(testMessage,channel:channel); _ = await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1)); + await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(true); #endregion #region Assert @@ -215,6 +216,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() }, (error) => { }, channel:channel); _ = await contractConnection.QueryAsync(message,channel:channel); + await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(true); #endregion #region Assert From 5a833f35c48be19cc0d8b67261b2bb00a6959284 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Tue, 24 Sep 2024 16:11:39 -0400 Subject: [PATCH 17/22] code cleanup, test extensions added in more testing to cover middleware usage as well as cleaned up some code. Need to extend logging. --- .../Interfaces/IContractConnection.cs | 46 +-- .../Middleware/IAfterDecodeMiddleware.cs | 2 +- .../IAfterDecodeSpecificTypeMiddleware.cs | 2 +- .../Middleware/IAfterEncodeMiddleware.cs | 2 +- .../Middleware/IBeforeDecodeMiddleware.cs | 2 +- .../Middleware/IBeforeEncodeMiddleware.cs | 2 +- .../IBeforeEncodeSpecificTypeMiddleware.cs | 2 +- .../Interfaces/Middleware/IMiddleware.cs | 9 + .../Middleware/ISpecificTypeMiddleware.cs | 10 + Abstractions/Readme.md | 65 +++++ .../MiddleWareTests.cs | 274 ++++++++++++++++++ .../Middlewares/ChannelChangeMiddleware.cs | 12 + .../ChannelChangeMiddlewareForBasicMessage.cs | 13 + .../InjectedChannelChangeMiddleware.cs | 17 ++ Core/ContractConnection.cs | 2 +- Core/Subscriptions/QueryResponseHelper.cs | 4 +- .../QueryResponseSubscription.cs | 9 +- 17 files changed, 436 insertions(+), 37 deletions(-) create mode 100644 Abstractions/Interfaces/Middleware/IMiddleware.cs create mode 100644 Abstractions/Interfaces/Middleware/ISpecificTypeMiddleware.cs create mode 100644 AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs create mode 100644 AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddleware.cs create mode 100644 AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs create mode 100644 AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs diff --git a/Abstractions/Interfaces/IContractConnection.cs b/Abstractions/Interfaces/IContractConnection.cs index aca0acd..d64a8f3 100644 --- a/Abstractions/Interfaces/IContractConnection.cs +++ b/Abstractions/Interfaces/IContractConnection.cs @@ -15,7 +15,7 @@ public interface IContractConnection : IDisposable,IAsyncDisposable /// The type of middle ware to register, it must implement IBeforeDecodeMiddleware or IBeforeEncodeMiddleware or IAfterDecodeMiddleware or IAfterEncodeMiddleware /// The Contract Connection instance to allow chaining calls IContractConnection RegisterMiddleware() - where T : IBeforeDecodeMiddleware, IBeforeEncodeMiddleware, IAfterDecodeMiddleware, IAfterEncodeMiddleware; + where T : IMiddleware; /// /// Register a middleware of a given type T to be used by the contract connection /// @@ -23,15 +23,15 @@ IContractConnection RegisterMiddleware() /// The type of middle ware to register, it must implement IBeforeDecodeMiddleware or IBeforeEncodeMiddleware or IAfterDecodeMiddleware or IAfterEncodeMiddleware /// The Contract Connection instance to allow chaining calls IContractConnection RegisterMiddleware(Func constructInstance) - where T : IBeforeDecodeMiddleware, IBeforeEncodeMiddleware, IAfterDecodeMiddleware, IAfterEncodeMiddleware; + where T : IMiddleware; /// /// Register a middleware of a given type T to be used by the contract connection /// /// The type of middle ware to register, it must implement IBeforeEncodeSpecificTypeMiddleware<M> or IAfterDecodeSpecificTypeMiddleware<M> /// The message type that this middleware is specifically called for /// The Contract Connection instance to allow chaining calls - IContractConnection RegisterMiddleware() - where T : IBeforeEncodeSpecificTypeMiddleware, IAfterDecodeSpecificTypeMiddleware + IContractConnection RegisterMiddleware() + where T : ISpecificTypeMiddleware where M : class; /// /// Register a middleware of a given type T to be used by the contract connection @@ -41,7 +41,7 @@ IContractConnection RegisterMiddleware() /// The message type that this middleware is specifically called for /// The Contract Connection instance to allow chaining calls IContractConnection RegisterMiddleware(Func constructInstance) - where T : IBeforeEncodeSpecificTypeMiddleware, IAfterDecodeSpecificTypeMiddleware + where T : ISpecificTypeMiddleware where M : class; /// /// Called to activate the metrics tracking middleware for this connection instance @@ -51,24 +51,24 @@ IContractConnection RegisterMiddleware(Func constructInstance) /// The Contract Connection instance to allow chaining calls /// /// For the Meter metrics, all durations are in ms and the following values and patterns will apply: - /// mqcontract.messages.sent.count = count of messages sent (Counter) - /// mqcontract.messages.sent.bytes = count of bytes sent (message data) (Counter) - /// mqcontract.messages.received.count = count of messages received (Counter) - /// mqcontract.messages.received.bytes = count of bytes received (message data) (Counter) - /// mqcontract.messages.encodingduration = milliseconds to encode messages (Histogram) - /// mqcontract.messages.decodingduration = milliseconds to decode messages (Histogram) - /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.sent.count = count of messages sent of a given type (Counter) - /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.sent.bytes = count of bytes sent (message data) of a given type (Counter) - /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.received.count = count of messages received of a given type (Counter) - /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.received.bytes = count of bytes received (message data) of a given type (Counter) - /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.encodingduration = milliseconds to encode messages of a given type (Histogram) - /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.decodingduration = milliseconds to decode messages of a given type (Histogram) - /// mqcontract.channels.{Channel}.sent.count = count of messages sent for a given channel (Counter) - /// mqcontract.channels.{Channel}.sent.bytes = count of bytes sent (message data) for a given channel (Counter) - /// mqcontract.channels.{Channel}.received.count = count of messages received for a given channel (Counter) - /// mqcontract.channels.{Channel}.received.bytes = count of bytes received (message data) for a given channel (Counter) - /// mqcontract.channels.{Channel}.encodingduration = milliseconds to encode messages for a given channel (Histogram) - /// mqcontract.channels.{Channel}.decodingduration = milliseconds to decode messages for a given channel (Histogram) + /// mqcontract.messages.sent.count = count of messages sent (Counter<long>) + /// mqcontract.messages.sent.bytes = count of bytes sent (message data) (Counter<long>) + /// mqcontract.messages.received.count = count of messages received (Counter<long>) + /// mqcontract.messages.received.bytes = count of bytes received (message data) (Counter<long>) + /// mqcontract.messages.encodingduration = milliseconds to encode messages (Histogram<double>) + /// mqcontract.messages.decodingduration = milliseconds to decode messages (Histogram<double>) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.sent.count = count of messages sent of a given type (Counter<long>) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.sent.bytes = count of bytes sent (message data) of a given type (Counter<long>) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.received.count = count of messages received of a given type (Counter<long>) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.received.bytes = count of bytes received (message data) of a given type (Counter<long>) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.encodingduration = milliseconds to encode messages of a given type (Histogram<double>) + /// mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.decodingduration = milliseconds to decode messages of a given type (Histogram<double>) + /// mqcontract.channels.{Channel}.sent.count = count of messages sent for a given channel (Counter<long>) + /// mqcontract.channels.{Channel}.sent.bytes = count of bytes sent (message data) for a given channel (Counter<long>) + /// mqcontract.channels.{Channel}.received.count = count of messages received for a given channel (Counter<long>) + /// mqcontract.channels.{Channel}.received.bytes = count of bytes received (message data) for a given channel (Counter<long>) + /// mqcontract.channels.{Channel}.encodingduration = milliseconds to encode messages for a given channel (Histogram<double>) + /// mqcontract.channels.{Channel}.decodingduration = milliseconds to decode messages for a given channel (Histogram<double>) /// IContractConnection AddMetrics(Meter? meter, bool useInternal); /// diff --git a/Abstractions/Interfaces/Middleware/IAfterDecodeMiddleware.cs b/Abstractions/Interfaces/Middleware/IAfterDecodeMiddleware.cs index 2f8d3d7..6687a9f 100644 --- a/Abstractions/Interfaces/Middleware/IAfterDecodeMiddleware.cs +++ b/Abstractions/Interfaces/Middleware/IAfterDecodeMiddleware.cs @@ -5,7 +5,7 @@ namespace MQContract.Interfaces.Middleware /// /// This interface represents a Middleware to execute after a Message has been decoded from a ServiceMessage to the expected Class /// - public interface IAfterDecodeMiddleware + public interface IAfterDecodeMiddleware : IMiddleware { /// /// This is the method invoked as part of the Middleware processing during message decoding diff --git a/Abstractions/Interfaces/Middleware/IAfterDecodeSpecificTypeMiddleware.cs b/Abstractions/Interfaces/Middleware/IAfterDecodeSpecificTypeMiddleware.cs index 9f0567e..d976e85 100644 --- a/Abstractions/Interfaces/Middleware/IAfterDecodeSpecificTypeMiddleware.cs +++ b/Abstractions/Interfaces/Middleware/IAfterDecodeSpecificTypeMiddleware.cs @@ -5,7 +5,7 @@ namespace MQContract.Interfaces.Middleware /// /// This interface represents a Middleware to execute after a Message of the given type T has been decoded from a ServiceMessage to the expected Class /// - public interface IAfterDecodeSpecificTypeMiddleware + public interface IAfterDecodeSpecificTypeMiddleware : ISpecificTypeMiddleware where T : class { /// diff --git a/Abstractions/Interfaces/Middleware/IAfterEncodeMiddleware.cs b/Abstractions/Interfaces/Middleware/IAfterEncodeMiddleware.cs index 118e68e..c7b6c45 100644 --- a/Abstractions/Interfaces/Middleware/IAfterEncodeMiddleware.cs +++ b/Abstractions/Interfaces/Middleware/IAfterEncodeMiddleware.cs @@ -5,7 +5,7 @@ namespace MQContract.Interfaces.Middleware /// /// This interface represents a Middleware to execute after a Message has been encoded to a ServiceMessage from the supplied Class /// - public interface IAfterEncodeMiddleware + public interface IAfterEncodeMiddleware : IMiddleware { /// /// This is the method invoked as part of the Middleware processing during message encoding diff --git a/Abstractions/Interfaces/Middleware/IBeforeDecodeMiddleware.cs b/Abstractions/Interfaces/Middleware/IBeforeDecodeMiddleware.cs index fc74aa7..854b35f 100644 --- a/Abstractions/Interfaces/Middleware/IBeforeDecodeMiddleware.cs +++ b/Abstractions/Interfaces/Middleware/IBeforeDecodeMiddleware.cs @@ -5,7 +5,7 @@ namespace MQContract.Interfaces.Middleware /// /// This interface represents a Middleware to execute before decoding a ServiceMessage /// - public interface IBeforeDecodeMiddleware + public interface IBeforeDecodeMiddleware : IMiddleware { /// /// This is the method invoked as part of the Middleware processing prior to the message decoding diff --git a/Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs b/Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs index 6ef0cc6..0413d92 100644 --- a/Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs +++ b/Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs @@ -5,7 +5,7 @@ namespace MQContract.Interfaces.Middleware /// /// This interface represents a Middleware to execute Before a message is encoded /// - public interface IBeforeEncodeMiddleware + public interface IBeforeEncodeMiddleware : IMiddleware { /// /// This is the method invoked as part of the Middle Ware processing during message encoding diff --git a/Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs b/Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs index 4e293fb..50f914a 100644 --- a/Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs +++ b/Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs @@ -5,7 +5,7 @@ namespace MQContract.Interfaces.Middleware /// /// This interface represents a Middleware to execute Before a specific message type is encoded /// - public interface IBeforeEncodeSpecificTypeMiddleware + public interface IBeforeEncodeSpecificTypeMiddleware : ISpecificTypeMiddleware where T : class { /// diff --git a/Abstractions/Interfaces/Middleware/IMiddleware.cs b/Abstractions/Interfaces/Middleware/IMiddleware.cs new file mode 100644 index 0000000..22b0fee --- /dev/null +++ b/Abstractions/Interfaces/Middleware/IMiddleware.cs @@ -0,0 +1,9 @@ +namespace MQContract.Interfaces.Middleware +{ + /// + /// Base Middleware just used to limit Generic Types for Register Middleware + /// + public interface IMiddleware + { + } +} diff --git a/Abstractions/Interfaces/Middleware/ISpecificTypeMiddleware.cs b/Abstractions/Interfaces/Middleware/ISpecificTypeMiddleware.cs new file mode 100644 index 0000000..af90822 --- /dev/null +++ b/Abstractions/Interfaces/Middleware/ISpecificTypeMiddleware.cs @@ -0,0 +1,10 @@ +namespace MQContract.Interfaces.Middleware +{ + /// + /// Base Specific Type Middleware just used to limit Generic Types for Register Middleware + /// + public interface ISpecificTypeMiddleware + where T : class + { + } +} diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index 94f10e4..b103e3d 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -18,6 +18,7 @@ - [IContext](#T-MQContract-Interfaces-Middleware-IContext 'MQContract.Interfaces.Middleware.IContext') - [Item](#P-MQContract-Interfaces-Middleware-IContext-Item-System-String- 'MQContract.Interfaces.Middleware.IContext.Item(System.String)') - [IContractConnection](#T-MQContract-Interfaces-IContractConnection 'MQContract.Interfaces.IContractConnection') + - [AddMetrics(meter,useInternal)](#M-MQContract-Interfaces-IContractConnection-AddMetrics-System-Diagnostics-Metrics-Meter,System-Boolean- 'MQContract.Interfaces.IContractConnection.AddMetrics(System.Diagnostics.Metrics.Meter,System.Boolean)') - [CloseAsync()](#M-MQContract-Interfaces-IContractConnection-CloseAsync 'MQContract.Interfaces.IContractConnection.CloseAsync') - [GetSnapshot(sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot-System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot(System.Boolean)') - [GetSnapshot(messageType,sent)](#M-MQContract-Interfaces-IContractConnection-GetSnapshot-System-Type,System-Boolean- 'MQContract.Interfaces.IContractConnection.GetSnapshot(System.Type,System.Boolean)') @@ -66,6 +67,7 @@ - [DecodeAsync(stream)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-DecodeAsync-System-IO-Stream- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.DecodeAsync(System.IO.Stream)') - [EncodeAsync(message)](#M-MQContract-Interfaces-Encoding-IMessageTypeEncoder`1-EncodeAsync-`0- 'MQContract.Interfaces.Encoding.IMessageTypeEncoder`1.EncodeAsync(`0)') - [IMessageTypeEncryptor\`1](#T-MQContract-Interfaces-Encrypting-IMessageTypeEncryptor`1 'MQContract.Interfaces.Encrypting.IMessageTypeEncryptor`1') +- [IMiddleware](#T-MQContract-Interfaces-Middleware-IMiddleware 'MQContract.Interfaces.Middleware.IMiddleware') - [IPingableMessageServiceConnection](#T-MQContract-Interfaces-Service-IPingableMessageServiceConnection 'MQContract.Interfaces.Service.IPingableMessageServiceConnection') - [PingAsync()](#M-MQContract-Interfaces-Service-IPingableMessageServiceConnection-PingAsync 'MQContract.Interfaces.Service.IPingableMessageServiceConnection.PingAsync') - [IQueryableMessageServiceConnection](#T-MQContract-Interfaces-Service-IQueryableMessageServiceConnection 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection') @@ -80,6 +82,7 @@ - [ReceivedTimestamp](#P-MQContract-Interfaces-IReceivedMessage`1-ReceivedTimestamp 'MQContract.Interfaces.IReceivedMessage`1.ReceivedTimestamp') - [IServiceSubscription](#T-MQContract-Interfaces-Service-IServiceSubscription 'MQContract.Interfaces.Service.IServiceSubscription') - [EndAsync()](#M-MQContract-Interfaces-Service-IServiceSubscription-EndAsync 'MQContract.Interfaces.Service.IServiceSubscription.EndAsync') +- [ISpecificTypeMiddleware\`1](#T-MQContract-Interfaces-Middleware-ISpecificTypeMiddleware`1 'MQContract.Interfaces.Middleware.ISpecificTypeMiddleware`1') - [ISubscription](#T-MQContract-Interfaces-ISubscription 'MQContract.Interfaces.ISubscription') - [EndAsync()](#M-MQContract-Interfaces-ISubscription-EndAsync 'MQContract.Interfaces.ISubscription.EndAsync') - [MessageChannelAttribute](#T-MQContract-Attributes-MessageChannelAttribute 'MQContract.Attributes.MessageChannelAttribute') @@ -385,6 +388,46 @@ MQContract.Interfaces This interface represents the Core class for the MQContract system, IE the ContractConnection + +### AddMetrics(meter,useInternal) `method` + +##### Summary + +Called to activate the metrics tracking middleware for this connection instance + +##### Returns + +The Contract Connection instance to allow chaining calls + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| meter | [System.Diagnostics.Metrics.Meter](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Diagnostics.Metrics.Meter 'System.Diagnostics.Metrics.Meter') | The Meter item to create all system metrics against | +| useInternal | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | Indicates if the internal metrics collector should be used | + +##### Remarks + +For the Meter metrics, all durations are in ms and the following values and patterns will apply: +mqcontract.messages.sent.count = count of messages sent (Counter) +mqcontract.messages.sent.bytes = count of bytes sent (message data) (Counter) +mqcontract.messages.received.count = count of messages received (Counter) +mqcontract.messages.received.bytes = count of bytes received (message data) (Counter) +mqcontract.messages.encodingduration = milliseconds to encode messages (Histogram) +mqcontract.messages.decodingduration = milliseconds to decode messages (Histogram) +mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.sent.count = count of messages sent of a given type (Counter) +mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.sent.bytes = count of bytes sent (message data) of a given type (Counter) +mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.received.count = count of messages received of a given type (Counter) +mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.received.bytes = count of bytes received (message data) of a given type (Counter) +mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.encodingduration = milliseconds to encode messages of a given type (Histogram) +mqcontract.types.{MessageTypeName}.{MessageVersion(_ instead of .)}.decodingduration = milliseconds to decode messages of a given type (Histogram) +mqcontract.channels.{Channel}.sent.count = count of messages sent for a given channel (Counter) +mqcontract.channels.{Channel}.sent.bytes = count of bytes sent (message data) for a given channel (Counter) +mqcontract.channels.{Channel}.received.count = count of messages received for a given channel (Counter) +mqcontract.channels.{Channel}.received.bytes = count of bytes received (message data) for a given channel (Counter) +mqcontract.channels.{Channel}.encodingduration = milliseconds to encode messages for a given channel (Histogram) +mqcontract.channels.{Channel}.decodingduration = milliseconds to decode messages for a given channel (Histogram) + ### CloseAsync() `method` @@ -1179,6 +1222,17 @@ as well as the default of not encrypting the message body | ---- | ----------- | | T | The type of message that this encryptor supports | + +## IMiddleware `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +Base Middleware just used to limit Generic Types for Register Middleware + ## IPingableMessageServiceConnection `type` @@ -1341,6 +1395,17 @@ A task to allow for asynchronous ending of the subscription This method has no parameters. + +## ISpecificTypeMiddleware\`1 `type` + +##### Namespace + +MQContract.Interfaces.Middleware + +##### Summary + +Base Specific Type Middleware just used to limit Generic Types for Register Middleware + ## ISubscription `type` diff --git a/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs b/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs new file mode 100644 index 0000000..0c9d22d --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs @@ -0,0 +1,274 @@ +using Moq; +using MQContract.Interfaces.Service; +using MQContract; +using AutomatedTesting.Messages; +using AutomatedTesting.ContractConnectionTests.Middlewares; +using MQContract.Interfaces.Middleware; +using MQContract.Interfaces; + +namespace AutomatedTesting.ContractConnectionTests +{ + [TestClass] + public class MiddleWareTests + { + [TestMethod] + public async Task TestRegisterGenericMiddleware() + { + #region Arrange + var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); + + var testMessage = new BasicMessage("testMessage"); + var messageChannel = "TestRegisterGenericMiddleware"; + + List messages = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) + .ReturnsAsync(transmissionResult); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .RegisterMiddleware(); + #endregion + + #region Act + _ = await contractConnection.PublishAsync(testMessage,channel:messageChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.AreEqual(messages[0].Channel, ChannelChangeMiddleware.ChangeChannel(messageChannel)); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestRegisterGenericMiddlewareThroughFunction() + { + #region Arrange + var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); + + var testMessage = new BasicMessage("testMessage"); + var messageChannel = "TestRegisterGenericMiddlewareThrouwFunction"; + var newChannel = "NewTestRegisterGenericMiddlewareThrouwFunction"; + var headers = new MessageHeader([ + new KeyValuePair("test","test") + ]); + + List messages = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) + .ReturnsAsync(transmissionResult); + + var mockMiddleware = new Mock(); + mockMiddleware.Setup(x => x.BeforeMessageEncodeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((IContext context, BasicMessage message, string? channel, MessageHeader messageHeader) => + { + return ValueTask.FromResult((message,newChannel,headers)); + }); + + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .RegisterMiddleware(() => mockMiddleware.Object); + #endregion + + #region Act + _ = await contractConnection.PublishAsync(testMessage, channel: messageChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.AreEqual(messages[0].Channel, newChannel); + Assert.AreEqual(1, messages[0].Header.Keys.Count()); + Assert.AreEqual(headers[headers.Keys.First()], messages[0].Header[messages[0].Header.Keys.First()]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockMiddleware.Verify(x => x.BeforeMessageEncodeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestRegisterSpecificTypeMiddleware() + { + #region Arrange + var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); + + var testMessage = new BasicMessage("testMessage"); + var messageChannel = "TestRegisterSpecificTypeMiddleware"; + + List messages = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) + .ReturnsAsync(transmissionResult); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .RegisterMiddleware(); + #endregion + + #region Act + _ = await contractConnection.PublishAsync(testMessage, channel: messageChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.AreEqual(messages[0].Channel, ChannelChangeMiddlewareForBasicMessage.ChangeChannel(messageChannel)); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestRegisterSpecificTypeMiddlewareThroughFunction() + { + #region Arrange + var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); + + var testMessage = new BasicMessage("testMessage"); + var messageChannel = "TestRegisterSpecificTypeMiddlewareThroughFunction"; + var newChannel = "NewTestRegisterSpecificTypeMiddlewareThroughFunction"; + var headers = new MessageHeader([ + new KeyValuePair("test","test") + ]); + + List messages = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) + .ReturnsAsync(transmissionResult); + + var mockMiddleware = new Mock>(); + mockMiddleware.Setup(x => x.BeforeMessageEncodeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((IContext context, BasicMessage message, string? channel, MessageHeader messageHeader) => + { + return ValueTask.FromResult((message, newChannel, headers)); + }); + + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .RegisterMiddleware,BasicMessage>(() => mockMiddleware.Object); + #endregion + + #region Act + _ = await contractConnection.PublishAsync(testMessage, channel: messageChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.AreEqual(messages[0].Channel, newChannel); + Assert.AreEqual(1, messages[0].Header.Keys.Count()); + Assert.AreEqual(headers[headers.Keys.First()], messages[0].Header[messages[0].Header.Keys.First()]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockMiddleware.Verify(x => x.BeforeMessageEncodeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestRegisterGenericMiddlewareWithService() + { + #region Arrange + var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); + + var testMessage = new BasicMessage("testMessage"); + var messageChannel = "TestRegisterGenericMiddleware"; + var expectedChannel = "TestRegisterGenericMiddlewareWithService"; + + var services = Helper.ProduceServiceProvider(expectedChannel); + + List messages = []; + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.PublishAsync(Capture.In(messages), It.IsAny())) + .ReturnsAsync(transmissionResult); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object, serviceProvider: services) + .RegisterMiddleware(); + #endregion + + #region Act + _ = await contractConnection.PublishAsync(testMessage, channel: messageChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.AreEqual(messages[0].Channel, expectedChannel); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestRegisterSpecificTypeMiddlewarePostDecodingThroughFunction() + { + #region Arrange + var transmissionResult = new TransmissionResult(Guid.NewGuid().ToString()); + var serviceSubscription = new Mock(); + + var testMessage = new BasicMessage("testMessage"); + var messageChannel = "TestRegisterSpecificTypeMiddlewareThroughFunction"; + var headers = new MessageHeader([ + new KeyValuePair("test","test") + ]); + + var actions = new List>(); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.SubscribeAsync(Moq.Capture.In>(actions),It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync(serviceSubscription.Object); + serviceConnection.Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, CancellationToken cancellationToken) => + { + var rmessage = Helper.ProduceReceivedServiceMessage(message); + foreach (var act in actions) + act(rmessage); + return ValueTask.FromResult(transmissionResult); + }); + + var mockMiddleware = new Mock>(); + mockMiddleware.Setup(x => x.AfterMessageDecodeAsync(It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny())) + .Returns((IContext context, BasicMessage message, string ID, MessageHeader messageHeader,DateTime recievedTimestamp,DateTime processedTimeStamp) => + { + return ValueTask.FromResult((message,headers)); + }); + + + var contractConnection = ContractConnection.Instance(serviceConnection.Object) + .RegisterMiddleware, BasicMessage>(() => mockMiddleware.Object); + #endregion + + #region Act + var messages = new List>(); + _ = await contractConnection.SubscribeAsync((msg) => + { + messages.Add(msg); + return ValueTask.CompletedTask; + }, (error) => { }); + _ = await contractConnection.PublishAsync(testMessage, channel: messageChannel); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount>(messages, 1, TimeSpan.FromMinutes(1))); + Assert.AreEqual(headers.Keys.Count(), messages[0].Headers.Keys.Count()); + Assert.AreEqual(headers[headers.Keys.First()], messages[0].Headers[messages[0].Headers.Keys.First()]); + #endregion + + #region Verify + serviceConnection.Verify(x => x.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + mockMiddleware.Verify(x => x.AfterMessageDecodeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + } +} diff --git a/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddleware.cs b/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddleware.cs new file mode 100644 index 0000000..25a9d6a --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddleware.cs @@ -0,0 +1,12 @@ +using MQContract.Interfaces.Middleware; + +namespace AutomatedTesting.ContractConnectionTests.Middlewares +{ + internal class ChannelChangeMiddleware : IBeforeEncodeMiddleware + { + public static string ChangeChannel(string? channel) + => $"{channel}-Modified"; + public ValueTask<(T message, string? channel, MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, T message, string? channel, MessageHeader messageHeader) + => ValueTask.FromResult((message, ChangeChannel(channel), messageHeader)); + } +} diff --git a/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs b/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs new file mode 100644 index 0000000..cb23355 --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs @@ -0,0 +1,13 @@ +using AutomatedTesting.Messages; +using MQContract.Interfaces.Middleware; + +namespace AutomatedTesting.ContractConnectionTests.Middlewares +{ + internal class ChannelChangeMiddlewareForBasicMessage : IBeforeEncodeSpecificTypeMiddleware + { + public static string ChangeChannel(string? channel) + => $"{channel}-ModifiedSpecifically"; + public ValueTask<(BasicMessage message, string? channel, MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, BasicMessage message, string? channel, MessageHeader messageHeader) + => ValueTask.FromResult((message, ChangeChannel(channel), messageHeader)); + } +} diff --git a/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs b/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs new file mode 100644 index 0000000..8556b87 --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs @@ -0,0 +1,17 @@ +using AutomatedTesting.ServiceInjection; +using MQContract.Interfaces.Middleware; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutomatedTesting.ContractConnectionTests.Middlewares +{ + internal class InjectedChannelChangeMiddleware(IInjectableService service) + : IBeforeEncodeMiddleware + { + public ValueTask<(T message, string? channel, MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, T message, string? channel, MessageHeader messageHeader) + => ValueTask.FromResult((message, service.Name, messageHeader)); + } +} diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index acb64e2..0a43069 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -209,7 +209,7 @@ private async ValueTask ProduceSubscribeQueryResponseAsync( { var queryMessageFactory = GetMessageFactory(ignoreMessageHeader); var responseMessageFactory = GetMessageFactory(); - var subscription = new QueryResponseSubscription( + var subscription = new QueryResponseSubscription( async (message,replyChannel) => { var context = new Context(ChannelMapper.MapTypes.QuerySubscription); diff --git a/Core/Subscriptions/QueryResponseHelper.cs b/Core/Subscriptions/QueryResponseHelper.cs index ff5c2b3..34cdc5a 100644 --- a/Core/Subscriptions/QueryResponseHelper.cs +++ b/Core/Subscriptions/QueryResponseHelper.cs @@ -8,7 +8,7 @@ internal static class QueryResponseHelper private const string QUERY_IDENTIFIER_HEADER = "_QueryClientID"; private const string REPLY_ID = "_QueryReplyID"; private const string REPLY_CHANNEL_HEADER = "_QueryReplyChannel"; - private static readonly List REQUIRED_HEADERS = [QUERY_IDENTIFIER_HEADER, REPLY_ID, REPLY_CHANNEL_HEADER]; + private static readonly string[] REQUIRED_HEADERS = [QUERY_IDENTIFIER_HEADER, REPLY_ID, REPLY_CHANNEL_HEADER]; public static MessageHeader StripHeaders(ServiceMessage originalMessage,out Guid queryClientID,out Guid replyID,out string? replyChannel) { @@ -36,7 +36,7 @@ public static ServiceMessage EncodeMessage(ServiceMessage originalMessage, Guid ); public static bool IsValidMessage(ReceivedServiceMessage serviceMessage) - => REQUIRED_HEADERS.All(key=>serviceMessage.Header.Keys.Contains(key)); + => Array.TrueForAll(REQUIRED_HEADERS,key=>serviceMessage.Header.Keys.Contains(key)); public static async Task, CancellationTokenSource>> StartResponseListenerAsync(IMessageServiceConnection connection,TimeSpan timeout,Guid identifier,Guid callID,string replyChannel,CancellationToken cancellationToken) { diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index 6985533..a726d3c 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -4,15 +4,14 @@ namespace MQContract.Subscriptions { - internal sealed class QueryResponseSubscription( + internal sealed class QueryResponseSubscription( Func> processMessage, Action errorReceived, Func> mapChannel, string? channel = null, string? group = null, bool synchronous=false,ILogger? logger=null) - : SubscriptionBase(mapChannel,channel,synchronous) - where Q : class - where R : class + : SubscriptionBase(mapChannel,channel,synchronous) + where T : class { private ManualResetEventSlim? manualResetEvent = new(true); private CancellationTokenSource? token = new(); @@ -44,7 +43,7 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio QueryResponseHelper.StripHeaders(serviceMessage, out var queryClientID, out var replyID, out var replyChannel), serviceMessage.Data ), - replyChannel + replyChannel! ); await connection.PublishAsync(QueryResponseHelper.EncodeMessage(result, queryClientID, replyID, null, replyChannel), cancellationToken); } From f234a48175d171bc32b356a867804245a15c3541 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Wed, 25 Sep 2024 13:56:30 -0400 Subject: [PATCH 18/22] Updating versions updating linked nuget package versions --- AutomatedTesting/AutomatedTesting.csproj | 8 ++++---- Connectors/Kafka/Kafka.csproj | 4 ++-- Connectors/KubeMQ/KubeMQ.csproj | 8 ++++---- Connectors/NATS/NATS.csproj | 4 ++-- Connectors/Redis/Redis.csproj | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/AutomatedTesting/AutomatedTesting.csproj b/AutomatedTesting/AutomatedTesting.csproj index 1689474..9ed3c83 100644 --- a/AutomatedTesting/AutomatedTesting.csproj +++ b/AutomatedTesting/AutomatedTesting.csproj @@ -11,10 +11,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Connectors/Kafka/Kafka.csproj b/Connectors/Kafka/Kafka.csproj index fde9302..d77e75d 100644 --- a/Connectors/Kafka/Kafka.csproj +++ b/Connectors/Kafka/Kafka.csproj @@ -1,6 +1,6 @@  - + net8.0 @@ -16,7 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Connectors/KubeMQ/KubeMQ.csproj b/Connectors/KubeMQ/KubeMQ.csproj index 12d1be1..0f8bf69 100644 --- a/Connectors/KubeMQ/KubeMQ.csproj +++ b/Connectors/KubeMQ/KubeMQ.csproj @@ -1,6 +1,6 @@  - + net8.0 @@ -16,9 +16,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Connectors/NATS/NATS.csproj b/Connectors/NATS/NATS.csproj index 172f204..fd15ba4 100644 --- a/Connectors/NATS/NATS.csproj +++ b/Connectors/NATS/NATS.csproj @@ -1,6 +1,6 @@  - + net8.0 @@ -16,7 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Connectors/Redis/Redis.csproj b/Connectors/Redis/Redis.csproj index baa8024..26525c7 100644 --- a/Connectors/Redis/Redis.csproj +++ b/Connectors/Redis/Redis.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From d972213ff994bcdf6fe242cb8ad7fe77ab9a5b2e Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Wed, 25 Sep 2024 13:56:43 -0400 Subject: [PATCH 19/22] adding HiveMQ adding support for HiveMQ messaging --- Connectors/HiveMQ/Connection.cs | 101 +++++++++++++++++++++++ Connectors/HiveMQ/HiveMQ.csproj | 34 ++++++++ Connectors/HiveMQ/Readme.md | 6 ++ Connectors/HiveMQ/Subscription.cs | 75 +++++++++++++++++ MQContract.sln | 18 +++- Samples/HiveMQSample/HiveMQSample.csproj | 16 ++++ Samples/HiveMQSample/Program.cs | 14 ++++ Samples/Messages/SampleExecution.cs | 4 + 8 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 Connectors/HiveMQ/Connection.cs create mode 100644 Connectors/HiveMQ/HiveMQ.csproj create mode 100644 Connectors/HiveMQ/Readme.md create mode 100644 Connectors/HiveMQ/Subscription.cs create mode 100644 Samples/HiveMQSample/HiveMQSample.csproj create mode 100644 Samples/HiveMQSample/Program.cs diff --git a/Connectors/HiveMQ/Connection.cs b/Connectors/HiveMQ/Connection.cs new file mode 100644 index 0000000..1272acd --- /dev/null +++ b/Connectors/HiveMQ/Connection.cs @@ -0,0 +1,101 @@ +using HiveMQtt.Client; +using HiveMQtt.Client.Options; +using HiveMQtt.MQTT5.Types; +using MQContract.Interfaces.Service; +using MQContract.Messages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.HiveMQ +{ + public class Connection : IMessageServiceConnection,IDisposable + { + private readonly HiveMQClientOptions clientOptions; + private readonly HiveMQClient client; + private bool disposedValue; + + public Connection(HiveMQClientOptions clientOptions) + { + this.clientOptions = clientOptions; + client = new(clientOptions); + var connectTask = client.ConnectAsync(); + connectTask.Wait(); + if (connectTask.Result.ReasonCode!=HiveMQtt.MQTT5.ReasonCodes.ConnAckReasonCode.Success) + throw new Exception($"Failed to connect: {connectTask.Result.ReasonString}"); + } + + uint? IMessageServiceConnection.MaxMessageBodySize => (uint?)clientOptions.ClientMaximumPacketSize; + + async ValueTask IMessageServiceConnection.CloseAsync() + =>await client.DisconnectAsync(); + + private const string MessageID = "_ID"; + private const string MessageTypeID = "_MessageTypeID"; + + internal static MQTT5PublishMessage ConvertMessage(ServiceMessage message) + => new() + { + Topic=message.Channel, + QoS=QualityOfService.AtLeastOnceDelivery, + Payload=message.Data.ToArray(), + UserProperties=new Dictionary( + message.Header.Keys + .Select(k=>new KeyValuePair(k,message.Header[k]!)) + .Concat([ + new(MessageID,message.ID), + new(MessageTypeID,message.MessageTypeID) + ]) + ) + }; + + internal static ReceivedServiceMessage ConvertMessage(MQTT5PublishMessage message) + => new( + message.UserProperties[MessageID], + message.UserProperties[MessageTypeID], + message.Topic!, + new(message.UserProperties.AsEnumerable().Where(pair => !Equals(pair.Key, MessageID)&&!Equals(pair.Key, MessageTypeID))), + message.Payload + ); + + async ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) + { + try + { + _ = await client.PublishAsync(ConvertMessage(message), cancellationToken); + }catch(Exception e) + { + return new(message.ID, e.Message); + } + return new(message.ID); + } + + async ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) + { + var result = new Subscription(clientOptions,messageReceived,errorReceived,channel,group); + await result.EstablishAsync(); + return result; + } + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + client.Dispose(); + } + disposedValue=true; + } + } + + void IDisposable.Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Connectors/HiveMQ/HiveMQ.csproj b/Connectors/HiveMQ/HiveMQ.csproj new file mode 100644 index 0000000..eaaabcf --- /dev/null +++ b/Connectors/HiveMQ/HiveMQ.csproj @@ -0,0 +1,34 @@ + + + + + net8.0 + enable + enable + MQContract.$(MSBuildProjectName) + MQContract.$(MSBuildProjectName) + HiveMQ Connector for MQContract + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + True + \ + + + diff --git a/Connectors/HiveMQ/Readme.md b/Connectors/HiveMQ/Readme.md new file mode 100644 index 0000000..407f0a4 --- /dev/null +++ b/Connectors/HiveMQ/Readme.md @@ -0,0 +1,6 @@ + +# MQContract.HiveMQ + +## Contents + + diff --git a/Connectors/HiveMQ/Subscription.cs b/Connectors/HiveMQ/Subscription.cs new file mode 100644 index 0000000..1df1b70 --- /dev/null +++ b/Connectors/HiveMQ/Subscription.cs @@ -0,0 +1,75 @@ +using HiveMQtt.Client; +using HiveMQtt.Client.Options; +using HiveMQtt.Client.Results; +using MQContract.Interfaces.Service; +using MQContract.Messages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.HiveMQ +{ + internal class Subscription(HiveMQClientOptions clientOptions, Action messageReceived, Action errorReceived, + string channel, string? group) : IServiceSubscription,IDisposable + { + private readonly HiveMQClient client = new(CloneOptions(clientOptions,channel)); + + private static HiveMQClientOptions CloneOptions(HiveMQClientOptions clientOptions,string channel) + { + var result = new HiveMQClientOptions(); + foreach (var prop in typeof(HiveMQClientOptions).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).Where(p => !Equals(p.Name, nameof(clientOptions.ClientId)))) + prop.SetValue(result, prop.GetValue(clientOptions, [])); + result.ClientId=$"{clientOptions.ClientId}.{channel}.{Guid.NewGuid()}"; + return result; + } + + private bool disposedValue; + + private string Topic => $"{(group==null ? "" : $"$share/{group}/")}{channel}"; + + public async ValueTask EstablishAsync() + { + client.OnMessageReceived += (sender, args) => + { + try + { + messageReceived(Connection.ConvertMessage(args.PublishMessage)); + }catch(Exception e) + { + errorReceived(e); + } + }; + var connectResult = await client.ConnectAsync(); + if (connectResult.ReasonCode != HiveMQtt.MQTT5.ReasonCodes.ConnAckReasonCode.Success) + throw new Exception($"Failed to connect: {connectResult.ReasonString}"); + _ = await client.SubscribeAsync(Topic, HiveMQtt.MQTT5.Types.QualityOfService.AtLeastOnceDelivery); + } + + async ValueTask IServiceSubscription.EndAsync() + { + await client.UnsubscribeAsync(Topic); + await client.DisconnectAsync(); + } + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + client.Dispose(); + } + disposedValue=true; + } + } + + void IDisposable.Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/MQContract.sln b/MQContract.sln index f928f46..067369c 100644 --- a/MQContract.sln +++ b/MQContract.sln @@ -35,9 +35,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ", "Connectors\Rabb EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redis", "Connectors\Redis\Redis.csproj", "{DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisSample", "Samples\RedisSample\RedisSample.csproj", "{EA577C5C-036F-4647-80F1-03F0EFAF6081}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RedisSample", "Samples\RedisSample\RedisSample.csproj", "{EA577C5C-036F-4647-80F1-03F0EFAF6081}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RabbitMQSample", "Samples\RabbitMQSample\RabbitMQSample.csproj", "{0740514D-A6AB-41CC-9820-A619125C6C90}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQSample", "Samples\RabbitMQSample\RabbitMQSample.csproj", "{0740514D-A6AB-41CC-9820-A619125C6C90}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HiveMQ", "Connectors\HiveMQ\HiveMQ.csproj", "{CF37AF9A-199E-4974-B33F-D3B34CE52988}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HiveMQSample", "Samples\HiveMQSample\HiveMQSample.csproj", "{17F3294A-6A89-4BDD-86E3-629D3506D147}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -109,6 +113,14 @@ Global {0740514D-A6AB-41CC-9820-A619125C6C90}.Debug|Any CPU.Build.0 = Debug|Any CPU {0740514D-A6AB-41CC-9820-A619125C6C90}.Release|Any CPU.ActiveCfg = Release|Any CPU {0740514D-A6AB-41CC-9820-A619125C6C90}.Release|Any CPU.Build.0 = Release|Any CPU + {CF37AF9A-199E-4974-B33F-D3B34CE52988}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF37AF9A-199E-4974-B33F-D3B34CE52988}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF37AF9A-199E-4974-B33F-D3B34CE52988}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF37AF9A-199E-4974-B33F-D3B34CE52988}.Release|Any CPU.Build.0 = Release|Any CPU + {17F3294A-6A89-4BDD-86E3-629D3506D147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17F3294A-6A89-4BDD-86E3-629D3506D147}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17F3294A-6A89-4BDD-86E3-629D3506D147}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17F3294A-6A89-4BDD-86E3-629D3506D147}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -127,6 +139,8 @@ Global {DF36557A-1F5B-4C45-8B4D-9B3C7EE5A541} = {FCAD12F9-6992-44D7-8E78-464181584E06} {EA577C5C-036F-4647-80F1-03F0EFAF6081} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} {0740514D-A6AB-41CC-9820-A619125C6C90} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} + {CF37AF9A-199E-4974-B33F-D3B34CE52988} = {FCAD12F9-6992-44D7-8E78-464181584E06} + {17F3294A-6A89-4BDD-86E3-629D3506D147} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F4431E3A-7CBE-469C-A7FE-0A48F6768E02} diff --git a/Samples/HiveMQSample/HiveMQSample.csproj b/Samples/HiveMQSample/HiveMQSample.csproj new file mode 100644 index 0000000..8bc0839 --- /dev/null +++ b/Samples/HiveMQSample/HiveMQSample.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/Samples/HiveMQSample/Program.cs b/Samples/HiveMQSample/Program.cs new file mode 100644 index 0000000..e854114 --- /dev/null +++ b/Samples/HiveMQSample/Program.cs @@ -0,0 +1,14 @@ +using HiveMQtt.Client; +using HiveMQtt.Client.Options; +using Messages; +using MQContract.HiveMQ; + +var serviceConnection = new Connection(new HiveMQClientOptions +{ + Host = "127.0.0.1", + Port = 1883, + CleanStart = false, // <--- Set to false to receive messages queued on the broker + ClientId = "HiveMQSample" +}); + +await SampleExecution.ExecuteSample(serviceConnection, "HiveMQ"); diff --git a/Samples/Messages/SampleExecution.cs b/Samples/Messages/SampleExecution.cs index 95039ef..c11f7b2 100644 --- a/Samples/Messages/SampleExecution.cs +++ b/Samples/Messages/SampleExecution.cs @@ -61,6 +61,10 @@ await Task.WhenAll( await contractConnection.CloseAsync().ConfigureAwait(true); }, true); + Console.WriteLine("Awaiting 5 seconds to ensure that all subscriptions are established fully."); + await Task.Delay(TimeSpan.FromSeconds(5)).ConfigureAwait(false); + Console.WriteLine("Beginning message transmissions..."); + var result = await contractConnection.PublishAsync(new("Bob", "Loblaw"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Result 1 [Success:{!result.IsError}, ID:{result.ID}]"); result = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); From 566da188360486668d05075dac1a4f05216fe7ff Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Wed, 25 Sep 2024 14:28:11 -0400 Subject: [PATCH 20/22] updated HiveMQ updated HiveMQ to implement an inbox pattern for the query response model similar to the Redix design --- .../InjectedChannelChangeMiddleware.cs | 5 - Connectors/HiveMQ/Connection.cs | 142 ++++++++++++++++-- Connectors/HiveMQ/Readme.md | 33 ++++ Connectors/HiveMQ/Subscription.cs | 23 +-- Connectors/Redis/Connection.cs | 2 +- Connectors/Redis/Readme.md | 2 +- Samples/HiveMQSample/Program.cs | 3 +- 7 files changed, 169 insertions(+), 41 deletions(-) diff --git a/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs b/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs index 8556b87..c485077 100644 --- a/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs +++ b/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs @@ -1,10 +1,5 @@ using AutomatedTesting.ServiceInjection; using MQContract.Interfaces.Middleware; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace AutomatedTesting.ContractConnectionTests.Middlewares { diff --git a/Connectors/HiveMQ/Connection.cs b/Connectors/HiveMQ/Connection.cs index 1272acd..2bc024a 100644 --- a/Connectors/HiveMQ/Connection.cs +++ b/Connectors/HiveMQ/Connection.cs @@ -3,20 +3,26 @@ using HiveMQtt.MQTT5.Types; using MQContract.Interfaces.Service; using MQContract.Messages; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.HiveMQ { - public class Connection : IMessageServiceConnection,IDisposable + /// + /// This is the MessageServiceConnection implementation for using HiveMQ + /// + public class Connection : IQueryableMessageServiceConnection, IDisposable { private readonly HiveMQClientOptions clientOptions; private readonly HiveMQClient client; + private readonly Guid connectionID = Guid.NewGuid(); + private readonly SemaphoreSlim semQueryLock = new(1, 1); + private Subscription? responseInboxSubscription = null; + private readonly Dictionary> waitingResponses = []; private bool disposedValue; + /// + /// Default constructor that requires the HiveMQ client options settings to be provided + /// + /// The required client options to connect to the HiveMQ instance public Connection(HiveMQClientOptions clientOptions) { this.clientOptions = clientOptions; @@ -29,18 +35,29 @@ public Connection(HiveMQClientOptions clientOptions) uint? IMessageServiceConnection.MaxMessageBodySize => (uint?)clientOptions.ClientMaximumPacketSize; + /// + /// The default timeout to allow for a Query Response call to execute, defaults to 1 minute + /// + public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + async ValueTask IMessageServiceConnection.CloseAsync() - =>await client.DisconnectAsync(); + { + if (responseInboxSubscription!=null) + await ((IServiceSubscription)responseInboxSubscription).EndAsync(); + await client.DisconnectAsync(); + } private const string MessageID = "_ID"; private const string MessageTypeID = "_MessageTypeID"; + private const string ResponseID = "_MessageResponseID"; - internal static MQTT5PublishMessage ConvertMessage(ServiceMessage message) + private static MQTT5PublishMessage ConvertMessage(ServiceMessage message,string? responseTopic=null,Guid? responseID=null,string? respondToTopic=null) => new() { - Topic=message.Channel, + Topic=respondToTopic??message.Channel, QoS=QualityOfService.AtLeastOnceDelivery, Payload=message.Data.ToArray(), + ResponseTopic=responseTopic, UserProperties=new Dictionary( message.Header.Keys .Select(k=>new KeyValuePair(k,message.Header[k]!)) @@ -48,17 +65,21 @@ internal static MQTT5PublishMessage ConvertMessage(ServiceMessage message) new(MessageID,message.ID), new(MessageTypeID,message.MessageTypeID) ]) + .Concat(responseID!=null ?[new(ResponseID,responseID.ToString())] : []) ) }; - internal static ReceivedServiceMessage ConvertMessage(MQTT5PublishMessage message) - => new( + private static ReceivedServiceMessage ConvertMessage(MQTT5PublishMessage message, out string? responseID) + { + message.UserProperties.TryGetValue(ResponseID, out responseID); + return new( message.UserProperties[MessageID], message.UserProperties[MessageTypeID], message.Topic!, - new(message.UserProperties.AsEnumerable().Where(pair => !Equals(pair.Key, MessageID)&&!Equals(pair.Key, MessageTypeID))), + new(message.UserProperties.AsEnumerable().Where(pair => !Equals(pair.Key, MessageID)&&!Equals(pair.Key, MessageTypeID)&&!Equals(pair.Key,ResponseID))), message.Payload ); + } async ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) { @@ -74,7 +95,100 @@ async ValueTask IMessageServiceConnection.PublishAsync(Servi async ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) { - var result = new Subscription(clientOptions,messageReceived,errorReceived,channel,group); + var result = new Subscription( + clientOptions, + (msg) => + { + try + { + messageReceived(ConvertMessage(msg,out _)); + }catch(Exception e) + { + errorReceived(e); + } + } + ,channel,group); + await result.EstablishAsync(); + return result; + } + + private string InboxChannel => $"_inbox/{connectionID}"; + + async ValueTask EnsureResponseSubscriptionRunning() + { + await semQueryLock.WaitAsync(); + if (responseInboxSubscription==null) + { + responseInboxSubscription = new Subscription( + clientOptions, + async (msg) => + { + var incomingMessage = ConvertMessage(msg, out var responseID); + if (responseID!=null && Guid.TryParse(responseID,out var responseGuid)) + { + await semQueryLock.WaitAsync(); + if (waitingResponses.TryGetValue(responseGuid,out var taskCompletion)) + { + taskCompletion.TrySetResult(new( + incomingMessage.ID, + incomingMessage.Header, + incomingMessage.MessageTypeID, + incomingMessage.Data + )); + } + semQueryLock.Release(); + } + }, + InboxChannel, + null + ); + await responseInboxSubscription.EstablishAsync(); + } + semQueryLock.Release(); + } + + async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) + { + await EnsureResponseSubscriptionRunning(); + var responseGuid = Guid.NewGuid(); + var responseSource = new TaskCompletionSource(); + await semQueryLock.WaitAsync(); + waitingResponses.Add(responseGuid, responseSource); + semQueryLock.Release(); + try + { + _ = await client.PublishAsync(ConvertMessage(message,InboxChannel,responseGuid), cancellationToken); + } + catch (Exception e) + { + await semQueryLock.WaitAsync(); + waitingResponses.Remove(responseGuid); + semQueryLock.Release(); + throw new InvalidOperationException("Unable to transmit query request"); + } + await responseSource.Task.WaitAsync(timeout, cancellationToken); + if (responseSource.Task.IsCompleted) + return responseSource.Task.Result; + throw new TimeoutException(); + } + + async ValueTask IQueryableMessageServiceConnection.SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) + { + var result = new Subscription( + clientOptions, + async (msg) => + { + try + { + var result = await messageReceived(ConvertMessage(msg, out var responseID)); + _ = await client.PublishAsync(ConvertMessage(result,responseID:new Guid(responseID!),respondToTopic:msg.ResponseTopic), cancellationToken); + } + catch (Exception e) + { + errorReceived(e); + } + } + , channel, group); await result.EstablishAsync(); return result; } @@ -86,6 +200,8 @@ private void Dispose(bool disposing) if (disposing) { client.Dispose(); + ((IDisposable?)responseInboxSubscription)?.Dispose(); + semQueryLock.Dispose(); } disposedValue=true; } diff --git a/Connectors/HiveMQ/Readme.md b/Connectors/HiveMQ/Readme.md index 407f0a4..b8e3656 100644 --- a/Connectors/HiveMQ/Readme.md +++ b/Connectors/HiveMQ/Readme.md @@ -3,4 +3,37 @@ ## Contents +- [Connection](#T-MQContract-HiveMQ-Connection 'MQContract.HiveMQ.Connection') + - [#ctor(clientOptions)](#M-MQContract-HiveMQ-Connection-#ctor-HiveMQtt-Client-Options-HiveMQClientOptions- 'MQContract.HiveMQ.Connection.#ctor(HiveMQtt.Client.Options.HiveMQClientOptions)') + - [DefaultTimout](#P-MQContract-HiveMQ-Connection-DefaultTimout 'MQContract.HiveMQ.Connection.DefaultTimout') + +## Connection `type` + +##### Namespace + +MQContract.HiveMQ + +##### Summary + +This is the MessageServiceConnection implementation for using HiveMQ + + +### #ctor(clientOptions) `constructor` + +##### Summary + +Default constructor that requires the HiveMQ client options settings to be provided + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| clientOptions | [HiveMQtt.Client.Options.HiveMQClientOptions](#T-HiveMQtt-Client-Options-HiveMQClientOptions 'HiveMQtt.Client.Options.HiveMQClientOptions') | The required client options to connect to the HiveMQ instance | + + +### DefaultTimout `property` + +##### Summary + +The default timeout to allow for a Query Response call to execute, defaults to 1 minute diff --git a/Connectors/HiveMQ/Subscription.cs b/Connectors/HiveMQ/Subscription.cs index 1df1b70..e10161b 100644 --- a/Connectors/HiveMQ/Subscription.cs +++ b/Connectors/HiveMQ/Subscription.cs @@ -1,18 +1,11 @@ using HiveMQtt.Client; using HiveMQtt.Client.Options; -using HiveMQtt.Client.Results; +using HiveMQtt.MQTT5.Types; using MQContract.Interfaces.Service; -using MQContract.Messages; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.HiveMQ { - internal class Subscription(HiveMQClientOptions clientOptions, Action messageReceived, Action errorReceived, - string channel, string? group) : IServiceSubscription,IDisposable + internal class Subscription(HiveMQClientOptions clientOptions, Action messageReceived,string channel, string? group) : IServiceSubscription,IDisposable { private readonly HiveMQClient client = new(CloneOptions(clientOptions,channel)); @@ -31,16 +24,8 @@ private static HiveMQClientOptions CloneOptions(HiveMQClientOptions clientOption public async ValueTask EstablishAsync() { - client.OnMessageReceived += (sender, args) => - { - try - { - messageReceived(Connection.ConvertMessage(args.PublishMessage)); - }catch(Exception e) - { - errorReceived(e); - } - }; + client.OnMessageReceived += (sender, args) + => messageReceived(args.PublishMessage); var connectResult = await client.ConnectAsync(); if (connectResult.ReasonCode != HiveMQtt.MQTT5.ReasonCodes.ConnAckReasonCode.Success) throw new Exception($"Failed to connect: {connectResult.ReasonString}"); diff --git a/Connectors/Redis/Connection.cs b/Connectors/Redis/Connection.cs index a6183fc..b20eefa 100644 --- a/Connectors/Redis/Connection.cs +++ b/Connectors/Redis/Connection.cs @@ -20,7 +20,7 @@ public class Connection : IQueryableMessageServiceConnection,IAsyncDisposable,ID /// /// Default constructor that requires the Redis Configuration settings to be provided /// - /// + /// The configuration to use for the redis connections public Connection(ConfigurationOptions configuration) { connectionMultiplexer = ConnectionMultiplexer.Connect(configuration); diff --git a/Connectors/Redis/Readme.md b/Connectors/Redis/Readme.md index bb2671f..a1458cd 100644 --- a/Connectors/Redis/Readme.md +++ b/Connectors/Redis/Readme.md @@ -31,7 +31,7 @@ Default constructor that requires the Redis Configuration settings to be provide | Name | Type | Description | | ---- | ---- | ----------- | -| configuration | [StackExchange.Redis.ConfigurationOptions](#T-StackExchange-Redis-ConfigurationOptions 'StackExchange.Redis.ConfigurationOptions') | | +| configuration | [StackExchange.Redis.ConfigurationOptions](#T-StackExchange-Redis-ConfigurationOptions 'StackExchange.Redis.ConfigurationOptions') | The configuration to use for the redis connections | ### DefaultTimout `property` diff --git a/Samples/HiveMQSample/Program.cs b/Samples/HiveMQSample/Program.cs index e854114..1a175b8 100644 --- a/Samples/HiveMQSample/Program.cs +++ b/Samples/HiveMQSample/Program.cs @@ -1,5 +1,4 @@ -using HiveMQtt.Client; -using HiveMQtt.Client.Options; +using HiveMQtt.Client.Options; using Messages; using MQContract.HiveMQ; From b2a832d161b61e47d844e37301a95290fe59a641 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Thu, 26 Sep 2024 23:54:50 -0400 Subject: [PATCH 21/22] added inbox added in concept of inbox style service connection, corrected typo for timeout and updated unit testing accordingly. --- ...IInboxQueryableMessageServiceConnection.cs | 27 ++ .../IQueryResponseMessageServiceConnection.cs | 19 ++ .../IQueryableMessageServiceConnection.cs | 17 +- .../Messages/RecievedInboxServiceMessage.cs | 19 ++ Abstractions/Readme.md | 134 +++++++++- AutomatedTesting/ChannelMapperTests.cs | 30 +-- .../InternalMetricTests.cs | 8 +- .../MiddleWareTests.cs | 8 +- .../Middlewares/ChannelChangeMiddleware.cs | 2 +- .../ChannelChangeMiddlewareForBasicMessage.cs | 2 +- .../InjectedChannelChangeMiddleware.cs | 2 +- .../QueryInboxTests.cs | 187 ++++++++++++++ .../ContractConnectionTests/QueryTests.cs | 102 +++++--- .../SubscribeQueryResponseTests.cs | 26 +- .../SystemMetricTests.cs | 2 +- Connectors/HiveMQ/Connection.cs | 99 +++----- Connectors/HiveMQ/Exceptions.cs | 18 ++ Connectors/HiveMQ/Readme.md | 18 +- Connectors/KubeMQ/Connection.cs | 6 +- Connectors/NATS/Connection.cs | 6 +- Connectors/NATS/Readme.md | 6 +- Connectors/RabbitMQ/Connection.cs | 108 ++++---- Connectors/RabbitMQ/Readme.md | 6 +- Connectors/RabbitMQ/Subscription.cs | 4 +- Connectors/Redis/Connection.cs | 6 +- Connectors/Redis/Readme.md | 6 +- Core/ContractConnection.Middleware.cs | 21 ++ Core/ContractConnection.PubSub.cs | 47 ++++ Core/ContractConnection.QueryResponse.cs | 237 ++++++++++++++++++ Core/ContractConnection.cs | 230 ++--------------- Core/Exceptions.cs | 9 + Core/Middleware/MetricsMiddleware.cs | 8 +- Core/Readme.md | 12 + .../QueryResponseSubscription.cs | 4 +- README.md | 6 +- Samples/Messages/SampleExecution.cs | 6 +- 36 files changed, 978 insertions(+), 470 deletions(-) create mode 100644 Abstractions/Interfaces/Service/IInboxQueryableMessageServiceConnection.cs create mode 100644 Abstractions/Interfaces/Service/IQueryResponseMessageServiceConnection.cs create mode 100644 Abstractions/Messages/RecievedInboxServiceMessage.cs create mode 100644 AutomatedTesting/ContractConnectionTests/QueryInboxTests.cs create mode 100644 Connectors/HiveMQ/Exceptions.cs create mode 100644 Core/ContractConnection.PubSub.cs create mode 100644 Core/ContractConnection.QueryResponse.cs diff --git a/Abstractions/Interfaces/Service/IInboxQueryableMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IInboxQueryableMessageServiceConnection.cs new file mode 100644 index 0000000..20f9015 --- /dev/null +++ b/Abstractions/Interfaces/Service/IInboxQueryableMessageServiceConnection.cs @@ -0,0 +1,27 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Service +{ + /// + /// Used to implement an Inbox style query response underlying service, this is if the service does not support QueryResponse messaging but does support a sort of query inbox response + /// style pub sub where you can specify the destination down to a specific instance. + /// + public interface IInboxQueryableMessageServiceConnection : IQueryableMessageServiceConnection + { + /// + /// Establish the inbox subscription with the underlying service connection + /// + /// Callback called when a message is recieved in the RPC inbox + /// A cancellation token + /// A service subscription object specifically tied to the RPC inbox for this particular connection instance + ValueTask EstablishInboxSubscriptionAsync(Action messageReceived,CancellationToken cancellationToken = new CancellationToken()); + /// + /// Called to publish a Query Request when using the inbox style + /// + /// The service message to submit + /// The unique ID of the message to use for handling when the response is proper and is expected in the inbox subscription + /// A cancellation token + /// The transmission result of submitting the message + ValueTask QueryAsync(ServiceMessage message,Guid correlationID, CancellationToken cancellationToken = new CancellationToken()); + } +} diff --git a/Abstractions/Interfaces/Service/IQueryResponseMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IQueryResponseMessageServiceConnection.cs new file mode 100644 index 0000000..5d3463d --- /dev/null +++ b/Abstractions/Interfaces/Service/IQueryResponseMessageServiceConnection.cs @@ -0,0 +1,19 @@ +using MQContract.Messages; + +namespace MQContract.Interfaces.Service +{ + /// + /// Extends the base MessageServiceConnection Interface to Response Query messaging methodology if the underlying service supports it + /// + public interface IQueryResponseMessageServiceConnection : IQueryableMessageServiceConnection + { + /// + /// Implements a call to submit a response query request into the underlying service + /// + /// The message to query with + /// The timeout for recieving a response + /// A cancellation token + /// A Query Result instance based on what happened + ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = new CancellationToken()); + } +} diff --git a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs index 564a9de..0166f56 100644 --- a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs @@ -1,24 +1,21 @@ using MQContract.Messages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace MQContract.Interfaces.Service { /// - /// Extends the base MessageServiceConnection Interface to Response Query messaging methodology if the underlying service supports it + /// Used to identify a message service that supports response query style messaging, either through inbox or directly /// public interface IQueryableMessageServiceConnection : IMessageServiceConnection { /// /// The default timeout to use for RPC calls when it's not specified /// - TimeSpan DefaultTimout { get; } - /// - /// Implements a call to submit a response query request into the underlying service - /// - /// The message to query with - /// The timeout for recieving a response - /// A cancellation token - /// A Query Result instance based on what happened - ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = new CancellationToken()); + TimeSpan DefaultTimeout { get; } /// /// Implements a call to create a subscription to a given channel as a member of a given group for responding to queries /// diff --git a/Abstractions/Messages/RecievedInboxServiceMessage.cs b/Abstractions/Messages/RecievedInboxServiceMessage.cs new file mode 100644 index 0000000..fd5f64f --- /dev/null +++ b/Abstractions/Messages/RecievedInboxServiceMessage.cs @@ -0,0 +1,19 @@ +using System.Diagnostics.CodeAnalysis; + +namespace MQContract.Messages +{ + /// + /// A Received Service Message that gets passed back up into the Contract Connection when a message is received from the underlying service connection + /// + /// The unique ID of the message + /// The message type id which is used for decoding to a class + /// The channel the message was received on + /// The message headers that came through + /// The query message correlation id supplied by the query call to tie to the response + /// The binary content of the message that should be the encoded class + /// The acknowledgement callback to be called when the message is received if the underlying service requires it + [ExcludeFromCodeCoverage(Justification ="This is a record class and has nothing to test")] + public record ReceivedInboxServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header,Guid CorrelationID, ReadOnlyMemory Data,Func? Acknowledge=null) + : ReceivedServiceMessage(ID,MessageTypeID,Channel,Header,Data,Acknowledge) + {} +} diff --git a/Abstractions/Readme.md b/Abstractions/Readme.md index b103e3d..a89e0d5 100644 --- a/Abstractions/Readme.md +++ b/Abstractions/Readme.md @@ -50,6 +50,9 @@ - [Data](#P-MQContract-Interfaces-Messages-IEncodedMessage-Data 'MQContract.Interfaces.Messages.IEncodedMessage.Data') - [Header](#P-MQContract-Interfaces-Messages-IEncodedMessage-Header 'MQContract.Interfaces.Messages.IEncodedMessage.Header') - [MessageTypeID](#P-MQContract-Interfaces-Messages-IEncodedMessage-MessageTypeID 'MQContract.Interfaces.Messages.IEncodedMessage.MessageTypeID') +- [IInboxQueryableMessageServiceConnection](#T-MQContract-Interfaces-Service-IInboxQueryableMessageServiceConnection 'MQContract.Interfaces.Service.IInboxQueryableMessageServiceConnection') + - [EstablishInboxSubscriptionAsync(messageReceived,cancellationToken)](#M-MQContract-Interfaces-Service-IInboxQueryableMessageServiceConnection-EstablishInboxSubscriptionAsync-System-Action{MQContract-Messages-ReceivedInboxServiceMessage},System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IInboxQueryableMessageServiceConnection.EstablishInboxSubscriptionAsync(System.Action{MQContract.Messages.ReceivedInboxServiceMessage},System.Threading.CancellationToken)') + - [QueryAsync(message,correlationID,cancellationToken)](#M-MQContract-Interfaces-Service-IInboxQueryableMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-Guid,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IInboxQueryableMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.Guid,System.Threading.CancellationToken)') - [IMessageConverter\`2](#T-MQContract-Interfaces-Conversion-IMessageConverter`2 'MQContract.Interfaces.Conversion.IMessageConverter`2') - [ConvertAsync(source)](#M-MQContract-Interfaces-Conversion-IMessageConverter`2-ConvertAsync-`0- 'MQContract.Interfaces.Conversion.IMessageConverter`2.ConvertAsync(`0)') - [IMessageEncoder](#T-MQContract-Interfaces-Encoding-IMessageEncoder 'MQContract.Interfaces.Encoding.IMessageEncoder') @@ -70,9 +73,10 @@ - [IMiddleware](#T-MQContract-Interfaces-Middleware-IMiddleware 'MQContract.Interfaces.Middleware.IMiddleware') - [IPingableMessageServiceConnection](#T-MQContract-Interfaces-Service-IPingableMessageServiceConnection 'MQContract.Interfaces.Service.IPingableMessageServiceConnection') - [PingAsync()](#M-MQContract-Interfaces-Service-IPingableMessageServiceConnection-PingAsync 'MQContract.Interfaces.Service.IPingableMessageServiceConnection.PingAsync') +- [IQueryResponseMessageServiceConnection](#T-MQContract-Interfaces-Service-IQueryResponseMessageServiceConnection 'MQContract.Interfaces.Service.IQueryResponseMessageServiceConnection') + - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryResponseMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryResponseMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') - [IQueryableMessageServiceConnection](#T-MQContract-Interfaces-Service-IQueryableMessageServiceConnection 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection') - - [DefaultTimout](#P-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-DefaultTimout 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.DefaultTimout') - - [QueryAsync(message,timeout,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-QueryAsync-MQContract-Messages-ServiceMessage,System-TimeSpan,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.QueryAsync(MQContract.Messages.ServiceMessage,System.TimeSpan,System.Threading.CancellationToken)') + - [DefaultTimeout](#P-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-DefaultTimeout 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.DefaultTimeout') - [SubscribeQueryAsync(messageReceived,errorReceived,channel,group,cancellationToken)](#M-MQContract-Interfaces-Service-IQueryableMessageServiceConnection-SubscribeQueryAsync-System-Func{MQContract-Messages-ReceivedServiceMessage,System-Threading-Tasks-ValueTask{MQContract-Messages-ServiceMessage}},System-Action{System-Exception},System-String,System-String,System-Threading-CancellationToken- 'MQContract.Interfaces.Service.IQueryableMessageServiceConnection.SubscribeQueryAsync(System.Func{MQContract.Messages.ReceivedServiceMessage,System.Threading.Tasks.ValueTask{MQContract.Messages.ServiceMessage}},System.Action{System.Exception},System.String,System.String,System.Threading.CancellationToken)') - [IReceivedMessage\`1](#T-MQContract-Interfaces-IReceivedMessage`1 'MQContract.Interfaces.IReceivedMessage`1') - [Headers](#P-MQContract-Interfaces-IReceivedMessage`1-Headers 'MQContract.Interfaces.IReceivedMessage`1.Headers') @@ -123,6 +127,9 @@ - [#ctor(ID,Header,Result,Error)](#M-MQContract-Messages-QueryResult`1-#ctor-System-String,MQContract-Messages-MessageHeader,`0,System-String- 'MQContract.Messages.QueryResult`1.#ctor(System.String,MQContract.Messages.MessageHeader,`0,System.String)') - [Header](#P-MQContract-Messages-QueryResult`1-Header 'MQContract.Messages.QueryResult`1.Header') - [Result](#P-MQContract-Messages-QueryResult`1-Result 'MQContract.Messages.QueryResult`1.Result') +- [ReceivedInboxServiceMessage](#T-MQContract-Messages-ReceivedInboxServiceMessage 'MQContract.Messages.ReceivedInboxServiceMessage') + - [#ctor(ID,MessageTypeID,Channel,Header,CorrelationID,Data,Acknowledge)](#M-MQContract-Messages-ReceivedInboxServiceMessage-#ctor-System-String,System-String,System-String,MQContract-Messages-MessageHeader,System-Guid,System-ReadOnlyMemory{System-Byte},System-Func{System-Threading-Tasks-ValueTask}- 'MQContract.Messages.ReceivedInboxServiceMessage.#ctor(System.String,System.String,System.String,MQContract.Messages.MessageHeader,System.Guid,System.ReadOnlyMemory{System.Byte},System.Func{System.Threading.Tasks.ValueTask})') + - [CorrelationID](#P-MQContract-Messages-ReceivedInboxServiceMessage-CorrelationID 'MQContract.Messages.ReceivedInboxServiceMessage.CorrelationID') - [ReceivedServiceMessage](#T-MQContract-Messages-ReceivedServiceMessage 'MQContract.Messages.ReceivedServiceMessage') - [#ctor(ID,MessageTypeID,Channel,Header,Data,Acknowledge)](#M-MQContract-Messages-ReceivedServiceMessage-#ctor-System-String,System-String,System-String,MQContract-Messages-MessageHeader,System-ReadOnlyMemory{System-Byte},System-Func{System-Threading-Tasks-ValueTask}- 'MQContract.Messages.ReceivedServiceMessage.#ctor(System.String,System.String,System.String,MQContract.Messages.MessageHeader,System.ReadOnlyMemory{System.Byte},System.Func{System.Threading.Tasks.ValueTask})') - [Acknowledge](#P-MQContract-Messages-ReceivedServiceMessage-Acknowledge 'MQContract.Messages.ReceivedServiceMessage.Acknowledge') @@ -933,6 +940,55 @@ The header for the given message The message type id to transmit across + +## IInboxQueryableMessageServiceConnection `type` + +##### Namespace + +MQContract.Interfaces.Service + +##### Summary + +Used to implement an Inbox style query response underlying service, this is if the service does not support QueryResponse messaging but does support a sort of query inbox response +style pub sub where you can specify the destination down to a specific instance. + + +### EstablishInboxSubscriptionAsync(messageReceived,cancellationToken) `method` + +##### Summary + +Establish the inbox subscription with the underlying service connection + +##### Returns + +A service subscription object specifically tied to the RPC inbox for this particular connection instance + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| messageReceived | [System.Action{MQContract.Messages.ReceivedInboxServiceMessage}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Action 'System.Action{MQContract.Messages.ReceivedInboxServiceMessage}') | Callback called when a message is recieved in the RPC inbox | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + + +### QueryAsync(message,correlationID,cancellationToken) `method` + +##### Summary + +Called to publish a Query Request when using the inbox style + +##### Returns + +The transmission result of submitting the message + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| message | [MQContract.Messages.ServiceMessage](#T-MQContract-Messages-ServiceMessage 'MQContract.Messages.ServiceMessage') | The service message to submit | +| correlationID | [System.Guid](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Guid 'System.Guid') | The unique ID of the message to use for handling when the response is proper and is expected in the inbox subscription | +| cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + ## IMessageConverter\`2 `type` @@ -1259,8 +1315,8 @@ A Ping Result This method has no parameters. - -## IQueryableMessageServiceConnection `type` + +## IQueryResponseMessageServiceConnection `type` ##### Namespace @@ -1270,14 +1326,7 @@ MQContract.Interfaces.Service Extends the base MessageServiceConnection Interface to Response Query messaging methodology if the underlying service supports it - -### DefaultTimout `property` - -##### Summary - -The default timeout to use for RPC calls when it's not specified - - + ### QueryAsync(message,timeout,cancellationToken) `method` ##### Summary @@ -1296,6 +1345,24 @@ A Query Result instance based on what happened | timeout | [System.TimeSpan](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.TimeSpan 'System.TimeSpan') | The timeout for recieving a response | | cancellationToken | [System.Threading.CancellationToken](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Threading.CancellationToken 'System.Threading.CancellationToken') | A cancellation token | + +## IQueryableMessageServiceConnection `type` + +##### Namespace + +MQContract.Interfaces.Service + +##### Summary + +Used to identify a message service that supports response query style messaging, either through inbox or directly + + +### DefaultTimeout `property` + +##### Summary + +The default timeout to use for RPC calls when it's not specified + ### SubscribeQueryAsync(messageReceived,errorReceived,channel,group,cancellationToken) `method` @@ -1975,6 +2042,49 @@ The response headers The resulting response if there was one + +## ReceivedInboxServiceMessage `type` + +##### Namespace + +MQContract.Messages + +##### Summary + +A Received Service Message that gets passed back up into the Contract Connection when a message is received from the underlying service connection + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| ID | [T:MQContract.Messages.ReceivedInboxServiceMessage](#T-T-MQContract-Messages-ReceivedInboxServiceMessage 'T:MQContract.Messages.ReceivedInboxServiceMessage') | The unique ID of the message | + + +### #ctor(ID,MessageTypeID,Channel,Header,CorrelationID,Data,Acknowledge) `constructor` + +##### Summary + +A Received Service Message that gets passed back up into the Contract Connection when a message is received from the underlying service connection + +##### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| ID | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The unique ID of the message | +| MessageTypeID | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The message type id which is used for decoding to a class | +| Channel | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The channel the message was received on | +| Header | [MQContract.Messages.MessageHeader](#T-MQContract-Messages-MessageHeader 'MQContract.Messages.MessageHeader') | The message headers that came through | +| CorrelationID | [System.Guid](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Guid 'System.Guid') | The query message correlation id supplied by the query call to tie to the response | +| Data | [System.ReadOnlyMemory{System.Byte}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.ReadOnlyMemory 'System.ReadOnlyMemory{System.Byte}') | The binary content of the message that should be the encoded class | +| Acknowledge | [System.Func{System.Threading.Tasks.ValueTask}](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Func 'System.Func{System.Threading.Tasks.ValueTask}') | The acknowledgement callback to be called when the message is received if the underlying service requires it | + + +### CorrelationID `property` + +##### Summary + +The query message correlation id supplied by the query call to tie to the response + ## ReceivedServiceMessage `type` diff --git a/AutomatedTesting/ChannelMapperTests.cs b/AutomatedTesting/ChannelMapperTests.cs index 3599914..b44d217 100644 --- a/AutomatedTesting/ChannelMapperTests.cs +++ b/AutomatedTesting/ChannelMapperTests.cs @@ -519,10 +519,10 @@ public async Task TestQueryMapWithStringToString() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var newChannel = $"{typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -570,10 +570,10 @@ public async Task TestQueryMapWithStringToFunction() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var newChannel = $"{typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -628,10 +628,10 @@ public async Task TestQueryMapWithMatchToFunction() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var newChannel = $"{typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -684,10 +684,10 @@ public async Task TestQueryMapWithDefaultFunction() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var newChannel = $"{typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -741,10 +741,10 @@ public async Task TestQueryMapWithSingleMapAndWithDefaultFunction() List messages = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), It.IsAny(), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var newChannel = $"{typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name}-Modded"; @@ -788,7 +788,7 @@ public async Task TestQuerySubscribeMapWithStringToString() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -831,7 +831,7 @@ public async Task TestQuerySubscribeMapWithStringToFunction() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -889,7 +889,7 @@ public async Task TestQuerySubscribeMapWithMatchToFunction() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -945,7 +945,7 @@ public async Task TestQuerySubscribeMapWithDefaultFunction() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -1002,7 +1002,7 @@ public async Task TestQuerySubscribeMapWithSingleMapAndWithDefaultFunction() var channels = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), diff --git a/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs b/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs index 084240b..0c7f40d 100644 --- a/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs +++ b/AutomatedTesting/ContractConnectionTests/InternalMetricTests.cs @@ -197,7 +197,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() var receivedActions = new List>>(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(receivedActions), It.IsAny>(), @@ -248,7 +248,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() IContractMetric? globalSentMetrics = contractConnection.GetSnapshot(true); IContractMetric? globalReceivedMetrics = contractConnection.GetSnapshot(false); IContractMetric? channelSentMetrics = contractConnection.GetSnapshot(channel, true); - IContractMetric? channelRecievedMetrics = contractConnection.GetSnapshot(channel, false); + IContractMetric? channelReceivedMetrics = contractConnection.GetSnapshot(channel, false); #endregion #region Assert @@ -258,8 +258,8 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() Assert.AreEqual(2, globalReceivedMetrics.Messages); Assert.IsNotNull(channelSentMetrics); Assert.AreEqual(1, channelSentMetrics.Messages); - Assert.IsNotNull(channelRecievedMetrics); - Assert.AreEqual(1, channelRecievedMetrics.Messages); + Assert.IsNotNull(channelReceivedMetrics); + Assert.AreEqual(1, channelReceivedMetrics.Messages); Assert.IsTrue(Array.TrueForAll(querySentMetrics, (q) => q!=null)); Assert.IsTrue(Array.TrueForAll(queryReceivedMetrics, (q) => q!=null)); Assert.IsTrue(Array.TrueForAll(responseSentMetrics, (q) => q!=null)); diff --git a/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs b/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs index 0c9d22d..f5f13ec 100644 --- a/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs +++ b/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs @@ -54,7 +54,7 @@ public async Task TestRegisterGenericMiddlewareThroughFunction() var messageChannel = "TestRegisterGenericMiddlewareThrouwFunction"; var newChannel = "NewTestRegisterGenericMiddlewareThrouwFunction"; var headers = new MessageHeader([ - new KeyValuePair("test","test") + new KeyValuePair("test","test") ]); List messages = []; @@ -67,7 +67,7 @@ public async Task TestRegisterGenericMiddlewareThroughFunction() mockMiddleware.Setup(x => x.BeforeMessageEncodeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((IContext context, BasicMessage message, string? channel, MessageHeader messageHeader) => { - return ValueTask.FromResult((message,newChannel,headers)); + return ValueTask.FromResult<(BasicMessage message, string? channel, MessageHeader messageHeader)>((message,newChannel,headers)); }); @@ -135,7 +135,7 @@ public async Task TestRegisterSpecificTypeMiddlewareThroughFunction() var messageChannel = "TestRegisterSpecificTypeMiddlewareThroughFunction"; var newChannel = "NewTestRegisterSpecificTypeMiddlewareThroughFunction"; var headers = new MessageHeader([ - new KeyValuePair("test","test") + new KeyValuePair("test","test") ]); List messages = []; @@ -219,7 +219,7 @@ public async Task TestRegisterSpecificTypeMiddlewarePostDecodingThroughFunction( var testMessage = new BasicMessage("testMessage"); var messageChannel = "TestRegisterSpecificTypeMiddlewareThroughFunction"; var headers = new MessageHeader([ - new KeyValuePair("test","test") + new KeyValuePair("test","test") ]); var actions = new List>(); diff --git a/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddleware.cs b/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddleware.cs index 25a9d6a..1e4d32d 100644 --- a/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddleware.cs +++ b/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddleware.cs @@ -7,6 +7,6 @@ internal class ChannelChangeMiddleware : IBeforeEncodeMiddleware public static string ChangeChannel(string? channel) => $"{channel}-Modified"; public ValueTask<(T message, string? channel, MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, T message, string? channel, MessageHeader messageHeader) - => ValueTask.FromResult((message, ChangeChannel(channel), messageHeader)); + => ValueTask.FromResult<(T message, string? channel, MessageHeader messageHeader)>((message, ChangeChannel(channel), messageHeader)); } } diff --git a/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs b/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs index cb23355..86040d8 100644 --- a/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs +++ b/AutomatedTesting/ContractConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs @@ -8,6 +8,6 @@ internal class ChannelChangeMiddlewareForBasicMessage : IBeforeEncodeSpecificTyp public static string ChangeChannel(string? channel) => $"{channel}-ModifiedSpecifically"; public ValueTask<(BasicMessage message, string? channel, MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, BasicMessage message, string? channel, MessageHeader messageHeader) - => ValueTask.FromResult((message, ChangeChannel(channel), messageHeader)); + => ValueTask.FromResult<(BasicMessage message, string? channel, MessageHeader messageHeader)>((message, ChangeChannel(channel), messageHeader)); } } diff --git a/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs b/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs index c485077..3e6ec99 100644 --- a/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs +++ b/AutomatedTesting/ContractConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs @@ -7,6 +7,6 @@ internal class InjectedChannelChangeMiddleware(IInjectableService service) : IBeforeEncodeMiddleware { public ValueTask<(T message, string? channel, MessageHeader messageHeader)> BeforeMessageEncodeAsync(IContext context, T message, string? channel, MessageHeader messageHeader) - => ValueTask.FromResult((message, service.Name, messageHeader)); + => ValueTask.FromResult<(T message, string? channel, MessageHeader messageHeader)>((message, service.Name, messageHeader)); } } diff --git a/AutomatedTesting/ContractConnectionTests/QueryInboxTests.cs b/AutomatedTesting/ContractConnectionTests/QueryInboxTests.cs new file mode 100644 index 0000000..8967908 --- /dev/null +++ b/AutomatedTesting/ContractConnectionTests/QueryInboxTests.cs @@ -0,0 +1,187 @@ +using AutomatedTesting.Messages; +using Moq; +using MQContract.Attributes; +using MQContract.Interfaces.Service; +using MQContract; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json; +using System.Reflection; + +namespace AutomatedTesting.ContractConnectionTests +{ + [TestClass] + public class QueryInboxTests + { + [TestMethod] + public async Task TestQueryAsyncWithNoExtendedAspects() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var responseMessage = new BasicResponseMessage("testResponse"); + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, responseMessage); + var responseData = (ReadOnlyMemory)ms.ToArray(); + + var queryResult = new ServiceQueryResult(Guid.NewGuid().ToString(), new MessageHeader([]), "U-BasicResponseMessage-0.0.0.0", responseData); + + var mockSubscription = new Mock(); + + var defaultTimeout = TimeSpan.FromMinutes(1); + + List> receivedActions = []; + List messages = []; + List messageIDs = []; + var acknowledgeCount = 0; + + + var serviceConnection = new Mock(); + serviceConnection.Setup(x=>x.EstablishInboxSubscriptionAsync(Capture.In>(receivedActions),It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(messageIDs), It.IsAny())) + .Returns((ServiceMessage message,Guid messageID, CancellationToken cancellationToken) => + { + foreach (var action in receivedActions) + action(new( + queryResult.ID, + queryResult.MessageTypeID, + message.Channel, + queryResult.Header, + messageID, + queryResult.Data, + () => { + acknowledgeCount++; + return ValueTask.CompletedTask; + } + )); + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimeout) + .Returns(defaultTimeout); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var result = await contractConnection.QueryAsync(testMessage); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + await contractConnection.CloseAsync(); + #endregion + + #region Assert + Assert.IsTrue(await Helper.WaitForCount(messages, 1, TimeSpan.FromMinutes(1))); + Assert.IsNotNull(result); + Assert.AreEqual(queryResult.ID, result.ID); + Assert.IsNull(result.Error); + Assert.IsFalse(result.IsError); + Assert.AreEqual(typeof(BasicQueryMessage).GetCustomAttribute(false)?.Name, messages[0].Channel); + Assert.AreEqual(1, messageIDs.Count); + Assert.AreEqual(0, messages[0].Header.Keys.Count()); + Assert.AreEqual("U-BasicQueryMessage-0.0.0.0", messages[0].MessageTypeID); + Assert.IsTrue(messages[0].Data.Length>0); + Assert.AreEqual(testMessage, await JsonSerializer.DeserializeAsync(new MemoryStream(messages[0].Data.ToArray()))); + Assert.AreEqual(responseMessage, result.Result); + #endregion + + #region Verify + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.EstablishInboxSubscriptionAsync(It.IsAny>(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestQueryAsyncWithErrorInPublish() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + var errorMessage = "Unable to transmit"; + + var mockSubscription = new Mock(); + + var defaultTimeout = TimeSpan.FromMinutes(1); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.EstablishInboxSubscriptionAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, Guid messageID, CancellationToken cancellationToken) => + { + return ValueTask.FromResult(new TransmissionResult(message.ID,errorMessage)); + }); + serviceConnection.Setup(x => x.DefaultTimeout) + .Returns(defaultTimeout); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage)); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + await contractConnection.CloseAsync(); + #endregion + + #region Assert + Assert.IsNotNull(exception); + Assert.AreEqual(errorMessage, exception.Message); + #endregion + + #region Verify + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.EstablishInboxSubscriptionAsync(It.IsAny>(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + + [TestMethod] + public async Task TestQueryAsyncWithTimeout() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + + var mockSubscription = new Mock(); + + var defaultTimeout = TimeSpan.FromSeconds(5); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.EstablishInboxSubscriptionAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync(mockSubscription.Object); + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ServiceMessage message, Guid messageID, CancellationToken cancellationToken) => + { + return ValueTask.FromResult(new TransmissionResult(message.ID)); + }); + serviceConnection.Setup(x => x.DefaultTimeout) + .Returns(defaultTimeout); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object); + #endregion + + #region Act + var stopwatch = Stopwatch.StartNew(); + var exception = await Assert.ThrowsExceptionAsync(async () => await contractConnection.QueryAsync(testMessage)); + stopwatch.Stop(); + System.Diagnostics.Trace.WriteLine($"Time to publish message {stopwatch.ElapsedMilliseconds}ms"); + await contractConnection.CloseAsync(); + #endregion + + #region Assert + Assert.IsNotNull(exception); + #endregion + + #region Verify + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceConnection.Verify(x => x.EstablishInboxSubscriptionAsync(It.IsAny>(), It.IsAny()), Times.Once); + mockSubscription.Verify(x => x.EndAsync(), Times.Once); + #endregion + } + } +} diff --git a/AutomatedTesting/ContractConnectionTests/QueryTests.cs b/AutomatedTesting/ContractConnectionTests/QueryTests.cs index 0241bd6..f0e79e2 100644 --- a/AutomatedTesting/ContractConnectionTests/QueryTests.cs +++ b/AutomatedTesting/ContractConnectionTests/QueryTests.cs @@ -38,10 +38,10 @@ public async Task TestQueryAsyncWithNoExtendedAspects() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -93,10 +93,10 @@ public async Task TestQueryAsyncWithDifferentChannelName() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -148,10 +148,10 @@ public async Task TestQueryAsyncWithMessageHeaders() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var messageHeader = new MessageHeader([new("testing", "testing")]); @@ -208,10 +208,10 @@ public async Task TestQueryAsyncWithTimeout() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -263,10 +263,10 @@ public async Task TestQueryAsyncWithCompressionDueToMessageSize() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); serviceConnection.Setup(x => x.MaxMessageBodySize) .Returns(37); @@ -321,10 +321,10 @@ public async Task TestQueryAsyncWithGlobalEncoder() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var globalEncoder = new Mock(); @@ -391,10 +391,10 @@ public async Task TestQueryAsyncWithGlobalEncryptor() new KeyValuePair("test","test") ]); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var globalEncryptor = new Mock(); @@ -459,10 +459,10 @@ public async Task TestQueryAsyncWithTimeoutAttribute() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -514,10 +514,10 @@ public async Task TestQueryAsyncWithNamedAndVersionedMessage() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -569,10 +569,10 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncoder() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -626,10 +626,10 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncoder() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object, serviceProvider: services); @@ -683,10 +683,10 @@ public async Task TestQueryAsyncWithMessageWithDefinedEncryptor() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -740,10 +740,10 @@ public async Task TestQueryAsyncWithMessageWithDefinedServiceInjectableEncryptor List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object, serviceProvider: services); @@ -795,10 +795,10 @@ public async Task TestQueryAsyncWithNoMessageChannelThrowsError() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -840,10 +840,10 @@ public async Task TestQueryAsyncWithToLargeAMessageThrowsError() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); serviceConnection.Setup(x => x.MaxMessageBodySize) .Returns(1); @@ -888,10 +888,10 @@ public async Task TestQueryAsyncWithTwoDifferentMessageTypes() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -959,10 +959,10 @@ public async Task TestQueryAsyncWithAttributeReturnType() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); @@ -996,6 +996,36 @@ public async Task TestQueryAsyncWithAttributeReturnType() #endregion } + [TestMethod] + public async Task TestQueryAsyncWithAttributeReturnTypeThrowingTimeoutException() + { + #region Arrange + var testMessage = new BasicQueryMessage("testMessage"); + + var defaultTimeout = TimeSpan.FromMinutes(1); + + var serviceConnection = new Mock(); + serviceConnection.Setup(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(); + serviceConnection.Setup(x => x.DefaultTimeout) + .Returns(defaultTimeout); + + var contractConnection = ContractConnection.Instance(serviceConnection.Object); + #endregion + + #region Act + var exception = await Assert.ThrowsExceptionAsync(async ()=>await contractConnection.QueryAsync(testMessage)); + #endregion + + #region Assert + Assert.IsNotNull(exception); + #endregion + + #region Verify + serviceConnection.Verify(x => x.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + #endregion + } + [TestMethod] public async Task TestQueryAsyncWithNoReturnType() { @@ -1014,10 +1044,10 @@ public async Task TestQueryAsyncWithNoReturnType() List messages = []; List timeouts = []; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.QueryAsync(Capture.In(messages), Capture.In(timeouts), It.IsAny())) .ReturnsAsync(queryResult); - serviceConnection.Setup(x => x.DefaultTimout) + serviceConnection.Setup(x => x.DefaultTimeout) .Returns(defaultTimeout); var contractConnection = ContractConnection.Instance(serviceConnection.Object); diff --git a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs index 7cd6dec..cb63750 100644 --- a/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SubscribeQueryResponseTests.cs @@ -25,7 +25,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() var groups = new List(); var serviceMessages = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(receivedActions), Capture.In>(errorActions), @@ -103,7 +103,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificChannel() var channels = new List(); var channelName = "TestSubscribeQueryResponseWithSpecificChannel"; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -151,7 +151,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSpecificGroup() var groups = new List(); var groupName = "TestSubscribeQueryResponseWithSpecificGroup"; - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -194,7 +194,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() #region Arrange var serviceSubscription = new Mock(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -228,7 +228,7 @@ public async Task TestSubscribeQueryResponseAsyncNoMessageChannelThrowsError() public async Task TestSubscribeQueryResponseAsyncReturnFailedSubscription() { #region Arrange - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -264,7 +264,7 @@ public async Task TestSubscribeQueryResponseAsyncCleanup() serviceSubscription.Setup(x => x.EndAsync()) .Returns(ValueTask.CompletedTask); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( It.IsAny>>(), It.IsAny>(), @@ -306,7 +306,7 @@ public async Task TestSubscribeQueryResponseAsyncWithSynchronousActions() var groups = new List(); var serviceMessages = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(receivedActions), Capture.In>(errorActions), @@ -396,7 +396,7 @@ public async Task TestSubscribeQueryResponseAsyncErrorTriggeringInOurAction() var groups = new List(); var serviceMessages = new List(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(receivedActions), Capture.In>(errorActions), @@ -462,7 +462,7 @@ public async Task TestSubscribeQueryResponseAsyncEndAsync() serviceSubscription.Setup(x => x.EndAsync()) .Returns(ValueTask.CompletedTask); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -498,7 +498,7 @@ public async Task TestSubscribeQueryResponseAsyncAsyncCleanup() serviceSubscription.Setup(x => x.DisposeAsync()) .Returns(ValueTask.CompletedTask); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -531,7 +531,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNonAsyncCleanup() { #region Arrange var serviceSubscription = new Mock(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -565,7 +565,7 @@ public async Task TestSubscribeQueryResponseAsyncSubscriptionsCleanup() #region Arrange var serviceSubscription = new Mock(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync(It.IsAny>>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -612,7 +612,7 @@ public async Task TestSubscribeQueryResponseAsyncWithThrowsConversionError() var receivedActions = new List>>(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(receivedActions), It.IsAny>(), diff --git a/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs b/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs index f291ff4..401a14f 100644 --- a/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs +++ b/AutomatedTesting/ContractConnectionTests/SystemMetricTests.cs @@ -177,7 +177,7 @@ public async Task TestSubscribeQueryResponseAsyncWithNoExtendedAspects() var receivedActions = new List>>(); - var serviceConnection = new Mock(); + var serviceConnection = new Mock(); serviceConnection.Setup(x => x.SubscribeQueryAsync( Capture.In>>(receivedActions), It.IsAny>(), diff --git a/Connectors/HiveMQ/Connection.cs b/Connectors/HiveMQ/Connection.cs index 2bc024a..df06fbd 100644 --- a/Connectors/HiveMQ/Connection.cs +++ b/Connectors/HiveMQ/Connection.cs @@ -9,14 +9,11 @@ namespace MQContract.HiveMQ /// /// This is the MessageServiceConnection implementation for using HiveMQ /// - public class Connection : IQueryableMessageServiceConnection, IDisposable + public class Connection : IInboxQueryableMessageServiceConnection, IDisposable { private readonly HiveMQClientOptions clientOptions; private readonly HiveMQClient client; private readonly Guid connectionID = Guid.NewGuid(); - private readonly SemaphoreSlim semQueryLock = new(1, 1); - private Subscription? responseInboxSubscription = null; - private readonly Dictionary> waitingResponses = []; private bool disposedValue; /// @@ -30,7 +27,7 @@ public Connection(HiveMQClientOptions clientOptions) var connectTask = client.ConnectAsync(); connectTask.Wait(); if (connectTask.Result.ReasonCode!=HiveMQtt.MQTT5.ReasonCodes.ConnAckReasonCode.Success) - throw new Exception($"Failed to connect: {connectTask.Result.ReasonString}"); + throw new ConnectionFailedException(connectTask.Result.ReasonString); } uint? IMessageServiceConnection.MaxMessageBodySize => (uint?)clientOptions.ClientMaximumPacketSize; @@ -38,14 +35,10 @@ public Connection(HiveMQClientOptions clientOptions) /// /// The default timeout to allow for a Query Response call to execute, defaults to 1 minute /// - public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + public TimeSpan DefaultTimeout { get; init; } = TimeSpan.FromMinutes(1); async ValueTask IMessageServiceConnection.CloseAsync() - { - if (responseInboxSubscription!=null) - await ((IServiceSubscription)responseInboxSubscription).EndAsync(); - await client.DisconnectAsync(); - } + => await client.DisconnectAsync(); private const string MessageID = "_ID"; private const string MessageTypeID = "_MessageTypeID"; @@ -65,7 +58,7 @@ private static MQTT5PublishMessage ConvertMessage(ServiceMessage message,string? new(MessageID,message.ID), new(MessageTypeID,message.MessageTypeID) ]) - .Concat(responseID!=null ?[new(ResponseID,responseID.ToString())] : []) + .Concat(responseID!=null ?[new(ResponseID,responseID.Value.ToString())] : []) ) }; @@ -114,62 +107,44 @@ async ValueTask IMessageServiceConnection.PublishAsync(Servi private string InboxChannel => $"_inbox/{connectionID}"; - async ValueTask EnsureResponseSubscriptionRunning() + async ValueTask IInboxQueryableMessageServiceConnection.EstablishInboxSubscriptionAsync(Action messageReceived, CancellationToken cancellationToken) { - await semQueryLock.WaitAsync(); - if (responseInboxSubscription==null) - { - responseInboxSubscription = new Subscription( - clientOptions, - async (msg) => + var result = new Subscription( + clientOptions, + (msg) => + { + var incomingMessage = ConvertMessage(msg, out var responseID); + if (responseID!=null && Guid.TryParse(responseID, out var responseGuid)) { - var incomingMessage = ConvertMessage(msg, out var responseID); - if (responseID!=null && Guid.TryParse(responseID,out var responseGuid)) - { - await semQueryLock.WaitAsync(); - if (waitingResponses.TryGetValue(responseGuid,out var taskCompletion)) - { - taskCompletion.TrySetResult(new( - incomingMessage.ID, - incomingMessage.Header, - incomingMessage.MessageTypeID, - incomingMessage.Data - )); - } - semQueryLock.Release(); - } - }, - InboxChannel, - null - ); - await responseInboxSubscription.EstablishAsync(); - } - semQueryLock.Release(); + messageReceived(new( + incomingMessage.ID, + incomingMessage.MessageTypeID, + InboxChannel, + incomingMessage.Header, + responseGuid, + incomingMessage.Data, + incomingMessage.Acknowledge + )); + } + }, + InboxChannel, + null + ); + await result.EstablishAsync(); + return result; } - async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) + async ValueTask IInboxQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, Guid correlationID, CancellationToken cancellationToken) { - await EnsureResponseSubscriptionRunning(); - var responseGuid = Guid.NewGuid(); - var responseSource = new TaskCompletionSource(); - await semQueryLock.WaitAsync(); - waitingResponses.Add(responseGuid, responseSource); - semQueryLock.Release(); try { - _ = await client.PublishAsync(ConvertMessage(message,InboxChannel,responseGuid), cancellationToken); + _ = await client.PublishAsync(ConvertMessage(message,responseTopic:InboxChannel,responseID:correlationID), cancellationToken); } catch (Exception e) { - await semQueryLock.WaitAsync(); - waitingResponses.Remove(responseGuid); - semQueryLock.Release(); - throw new InvalidOperationException("Unable to transmit query request"); + return new(message.ID, e.Message); } - await responseSource.Task.WaitAsync(timeout, cancellationToken); - if (responseSource.Task.IsCompleted) - return responseSource.Task.Result; - throw new TimeoutException(); + return new(message.ID); } async ValueTask IQueryableMessageServiceConnection.SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) @@ -181,14 +156,16 @@ async ValueTask IQueryableMessageServiceConnection.QueryAsyn try { var result = await messageReceived(ConvertMessage(msg, out var responseID)); - _ = await client.PublishAsync(ConvertMessage(result,responseID:new Guid(responseID!),respondToTopic:msg.ResponseTopic), cancellationToken); + _ = await client.PublishAsync(ConvertMessage(result, responseID: new Guid(responseID!), respondToTopic: msg.ResponseTopic), cancellationToken); } catch (Exception e) { errorReceived(e); } - } - , channel, group); + }, + channel, + group + ); await result.EstablishAsync(); return result; } @@ -200,8 +177,6 @@ private void Dispose(bool disposing) if (disposing) { client.Dispose(); - ((IDisposable?)responseInboxSubscription)?.Dispose(); - semQueryLock.Dispose(); } disposedValue=true; } diff --git a/Connectors/HiveMQ/Exceptions.cs b/Connectors/HiveMQ/Exceptions.cs new file mode 100644 index 0000000..886e3d5 --- /dev/null +++ b/Connectors/HiveMQ/Exceptions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MQContract.HiveMQ +{ + /// + /// Thrown when the service connection is unable to connect to the HiveMQTT server + /// + public class ConnectionFailedException : Exception + { + internal ConnectionFailedException(string? reason) + : base($"Failed to connect: {reason}") + { } + } +} diff --git a/Connectors/HiveMQ/Readme.md b/Connectors/HiveMQ/Readme.md index b8e3656..3b5b266 100644 --- a/Connectors/HiveMQ/Readme.md +++ b/Connectors/HiveMQ/Readme.md @@ -5,7 +5,8 @@ - [Connection](#T-MQContract-HiveMQ-Connection 'MQContract.HiveMQ.Connection') - [#ctor(clientOptions)](#M-MQContract-HiveMQ-Connection-#ctor-HiveMQtt-Client-Options-HiveMQClientOptions- 'MQContract.HiveMQ.Connection.#ctor(HiveMQtt.Client.Options.HiveMQClientOptions)') - - [DefaultTimout](#P-MQContract-HiveMQ-Connection-DefaultTimout 'MQContract.HiveMQ.Connection.DefaultTimout') + - [DefaultTimeout](#P-MQContract-HiveMQ-Connection-DefaultTimeout 'MQContract.HiveMQ.Connection.DefaultTimeout') +- [ConnectionFailedException](#T-MQContract-HiveMQ-ConnectionFailedException 'MQContract.HiveMQ.ConnectionFailedException') ## Connection `type` @@ -31,9 +32,20 @@ Default constructor that requires the HiveMQ client options settings to be provi | ---- | ---- | ----------- | | clientOptions | [HiveMQtt.Client.Options.HiveMQClientOptions](#T-HiveMQtt-Client-Options-HiveMQClientOptions 'HiveMQtt.Client.Options.HiveMQClientOptions') | The required client options to connect to the HiveMQ instance | - -### DefaultTimout `property` + +### DefaultTimeout `property` ##### Summary The default timeout to allow for a Query Response call to execute, defaults to 1 minute + + +## ConnectionFailedException `type` + +##### Namespace + +MQContract.HiveMQ + +##### Summary + +Thrown when the service connection is unable to connect to the HiveMQTT server diff --git a/Connectors/KubeMQ/Connection.cs b/Connectors/KubeMQ/Connection.cs index 35c3d84..4e0a169 100644 --- a/Connectors/KubeMQ/Connection.cs +++ b/Connectors/KubeMQ/Connection.cs @@ -17,7 +17,7 @@ namespace MQContract.KubeMQ /// /// This is the MessageServiceConnection implementation for using KubeMQ /// - public sealed class Connection : IQueryableMessageServiceConnection,IPingableMessageServiceConnection, IDisposable,IAsyncDisposable + public sealed class Connection : IQueryResponseMessageServiceConnection,IPingableMessageServiceConnection, IDisposable,IAsyncDisposable { /// /// These are the different read styles to use when subscribing to a stored Event PubSub @@ -125,7 +125,7 @@ public Connection RegisterStoredChannel(string channelName, MessageReadStyle rea uint? IMessageServiceConnection.MaxMessageBodySize => (uint)Math.Abs(connectionOptions.MaxBodySize); - TimeSpan IQueryableMessageServiceConnection.DefaultTimout => TimeSpan.FromMilliseconds(connectionOptions.DefaultRPCTimeout??30000); + TimeSpan IQueryableMessageServiceConnection.DefaultTimeout => TimeSpan.FromMilliseconds(connectionOptions.DefaultRPCTimeout??30000); private KubeClient EstablishConnection() { @@ -182,7 +182,7 @@ async ValueTask IMessageServiceConnection.PublishAsync(Servi } } - async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) + async ValueTask IQueryResponseMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) { #pragma warning disable S2139 // Exceptions should be either logged or rethrown but not both try diff --git a/Connectors/NATS/Connection.cs b/Connectors/NATS/Connection.cs index 9c50ccf..c0a9e03 100644 --- a/Connectors/NATS/Connection.cs +++ b/Connectors/NATS/Connection.cs @@ -13,7 +13,7 @@ namespace MQContract.NATS /// /// This is the MessageServiceConnection implementation for using NATS.io /// - public sealed class Connection : IQueryableMessageServiceConnection,IPingableMessageServiceConnection, IAsyncDisposable,IDisposable + public sealed class Connection : IQueryResponseMessageServiceConnection,IPingableMessageServiceConnection, IAsyncDisposable,IDisposable { private const string MESSAGE_IDENTIFIER_HEADER = "_MessageID"; private const string MESSAGE_TYPE_HEADER = "_MessageTypeID"; @@ -63,7 +63,7 @@ private async Task ProcessConnection() /// The default timeout to use for RPC calls when not specified by class or in the call. /// DEFAULT: 30 seconds /// - public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + public TimeSpan DefaultTimeout { get; init; } = TimeSpan.FromMinutes(1); /// /// Called to define a Stream inside the underlying NATS context. This is an exposure of the NatsJSContext.CreateStreamAsync @@ -149,7 +149,7 @@ await natsConnection.PublishAsync( } } - async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) + async ValueTask IQueryResponseMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) { var result = await natsConnection.RequestAsync( message.Channel, diff --git a/Connectors/NATS/Readme.md b/Connectors/NATS/Readme.md index aa1f9cc..6fd62dc 100644 --- a/Connectors/NATS/Readme.md +++ b/Connectors/NATS/Readme.md @@ -5,7 +5,7 @@ - [Connection](#T-MQContract-NATS-Connection 'MQContract.NATS.Connection') - [#ctor(options)](#M-MQContract-NATS-Connection-#ctor-NATS-Client-Core-NatsOpts- 'MQContract.NATS.Connection.#ctor(NATS.Client.Core.NatsOpts)') - - [DefaultTimout](#P-MQContract-NATS-Connection-DefaultTimout 'MQContract.NATS.Connection.DefaultTimout') + - [DefaultTimeout](#P-MQContract-NATS-Connection-DefaultTimeout 'MQContract.NATS.Connection.DefaultTimeout') - [MaxMessageBodySize](#P-MQContract-NATS-Connection-MaxMessageBodySize 'MQContract.NATS.Connection.MaxMessageBodySize') - [CreateStreamAsync(streamConfig,cancellationToken)](#M-MQContract-NATS-Connection-CreateStreamAsync-NATS-Client-JetStream-Models-StreamConfig,System-Threading-CancellationToken- 'MQContract.NATS.Connection.CreateStreamAsync(NATS.Client.JetStream.Models.StreamConfig,System.Threading.CancellationToken)') - [RegisterConsumerConfig(channelName,consumerConfig)](#M-MQContract-NATS-Connection-RegisterConsumerConfig-System-String,NATS-Client-JetStream-Models-ConsumerConfig- 'MQContract.NATS.Connection.RegisterConsumerConfig(System.String,NATS.Client.JetStream.Models.ConsumerConfig)') @@ -35,8 +35,8 @@ Primary constructor to create an instance using the supplied configuration optio | ---- | ---- | ----------- | | options | [NATS.Client.Core.NatsOpts](#T-NATS-Client-Core-NatsOpts 'NATS.Client.Core.NatsOpts') | | - -### DefaultTimout `property` + +### DefaultTimeout `property` ##### Summary diff --git a/Connectors/RabbitMQ/Connection.cs b/Connectors/RabbitMQ/Connection.cs index ed2b412..81b637f 100644 --- a/Connectors/RabbitMQ/Connection.cs +++ b/Connectors/RabbitMQ/Connection.cs @@ -9,15 +9,15 @@ namespace MQContract.RabbitMQ /// /// This is the MessageServiceConnection implemenation for using RabbitMQ /// - public sealed class Connection : IQueryableMessageServiceConnection, IDisposable + public sealed class Connection : IInboxQueryableMessageServiceConnection, IDisposable { + private const string InboxExchange = "_Inbox"; + private readonly ConnectionFactory factory; private readonly IConnection conn; private readonly IModel channel; private readonly SemaphoreSlim semaphore = new(1, 1); - private readonly Dictionary> awaitingResponses = []; - private IModel? responseListener; - private string? responseListenerTag; + private readonly string inboxChannel; private bool disposedValue; /// @@ -32,6 +32,7 @@ public Connection(ConnectionFactory factory) conn = this.factory.CreateConnection(); channel = conn.CreateModel(); MaxMessageBodySize = factory.MaxMessageSize; + inboxChannel = $"{InboxExchange}.{factory.ClientProvidedName}"; } /// @@ -84,7 +85,7 @@ public void QueueDelete(string queue, bool ifUnused, bool ifEmpty) /// The default timeout to use for RPC calls when not specified by class or in the call. /// DEFAULT: 1 minute /// - public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + public TimeSpan DefaultTimeout { get; init; } = TimeSpan.FromMinutes(1); internal static (IBasicProperties props, ReadOnlyMemory) ConvertMessage(ServiceMessage message, IModel channel, Guid? messageId = null) { @@ -178,76 +179,62 @@ private Subscription ProduceSubscription(IConnection conn, string channel, strin errorReceived )); - private const string InboxChannel = "_Inbox"; - - async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) + ValueTask IInboxQueryableMessageServiceConnection.EstablishInboxSubscriptionAsync(Action messageReceived, CancellationToken cancellationToken) { - var messageID = Guid.NewGuid(); - (var props, var data) = ConvertMessage(message, this.channel,messageID); - props.ReplyTo = $"{InboxChannel}.${factory.ClientProvidedName}"; - var success = true; - await semaphore.WaitAsync(cancellationToken); - var result = new TaskCompletionSource(); - awaitingResponses.Add(messageID, result); - if (responseListener==null) - { - ExchangeDeclare(InboxChannel, ExchangeType.Direct); - QueueDeclare(props.ReplyTo); - responseListener = this.conn.CreateModel(); - responseListener.QueueBind(props.ReplyTo, InboxChannel, props.ReplyTo); - responseListener.BasicQos(0, 1, false); - var consumer = new EventingBasicConsumer(responseListener!); - consumer.Received+=(sender, @event) => + channel.ExchangeDeclare(InboxExchange, ExchangeType.Direct, durable: false, autoDelete: true); + channel.QueueDeclare(inboxChannel, durable: false, exclusive: false, autoDelete: true); + return ValueTask.FromResult(new Subscription( + conn, + InboxExchange, + inboxChannel, + (@event, model, acknowledge) => { - var responseMessage = ConvertMessage(@event, string.Empty, () => ValueTask.CompletedTask, out var messageId); + var responseMessage = ConvertMessage(@event, string.Empty, acknowledge, out var messageId); if (messageId!=null) - { - semaphore.Wait(); - if (awaitingResponses.TryGetValue(messageId.Value, out var taskCompletionSource)) - { - taskCompletionSource.TrySetResult(new( - responseMessage.ID, - responseMessage.Header, - responseMessage.MessageTypeID, - responseMessage.Data - )); - responseListener.BasicAck(@event.DeliveryTag, false); - awaitingResponses.Remove(messageId.Value); - } - semaphore.Release(); - } - }; - responseListenerTag = responseListener.BasicConsume(props.ReplyTo, false, consumer); - } + messageReceived(new( + responseMessage.ID, + responseMessage.MessageTypeID, + inboxChannel, + responseMessage.Header, + messageId.Value, + responseMessage.Data, + acknowledge + )); + }, + (error) => { }, + routingKey:inboxChannel + )); + } + + async ValueTask IInboxQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, Guid correlationID, CancellationToken cancellationToken) + { + (var props, var data) = ConvertMessage(message, this.channel, correlationID); + props.ReplyTo = inboxChannel; + await semaphore.WaitAsync(cancellationToken); + TransmissionResult result; try { channel.BasicPublish(message.Channel, string.Empty, props, data); + result = new TransmissionResult(message.ID); } - catch (Exception) + catch (Exception e) { - success=false; + result = new TransmissionResult(message.ID, e.Message); } - if (!success) - awaitingResponses.Remove(messageID); semaphore.Release(); - if (!success) - throw new InvalidOperationException("An error occured attempting to submit the requested query"); - await result.Task.WaitAsync(timeout, cancellationToken); - if (result.Task.IsCompleted) - return result.Task.Result; - throw new TimeoutException(); + return result; } ValueTask IQueryableMessageServiceConnection.SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) - => ValueTask.FromResult(ProduceSubscription(conn, channel, group, - async (@event,model, acknowledge) => + => ValueTask.FromResult(ProduceSubscription(conn, channel, group, + async (@event, model, acknowledge) => { - var result = await messageReceived(ConvertMessage(@event, channel, acknowledge,out var messageID)); + var result = await messageReceived(ConvertMessage(@event, channel, acknowledge, out var messageID)); + (var props, var data) = ConvertMessage(result, model, messageID); await semaphore.WaitAsync(cancellationToken); try { - (var props, var data) = ConvertMessage(result,model,messageID); - this.channel.BasicPublish(InboxChannel, @event.BasicProperties.ReplyTo, props, data); + this.channel.BasicPublish(InboxExchange, @event.BasicProperties.ReplyTo, props, data); } catch (Exception e) { @@ -273,11 +260,6 @@ private void Dispose(bool disposing) semaphore.Wait(); channel.Close(); channel.Dispose(); - if (responseListener!=null) - { - responseListener.BasicCancel(responseListenerTag); - responseListener.Close(); - } conn.Close(); conn.Dispose(); semaphore.Release(); diff --git a/Connectors/RabbitMQ/Readme.md b/Connectors/RabbitMQ/Readme.md index 1cb9c6b..244380a 100644 --- a/Connectors/RabbitMQ/Readme.md +++ b/Connectors/RabbitMQ/Readme.md @@ -5,7 +5,7 @@ - [Connection](#T-MQContract-RabbitMQ-Connection 'MQContract.RabbitMQ.Connection') - [#ctor(factory)](#M-MQContract-RabbitMQ-Connection-#ctor-RabbitMQ-Client-ConnectionFactory- 'MQContract.RabbitMQ.Connection.#ctor(RabbitMQ.Client.ConnectionFactory)') - - [DefaultTimout](#P-MQContract-RabbitMQ-Connection-DefaultTimout 'MQContract.RabbitMQ.Connection.DefaultTimout') + - [DefaultTimeout](#P-MQContract-RabbitMQ-Connection-DefaultTimeout 'MQContract.RabbitMQ.Connection.DefaultTimeout') - [MaxMessageBodySize](#P-MQContract-RabbitMQ-Connection-MaxMessageBodySize 'MQContract.RabbitMQ.Connection.MaxMessageBodySize') - [ExchangeDeclare(exchange,type,durable,autoDelete,arguments)](#M-MQContract-RabbitMQ-Connection-ExchangeDeclare-System-String,System-String,System-Boolean,System-Boolean,System-Collections-Generic-IDictionary{System-String,System-Object}- 'MQContract.RabbitMQ.Connection.ExchangeDeclare(System.String,System.String,System.Boolean,System.Boolean,System.Collections.Generic.IDictionary{System.String,System.Object})') - [QueueDeclare(queue,durable,exclusive,autoDelete,arguments)](#M-MQContract-RabbitMQ-Connection-QueueDeclare-System-String,System-Boolean,System-Boolean,System-Boolean,System-Collections-Generic-IDictionary{System-String,System-Object}- 'MQContract.RabbitMQ.Connection.QueueDeclare(System.String,System.Boolean,System.Boolean,System.Boolean,System.Collections.Generic.IDictionary{System.String,System.Object})') @@ -35,8 +35,8 @@ Default constructor for creating instance | ---- | ---- | ----------- | | factory | [RabbitMQ.Client.ConnectionFactory](#T-RabbitMQ-Client-ConnectionFactory 'RabbitMQ.Client.ConnectionFactory') | The connection factory to use that was built with required authentication and connection information | - -### DefaultTimout `property` + +### DefaultTimeout `property` ##### Summary diff --git a/Connectors/RabbitMQ/Subscription.cs b/Connectors/RabbitMQ/Subscription.cs index 2b77190..409e592 100644 --- a/Connectors/RabbitMQ/Subscription.cs +++ b/Connectors/RabbitMQ/Subscription.cs @@ -10,10 +10,10 @@ internal class Subscription : IServiceSubscription private readonly Guid subscriptionID = Guid.NewGuid(); private readonly string consumerTag; - public Subscription(IConnection conn,string channel,string group, Action> messageReceived, Action errorReceived) + public Subscription(IConnection conn,string channel,string group, Action> messageReceived, Action errorReceived,string? routingKey=null) { this.channel = conn.CreateModel(); - this.channel.QueueBind(group, channel, subscriptionID.ToString()); + this.channel.QueueBind(group, channel, routingKey??subscriptionID.ToString()); this.channel.BasicQos(0, 1, false); var consumer = new EventingBasicConsumer(this.channel); consumer.Received+=(sender, @event) => diff --git a/Connectors/Redis/Connection.cs b/Connectors/Redis/Connection.cs index b20eefa..834c533 100644 --- a/Connectors/Redis/Connection.cs +++ b/Connectors/Redis/Connection.cs @@ -10,7 +10,7 @@ namespace MQContract.Redis /// /// This is the MessageServiceConnection implementation for using Redis /// - public class Connection : IQueryableMessageServiceConnection,IAsyncDisposable,IDisposable + public class Connection : IQueryResponseMessageServiceConnection,IAsyncDisposable,IDisposable { private readonly ConnectionMultiplexer connectionMultiplexer; private readonly IDatabase database; @@ -50,7 +50,7 @@ public async ValueTask DefineConsumerGroupAsync(string channel,string group) /// /// The default timeout to allow for a Query Response call to execute, defaults to 1 minute /// - public TimeSpan DefaultTimout { get; init; } = TimeSpan.FromMinutes(1); + public TimeSpan DefaultTimeout { get; init; } = TimeSpan.FromMinutes(1); async ValueTask IMessageServiceConnection.CloseAsync() => await connectionMultiplexer.CloseAsync(); @@ -141,7 +141,7 @@ async ValueTask IMessageServiceConnection.PublishAsync(Servi return result; } - async ValueTask IQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) + async ValueTask IQueryResponseMessageServiceConnection.QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken) { var replyID = $"_inbox.{Guid.NewGuid()}"; await database.StreamAddAsync(message.Channel, ConvertMessage(message, replyID, timeout)); diff --git a/Connectors/Redis/Readme.md b/Connectors/Redis/Readme.md index a1458cd..3b1d526 100644 --- a/Connectors/Redis/Readme.md +++ b/Connectors/Redis/Readme.md @@ -5,7 +5,7 @@ - [Connection](#T-MQContract-Redis-Connection 'MQContract.Redis.Connection') - [#ctor(configuration)](#M-MQContract-Redis-Connection-#ctor-StackExchange-Redis-ConfigurationOptions- 'MQContract.Redis.Connection.#ctor(StackExchange.Redis.ConfigurationOptions)') - - [DefaultTimout](#P-MQContract-Redis-Connection-DefaultTimout 'MQContract.Redis.Connection.DefaultTimout') + - [DefaultTimeout](#P-MQContract-Redis-Connection-DefaultTimeout 'MQContract.Redis.Connection.DefaultTimeout') - [MaxMessageBodySize](#P-MQContract-Redis-Connection-MaxMessageBodySize 'MQContract.Redis.Connection.MaxMessageBodySize') - [DefineConsumerGroupAsync(channel,group)](#M-MQContract-Redis-Connection-DefineConsumerGroupAsync-System-String,System-String- 'MQContract.Redis.Connection.DefineConsumerGroupAsync(System.String,System.String)') @@ -33,8 +33,8 @@ Default constructor that requires the Redis Configuration settings to be provide | ---- | ---- | ----------- | | configuration | [StackExchange.Redis.ConfigurationOptions](#T-StackExchange-Redis-ConfigurationOptions 'StackExchange.Redis.ConfigurationOptions') | The configuration to use for the redis connections | - -### DefaultTimout `property` + +### DefaultTimeout `property` ##### Summary diff --git a/Core/ContractConnection.Middleware.cs b/Core/ContractConnection.Middleware.cs index 422d87e..fc4a061 100644 --- a/Core/ContractConnection.Middleware.cs +++ b/Core/ContractConnection.Middleware.cs @@ -1,7 +1,9 @@ using Microsoft.Extensions.DependencyInjection; using MQContract.Interfaces; +using MQContract.Interfaces.Factories; using MQContract.Interfaces.Middleware; using MQContract.Messages; +using MQContract.Middleware; namespace MQContract { @@ -85,5 +87,24 @@ private async ValueTask AfterMessageEncodeAsync(IContext cont (message, messageHeader) = await handler.AfterMessageDecodeAsync(context, message, ID, messageHeader, receivedTimestamp, processedTimeStamp); return (message, messageHeader); } + + private async ValueTask ProduceServiceMessageAsync(ChannelMapper.MapTypes mapType,IMessageFactory messageFactory, T message, bool ignoreChannel, string? channel = null, MessageHeader? messageHeader = null) + where T : class + { + var context = new Context(mapType); + (message, channel, messageHeader) = await BeforeMessageEncodeAsync(context, message, channel??messageFactory.MessageChannel, messageHeader??new([])); + return await AfterMessageEncodeAsync(context, + await messageFactory.ConvertMessageAsync(message, ignoreChannel, channel, messageHeader) + ); + } + private async ValueTask<(T message,MessageHeader header)> DecodeServiceMessageAsync(ChannelMapper.MapTypes mapType, IMessageFactory messageFactory, ReceivedServiceMessage message) + where T : class + { + var context = new Context(mapType); + (var messageHeader, var data) = await BeforeMessageDecodeAsync(context, message.ID, message.Header, message.MessageTypeID, message.Channel, message.Data); + var taskMessage = await messageFactory.ConvertMessageAsync(logger, new ReceivedServiceMessage(message.ID, message.MessageTypeID, message.Channel, messageHeader, data, message.Acknowledge)) + ??throw new InvalidCastException($"Unable to convert incoming message {message.MessageTypeID} to {typeof(T).FullName}"); + return await AfterMessageDecodeAsync(context, taskMessage!, message.ID, messageHeader, message.ReceivedTimestamp, DateTime.Now); + } } } diff --git a/Core/ContractConnection.PubSub.cs b/Core/ContractConnection.PubSub.cs new file mode 100644 index 0000000..f0167aa --- /dev/null +++ b/Core/ContractConnection.PubSub.cs @@ -0,0 +1,47 @@ +using MQContract.Interfaces; +using MQContract.Messages; +using MQContract.Subscriptions; + +namespace MQContract +{ + public partial class ContractConnection + { + private async ValueTask CreateSubscriptionAsync(Func, ValueTask> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, CancellationToken cancellationToken) + where T : class + { + var messageFactory = GetMessageFactory(ignoreMessageHeader); + var subscription = new PubSubSubscription( + async (serviceMessage) => + { + (var taskMessage, var messageHeader) = await DecodeServiceMessageAsync(ChannelMapper.MapTypes.PublishSubscription, messageFactory, serviceMessage); + await messageReceived(new ReceivedMessage(serviceMessage.ID, taskMessage!, messageHeader, serviceMessage.ReceivedTimestamp, DateTime.Now)); + }, + errorReceived, + (originalChannel) => MapChannel(ChannelMapper.MapTypes.PublishSubscription, originalChannel)!, + channel: channel, + group: group, + synchronous: synchronous, + logger: logger); + if (await subscription.EstablishSubscriptionAsync(serviceConnection, cancellationToken)) + return subscription; + throw new SubscriptionFailedException(); + } + + async ValueTask IContractConnection.PublishAsync(T message, string? channel, MessageHeader? messageHeader, CancellationToken cancellationToken) + => await serviceConnection.PublishAsync( + await ProduceServiceMessageAsync(ChannelMapper.MapTypes.Publish,GetMessageFactory(),message,false,channel,messageHeader), + cancellationToken + ); + + ValueTask IContractConnection.SubscribeAsync(Func, ValueTask> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) where T : class + => CreateSubscriptionAsync(messageReceived, errorReceived, channel, group, ignoreMessageHeader, false, cancellationToken); + + ValueTask IContractConnection.SubscribeAsync(Action> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) where T : class + => CreateSubscriptionAsync((msg) => + { + messageReceived(msg); + return ValueTask.CompletedTask; + }, + errorReceived, channel, group, ignoreMessageHeader, true, cancellationToken); + } +} diff --git a/Core/ContractConnection.QueryResponse.cs b/Core/ContractConnection.QueryResponse.cs new file mode 100644 index 0000000..bfc63fe --- /dev/null +++ b/Core/ContractConnection.QueryResponse.cs @@ -0,0 +1,237 @@ +using MQContract.Attributes; +using MQContract.Interfaces.Service; +using MQContract.Interfaces; +using MQContract.Messages; +using MQContract.Subscriptions; +using System.Reflection; + +namespace MQContract +{ + public partial class ContractConnection + { + private async ValueTask> ProcessPubSubQuery(string? responseChannel, TimeSpan? realTimeout, ServiceMessage serviceMessage, CancellationToken cancellationToken) + where Q : class + where R : class + { + responseChannel ??=typeof(Q).GetCustomAttribute()?.Name; + ArgumentNullException.ThrowIfNullOrWhiteSpace(responseChannel); + var replyChannel = await MapChannel(ChannelMapper.MapTypes.QueryResponse, responseChannel!); + var callID = Guid.NewGuid(); + var (tcs, token) = await QueryResponseHelper.StartResponseListenerAsync( + serviceConnection, + realTimeout??TimeSpan.FromMinutes(1), + indentifier, + callID, + replyChannel, + cancellationToken + ); + var msg = QueryResponseHelper.EncodeMessage( + serviceMessage, + indentifier, + callID, + replyChannel, + null + ); + await serviceConnection.PublishAsync(msg, cancellationToken: cancellationToken); + try + { + await tcs.Task.WaitAsync(cancellationToken); + } + finally + { + if (!token.IsCancellationRequested) + await token.CancelAsync(); + } + return await ProduceResultAsync(tcs.Task.Result); + } + + private async ValueTask ProcessInboxMessage(IInboxQueryableMessageServiceConnection inboxMessageServiceConnection, ServiceMessage serviceMessage, TimeSpan timeout, CancellationToken cancellationToken) + { + var messageID = Guid.NewGuid(); + await inboxSemaphore.WaitAsync(cancellationToken); + inboxSubscription ??= await inboxMessageServiceConnection.EstablishInboxSubscriptionAsync( + async (message) => + { + await inboxSemaphore.WaitAsync(); + if (message.Acknowledge!=null) + await message.Acknowledge(); + if (inboxResponses.TryGetValue(message.CorrelationID,out var taskCompletionSource)) + { + taskCompletionSource.TrySetResult(new( + message.ID, + message.Header, + message.MessageTypeID, + message.Data + )); + } + inboxSemaphore.Release(); + }, + cancellationToken + ); + var tcs = new TaskCompletionSource(); + inboxResponses.Add(messageID, tcs); + inboxSemaphore.Release(); + using var token = new CancellationTokenSource(); + var reg = cancellationToken.Register(() => token.Cancel()); + token.Token.Register(async () => { + await reg.DisposeAsync(); + if (!tcs.Task.IsCompleted) + tcs.TrySetException(new QueryTimeoutException()); + }); + token.CancelAfter(timeout); + var result = await inboxMessageServiceConnection.QueryAsync(serviceMessage, messageID, cancellationToken); + if (result.IsError) + { + await inboxSemaphore.WaitAsync(); + inboxResponses.Remove(messageID); + inboxSemaphore.Release(); + throw new QuerySubmissionFailedException(result.Error!); + } + try + { + await tcs.Task.WaitAsync(cancellationToken); + } + finally + { + if (!token.IsCancellationRequested) + await token.CancelAsync(); + await inboxSemaphore.WaitAsync(); + inboxResponses.Remove(messageID); + inboxSemaphore.Release(); + } + return tcs.Task.Result; + } + + private async ValueTask> ExecuteQueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) + where Q : class + where R : class + { + var realTimeout = timeout??typeof(Q).GetCustomAttribute()?.TimeSpanValue; + var serviceMessage = await ProduceServiceMessageAsync(ChannelMapper.MapTypes.Query, GetMessageFactory(), message, false,channel: channel, messageHeader: messageHeader); + if (serviceConnection is IQueryResponseMessageServiceConnection queryableMessageServiceConnection) + return await ProduceResultAsync( + await queryableMessageServiceConnection.QueryAsync( + serviceMessage, + realTimeout??queryableMessageServiceConnection.DefaultTimeout, + cancellationToken + ) + ); + else if (serviceConnection is IInboxQueryableMessageServiceConnection inboxMessageServiceConnection) + return await ProduceResultAsync( + await ProcessInboxMessage(inboxMessageServiceConnection, serviceMessage, realTimeout??inboxMessageServiceConnection.DefaultTimeout,cancellationToken) + ); + return await ProcessPubSubQuery(responseChannel, realTimeout, serviceMessage, cancellationToken); + } + + private async ValueTask> ProduceResultAsync(ServiceQueryResult queryResult) where R : class + { + QueryResult result; + try + { + (var resultMessage, var messageHeader) = await DecodeServiceMessageAsync(ChannelMapper.MapTypes.QueryResponse, GetMessageFactory(true), new(queryResult.ID, queryResult.MessageTypeID, string.Empty, queryResult.Header, queryResult.Data)); + result = new QueryResult( + queryResult.ID, + messageHeader, + Result: resultMessage + ); + } + catch (QueryResponseException qre) + { + return new( + queryResult.ID, + queryResult.Header, + Result: default, + Error: qre.Message + ); + } + catch (Exception ex) + { + return new( + queryResult.ID, + queryResult.Header, + Result: default, + Error: ex.Message + ); + } + return result; + } + private async ValueTask ProduceSubscribeQueryResponseAsync(Func, ValueTask>> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, CancellationToken cancellationToken) + where Q : class + where R : class + { + var queryMessageFactory = GetMessageFactory(ignoreMessageHeader); + var responseMessageFactory = GetMessageFactory(); + var subscription = new QueryResponseSubscription( + async (message, replyChannel) => + { + (var taskMessage, var messageHeader) = await DecodeServiceMessageAsync( + ChannelMapper.MapTypes.QuerySubscription, + queryMessageFactory, + message + ); + var result = await messageReceived(new ReceivedMessage(message.ID, taskMessage!, messageHeader, message.ReceivedTimestamp, DateTime.Now)); + return await ProduceServiceMessageAsync( + ChannelMapper.MapTypes.QueryResponse, + responseMessageFactory, + result.Message, + true, + replyChannel, + new(result.Headers) + ); + }, + errorReceived, + (originalChannel) => MapChannel(ChannelMapper.MapTypes.QuerySubscription, originalChannel), + channel: channel, + group: group, + synchronous: synchronous, + logger: logger); + if (await subscription.EstablishSubscriptionAsync(serviceConnection, cancellationToken)) + return subscription; + throw new SubscriptionFailedException(); + } + + async ValueTask> IContractConnection.QueryAsync(Q message, TimeSpan? timeout, string? channel, string? responseChannel, MessageHeader? messageHeader, CancellationToken cancellationToken) + => await ExecuteQueryAsync(message, timeout: timeout, channel: channel, responseChannel: responseChannel, messageHeader: messageHeader, cancellationToken: cancellationToken); + + async ValueTask> IContractConnection.QueryAsync(Q message, TimeSpan? timeout, string? channel, string? responseChannel, MessageHeader? messageHeader, + CancellationToken cancellationToken) + { +#pragma warning disable CA2208 // Instantiate argument exceptions correctly + var responseType = (typeof(Q).GetCustomAttribute(false)?.ResponseType)??throw new UnknownResponseTypeException("ResponseType", typeof(Q)); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly +#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + var methodInfo = typeof(ContractConnection).GetMethod(nameof(ContractConnection.ExecuteQueryAsync), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(typeof(Q), responseType!); +#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + dynamic? queryResult; + try + { + queryResult = (dynamic?)await Utility.InvokeMethodAsync( + methodInfo, + this, [ + message, + timeout, + channel, + responseChannel, + messageHeader, + cancellationToken + ] + ); + } + catch (TimeoutException) + { + throw new QueryTimeoutException(); + } + return new QueryResult(queryResult?.ID??string.Empty, queryResult?.Header??new MessageHeader([]), queryResult?.Result, queryResult?.Error); + } + + ValueTask IContractConnection.SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) + => ProduceSubscribeQueryResponseAsync(messageReceived, errorReceived, channel, group, ignoreMessageHeader, false, cancellationToken); + + ValueTask IContractConnection.SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) + => ProduceSubscribeQueryResponseAsync((msg) => + { + var result = messageReceived(msg); + return ValueTask.FromResult(result); + }, errorReceived, channel, group, ignoreMessageHeader, true, cancellationToken); + } +} diff --git a/Core/ContractConnection.cs b/Core/ContractConnection.cs index 0a43069..594890f 100644 --- a/Core/ContractConnection.cs +++ b/Core/ContractConnection.cs @@ -1,16 +1,12 @@ using Microsoft.Extensions.Logging; -using MQContract.Attributes; using MQContract.Factories; using MQContract.Interfaces; using MQContract.Interfaces.Encoding; using MQContract.Interfaces.Encrypting; using MQContract.Interfaces.Factories; -using MQContract.Interfaces.Middleware; using MQContract.Interfaces.Service; using MQContract.Messages; using MQContract.Middleware; -using MQContract.Subscriptions; -using System.Reflection; namespace MQContract { @@ -29,6 +25,9 @@ public sealed partial class ContractConnection private readonly ILogger? logger; private readonly ChannelMapper? channelMapper; private readonly List middleware; + private readonly SemaphoreSlim inboxSemaphore = new(1, 1); + private readonly Dictionary> inboxResponses = []; + private IServiceSubscription? inboxSubscription; private IEnumerable typeFactories = []; private bool disposedValue; @@ -87,233 +86,32 @@ private IMessageFactory GetMessageFactory(bool ignoreMessageHeader = false private ValueTask MapChannel(ChannelMapper.MapTypes mapType, string originalChannel) => channelMapper?.MapChannel(mapType, originalChannel)??ValueTask.FromResult(originalChannel); - private async ValueTask<(IContext context, ServiceMessage)> ProduceServiceMessage(ChannelMapper.MapTypes mapType, T message, string? channel = null, MessageHeader? messageHeader = null) where T : class - { - var factory = GetMessageFactory(); - var context = new Context(mapType); - (message, channel, messageHeader) = await BeforeMessageEncodeAsync(context, message, channel??factory.MessageChannel, messageHeader??new([])); - var serviceMessage = await AfterMessageEncodeAsync(context, - await factory.ConvertMessageAsync(message,false, channel, messageHeader) - ); - return (context, serviceMessage); - } - - private async ValueTask CreateSubscriptionAsync(Func, ValueTask> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, CancellationToken cancellationToken) - where T : class - { - var messageFactory = GetMessageFactory(ignoreMessageHeader); - var subscription = new PubSubSubscription( - async (serviceMessage) => - { - var context = new Context(ChannelMapper.MapTypes.PublishSubscription); - (MessageHeader messageHeader, ReadOnlyMemory data) = await BeforeMessageDecodeAsync(context, serviceMessage.ID, serviceMessage.Header, serviceMessage.MessageTypeID, serviceMessage.Channel, serviceMessage.Data); - var taskMessage = await messageFactory.ConvertMessageAsync(logger, new ServiceMessage(serviceMessage.ID, serviceMessage.MessageTypeID, serviceMessage.Channel, messageHeader, data)) - ??throw new InvalidCastException($"Unable to convert incoming message {serviceMessage.MessageTypeID} to {typeof(T).FullName}"); - (taskMessage, messageHeader) = await AfterMessageDecodeAsync(context, taskMessage, serviceMessage.ID, messageHeader, serviceMessage.ReceivedTimestamp, DateTime.Now); - await messageReceived(new ReceivedMessage(serviceMessage.ID, taskMessage!, messageHeader, serviceMessage.ReceivedTimestamp, DateTime.Now)); - }, - errorReceived, - (originalChannel) => MapChannel(ChannelMapper.MapTypes.PublishSubscription, originalChannel)!, - channel: channel, - group: group, - synchronous: synchronous, - logger: logger); - if (await subscription.EstablishSubscriptionAsync(serviceConnection, cancellationToken)) - return subscription; - throw new SubscriptionFailedException(); - } - - private async ValueTask> ExecuteQueryAsync(Q message, TimeSpan? timeout = null, string? channel = null, string? responseChannel = null, MessageHeader? messageHeader = null, CancellationToken cancellationToken = new CancellationToken()) - where Q : class - where R : class - { - var realTimeout = timeout??typeof(Q).GetCustomAttribute()?.TimeSpanValue; - (var context, var serviceMessage) = await ProduceServiceMessage(ChannelMapper.MapTypes.Query, message, channel: channel, messageHeader: messageHeader); - if (serviceConnection is IQueryableMessageServiceConnection queryableMessageServiceConnection) - return await ProduceResultAsync( - context, - await queryableMessageServiceConnection.QueryAsync( - serviceMessage, - realTimeout??queryableMessageServiceConnection.DefaultTimout, - cancellationToken - ) - ); - responseChannel ??=typeof(Q).GetCustomAttribute()?.Name; - ArgumentNullException.ThrowIfNullOrWhiteSpace(responseChannel); - var replyChannel = await MapChannel(ChannelMapper.MapTypes.QueryResponse, responseChannel!); - var callID = Guid.NewGuid(); - var (tcs, token) = await QueryResponseHelper.StartResponseListenerAsync( - serviceConnection, - realTimeout??TimeSpan.FromMinutes(1), - indentifier, - callID, - replyChannel, - cancellationToken - ); - var msg = QueryResponseHelper.EncodeMessage( - serviceMessage, - indentifier, - callID, - replyChannel, - null - ); - await serviceConnection.PublishAsync(msg, cancellationToken: cancellationToken); - try - { - await tcs.Task.WaitAsync(cancellationToken); - } - finally - { - if (!token.IsCancellationRequested) - await token.CancelAsync(); - } - return await ProduceResultAsync(context, tcs.Task.Result); - } - private async ValueTask> ProduceResultAsync(IContext context, ServiceQueryResult queryResult) where R : class - { - var receivedTime = DateTime.Now; - (var messageHeader, var data) = await BeforeMessageDecodeAsync(context, queryResult.ID, queryResult.Header, queryResult.MessageTypeID, string.Empty, queryResult.Data); - QueryResult result; - try - { - result = new QueryResult( - queryResult.ID, - messageHeader, - Result: await GetMessageFactory(true).ConvertMessageAsync(logger, new ServiceQueryResult(queryResult.ID, messageHeader, queryResult.MessageTypeID, data)) - ); - } - catch (QueryResponseException qre) - { - return new( - queryResult.ID, - queryResult.Header, - Result: default, - Error: qre.Message - ); - } - catch (Exception ex) - { - return new( - queryResult.ID, - queryResult.Header, - Result: default, - Error: ex.Message - ); - } - (var decodedResult, var decodedHeader) = await AfterMessageDecodeAsync(context, result.Result!, queryResult.ID, result.Header, receivedTime, DateTime.Now); - return new QueryResult(result.ID, decodedHeader, decodedResult); - } - private async ValueTask ProduceSubscribeQueryResponseAsync(Func, ValueTask>> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, bool synchronous, CancellationToken cancellationToken) - where Q : class - where R : class - { - var queryMessageFactory = GetMessageFactory(ignoreMessageHeader); - var responseMessageFactory = GetMessageFactory(); - var subscription = new QueryResponseSubscription( - async (message,replyChannel) => - { - var context = new Context(ChannelMapper.MapTypes.QuerySubscription); - (var messageHeader, var data) = await BeforeMessageDecodeAsync(context, message.ID, message.Header, message.MessageTypeID, message.Channel, message.Data); - var taskMessage = await queryMessageFactory.ConvertMessageAsync(logger, new ReceivedServiceMessage(message.ID, message.MessageTypeID, message.Channel, messageHeader, data, message.Acknowledge)) - ??throw new InvalidCastException($"Unable to convert incoming message {message.MessageTypeID} to {typeof(Q).FullName}"); - (taskMessage, messageHeader) = await AfterMessageDecodeAsync(context, taskMessage!, message.ID, messageHeader, message.ReceivedTimestamp, DateTime.Now); - var result = await messageReceived(new ReceivedMessage(message.ID, taskMessage, messageHeader, message.ReceivedTimestamp, DateTime.Now)); - context = new Context(ChannelMapper.MapTypes.QueryResponse); - (var resultMessage, var resultChannel, var resultHeader) = await BeforeMessageEncodeAsync(context, result.Message, replyChannel, message.Header); - var encodedMessage = await responseMessageFactory.ConvertMessageAsync(resultMessage,true, resultChannel??replyChannel, resultHeader); - return await AfterMessageEncodeAsync(context, encodedMessage); - }, - errorReceived, - (originalChannel) => MapChannel(ChannelMapper.MapTypes.QuerySubscription, originalChannel), - channel: channel, - group: group, - synchronous: synchronous, - logger: logger); - if (await subscription.EstablishSubscriptionAsync(serviceConnection, cancellationToken)) - return subscription; - throw new SubscriptionFailedException(); - } - ValueTask IContractConnection.PingAsync() => (serviceConnection is IPingableMessageServiceConnection pingableService ? pingableService.PingAsync() : throw new NotSupportedException("The underlying service does not support Ping")); - async ValueTask IContractConnection.PublishAsync(T message, string? channel, MessageHeader? messageHeader, CancellationToken cancellationToken) + async ValueTask IContractConnection.CloseAsync() { - (_, var serviceMessage) = await ProduceServiceMessage(ChannelMapper.MapTypes.Publish, message, channel: channel, messageHeader: messageHeader); - return await serviceConnection.PublishAsync( - serviceMessage, - cancellationToken - ); + await (inboxSubscription?.EndAsync()??ValueTask.CompletedTask); + await (serviceConnection?.CloseAsync()??ValueTask.CompletedTask); } - ValueTask IContractConnection.SubscribeAsync(Func, ValueTask> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) where T : class - => CreateSubscriptionAsync(messageReceived, errorReceived, channel, group, ignoreMessageHeader,false, cancellationToken); - - ValueTask IContractConnection.SubscribeAsync(Action> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) where T : class - => CreateSubscriptionAsync((msg) => - { - messageReceived(msg); - return ValueTask.CompletedTask; - }, - errorReceived, channel, group, ignoreMessageHeader, true, cancellationToken); - - async ValueTask> IContractConnection.QueryAsync(Q message, TimeSpan? timeout, string? channel, string? responseChannel, MessageHeader? messageHeader, CancellationToken cancellationToken) - => await ExecuteQueryAsync(message, timeout: timeout, channel: channel,responseChannel:responseChannel, messageHeader: messageHeader, cancellationToken: cancellationToken); - - async ValueTask> IContractConnection.QueryAsync(Q message, TimeSpan? timeout, string? channel, string? responseChannel, MessageHeader? messageHeader, - CancellationToken cancellationToken) - { -#pragma warning disable CA2208 // Instantiate argument exceptions correctly - var responseType = (typeof(Q).GetCustomAttribute(false)?.ResponseType)??throw new UnknownResponseTypeException("ResponseType", typeof(Q)); -#pragma warning restore CA2208 // Instantiate argument exceptions correctly -#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields - var methodInfo = typeof(ContractConnection).GetMethod(nameof(ContractConnection.ExecuteQueryAsync), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(typeof(Q), responseType!); -#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields - dynamic? queryResult; - try - { - queryResult = (dynamic?)await Utility.InvokeMethodAsync( - methodInfo, - this, [ - message, - timeout, - channel, - responseChannel, - messageHeader, - cancellationToken - ] - ); - }catch(TimeoutException) - { - throw new QueryTimeoutException(); - } - return new QueryResult(queryResult?.ID??string.Empty, queryResult?.Header??new MessageHeader([]), queryResult?.Result, queryResult?.Error); - } - - ValueTask IContractConnection.SubscribeQueryAsyncResponseAsync(Func, ValueTask>> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) - => ProduceSubscribeQueryResponseAsync(messageReceived, errorReceived, channel, group, ignoreMessageHeader,false, cancellationToken); - - ValueTask IContractConnection.SubscribeQueryResponseAsync(Func, QueryResponseMessage> messageReceived, Action errorReceived, string? channel, string? group, bool ignoreMessageHeader, CancellationToken cancellationToken) - => ProduceSubscribeQueryResponseAsync((msg) => - { - var result = messageReceived(msg); - return ValueTask.FromResult(result); - }, errorReceived, channel, group, ignoreMessageHeader, true, cancellationToken); - - ValueTask IContractConnection.CloseAsync() - => serviceConnection?.CloseAsync()??ValueTask.CompletedTask; - private void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { + if (inboxSubscription is IDisposable subDisposable) + subDisposable.Dispose(); + else if (inboxSubscription is IAsyncDisposable asyncSubDisposable) + asyncSubDisposable.DisposeAsync().AsTask().Wait(); if (serviceConnection is IDisposable disposable) disposable.Dispose(); else if (serviceConnection is IAsyncDisposable asyncDisposable) asyncDisposable.DisposeAsync().AsTask().Wait(); } + dataLock.Dispose(); + inboxSemaphore.Dispose(); disposedValue=true; } } @@ -327,6 +125,10 @@ void IDisposable.Dispose() async ValueTask IAsyncDisposable.DisposeAsync() { + if (inboxSubscription is IAsyncDisposable asyncSubDisposable) + asyncSubDisposable.DisposeAsync().AsTask().Wait(); + else if (inboxSubscription is IDisposable subDisposable) + subDisposable.Dispose(); if (serviceConnection is IAsyncDisposable asyncDisposable) await asyncDisposable.DisposeAsync().ConfigureAwait(true); else if (serviceConnection is IDisposable disposable) diff --git a/Core/Exceptions.cs b/Core/Exceptions.cs index 6c258c1..7e68ce6 100644 --- a/Core/Exceptions.cs +++ b/Core/Exceptions.cs @@ -55,6 +55,15 @@ internal QueryExecutionFailedException() : base("Failed to execute query") { } } + /// + /// Thrown when a query call is being made to an inbox style service and the message fails to transmit + /// + public class QuerySubmissionFailedException : Exception + { + internal QuerySubmissionFailedException(string message) + : base(message) { } + } + /// /// Thrown when a query call times out waiting for the response /// diff --git a/Core/Middleware/MetricsMiddleware.cs b/Core/Middleware/MetricsMiddleware.cs index 56502d6..2c67393 100644 --- a/Core/Middleware/MetricsMiddleware.cs +++ b/Core/Middleware/MetricsMiddleware.cs @@ -12,7 +12,7 @@ internal class MetricsMiddleware : IBeforeEncodeMiddleware, IAfterEncodeMiddlewa { private const string StopWatchKey = "_MetricStopwatch"; private const string MessageReceivedChannelKey = "_MetricMessageReceivedChannel"; - private const string MessageRecievedSizeKey = "_MetricMessageRecievedSize"; + private const string MessageReceivedSizeKey = "_MetricMessageReceivedSize"; private readonly SystemMetricTracker? systemTracker; private readonly InternalMetricTracker? internalTracker; @@ -57,10 +57,10 @@ private async ValueTask AddStat(Type messageType, string? channel, bool sending, { var stopWatch = (Stopwatch?)context[StopWatchKey]; stopWatch?.Stop(); - await AddStat(typeof(T), (string?)context[MessageReceivedChannelKey]??string.Empty, false, (int?)context[MessageRecievedSizeKey]??0, stopWatch); + await AddStat(typeof(T), (string?)context[MessageReceivedChannelKey]??string.Empty, false, (int?)context[MessageReceivedSizeKey]??0, stopWatch); context[StopWatchKey]=null; context[MessageReceivedChannelKey]=null; - context[MessageRecievedSizeKey]=null; + context[MessageReceivedSizeKey]=null; return (message,messageHeader); } @@ -76,7 +76,7 @@ public async ValueTask AfterMessageEncodeAsync(Type messageType, public ValueTask<(MessageHeader messageHeader, ReadOnlyMemory data)> BeforeMessageDecodeAsync(IContext context, string id, MessageHeader messageHeader, string messageTypeID,string messageChannel, ReadOnlyMemory data) { context[MessageReceivedChannelKey] = messageChannel; - context[MessageRecievedSizeKey] = data.Length; + context[MessageReceivedSizeKey] = data.Length; var stopwatch = new Stopwatch(); context[StopWatchKey] = stopwatch; stopwatch.Start(); diff --git a/Core/Readme.md b/Core/Readme.md index 1325dd1..5a76378 100644 --- a/Core/Readme.md +++ b/Core/Readme.md @@ -31,6 +31,7 @@ - [MessageConversionException](#T-MQContract-MessageConversionException 'MQContract.MessageConversionException') - [QueryExecutionFailedException](#T-MQContract-QueryExecutionFailedException 'MQContract.QueryExecutionFailedException') - [QueryResponseException](#T-MQContract-QueryResponseException 'MQContract.QueryResponseException') +- [QuerySubmissionFailedException](#T-MQContract-QuerySubmissionFailedException 'MQContract.QuerySubmissionFailedException') - [QueryTimeoutException](#T-MQContract-QueryTimeoutException 'MQContract.QueryTimeoutException') - [SubscriptionFailedException](#T-MQContract-SubscriptionFailedException 'MQContract.SubscriptionFailedException') - [UnknownResponseTypeException](#T-MQContract-UnknownResponseTypeException 'MQContract.UnknownResponseTypeException') @@ -490,6 +491,17 @@ MQContract Thrown when a Query call is made and there is an error in the response + +## QuerySubmissionFailedException `type` + +##### Namespace + +MQContract + +##### Summary + +Thrown when a query call is being made to an inbox style service and the message fails to transmit + ## QueryTimeoutException `type` diff --git a/Core/Subscriptions/QueryResponseSubscription.cs b/Core/Subscriptions/QueryResponseSubscription.cs index a726d3c..a23c76a 100644 --- a/Core/Subscriptions/QueryResponseSubscription.cs +++ b/Core/Subscriptions/QueryResponseSubscription.cs @@ -20,10 +20,10 @@ public async ValueTask EstablishSubscriptionAsync(IMessageServiceConnectio { if (connection is IQueryableMessageServiceConnection queryableMessageServiceConnection) serviceSubscription = await queryableMessageServiceConnection.SubscribeQueryAsync( - serviceMessage => ProcessServiceMessageAsync(serviceMessage,string.Empty), + serviceMessage => ProcessServiceMessageAsync(serviceMessage, string.Empty), error => errorReceived(error), MessageChannel, - group:group, + group: group, cancellationToken: cancellationToken ); else diff --git a/README.md b/README.md index a68ec60..e491f81 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ global level or on a per message type level through implementation of the approp * [Abstractions](/Abstractions/Readme.md) * [Core](/Core/Readme.md) * Connectors - * [KubeMQ](/Connectors/KubeMQ/Readme.md) + * [ActiveMQ](/Connectors/ActiveMQ/Readme.md) + * [HiveMQ](/Connectors/HiveMQ/Readme.md) * [Kafka](/Connectors/Kafka/Readme.md) + * [KubeMQ](/Connectors/KubeMQ/Readme.md) * [Nats.io](/Connectors/NATS/Readme.md) + * [RabbitMQ](/Connectors/RabbitMQ/Readme.md) + * [Redis](/Connectors/Redis/Readme.md) \ No newline at end of file diff --git a/Samples/Messages/SampleExecution.cs b/Samples/Messages/SampleExecution.cs index c11f7b2..d5abfe6 100644 --- a/Samples/Messages/SampleExecution.cs +++ b/Samples/Messages/SampleExecution.cs @@ -89,11 +89,11 @@ await Task.WhenAll( WriteIndented = true }; Console.WriteLine($"Greetings Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting),true),jsonOptions)}"); - Console.WriteLine($"Greetings Recieved: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting),false), jsonOptions)}"); + Console.WriteLine($"Greetings Received: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting),false), jsonOptions)}"); Console.WriteLine($"StoredArrivals Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(ArrivalAnnouncement), true), jsonOptions)}"); - Console.WriteLine($"StoredArrivals Recieved: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(ArrivalAnnouncement), false), jsonOptions)}"); + Console.WriteLine($"StoredArrivals Received: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(ArrivalAnnouncement), false), jsonOptions)}"); Console.WriteLine($"Arrivals Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(StoredArrivalAnnouncement), true), jsonOptions)}"); - Console.WriteLine($"Arrivals Recieved: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(StoredArrivalAnnouncement), false), jsonOptions)}"); + Console.WriteLine($"Arrivals Received: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(StoredArrivalAnnouncement), false), jsonOptions)}"); } } } From 0d18ab61c0b96aa474c3b91961137497fe197e26 Mon Sep 17 00:00:00 2001 From: roger-castaldo Date: Tue, 1 Oct 2024 23:38:04 -0400 Subject: [PATCH 22/22] added in In Memory Added in an In Memory connector to allow for some semblance of virtualization where all messaging is handled internally --- .../IQueryableMessageServiceConnection.cs | 5 - .../MiddleWareTests.cs | 2 +- .../QueryInboxTests.cs | 5 - Connectors/HiveMQ/Exceptions.cs | 8 +- Connectors/InMemory/Connection.cs | 62 +++++++++ Connectors/InMemory/InMemory.csproj | 32 +++++ Connectors/InMemory/InternalServiceMessage.cs | 8 ++ Connectors/InMemory/MessageChannel.cs | 119 ++++++++++++++++++ Connectors/InMemory/MessageGroup.cs | 53 ++++++++ Connectors/InMemory/Readme.md | 35 ++++++ Connectors/InMemory/Subscription.cs | 28 +++++ MQContract.sln | 18 ++- README.md | 1 + Samples/InMemorySample/InMemorySample.csproj | 15 +++ Samples/InMemorySample/Program.cs | 6 + Samples/Messages/SampleExecution.cs | 14 +-- 16 files changed, 383 insertions(+), 28 deletions(-) create mode 100644 Connectors/InMemory/Connection.cs create mode 100644 Connectors/InMemory/InMemory.csproj create mode 100644 Connectors/InMemory/InternalServiceMessage.cs create mode 100644 Connectors/InMemory/MessageChannel.cs create mode 100644 Connectors/InMemory/MessageGroup.cs create mode 100644 Connectors/InMemory/Readme.md create mode 100644 Connectors/InMemory/Subscription.cs create mode 100644 Samples/InMemorySample/InMemorySample.csproj create mode 100644 Samples/InMemorySample/Program.cs diff --git a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs index 0166f56..8569ec5 100644 --- a/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs +++ b/Abstractions/Interfaces/Service/IQueryableMessageServiceConnection.cs @@ -1,9 +1,4 @@ using MQContract.Messages; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MQContract.Interfaces.Service { diff --git a/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs b/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs index f5f13ec..ca27897 100644 --- a/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs +++ b/AutomatedTesting/ContractConnectionTests/MiddleWareTests.cs @@ -148,7 +148,7 @@ public async Task TestRegisterSpecificTypeMiddlewareThroughFunction() mockMiddleware.Setup(x => x.BeforeMessageEncodeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((IContext context, BasicMessage message, string? channel, MessageHeader messageHeader) => { - return ValueTask.FromResult((message, newChannel, headers)); + return ValueTask.FromResult<(BasicMessage message,string? channel,MessageHeader headers)>((message, newChannel, headers)); }); diff --git a/AutomatedTesting/ContractConnectionTests/QueryInboxTests.cs b/AutomatedTesting/ContractConnectionTests/QueryInboxTests.cs index 8967908..06c9354 100644 --- a/AutomatedTesting/ContractConnectionTests/QueryInboxTests.cs +++ b/AutomatedTesting/ContractConnectionTests/QueryInboxTests.cs @@ -3,12 +3,7 @@ using MQContract.Attributes; using MQContract.Interfaces.Service; using MQContract; -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Text.Json; using System.Reflection; diff --git a/Connectors/HiveMQ/Exceptions.cs b/Connectors/HiveMQ/Exceptions.cs index 886e3d5..fd00116 100644 --- a/Connectors/HiveMQ/Exceptions.cs +++ b/Connectors/HiveMQ/Exceptions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MQContract.HiveMQ +namespace MQContract.HiveMQ { /// /// Thrown when the service connection is unable to connect to the HiveMQTT server diff --git a/Connectors/InMemory/Connection.cs b/Connectors/InMemory/Connection.cs new file mode 100644 index 0000000..b9982c1 --- /dev/null +++ b/Connectors/InMemory/Connection.cs @@ -0,0 +1,62 @@ +using MQContract.Interfaces.Service; +using MQContract.Messages; +using System.Collections.Concurrent; + +namespace MQContract.InMemory +{ + /// + /// Used as an in memory connection messaging system where all transmission are done through Channels within the connection. You must use the same underlying connection. + /// + public class Connection : IInboxQueryableMessageServiceConnection + { + private readonly ConcurrentDictionary channels = []; + private readonly string inboxChannel = $"_inbox/{Guid.NewGuid()}"; + /// + /// Default timeout for a given QueryResponse call + /// default: 1 minute + /// + public TimeSpan DefaultTimeout { get; init; } = TimeSpan.FromMinutes(1); + + /// + /// Maximum allowed message body size in bytes + /// default: 4MB + /// + public uint? MaxMessageBodySize { get; init; } = 1024*1024*4; + + private MessageChannel GetChannel(string channel) + { + if (!channels.TryGetValue(channel, out MessageChannel? messageChannel)) + { + messageChannel = new MessageChannel(); + channels.TryAdd(channel, messageChannel); + } + return messageChannel; + } + + ValueTask IMessageServiceConnection.CloseAsync() + { + var keys = channels.Keys.ToArray(); + foreach(var key in keys) + { + if (channels.TryRemove(key, out var channel)) + channel.Close(); + } + return ValueTask.CompletedTask; + } + + ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken) + => GetChannel(message.Channel).PublishAsync(message, cancellationToken); + + ValueTask IMessageServiceConnection.SubscribeAsync(Action messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) + => GetChannel(channel).RegisterSubscriptionAsync(messageReceived, errorReceived, group, cancellationToken); + + ValueTask IQueryableMessageServiceConnection.SubscribeQueryAsync(Func> messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken) + => GetChannel(channel).RegisterQuerySubscriptionAsync(messageReceived, errorReceived, + async (response) =>await GetChannel(inboxChannel).PublishAsync(response,cancellationToken), group, cancellationToken); + + ValueTask IInboxQueryableMessageServiceConnection.EstablishInboxSubscriptionAsync(Action messageReceived, CancellationToken cancellationToken) + => GetChannel(inboxChannel).EstablishInboxSubscriptionAsync(messageReceived, cancellationToken); + ValueTask IInboxQueryableMessageServiceConnection.QueryAsync(ServiceMessage message, Guid correlationID, CancellationToken cancellationToken) + => GetChannel(message.Channel).QueryAsync(message,inboxChannel, correlationID, cancellationToken); + } +} diff --git a/Connectors/InMemory/InMemory.csproj b/Connectors/InMemory/InMemory.csproj new file mode 100644 index 0000000..a7c7a98 --- /dev/null +++ b/Connectors/InMemory/InMemory.csproj @@ -0,0 +1,32 @@ + + + + + + net8.0 + enable + enable + MQContract.$(MSBuildProjectName) + MQContract.$(MSBuildProjectName) + In Memory Connector for MQContract + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + True + \ + + + + diff --git a/Connectors/InMemory/InternalServiceMessage.cs b/Connectors/InMemory/InternalServiceMessage.cs new file mode 100644 index 0000000..df31052 --- /dev/null +++ b/Connectors/InMemory/InternalServiceMessage.cs @@ -0,0 +1,8 @@ +using MQContract.Messages; + +namespace MQContract.InMemory +{ + internal record InternalServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header, ReadOnlyMemory Data,Guid? CorrelationID=null,string? ReplyChannel=null) + : ServiceMessage(ID,MessageTypeID,Channel,Header, Data) + { } +} diff --git a/Connectors/InMemory/MessageChannel.cs b/Connectors/InMemory/MessageChannel.cs new file mode 100644 index 0000000..f3d4d58 --- /dev/null +++ b/Connectors/InMemory/MessageChannel.cs @@ -0,0 +1,119 @@ +using MQContract.Interfaces.Service; +using MQContract.Messages; + +namespace MQContract.InMemory +{ + internal class MessageChannel + { + private readonly SemaphoreSlim semLock = new(1, 1); + private readonly List groups = []; + + private async ValueTask Publish(InternalServiceMessage message, CancellationToken cancellationToken) + { + await semLock.WaitAsync(cancellationToken); + var tasks = groups.Select(grp => grp.PublishMessage(message).AsTask()).ToArray(); + semLock.Release(); + await Task.WhenAll(tasks); + return Array.TrueForAll(tasks, t => t.Result); + } + + public void Close() + { + semLock.Wait(); + foreach (var group in groups.ToArray()) + group.Close(); + groups.Clear(); + semLock.Release(); + } + + internal async ValueTask PublishAsync(ServiceMessage message, CancellationToken cancellationToken) + { + if (!await Publish(new(message.ID,message.MessageTypeID,message.Channel,message.Header,message.Data), cancellationToken)) + return new(message.ID, "Unable to trasmit"); + return new(message.ID); + } + + internal async ValueTask PublishAsync(InternalServiceMessage message, CancellationToken cancellationToken) + => await Publish(message, cancellationToken); + + internal async ValueTask QueryAsync(ServiceMessage message,string inbox, Guid correlationID, CancellationToken cancellationToken) + { + if (!await Publish(new(message.ID,message.MessageTypeID,message.Channel,message.Header,message.Data,correlationID,inbox), cancellationToken)) + return new(message.ID, "Unable to trasmit"); + return new(message.ID); + } + + private async ValueTask GetGroupAsync(string? group) + { + group??=Guid.NewGuid().ToString(); + await semLock.WaitAsync(); + var grp = groups.Find(g => Equals(g.Group, group)); + if (grp==null) + { + grp = new MessageGroup(group, g => + { + semLock.Wait(); + groups.Remove(g); + semLock.Release(); + }); + groups.Add(grp); + } + semLock.Release(); + return grp; + } + + private async ValueTask CreateSubscription(Func processMessage,Action errorReceived,string? group,CancellationToken cancellationToken) + { + var sub = new Subscription(await GetGroupAsync(group), async (recievedMessage) => + { + try + { + await processMessage(recievedMessage); + } + catch (Exception ex) + { + errorReceived(ex); + } + }); + sub.Start(); + return sub; + } + + internal async ValueTask RegisterQuerySubscriptionAsync(Func> messageReceived, Action errorReceived, Action publishResponse, string? group, CancellationToken cancellationToken) + => await CreateSubscription( + async (recievedMessage) => + { + var result = await messageReceived(new(recievedMessage.ID, recievedMessage.MessageTypeID, recievedMessage.Channel, recievedMessage.Header, recievedMessage.Data)); + publishResponse(new(result.ID, result.MessageTypeID, recievedMessage.ReplyChannel!, result.Header, result.Data, recievedMessage.CorrelationID)); + }, + errorReceived, + group, + cancellationToken + ); + + internal async ValueTask RegisterSubscriptionAsync(Action messageReceived, Action errorReceived, string? group, CancellationToken cancellationToken) + => await CreateSubscription( + (receivedMessage) => + { + messageReceived(new(receivedMessage.ID, receivedMessage.MessageTypeID, receivedMessage.Channel, receivedMessage.Header, receivedMessage.Data)); + return ValueTask.CompletedTask; + }, + errorReceived, + group, + cancellationToken + ); + + internal async ValueTask EstablishInboxSubscriptionAsync(Action messageReceived, CancellationToken cancellationToken) + => await CreateSubscription( + (receivedMessage) => + { + if (receivedMessage.CorrelationID!=null) + messageReceived(new(receivedMessage.ID, receivedMessage.MessageTypeID, receivedMessage.Channel, receivedMessage.Header, receivedMessage.CorrelationID.Value, receivedMessage.Data)); + return ValueTask.CompletedTask; + }, + (error) => { }, + null, + cancellationToken + ); + } +} diff --git a/Connectors/InMemory/MessageGroup.cs b/Connectors/InMemory/MessageGroup.cs new file mode 100644 index 0000000..95b2f43 --- /dev/null +++ b/Connectors/InMemory/MessageGroup.cs @@ -0,0 +1,53 @@ +using System.Threading.Channels; + +namespace MQContract.InMemory +{ + internal class MessageGroup(string group,Action removeMe) + { + private readonly SemaphoreSlim semLock = new(1, 1); + private readonly List> channels = []; + private int index = 0; + public string Group => group; + + public Channel Register() + { + var result = Channel.CreateUnbounded(new UnboundedChannelOptions() { SingleReader=true,SingleWriter=true}); + channels.Add(result); + return result; + } + + public async ValueTask UnregisterAsync(Channel channel) + { + await semLock.WaitAsync(); + channels.Remove(channel); + if (channels.Count == 0) + removeMe(this); + semLock.Release(); + } + + public async ValueTask PublishMessage(InternalServiceMessage message) + { + var success = false; + await semLock.WaitAsync(); + if (index>=channels.Count) + index=0; + if (index +# MQContract.InMemory + +## Contents + +- [Connection](#T-MQContract-InMemory-Connection 'MQContract.InMemory.Connection') + - [DefaultTimeout](#P-MQContract-InMemory-Connection-DefaultTimeout 'MQContract.InMemory.Connection.DefaultTimeout') + - [MaxMessageBodySize](#P-MQContract-InMemory-Connection-MaxMessageBodySize 'MQContract.InMemory.Connection.MaxMessageBodySize') + + +## Connection `type` + +##### Namespace + +MQContract.InMemory + +##### Summary + +Used as an in memory connection messaging system where all transmission are done through Channels within the connection. You must use the same underlying connection. + + +### DefaultTimeout `property` + +##### Summary + +Default timeout for a given QueryResponse call +default: 1 minute + + +### MaxMessageBodySize `property` + +##### Summary + +Maximum allowed message body size in bytes +default: 4MB diff --git a/Connectors/InMemory/Subscription.cs b/Connectors/InMemory/Subscription.cs new file mode 100644 index 0000000..c168019 --- /dev/null +++ b/Connectors/InMemory/Subscription.cs @@ -0,0 +1,28 @@ +using MQContract.Interfaces.Service; +using System.Threading.Channels; + +namespace MQContract.InMemory +{ + internal class Subscription(MessageGroup group, Func messageRecieved) : IServiceSubscription + { + private readonly Channel channel = group.Register(); + + public void Start() + { + Task.Run(async () => + { + while (await channel.Reader.WaitToReadAsync()) + { + var message = await channel.Reader.ReadAsync(); + await messageRecieved(message); + } + }); + } + + async ValueTask IServiceSubscription.EndAsync() + { + channel.Writer.TryComplete(); + await group.UnregisterAsync(channel); + } + } +} diff --git a/MQContract.sln b/MQContract.sln index 067369c..0239d11 100644 --- a/MQContract.sln +++ b/MQContract.sln @@ -39,9 +39,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RedisSample", "Samples\Redi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQSample", "Samples\RabbitMQSample\RabbitMQSample.csproj", "{0740514D-A6AB-41CC-9820-A619125C6C90}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HiveMQ", "Connectors\HiveMQ\HiveMQ.csproj", "{CF37AF9A-199E-4974-B33F-D3B34CE52988}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HiveMQ", "Connectors\HiveMQ\HiveMQ.csproj", "{CF37AF9A-199E-4974-B33F-D3B34CE52988}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HiveMQSample", "Samples\HiveMQSample\HiveMQSample.csproj", "{17F3294A-6A89-4BDD-86E3-629D3506D147}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HiveMQSample", "Samples\HiveMQSample\HiveMQSample.csproj", "{17F3294A-6A89-4BDD-86E3-629D3506D147}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InMemory", "Connectors\InMemory\InMemory.csproj", "{B3E315EF-6FD3-46A8-9D7B-36E3B8D14787}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InMemorySample", "Samples\InMemorySample\InMemorySample.csproj", "{144D43A8-7154-4530-A78F-68DF7C4685E9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -121,6 +125,14 @@ Global {17F3294A-6A89-4BDD-86E3-629D3506D147}.Debug|Any CPU.Build.0 = Debug|Any CPU {17F3294A-6A89-4BDD-86E3-629D3506D147}.Release|Any CPU.ActiveCfg = Release|Any CPU {17F3294A-6A89-4BDD-86E3-629D3506D147}.Release|Any CPU.Build.0 = Release|Any CPU + {B3E315EF-6FD3-46A8-9D7B-36E3B8D14787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3E315EF-6FD3-46A8-9D7B-36E3B8D14787}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3E315EF-6FD3-46A8-9D7B-36E3B8D14787}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3E315EF-6FD3-46A8-9D7B-36E3B8D14787}.Release|Any CPU.Build.0 = Release|Any CPU + {144D43A8-7154-4530-A78F-68DF7C4685E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {144D43A8-7154-4530-A78F-68DF7C4685E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {144D43A8-7154-4530-A78F-68DF7C4685E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {144D43A8-7154-4530-A78F-68DF7C4685E9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -141,6 +153,8 @@ Global {0740514D-A6AB-41CC-9820-A619125C6C90} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} {CF37AF9A-199E-4974-B33F-D3B34CE52988} = {FCAD12F9-6992-44D7-8E78-464181584E06} {17F3294A-6A89-4BDD-86E3-629D3506D147} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} + {B3E315EF-6FD3-46A8-9D7B-36E3B8D14787} = {FCAD12F9-6992-44D7-8E78-464181584E06} + {144D43A8-7154-4530-A78F-68DF7C4685E9} = {0DAB9C52-BDAF-44CD-B10A-B9E057105696} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F4431E3A-7CBE-469C-A7FE-0A48F6768E02} diff --git a/README.md b/README.md index e491f81..8168b7e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ global level or on a per message type level through implementation of the approp * Connectors * [ActiveMQ](/Connectors/ActiveMQ/Readme.md) * [HiveMQ](/Connectors/HiveMQ/Readme.md) + * [InMemory](/Connectors/InMemory/Readme.md) * [Kafka](/Connectors/Kafka/Readme.md) * [KubeMQ](/Connectors/KubeMQ/Readme.md) * [Nats.io](/Connectors/NATS/Readme.md) diff --git a/Samples/InMemorySample/InMemorySample.csproj b/Samples/InMemorySample/InMemorySample.csproj new file mode 100644 index 0000000..3b2388f --- /dev/null +++ b/Samples/InMemorySample/InMemorySample.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/Samples/InMemorySample/Program.cs b/Samples/InMemorySample/Program.cs new file mode 100644 index 0000000..189cbbd --- /dev/null +++ b/Samples/InMemorySample/Program.cs @@ -0,0 +1,6 @@ +using Messages; +using MQContract.InMemory; + +var serviceConnection = new Connection(); + +await SampleExecution.ExecuteSample(serviceConnection, "InMemory"); \ No newline at end of file diff --git a/Samples/Messages/SampleExecution.cs b/Samples/Messages/SampleExecution.cs index d5abfe6..461ed16 100644 --- a/Samples/Messages/SampleExecution.cs +++ b/Samples/Messages/SampleExecution.cs @@ -11,10 +11,6 @@ public static async ValueTask ExecuteSample(IMessageServiceConnection serviceCon { using var sourceCancel = new CancellationTokenSource(); - Console.CancelKeyPress += delegate { - sourceCancel.Cancel(); - }; - var contractConnection = ContractConnection.Instance(serviceConnection,channelMapper:mapper); contractConnection.AddMetrics(null, true); @@ -80,16 +76,18 @@ await Task.WhenAll( storedResult = await contractConnection.PublishAsync(new("Fred", "Flintstone"), cancellationToken: sourceCancel.Token); Console.WriteLine($"Stored Result 2 [Success:{!storedResult.IsError}, ID:{storedResult.ID}]"); - Console.WriteLine("Press Ctrl+C to close"); + Console.WriteLine("Press Enter to close"); + + Console.ReadLine(); + await sourceCancel.CancelAsync(); - sourceCancel.Token.WaitHandle.WaitOne(); Console.WriteLine("System completed operation"); var jsonOptions = new JsonSerializerOptions() { WriteIndented = true }; - Console.WriteLine($"Greetings Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting),true),jsonOptions)}"); - Console.WriteLine($"Greetings Received: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting),false), jsonOptions)}"); + Console.WriteLine($"Greetings Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting), true), jsonOptions)}"); + Console.WriteLine($"Greetings Received: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(Greeting), false), jsonOptions)}"); Console.WriteLine($"StoredArrivals Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(ArrivalAnnouncement), true), jsonOptions)}"); Console.WriteLine($"StoredArrivals Received: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(ArrivalAnnouncement), false), jsonOptions)}"); Console.WriteLine($"Arrivals Sent: {JsonSerializer.Serialize(contractConnection.GetSnapshot(typeof(StoredArrivalAnnouncement), true), jsonOptions)}");