WopiHost.AzureStorageProvider
8.0.0
dotnet add package WopiHost.AzureStorageProvider --version 8.0.0
NuGet\Install-Package WopiHost.AzureStorageProvider -Version 8.0.0
<PackageReference Include="WopiHost.AzureStorageProvider" Version="8.0.0" />
<PackageVersion Include="WopiHost.AzureStorageProvider" Version="8.0.0" />
<PackageReference Include="WopiHost.AzureStorageProvider" />
paket add WopiHost.AzureStorageProvider --version 8.0.0
#r "nuget: WopiHost.AzureStorageProvider, 8.0.0"
#:package WopiHost.AzureStorageProvider@8.0.0
#addin nuget:?package=WopiHost.AzureStorageProvider&version=8.0.0
#tool nuget:?package=WopiHost.AzureStorageProvider&version=8.0.0
WopiHost.AzureStorageProvider
IWopiStorageProvider + IWopiWritableStorageProvider backed by Azure Blob Storage. WOPI files map directly to blobs; folders are virtual prefixes with a hidden zero-byte marker so empty folders remain addressable.
Suitable for production multi-instance deployments. Use with WopiHost.AzureLockProvider for distributed locking.
Install
dotnet add package WopiHost.AzureStorageProvider
Configure
Two authentication modes — connection string (Azurite, dev) and TokenCredential (managed identity, service principal):
// appsettings.json — connection string
"Wopi": {
"StorageProvider": {
"ConnectionString": "UseDevelopmentStorage=true",
"ContainerName": "wopi-files"
}
}
// appsettings.json — DefaultAzureCredential
"Wopi": {
"StorageProvider": {
"ServiceUri": "https://my-account.blob.core.windows.net",
"ContainerName": "wopi-files"
}
}
When ServiceUri is used the provider resolves a TokenCredential from DI; if none is registered, DefaultAzureCredential is created automatically. Register your own to override:
builder.Services.AddSingleton<TokenCredential>(new ManagedIdentityCredential(clientId));
The container is created on first use if it doesn't exist.
Register
builder.Services.AddAzureStorageProvider(builder.Configuration);
builder.Services.AddWopi(o =>
{
o.ClientUrl = new Uri("https://your-office-online-server.com");
});
The runnable sample exposes a small Sample:StorageProvider discriminator (FileSystem / Azure) and dispatches to the chosen provider's typed extension — see sample/WopiHost/ServiceCollectionExtensions.cs.
How it maps to Blob Storage
| WOPI concept | Azure Blob equivalent |
|---|---|
| File content | Blob bytes |
IWopiFile.Length, LastWriteTimeUtc |
Blob ContentLength, LastModified |
IWopiFile.Version |
Blob ETag (changes on every byte-level update) |
IWopiFile.Owner |
Blob metadata key wopi_owner (empty string when unset) |
IWopiFile.Checksum (SHA-256) |
Blob metadata key wopi_sha256 (lowercase hex), computed during upload |
| Folder | Virtual blob-name prefix (/ delimiter) + zero-byte marker .wopi.folder for materialising empty folders |
| Identifier | Hex-MD5 of the lowercased blob path (matches WopiHost.FileSystemProvider) |
The provider scans the container at first access to populate the in-memory id-to-path map. Identifiers are stable across process restarts (deterministic from path) but not across renames — a rename re-points the existing id to the new path so the WOPI URL doesn't break mid-edit.
Caveats
- Folder rename / delete-folder are O(N) over the children — plain Blob has no atomic prefix rename. If you do this often, consider switching to ADLS Gen2 (not currently supported, see issue #26) for atomic rename via the DFS endpoint.
- Empty folders are materialised by writing a zero-byte
.wopi.folderblob. Listings filter it out; deleting an empty folder removes the marker and drops the id. - SHA-256 is computed streaming on upload via
HashingBlobWriteStreamand stored as metadata. Pre-existing blobs that were not written through this provider will returnnullfromChecksumuntil they are next written. - Owner is read from blob metadata; the provider doesn't set an owner on its own. Hosts that care about ownership should set
wopi_ownerthemselves (e.g. via a custom write pipeline that enriches metadata afterGetWriteStream).
Local development
The provider works against Azurite. The repo's Aspire AppHost runs Azurite as a container resource when AppHost:UseAzureStorage=true:
AppHost__UseAzureStorage=true dotnet run --project infra/WopiHost.AppHost
The Aspire orchestrator forwards the emulator connection string into WopiHost as ConnectionStrings:BlobStorage, which you can map into Wopi:StorageProvider:ConnectionString via standard configuration substitution.
License
See the repo README.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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
- Azure.Identity (>= 1.21.0)
- Azure.Storage.Blobs (>= 12.28.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection (>= 10.0.8)
- WopiHost.Abstractions (>= 8.0.0)
-
net8.0
- Azure.Identity (>= 1.21.0)
- Azure.Storage.Blobs (>= 12.28.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection (>= 10.0.8)
- WopiHost.Abstractions (>= 8.0.0)
-
net9.0
- Azure.Identity (>= 1.21.0)
- Azure.Storage.Blobs (>= 12.28.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection (>= 10.0.8)
- WopiHost.Abstractions (>= 8.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.