﻿# Azure Storage Queues Transport

<!-- Version variant: asqn_13; default: [/samples/azure/storage-queues/index.md](/samples/azure/storage-queues/index.md) -->


## Prerequisites

Ensure an instance of the [Azurite Emulator](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio) is running.


## Azure Storage Queues Transport

This sample uses the [Azure Storage Queues Transport](/transports/azure-storage-queues/index.md).


## Code walk-through

This sample shows a simple two endpoint scenario.

 * `Endpoint1` sends a `MyRequest` message to `Endpoint2`
 * `Endpoint2` replies to `Endpoint1` with a `MyResponse`.


### Azure Table configuration

The `Server` endpoint is configured to use the Azure Table persistence in two locations.


#### The endpoint configuration

<!-- snippet: Config -->

```cs
var transport = new AzureStorageQueueTransport("UseDevelopmentStorage=true");
var routingSettings = endpointConfiguration.UseTransport(transport);
routingSettings.RouteToEndpoint(typeof(MyRequest), "Samples-Azure-StorageQueues-Endpoint2");
```

<!-- endsnippet -->

#### Sanitization

One of the endpoints is using a long name which needs to be sanitized.

<!-- snippet: endpointname -->

```cs
var endpointName = "Samples.Azure.StorageQueues.Endpoint1.With.A.Very.Long.Name.And.Invalid.Characters";
var endpointConfiguration = new EndpointConfiguration(endpointName);
```

<!-- endsnippet -->

To remain backwards compatible with the older versions of the transport, `MD5` based sanitization is registered. The sample also includes `SHA1` based sanitization. This sanitizer is suitable for endpoints with the transport version 7.x used to shorten queue names with `SHA1` hashing algorithm.

<!-- snippet: sanitization -->

```cs
transport.QueueNameSanitizer = BackwardsCompatibleQueueNameSanitizer.WithMd5Shortener;
```

<!-- endsnippet -->

