Binary Assets in Blob Storage
Table Storage held the metadata; the actual bytes — camera images, panoramas, videos, map tiles, location boundary files — live in Blob Storage. Most of this layer is exactly as dull as you'd hope: upload, download, delete. The one thing worth stopping on is a small dependency-injection trick that looks like three separate stores but is one.
The one idea to hold on to
ISingleImageStorageRepository, IPanoImageStorageRepository, and
IPreStitchImageStorageRepository are three marker interfaces over a single
ImageStorageRepository — all pointing at the same container. The class implements all
three, and every registration hands back the identical thing:
public class ImageStorageRepository : AzureStorageRepository<byte[]>,
ISingleImageStorageRepository, IPanoImageStorageRepository, IPreStitchImageStorageRepository
// all three DI registrations:
new ImageStorageRepository(blobServiceClient, blobStorageOptions.ContainerName);
So why three names? So a service that only handles panoramas injects
IPanoImageStorageRepository and reads as if panoramas were their own store — the interface
documents intent at the call site. But single, pano, and pre-stitch images share one physical
container; they're told apart by blob name, not by container.
Because all three interfaces resolve to the same container, there's no storage-level boundary between single, pano, and pre-stitch images. A naming collision across the "three stores" is a real collision on one container. If you need them physically separated, that's a config and registration change — the interfaces alone won't do it.
The base class
Every blob repository inherits AzureStorageRepository<T>, which is the whole upload/download
surface: DoesBlobExists, GetFileBlobAsync (bytes plus metadata as a BlobEntity),
UploadFileBlobAsync (with or without a metadata dictionary), UploadZipBlobAsync (builds a
ZIP at a chosen CompressionLevel), and DeleteFromBlob.
ImageStorageRepository adds the image-shaped conveniences on top — UploadImageAsync (the
metadata overload carries PTZ position and timestamps), UploadImageAsZipAsync (used to
archive pre-stitch frames), and DownloadImagesAsync (parallel bulk download).
Wrapping up
Blob Storage is the byte store, and it's mostly plain CRUD over per-purpose containers. The
one thing to carry forward: the three image interfaces are ergonomic aliases over a single
ImageStorageRepository and a single shared container — convenient for injection, but not a
real boundary between image types.
Next, the asynchronous side of storage — one service class wearing nine queue identities: One Service, Nine Queues.