Sanitization

Component: Azure Service Bus Transport
NuGet Package NServiceBus.Azure.Transports.WindowsAzureServiceBus (7.x)
Target NServiceBus Version: 6.x

Azure Service Bus entity naming rules

Azure Service Bus transport works with the Messaging namespaces and operates on four types of entities: queues, topics, subscriptions, and rules. Entities have their paths and names derived from NServiceBus endpoint and event names. Entities have path and name rules applied to:

  1. Allowed characters
  2. Maximum length
Only queues and topics can have a path.

When entity name does not follow the rules, an exception is raised by the broker and transport fails to start. Paths and names need to be validated and adhere to the Azure Service Bus service rules.

Entity TypeValid CharactersPath / Name Maximum Length
QueueLetters, numbers, periods (.), hyphens (-), underscores (_), and forward slashes (/).260
TopicLetters, numbers, periods (.), hyphens (-), underscores (_), and forward slashes (/).260
SubscriptionLetters, numbers, periods (.), hyphens (-), and underscores (_).50
RuleLetters, numbers, periods (.), hyphens (-), and underscores (_).50

When sanitization strategy is specified, the validation settings can be overridden. The validation settings determine how entity names are validated. They should correspond to the validation rules in the configured Azure Service Bus namespace. The rules implementations vary depending on the namespace type, and are changing over time (in some cases without notice and update of the relevant MSDN documentation). The default settings align with the recently created Standard namespaces.

Sanitization

Sanitization is an operation that is performed on an entity path or name to ensure the broker can operate on the entity, and no exception is thrown. Sanitization can be performed manually or by the Azure Service Bus transport.

To perform manual sanitization, inspect entity name for invalid characters and number of characters. For automated sanitization, the transport allows configuration outlined below.

Automated sanitization

By default, the transport uses the ThrowOnFailedValidation sanitization strategy. This strategy allows sanitization rules to be specified that remove invalid characters and hashing algorithm to shorten entity path/name that is exceeding maximum length. For an invalid entity path/name, an exception is thrown. Validation rules can be adjusted by providing custom validation rules per entity type.

var sanitization = transport.Sanitization();
var strategy = sanitization.UseStrategy<ThrowOnFailedValidation>();
strategy.QueuePathValidation(queuePath => new ValidationResult());
strategy.TopicPathValidation(topicPath => new ValidationResult());
strategy.SubscriptionNameValidation(
    subscriptionNameValidator: subscriptionName =>
    {
        return new ValidationResult();
    });
strategy.RuleNameValidation(
    ruleNameValidator: ruleName =>
    {
        return new ValidationResult();
    });

Where ValidationResult provides the following

  • Characters are valid or not
  • Length is valid or not

To customize sanitization for some of the entities, ValidateAndHashIfNeeded strategy can be used. This strategy allows to specify sanitization rules to remove invalid characters and hashing algorithm to shorten entity path/name that is exceeding maximum length.

ValidateAndHashIfNeeded is using validation rules to determine what needs to be sanitized. First step, invalid characters are removed. Second step, hashing is applied if length is still exceeding the maximum allowed length.
var sanitization = transport.Sanitization();
var strategy = sanitization.UseStrategy<ValidateAndHashIfNeeded>();
strategy.QueuePathSanitization(queuePath => "sanitized queuePath");
strategy.TopicPathSanitization(topicPath => "sanitized topicPath");
strategy.SubscriptionNameSanitization(
    subscriptionNameSanitizer: subscriptionName =>
    {
        return "sanitized subscriptionName";
    });
strategy.RuleNameSanitization(
    ruleNameSanitizer: ruleName =>
    {
        return "sanitized ruleName";
    });
strategy.Hash(
    hash: pathOrName =>
    {
        return "hashed pathOrName";
    });

In cases where an alternative sanitization is required, a custom sanitization can be applied.

var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var sanitization = transport.Sanitization();
sanitization.UseStrategy<CustomSanitization>();

Custom sanitization strategy definition:

class CustomSanitization :
    ISanitizationStrategy
{
    public string Sanitize(string entityPathOrName, EntityType entityType)
    {
        // apply sanitization on entityPathOrName
        return entityPathOrName;
    }
}

If the implementation of a sanitization strategy requires configuration settings, these settings can be accessed using dependency injection to access an instance of ReadOnlySettings.

public class CustomSanitizationWithSettings :
    ISanitizationStrategy
{
    ReadOnlySettings settings;

    public CustomSanitizationWithSettings(ReadOnlySettings settings)
    {
        this.settings = settings;
    }

    public string Sanitize(string entityPathOrName, EntityType entityType)
    {
        // implementation custom sanitization here
        return entityPathOrName;
    }
}

Backward compatibility with versions 6 and below

To remain backward compatible with endpoints versions 6 and below, endpoints version 7 and above should be configured to perform sanitization based on version 6 and below rules. The following custom sanitization will ensure entities are sanitized in a backwards compatible manner.

class V6Sanitization :
    ISanitizationStrategy
{
    public string Sanitize(string entityPathOrName, EntityType entityType)
    {
        // remove invalid characters
        var regex = new Regex(@"[^a-zA-Z0-9\-\._]");
        entityPathOrName = regex.Replace(entityPathOrName, string.Empty);

        var entityPathOrNameMaxLength = 0;

        switch (entityType)
        {
            case EntityType.Queue:
            case EntityType.Topic:
                entityPathOrNameMaxLength = 260;
                break;
            case EntityType.Subscription:
            case EntityType.Rule:
                entityPathOrNameMaxLength = 50;
                break;
        }

        // hash if still too long
        if (entityPathOrName.Length > entityPathOrNameMaxLength)
        {
            entityPathOrName = MD5DeterministicNameBuilder.Build(entityPathOrName);
        }

        return entityPathOrName;
    }

    static class MD5DeterministicNameBuilder
    {
        public static string Build(string input)
        {
            var inputBytes = Encoding.Default.GetBytes(input);
            // use MD5 hash to get a 16-byte hash of the string
            using (var provider = new MD5CryptoServiceProvider())
            {
                var hashBytes = provider.ComputeHash(inputBytes);
                return new Guid(hashBytes).ToString();
            }
        }
    }

}

Future consideration prior to using sanitization

When implementing custom sanitization, consider factors such as readability and discover-ability. Things to consider:

  • Truncated long entity names could conflict.
  • Hashed entity names could lead to difficult names to use during production troubleshooting or debugging.
  • Sanitized entity names stay in the system and cannot be replaced until no longer used.

Possible ways to avoid sanitization

  • Define endpoint name as short and meaningful.
  • Avoid message definitions in deep-nested namespaces.
  • Keep event names descriptive and short.

Samples


Last modified