﻿# Azure Storage Queues Name Sanitization

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


Queue name sanitization is not done by default for NServiceBus.Azure.Transports.WindowsAzureStorageQueues version 8.0 or NServiceBus.Azure.Transports.AzureStorageQueues versions 9 and up.

## Azure Storage Queues naming rules

Sanitization is necessary if queue names do not adhere to the following rules:

 1. A queue name can only contain letters, numbers, and the dash (`-`) character.
 1. The first and last letters in the queue name must be alphanumeric.
 1. Consecutive dash characters are not permitted in the queue name.
 1. All letters in a queue name must be lowercase.
 1. A queue name must be between 3 and 63 characters long.


## Queue name sanitization

To sanitize queue names, a sanitization function containing the required logic can be registered.

<!-- snippet: azure-storage-queue-sanitization -->

```cs
var transport = new AzureStorageQueueTransport("connection string")
{
    QueueNameSanitizer = queueName => queueName.Replace('.', '-')
};

endpointConfiguration.UseTransport(transport);
```

<!-- endsnippet -->

When an endpoint is started, the sanitizer function will be invoked for each queue the transport creates.


## Backward compatibility with versions 7 and below

To remain compatible with queues created by versions 7 and below of the transport, be sure to configure queues created by versions 8 and above with the same rules. The following custom code will ensure queues are sanitized in a backward-compatible manner.

<!-- snippet: azure-storage-queue-backwards-compatible-sanitization-with-md5 -->

```cs
var transport = new AzureStorageQueueTransport("connection string")
{
    QueueNameSanitizer = BackwardsCompatibleQueueNameSanitizer.WithMd5Shortener
};

endpointConfiguration.UseTransport(transport);
```

<!-- endsnippet -->

Sanitization code for MD5 and SHA1:

<!-- snippet: azure-storage-queue-backwards-compatible-sanitization -->

```cs
public static 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);
    }

    static Regex invalidCharacters = new Regex(@"[^a-z0-9\-]", RegexOptions.Compiled);
    static Regex multipleDashes = new Regex(@"\-+", RegexOptions.Compiled);
}
```

<!-- endsnippet -->


## Future consideration prior to using sanitization

When implementing custom sanitization, consider factors such as readability and discover-ability. Things to consider:

 * Truncated long queue names could conflict.
 * Hashed queue names could lead to difficult names to use during production troubleshooting or debugging.
 * Sanitized queue names stay in the system and cannot be replaced until no longer used.

Possible way to avoid sanitization is to define endpoint name short and meaningful.