The full contents of the sanitization code is shown at the [end of this document](#sanitizer-source-code).


## The data in Azure Storage

The queues for the two endpoints can be seen in the [Server Explorer](https://msdn.microsoft.com/en-us/library/x603htbk.aspx) of Visual Studio.

![](images/sanitized-queues.png)


### Reading the data using code

There are several helper methods in `AzureHelper.cs` in the `StorageReader` projects. These helpers are used to output the data seen below.


#### Writing Queue Messages

This helper peeks the first message from a given queue and writes out the contents of that message.

<!-- snippet: WriteOutQueue -->

```cs
static async Task WriteOutQueue(string queueName)
{
    var queueClient = new QueueClient("UseDevelopmentStorage=true", queueName);
    PeekedMessage[] message = await queueClient.PeekMessagesAsync(1);
    if (message?.Length >= 1)
    {
        Console.WriteLine("Message contents");
        WriteOutMessage(message[0]);
        return;
    }

    Console.WriteLine($"No messages found in the '{queueName}' queue. Execute 'Endpoint1' without running 'Endpoint2' and then try again");
}

static void WriteOutMessage(PeekedMessage message)
{
    var bytes = Convert.FromBase64String(message.MessageText);
    var json = Encoding.UTF8.GetString(bytes);

    dynamic parsedJson = JsonConvert.DeserializeObject(json);
    Console.WriteLine("Message contents:");
    Console.WriteLine(JsonConvert.SerializeObject((object) parsedJson, Formatting.Indented));

    var body = (string)parsedJson.Body;
    Console.WriteLine("Deserialized message body:");
    Console.WriteLine(body.Base64Decode());
}
```

<!-- endsnippet -->


#### Using the helper

<!-- snippet: UsingHelpers -->

```cs
await WriteOutQueue("samples-azure-storagequeues-endpoint2");
```

<!-- endsnippet -->


### The Message Data

Run only `Endpoint1` and send a message. Notice the contents of the message in the `samples-azure-storagequeues-endpoint2` queue.


### CloudQueueMessage contents

```json
{
  "IdForCorrelation": null,
  "Id": "5957b746-6636-43c7-89b9-ac6e01853eae",
  "MessageIntent": 1,
  "ReplyToAddress": "samples-azure-storagequeues-endpoint1",
  "TimeToBeReceived": "00:00:00",
  "Headers": {
    "NServiceBus.MessageId": "5957b746-6636-43c7-89b9-ac6e01853eae",
    "NServiceBus.MessageIntent": "Send",
    "NServiceBus.ConversationId": "b01e3778-23a1-42b5-bcbd-ac6e01853eaf",
    "NServiceBus.CorrelationId": "5957b746-6636-43c7-89b9-ac6e01853eae",
    "NServiceBus.ReplyToAddress": "samples-azure-storagequeues-endpoint1",
    "NServiceBus.OriginatingMachine": "BEAST",
    "NServiceBus.OriginatingEndpoint": "Samples-Azure-StorageQueues-Endpoint1",
    "$.diagnostics.originating.hostid": "27bfc91ba004f906eed90fc507597a11",
    "NServiceBus.ContentType": "application/json",
    "NServiceBus.EnclosedMessageTypes": "Endpoint2.MyRequest, Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
    "NServiceBus.Version": "7.4.4",
    "NServiceBus.TimeSent": "2020-11-09 23:37:11:901738 Z"
  },
  "Body": "77u/eyJQcm9wZXJ0eSI6IkhlbGxvIGZyb20gRW5kcG9pbnQxIn0=",
  "CorrelationId": "5957b746-6636-43c7-89b9-ac6e01853eae",
  "Recoverable": true
}
```


#### Decoded Body

Note that above there is a encoded `Body` property. Decoding this message will produce the following.

```json
{"Property":"Hello from Endpoint1"}

```

## Sanitizer source code

<!-- snippet: BackwardsCompatibleQueueNameSanitizer -->

```cs
public static partial class BackwardsCompatibleQueueNameSanitizer
{
    public static string WithMd5Shortener(string queueName)
    {
        return Sanitize(queueName, useMd5Hashing: true);
    }

    public static string WithSha1Shortener(string queueName)
    {
        return Sanitize(queueName, useMd5Hashing: false);
    }

    static string Sanitize(string queueName, bool useMd5Hashing = true)
    {
        var queueNameInLowerCase = queueName.ToLowerInvariant();
        return ShortenQueueNameIfNecessary(SanitizeQueueName(queueNameInLowerCase), useMd5Hashing);
    }

    static string ShortenQueueNameIfNecessary(string sanitizedQueueName, bool useMd5Hashing)
    {
        if (sanitizedQueueName.Length <= 63)
        {
            return sanitizedQueueName;
        }

        var shortenedName = useMd5Hashing ? ShortenWithMd5(sanitizedQueueName) : ShortenWithSha1(sanitizedQueueName);

        return $"{sanitizedQueueName.Substring(0, 63 - shortenedName.Length - 1).Trim('-')}-{shortenedName}";
    }

    static string SanitizeQueueName(string queueName)
    {
        // this can lead to multiple '-' occurrences in a row
        var sanitized = InvalidCharacters().Replace(queueName, "-");
        return MultipleDashes().Replace(sanitized, "-");
    }

    static string ShortenWithMd5(string test)
    {
        //use MD5 hash to get a 16-byte hash of the string
        using var provider = MD5.Create();
        var inputBytes = Encoding.Default.GetBytes(test);
        var hashBytes = provider.ComputeHash(inputBytes);
        //generate a guid from the hash:
        return new Guid(hashBytes).ToString();
    }

    static string ShortenWithSha1(string queueName)
    {
        using var provider = SHA1.Create();
        var inputBytes = Encoding.Default.GetBytes(queueName);
        var hashBytes = provider.ComputeHash(inputBytes);

        return ToChars(hashBytes);
    }

    static string ToChars(byte[] hashBytes)
    {
        var chars = new char[hashBytes.Length * 2];
        for (var i = 0; i < chars.Length; i += 2)
        {
            var byteIndex = i / 2;
            chars[i] = HexToChar((byte)(hashBytes[byteIndex] >> 4));
            chars[i + 1] = HexToChar(hashBytes[byteIndex]);
        }

        return new string(chars);
    }

    static char HexToChar(byte a)
    {
        a &= 15;
        return a > 9 ? (char)(a - 10 + 97) : (char)(a + 48);
    }

    [GeneratedRegex(@"[^a-z0-9\-]", RegexOptions.Compiled)]
    private static partial Regex InvalidCharacters();
    [GeneratedRegex(@"\-+", RegexOptions.Compiled)]
    private static partial Regex MultipleDashes();
}
```

<!-- endsnippet -->
