public MessageSender(AzureServiceBusSettings serviceBusSettings,
                             ServiceBusConnection serviceBusConnection,
                             NamespaceInfo namespaceInfo,
                             MessageProcessingContext context,
                             string destinationQueue)
        {
            _messageProcessingContext = context;
            _settings      = serviceBusSettings;
            _namespaceInfo = namespaceInfo;

            // The default retry policy passed in to the sender provides an exponential backoff for transient failures.
            if (context == null)
            {
                _messageSender = new Microsoft.Azure.ServiceBus.Core.MessageSender(serviceBusConnection, destinationQueue, RetryPolicy.Default);
            }
            else
            {
                var viaQueue = context.GetMessageReceiver().Path;
                if (viaQueue == destinationQueue)
                {
                    _messageSender = new Microsoft.Azure.ServiceBus.Core.MessageSender(serviceBusConnection, destinationQueue, RetryPolicy.Default);
                }
                else
                {
                    _messageSender = new Microsoft.Azure.ServiceBus.Core.MessageSender(serviceBusConnection, destinationQueue, viaQueue, RetryPolicy.Default);
                }
            }
        }
        private async Task InnerMessageHandler(Message message, Message controlMessage, CancellationToken ct)
        {
            byte[] messageBody = null;

            // Check if this is a large message that we need to download the payload for
            if (message.UserProperties.TryGetValue(Constants.HeaderKeys.RPBlobSizeBytes, out var blobSizeBytes))
            {
                messageBody = await DownloadMessageBodyFromBlob(message.MessageId, long.Parse(blobSizeBytes.ToString())).ConfigureAwait(false);
            }
            else
            {
                messageBody = message.Body;
            }

            // Deserialize
            if (message.UserProperties.TryGetValue(Constants.HeaderKeys.RPMessageType, out var assemblyQualifiedNameObject))
            {
                var type = Type.GetType(assemblyQualifiedNameObject.ToString());

                IMessageHandler handler = _messageHandlerFactory.CreateMessageHandler(type);

                object deserializedMessage;
                try
                {
                    var messageString = Encoding.UTF8.GetString(messageBody);
                    deserializedMessage = JsonConvert.DeserializeObject(messageString, type);
                }
                catch (Exception ex)
                {
                    await _messageReceiver.DeadLetterAsync(message.SystemProperties.LockToken,
                                                           $"Error deserializing message body: {ex}")
                    .ConfigureAwait(false);

                    return;
                }

                // Determine if we need to use a transaction for this handler - check the "Transactional" attribute
                // on the handler class
                var useTransaction          = true;
                var transactionalAttributes = handler.GetType().GetCustomAttributes(typeof(TransactionalAttribute), true);
                if (transactionalAttributes == null || transactionalAttributes.Length == 0)
                {
                    useTransaction = false;
                }

                var context = new MessageProcessingContext(_settings, _messageReceiver, message, controlMessage, _messagingFactory, useTransaction);

                using (var tx = useTransaction ? new TransactionScope(TransactionScopeAsyncFlowOption.Enabled) : null)
                {
                    try
                    {
                        await handler.HandleMessage(deserializedMessage, context).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        // If an exception was thrown on the last attempt, explicitly dead-letter the message.
                        // If we don't do this, ASB will dead letter the message with a DeadLetterReason field
                        // value of MaxDeliveryCountExceeded, which is not particularly useful for diagnosis.
                        // Instead, we explicitly dead-letter the message with some details of the exception that
                        // was thrown.
                        if (message.SystemProperties.DeliveryCount == _queueDescription.MaxDeliveryCount)
                        {
                            var deadLetterReason      = "";
                            var deadLetterDescription = "";
                            GetDeadLetterFields(ex, out deadLetterReason, out deadLetterDescription);

                            await context.DeadLetter(deadLetterReason, deadLetterDescription).ConfigureAwait(false);
                        }

                        throw;
                    }

                    if (!context.IsAbandoned)
                    {
                        tx?.Complete();
                    }
                }

                if (context.IsAbandoned)
                {
                    await _messageReceiver.AbandonAsync(message.SystemProperties.LockToken).ConfigureAwait(false);

                    if (controlMessage != null)
                    {
                        await _messageReceiver.AbandonAsync(controlMessage.SystemProperties.LockToken).ConfigureAwait(false);
                    }
                }
            }
            else
            {
                await _messageReceiver.DeadLetterAsync(message.SystemProperties.LockToken,
                                                       $"Message has no type specified in the {Constants.HeaderKeys.RPMessageType} header, cannot deserialize message.")
                .ConfigureAwait(false);

                return;
            }
        }