public async Task <bool> DeadletterMessageAsync(MessageBrokerContext context, TransactionContext transactionContext, string deadLetterReason, string deadLetterErrorDescription, CancellationToken cancellationToken)
        {
            ReceivedMessage msg = null;

            if (!context?.Container.TryGet(out msg) ?? false)
            {
                throw new ArgumentException($"Unable to deadletter message. No {nameof(ReceivedMessage)} contained in {nameof(context)}.", nameof(msg));
            }

            transactionContext.Container.TryGet <SqlConnection>(out var connection);
            transactionContext.Container.TryGet <SqlTransaction>(out var transaction);

            try
            {
                var edc = new EndDialogConversationCommand(connection,
                                                           msg.ConvHandle,
                                                           enableCleanup: _ssbOptions.CleanupOnEndConversation,
                                                           transaction: transaction);
                await edc.ExecuteAsync(cancellationToken);

                using var scope = _serviceFactory.CreateScope();
                var ssbSender     = scope.ServiceProvider.GetRequiredService <SqlServiceBrokerSender>();
                var bodyConverter = _bodyConverterFactory.CreateBodyConverter(_ssbOptions.MessageBodyType);

                _localReceiverDeliveryAttempts.TryGetValue(msg.ConvHandle, out var deliveryAttempts);

                var headers = new Dictionary <string, object>()
                {
                    [SSBMessageContext.ConversationHandle]  = msg.ConvHandle,
                    [SSBMessageContext.ServiceName]         = msg.ServiceName,
                    [MessageContext.FailureDescription]     = deadLetterErrorDescription,
                    [MessageContext.FailureDetails]         = deadLetterReason,
                    [MessageContext.InfrastructureType]     = SSBMessageContext.InfrastructureType,
                    [SSBMessageContext.MessageTypeName]     = ServicesMessageTypes.ChatterBrokeredMessageType,
                    [SSBMessageContext.ServiceContractName] = ServicesMessageTypes.ChatterServiceContract,
                    [MessageContext.ReceiveAttempts]        = deliveryAttempts
                };
                await ssbSender.Dispatch(new OutboundBrokeredMessage(context.BrokeredMessage.MessageId, msg.Body, headers, _options.DeadLetterQueuePath, bodyConverter), transactionContext);

                await transaction?.CommitAsync(cancellationToken);

                _localReceiverDeliveryAttempts.TryRemove(msg.ConvHandle, out var _);
                _logger.LogTrace($"Message deadlettered.");
                return(true);
            }
            finally
            {
                transaction?.Dispose();
                connection?.Dispose();
            }
        }
        public async Task <bool> AckMessageAsync(MessageBrokerContext context, TransactionContext transactionContext, CancellationToken cancellationToken)
        {
            ReceivedMessage msg = null;

            if (!context?.Container.TryGet(out msg) ?? false)
            {
                _logger.LogTrace($"No {nameof(ReceivedMessage)} contained in {nameof(context)}.");
            }

            transactionContext.Container.TryGet <SqlConnection>(out var connection);
            transactionContext.Container.TryGet <SqlTransaction>(out var transaction);

            try
            {
                if (msg != null)
                {
                    var edc = new EndDialogConversationCommand(connection,
                                                               msg.ConvHandle,
                                                               enableCleanup: _ssbOptions.CleanupOnEndConversation,
                                                               transaction: transaction);
                    await edc.ExecuteAsync(cancellationToken);

                    _localReceiverDeliveryAttempts.TryRemove(msg.ConvHandle, out var _);
                }
                else
                {
                    _logger.LogTrace($"Unable end dialog conversation during message acknowledgment. {nameof(msg)} is null.");
                }
                await transaction?.CommitAsync(cancellationToken);

                _logger.LogTrace("Message acknowledgment complete");
                return(true);
            }
            finally
            {
                transaction?.Dispose();
                connection?.Dispose();
            }
        }
        public async Task <MessageBrokerContext> ReceiveMessageAsync(TransactionContext transactionContext, CancellationToken cancellationToken)
        {
            ReceivedMessage      message        = null;
            MessageBrokerContext messageContext = null;
            SqlConnection        connection     = null;
            SqlTransaction       transaction    = null;

            try
            {
                connection = new SqlConnection(_ssbOptions.ConnectionString);
                await connection.OpenAsync(cancellationToken);

                transaction = await CreateTransaction(connection, cancellationToken);
            }
            catch (Exception ex) when(ex is ArgumentException || ex is InvalidOperationException)
            {
                transaction?.Dispose();
                connection?.Dispose();
                throw new CriticalReceiverException("Error connecting to sql", ex);
            }

            try
            {
                message = await ReceiveAsync(connection, transaction, cancellationToken);
            }
#if NET5_0_OR_GREATER
            catch (SqlException e) when(e.IsTransient)
            {
                _logger.LogWarning(e, "Failure to receive message from Sql Service Broker due to transient error");
                throw;
            }
#endif
            catch (SqlException e) when(e.Number == 102)
            {
                throw new CriticalReceiverException($"Unable to receive message from configured queue '{_options.MessageReceiverPath}'", e);
            }
            catch (Exception e)
            {
                _logger.LogError(e, $"Error receiving sql service broker message from queue '{_options.MessageReceiverPath}'");
                throw;
            }
            finally
            {
                if (message == null)
                {
                    transaction?.Dispose();
                    connection?.Dispose();
                }
            }

            if (message is null)
            {
                await DiscardMessageAsync(connection, transaction, "Discarding null message", cancellationToken);

                return(null);
            }

            if (message.MessageTypeName != ServicesMessageTypes.DefaultType && message.MessageTypeName != ServicesMessageTypes.ChatterBrokeredMessageType)
            {
                await DiscardMessageAsync(connection, transaction
                                          , $"Discarding message of type '{message.MessageTypeName}'. Only messages of type '{ServicesMessageTypes.DefaultType}' or '{ServicesMessageTypes.ChatterBrokeredMessageType}' will be received."
                                          , cancellationToken);

                return(null);
            }

            if (message.Body == null)
            {
                await DiscardMessageAsync(connection, transaction
                                          , $"Discarding message of type '{message.MessageTypeName}' with null message body"
                                          , cancellationToken);

                return(null);
            }

            transactionContext.Container.Include(connection);
            if (_transactionMode != TransactionMode.None && transaction != null)
            {
                transactionContext.Container.Include(transaction);
            }

            _localReceiverDeliveryAttempts.AddOrUpdate(message.ConvHandle, 1, (ch, deliveryAttempts) => deliveryAttempts + 1);

            IBrokeredMessageBodyConverter bodyConverter = new JsonUnicodeBodyConverter();
            byte[] messagePayload = message.Body;
            string messageId      = message.ConvHandle.ToString();
            IDictionary <string, object> headers = new Dictionary <string, object>();

            try
            {
                bodyConverter = _bodyConverterFactory.CreateBodyConverter(_ssbOptions.MessageBodyType);
                if (message.MessageTypeName == ServicesMessageTypes.ChatterBrokeredMessageType)
                {
                    var brokeredMessage = bodyConverter.Convert <OutboundBrokeredMessage>(message.Body);

                    if (brokeredMessage == null)
                    {
                        throw new ArgumentNullException(nameof(brokeredMessage), $"Unable to deserialize {nameof(OutboundBrokeredMessage)} from message body");
                    }

                    messagePayload = brokeredMessage.Body;
                    messageId      = brokeredMessage.MessageId;
                    headers        = brokeredMessage.MessageContext;
                }
            }
            catch (Exception e)
            {
                _logger.LogWarning(e, $"Error creating body converter for content type '{_ssbOptions.MessageBodyType}'. Defaulting to {nameof(JsonUnicodeBodyConverter)}.");
            }
            finally
            {
                _localReceiverDeliveryAttempts.TryGetValue(message.ConvHandle, out var deliveryAttempts);

                headers[SSBMessageContext.ConversationGroupId]   = message.ConvGroupHandle;
                headers[SSBMessageContext.ConversationHandle]    = message.ConvHandle;
                headers[SSBMessageContext.MessageSequenceNumber] = message.MessageSeqNo;
                headers[SSBMessageContext.ServiceName]           = message.ServiceName;
                headers[SSBMessageContext.ServiceContractName]   = message.ServiceContractName;
                headers[SSBMessageContext.MessageTypeName]       = message.MessageTypeName;
                headers[MessageContext.InfrastructureType]       = SSBMessageContext.InfrastructureType;
                headers[MessageContext.ReceiveAttempts]          = deliveryAttempts;

                messageContext = new MessageBrokerContext(messageId, messagePayload, headers, _options.MessageReceiverPath, cancellationToken, bodyConverter);
                messageContext.Container.Include(message);
            }

            return(messageContext);
        }