DKNet

Configure the Azure blob client

Part of DKNet.Svc.BlobStorage.AzureStorage. Pick how BlobServiceClient gets built, then work with the save, SAS, and delete behavior specific to Azure Blob Storage.

using Azure.Identity;
using Azure.Storage.Blobs;
using DKNet.Svc.BlobStorage.AzureStorage;

builder.Services.AddAzureStorageAdapter(builder.Configuration);
builder.Services.Configure<AzureStorageOptions>(options =>
{
    options.ContainerName = "documents";
    options.BlobServiceClientFactory = _ => Task.FromResult(
        new BlobServiceClient(
            new Uri("https://myaccount.blob.core.windows.net"),
            new DefaultAzureCredential()));
});

BlobServiceClientFactory is the hook for Azure AD / managed-identity auth — there is no separate “use Azure AD” flag. When it is set it takes priority over ConnectionString; when neither is set, the first blob operation throws ArgumentException. The factory receives the resolved AzureStorageOptions, so it can read your own configuration values off it, and it runs once per service instance — the resulting container client is cached.

SaveAsync validates the stream and calls UploadAsync with Overwrite passed straight through, surfacing RequestFailedException (409) on an unflagged conflict; GetPublicAccessUrl checks CanGenerateSasUri before building a read-only SAS, throwing NotSupportedException for a client that cannot sign one.

var blob = new BlobDetails.BlobData("reports/2026/q1.pdf", BinaryData.FromString("...")) { Overwrite = true };
var location = await blobService.SaveAsync(blob); // "reports/2026/q1.pdf"

var url = await blobService.GetPublicAccessUrl(
    new BlobRequest("reports/monthly.pdf"), TimeSpan.FromMinutes(15));

Unlike S3 and Local, this provider raises no exception of its own on a save conflict — SaveAsync passes Overwrite straight to the SDK’s UploadAsync, so a duplicate upload with Overwrite = false surfaces the SDK’s RequestFailedException (409 BlobAlreadyExists) instead. GetPublicAccessUrl builds a read-only SAS valid for expiresFromNow, defaulting to one day, and throws NotSupportedException when CanGenerateSasUri is false (a client built from a token credential rather than an account key). The diagram does not show folder delete: it walks the prefix breadth-first with a queue, deleting nested blobs as it finds them, then removes the folder markers from the deepest level up; a delete targeting the container root throws ArgumentException instead of deleting everything.

⚙️ Configuration reference

AzureStorageOptions extends BlobServiceOptions, documented on the Abstractions feature page:

Option Type Default Effect
ContainerName string (required) Target container; created on first use when missing.
ConnectionString string? null Storage account connection string. Used only when BlobServiceClientFactory is unset.
BlobServiceClientFactory Func<AzureStorageOptions, Task<BlobServiceClient>>? null Builds the BlobServiceClient yourself — the hook for managed identity or any custom client. Takes priority over ConnectionString; cannot be set from configuration binding.
AzureStorageOptions.Name (static) string "BlobService:AzureStorage" Configuration section key the IConfiguration overload binds from.

🧱 Where it fits

AzureStorageBlobService derives from the BlobService base class in Abstractions; application code depends on IBlobService, and only the composition root references this package. Pair it with DKNet.Svc.BlobStorage.Local for local runs and CI where no storage account or emulator should be required.

⚠️ Gotchas & limits