AF.Umbraco.Azure.Blob.Media.Storage
2.0.0
dotnet add package AF.Umbraco.Azure.Blob.Media.Storage --version 2.0.0
NuGet\Install-Package AF.Umbraco.Azure.Blob.Media.Storage -Version 2.0.0
<PackageReference Include="AF.Umbraco.Azure.Blob.Media.Storage" Version="2.0.0" />
<PackageVersion Include="AF.Umbraco.Azure.Blob.Media.Storage" Version="2.0.0" />
<PackageReference Include="AF.Umbraco.Azure.Blob.Media.Storage" />
paket add AF.Umbraco.Azure.Blob.Media.Storage --version 2.0.0
#r "nuget: AF.Umbraco.Azure.Blob.Media.Storage, 2.0.0"
#:package AF.Umbraco.Azure.Blob.Media.Storage@2.0.0
#addin nuget:?package=AF.Umbraco.Azure.Blob.Media.Storage&version=2.0.0
#tool nuget:?package=AF.Umbraco.Azure.Blob.Media.Storage&version=2.0.0
AF.Umbraco.Azure.Blob.Media.Storage
Azure Blob media storage provider for Umbraco 15/16/17/18 on .NET 9/10.
This package configures Umbraco to use Azure Blob for:
- Media filesystem
- ImageSharp cache
- Startup fail-fast checks using the provider-configured Azure clients
- Bounded, multi-instance-safe ImageSharp cache-retention cleanup
No Program.cs changes are required.
Compatibility
- Umbraco CMS:
15.x,16.x,17.x,18.x - .NET:
9.0,10.0(Umbraco 18.x requires10.0)
Dependencies
Umbraco.StorageProviders.AzureBlobUmbraco.StorageProviders.AzureBlob.ImageSharp
Compatibility Hosts and Local Smoke Checks
- Local compatibility hosts are included under
src/Umbraco.Cms.15.x,src/Umbraco.Cms.16.x,src/Umbraco.Cms.17.x, andsrc/Umbraco.Cms.18.x. - Each host supports local overrides through
appsettings.Local.json. - VS Code Run and Debug configurations and their build tasks are available for every compatibility host.
- The
All hostsVS Code compound starts every Kestrel host in one action, each with its own debug session. See Development for prerequisites and verification steps. - Automated unit tests target both .NET 9 and .NET 10.
| Host | HTTP | HTTPS |
|---|---|---|
| Umbraco 15 | http://localhost:5015 |
https://localhost:44375 |
| Umbraco 16 | http://localhost:5016 |
https://localhost:44376 |
| Umbraco 17 | http://localhost:5017 |
https://localhost:44377 |
| Umbraco 18 | http://localhost:5018 |
https://localhost:44378 |
Umbraco 18 uses Umbraco:CMS:Imaging:HMACSecretKey. The tracked appsettings.json contains an empty placeholder; set the real value only in the git-ignored appsettings.Local.json.
Installation
dotnet add package AF.Umbraco.Azure.Blob.Media.Storage --version 2.0.0
Upgrading from 1.0.0
2.0.0 is a major release because the public type AzureBlobSmokeTestsMiddleware is no longer
published: diagnostic smoke support moved out of the package and AzureBlobComposer is now its only
public type.
Installations that rely on auto-composition - the documented and expected usage - need no change: the composer, configuration keys, startup modes, and retention behaviour are unchanged. Only a host that referenced the smoke middleware directly in its own code will fail to compile; that host must drop the reference, since the middleware now lives in a repository-only project that is not distributed.
Build and Test
dotnet restore src/AF.Umbraco.Azure.Blob.Media.Storage.sln
dotnet build src/AF.Umbraco.Azure.Blob.Media.Storage.sln -c Release --no-restore --maxcpucount:1 /p:GeneratePackageOnBuild=false
dotnet test src/AF.Umbraco.Azure.Blob.Media.Storage.Tests/AF.Umbraco.Azure.Blob.Media.Storage.Tests.csproj -c Release --no-build --no-restore
dotnet format src/AF.Umbraco.Azure.Blob.Media.Storage.sln --verify-no-changes --no-restore
Set AFUABMS_AZURITE_CONNECTION_STRING and run the Category=Azurite test filter to execute the storage-backed lease-contention and lease-loss regressions. See Testing for the command and safety notes.
Each Umbraco compatibility host must use a dedicated smoke-test database. The smoke script disables unattended upgrades, so a database created by another Umbraco major makes the run fail at startup instead of being migrated in place. Do not reuse a database across Umbraco 15, 16, 17, and 18. See Testing for the recommended names and per-host commands.
Generate a local NuGet package only when release metadata is ready:
dotnet pack src/AF.Umbraco.Azure.Blob.Media.Storage/AF.Umbraco.Azure.Blob.Media.Storage.csproj -c Release --no-build
Required Configuration
Required sections:
Umbraco:Storage:AzureBlob:MediaUmbraco:Storage:AzureBlob:ImageSharp
Required keys in both sections:
ConnectionStringContainerName
The named ImageSharp filesystem additionally requires:
VirtualPath
If a required section/key is missing, startup is blocked.
Optional Configuration
The following key is optional in both the Media and ImageSharp sections:
ContainerRootPath
The following key is optional only in the Media section; it is required by the official JSON schema for the named ImageSharp filesystem:
VirtualPath
The Startup and ImageSharp:CacheRetention sections are also optional. The package applies the documented defaults when these sections or individual keys are omitted.
Startup Validation
Configuration path:
AFUABMS:Startup:Mode
The Startup section and its Mode key are optional. When omitted, Mode defaults to CreateIfMissing.
Supported modes:
CreateIfMissing(default): verifies access and creates either container when absent.ValidateOnly: verifies that both containers already exist and never attempts creation.
Startup validation uses the same named BlobContainerClient factories as the media and ImageSharp providers. Custom credentials, endpoints, client options, and retry policies therefore apply consistently to startup and runtime operations. Azure 401/403 failures remain visible as authorization errors and block startup.
Cache Retention Cleanup
Configuration path:
Umbraco:Storage:AzureBlob:ImageSharp:CacheRetention
The CacheRetention section and every key below are optional. Omitting the entire section keeps cleanup disabled.
| Key | Required | Default | Notes |
|---|---|---|---|
Enabled |
No | false |
Enables normal retention cleanup. |
NumberOfDays |
No | 90 |
Maximum cache age in normal mode. Clamped to 1-36500 days. |
MaxBlobsPerSweep |
No | 10000 |
Clamped to 1-1000000. |
SweepJitterSeconds |
No | 300 |
Clamped to 0-3600. |
TestModeEnable |
No | false |
Enables the test-mode schedule and maximum age. |
TestModeSweepSeconds |
No | 30 |
Minimum effective value: 5. |
TestModeMaxAgeMinutes |
No | 10 |
Minimum effective value: 1. |
Behavior:
- normal mode: deletes cache blobs older than
NumberOfDaysevery 12 hours, plus randomized jitter - test mode: sweep/max-age controlled by
TestModeSweepSeconds/TestModeMaxAgeMinutes
Only one application instance sweeps a cache root at a time. Instances coordinate through the renewable cache/.af-cache-retention.lock blob, and the active sweep stops if lease renewal fails. Each sweep is bounded by MaxBlobsPerSweep and logs scanned, deleted, failed, limit, and duration values.
Storage Layout
Recommended setup: separate containers.
Media.ContainerName = umbraco17-mediaImageSharp.ContainerName = umbraco17-cache
Shared-container setup is supported, but use explicit root isolation:
Media.ContainerRootPath = mediaImageSharp.ContainerRootPath = cache
Without explicit isolation, media and cache paths can overlap.
The ImageSharp ContainerRootPath is the complete cache prefix. When it is omitted or blank, the package uses cache. The retention service uses the same resolved prefix and never scans fallback prefixes outside that root.
Configuration Example
{
"AFUABMS": {
"Startup": {
"Mode": "CreateIfMissing"
}
},
"Umbraco": {
"Storage": {
"AzureBlob": {
"Media": {
"ConnectionString": "DefaultEndpointsProtocol=http;AccountName=azurite-storage;AccountKey=...;BlobEndpoint=http://127.0.0.1:10000/azurite-storage;",
"ContainerName": "umbraco17",
"ContainerRootPath": "media",
"VirtualPath": "~/media"
},
"ImageSharp": {
"ConnectionString": "DefaultEndpointsProtocol=http;AccountName=azurite-storage;AccountKey=...;BlobEndpoint=http://127.0.0.1:10000/azurite-storage;",
"ContainerName": "umbraco17",
"ContainerRootPath": "cache",
"VirtualPath": "~/media",
"CacheRetention": {
"Enabled": false,
"NumberOfDays": 90,
"MaxBlobsPerSweep": 10000,
"SweepJitterSeconds": 300,
"TestModeEnable": false,
"TestModeSweepSeconds": 30,
"TestModeMaxAgeMinutes": 10
}
}
}
}
}
}
Managed Identity
Verification status. Managed Identity support is covered by unit tests and by the startup validation paths, but the opt-in end-to-end verification against a live Azure Storage account with RBAC has not been run for this release. Validate it in your own environment before relying on passwordless authentication in production. The procedure is documented in Testing.
For passwordless Azure deployments, add the Azure.Identity package to the Umbraco host, set each ConnectionString value to the Blob service URI, for example https://myaccount.blob.core.windows.net, and configure the official provider client factory with an Azure Identity credential:
using Azure.Identity;
using Azure.Storage.Blobs;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.StorageProviders.AzureBlob.IO;
builder.Services.PostConfigure<AzureBlobFileSystemOptions>("Media", options =>
options.TryCreateBlobContainerClientUsingUri(uri =>
new BlobContainerClient(uri, new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned))));
builder.Services.PostConfigure<AzureBlobFileSystemOptions>("ImageSharp", options =>
options.TryCreateBlobContainerClientUsingUri(uri =>
new BlobContainerClient(uri, new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned))));
Use DefaultAzureCredential for local passwordless development when appropriate, and prefer an explicit ManagedIdentityCredential in Azure production environments. For a user-assigned identity, use ManagedIdentityId.FromUserAssignedClientId(clientId). Pre-provision containers, grant Storage Blob Data Contributor at the narrowest practical scope, and select ValidateOnly when the application identity must not create containers. The opt-in live verification procedure is documented in Testing. See the Azure SDK authentication guidance.
Smoke Endpoints (Opt-In)
Smoke endpoints are not part of the published package. They live in the repository-only
AF.Umbraco.Azure.Blob.Media.Storage.SmokeTests project, which the compatibility hosts reference to
verify the package through its public API. A consumer installing the NuGet package never receives
them. Running them from a clone requires both the Development environment and the explicit feature
flag:
ASPNETCORE_ENVIRONMENT=Development
AF_SMOKE_TESTS=1
Endpoints:
GET /smoke/health- reports the Umbraco runtime level;503unless it reachedRunGET /smoke/debug-testPOST /smoke/media-uploadPOST /smoke/image-transform
The media check validates upload, overwrite, read-back, and delete. The image check uploads a temporary PNG, generates HMAC-aware width, height, mode=crop, and v query variants through Umbraco's image URL generator, exercises them through the local ImageSharp pipeline, verifies that missing media returns 404, and deletes the original PNG. Generated ImageSharp cache entries remain subject to the configured retention policy. Never enable diagnostic routes in staging or production.
Security
- Keep connection strings, HMAC keys, database credentials, and unattended-install passwords in environment variables, user secrets, or git-ignored
appsettings.Local.jsonfiles. - Grant only the Azure Storage permissions required by the selected startup mode, media/cache operations, and optional retention cleanup and lease coordination.
- Keep
AF_SMOKE_TESTSdisabled outside local or isolated CI environments. - Review dependency advisories before publishing each release.
Documentation
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Umbraco.StorageProviders.AzureBlob (>= 15.0.0 && < 19.0.0)
- Umbraco.StorageProviders.AzureBlob.ImageSharp (>= 15.0.0 && < 19.0.0)
-
net9.0
- Umbraco.StorageProviders.AzureBlob (>= 15.0.0 && < 19.0.0)
- Umbraco.StorageProviders.AzureBlob.ImageSharp (>= 15.0.0 && < 19.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
2.0.0 - Breaking: the public type AzureBlobSmokeTestsMiddleware is no longer published and diagnostic smoke support moved out of the package, leaving AzureBlobComposer as the only public type and shipping runtime code only. Installations relying on auto-composition are unaffected. Also hardens the compatibility hosts against unattended database upgrades and drops the redundant Umbraco.Cms.Web.Common dependency.