Azure Storage Queues¶
Cheap, durable and simple. Commands only — no pub/sub — but it is the transport with the best support
for long-running work, and the package supplies the attachment provider, saga store and singleton
lock manager that the rest of KnightBus builds on. Those three are host-wide features and work with
any transport; they need UseBlobStorage(...) for the storage account, not
UseTransport<StorageTransport>().
Registration¶
The package name says "blob storage" because the same configuration covers queues, the attachment blobs and the saga blobs.
Messages¶
Only one marker interface — this transport has no events:
public class ResizeImage : IStorageQueueCommand
{
public string ImageId { get; set; }
}
public class ResizeImageMapping : IMessageMapping<ResizeImage>
{
public string QueueName => "resize-image";
}
Client¶
var bus = scope.ServiceProvider.GetRequiredService<IStorageBus>();
await bus.SendAsync(new ResizeImage { ImageId = "1" });
await bus.ScheduleAsync(new ResizeImage { ImageId = "2" }, TimeSpan.FromMinutes(30));
ScheduleAsync sets the message's visibility delay. There is no batch overload and no way to cancel
a deferred message — for that, use Service Bus.
What gets created¶
One logical KnightBus queue is three storage objects:
| Object | Purpose |
|---|---|
Queue {name} |
The queue itself, holding message properties. |
Queue {name}-dl |
Dead letters. |
Blob container {name} |
Message payloads and attachments. |
The payload lives in a blob and the queue message only references it, which is how this transport sidesteps the 64 KB queue message limit entirely. It also means a queue message whose payload blob is missing is deleted on read as unrecoverable.
Dead letter queues are hidden from the management API listing, so they do not appear as queues in their own right.
Long-running work¶
This is the only transport that can extend a message lock while your handler runs, which makes it the
right choice for work measured in minutes or hours. Implement IExtendMessageLockTimeout on your
settings and register the middleware:
public class LongRunningSettings : IProcessingSettings, IExtendMessageLockTimeout
{
public int MaxConcurrentCalls => 1;
public int PrefetchCount => 0;
public TimeSpan MessageLockTimeout => TimeSpan.FromHours(4); // total budget
public int DeadLetterDeliveryLimit => 2;
public TimeSpan ExtensionDuration => TimeSpan.FromMinutes(5); // lock actually held
public TimeSpan ExtensionInterval => TimeSpan.FromMinutes(1); // renewal cadence
}
The benefit over one enormous MessageLockTimeout is recovery time: if the host crashes, the message
becomes visible again after ExtensionDuration rather than after the full four hours. See
extending the lock.
The middleware itself is a KnightBus.Core type rather than a Storage Queues one: it is registered
per host and runs on every listener. What it cannot do elsewhere is renew — that needs the message
state handler to implement IMessageLockHandler<T>, which today is this transport alone. On other
transports it passes messages through untouched, with one exception: on PostgreSQL, implementing
IExtendMessageLockTimeout shortens the fetch lock with nothing able to renew it, which causes
duplicate processing.
Attachments¶
The Blob Storage attachment provider is the one most applications use, whatever transport carries the messages:
Optional Brotli compression:
services.UseBlobStorageAttachments(options =>
{
options.EnableCompression = true;
options.CompressionLevel = CompressionLevel.Optimal;
});
Compression is off by default and safe to enable on an existing store — whether a blob is compressed is recorded in its name, so old attachments keep working. See attachments.
Saga store¶
State is stored in the knightbus-sagas container with the blob ETag providing optimistic
concurrency, so concurrent writes to the same saga are detected — of the shipped stores only this one
and Redis do that, whatever transport carries the saga's messages. Expiry is evaluated on read, so
expired blobs are reported as not found but are not deleted for you. See sagas.
Singleton lock manager¶
Blob leases are KnightBus' only shipped distributed lock implementation, so this package is what enables singleton processing and cron scheduling — even for applications whose messages travel over another transport entirely.
Locks are blobs under knight-data/locks. To change that, implement IBlobLockScheme:
public class MyLockScheme : IBlobLockScheme
{
public string ContainerName => "my-container";
public string Directory => "my-locks";
}
services.UseBlobStorageLockManager(new MyLockScheme());
Management¶
PeekScheduled is not supported. Note that MoveDeadLetters returns the number of messages you
asked it to move rather than the number it actually moved, and that it removes messages from the
dead letter queue even when a requeue predicate rejects them.
Serialization¶
Defaults to NewtonsoftSerializer. Queue messages are Base64-encoded by default for compatibility
with older storage clients; changing MessageEncoding requires passing a constructed
StorageBusConfiguration rather than using the configuration callback, since the property is
read-only on the interface.
Example¶
KnightBus.Samples.Azure.Storage
shows attachments, the blob lock manager, the blob saga store and distributed tracing together. Run
it against Azurite with
UseDevelopmentStorage=true.