WopiHost.FileSystemProvider
9.3.0
dotnet add package WopiHost.FileSystemProvider --version 9.3.0
NuGet\Install-Package WopiHost.FileSystemProvider -Version 9.3.0
<PackageReference Include="WopiHost.FileSystemProvider" Version="9.3.0" />
<PackageVersion Include="WopiHost.FileSystemProvider" Version="9.3.0" />
<PackageReference Include="WopiHost.FileSystemProvider" />
paket add WopiHost.FileSystemProvider --version 9.3.0
#r "nuget: WopiHost.FileSystemProvider, 9.3.0"
#:package WopiHost.FileSystemProvider@9.3.0
#addin nuget:?package=WopiHost.FileSystemProvider&version=9.3.0
#tool nuget:?package=WopiHost.FileSystemProvider&version=9.3.0
WopiHost.FileSystemProvider
Reference implementation of IWopiStorageProvider and IWopiWritableStorageProvider backed by a local directory tree. Identifiers are deterministic SHA-256 hashes of the canonical path, cached in-memory and mapped back to absolute paths.
Suitable for development, validator runs, and single-instance deployments. Storage only — token issuance and ACLs live in WopiHost.Core.
Install
dotnet add package WopiHost.FileSystemProvider
Configure
// appsettings.json
"Wopi": {
"StorageProvider": {
"RootPath": "./wopi-docs", // absolute or relative to ContentRootPath
"WatchForExternalChanges": true // default; see "How identifiers work"
}
}
RootPath is bound from the Wopi:StorageProvider section (WopiFileSystemProviderOptions.SectionName). Set WatchForExternalChanges to false on storage where change notifications are unreliable — network shares (SMB/NFS) and some container bind mounts — to skip the FileSystemWatcher and rely on the fallback resolution described below.
Register
builder.Services.AddFileSystemStorageProvider(builder.Configuration);
builder.Services.AddWopi(o =>
{
o.ClientUrl = new Uri("https://your-office-online-server.com");
});
AddFileSystemStorageProvider registers WopiFileSystemProvider as both IWopiStorageProvider and IWopiWritableStorageProvider (one shared singleton instance) plus the singleton InMemoryFileIds map the provider uses for path↔id round-tripping. The runnable sample drives all of this through a sample-local Sample:StorageProvider discriminator — see sample/WopiHost/Program.cs.
How identifiers work
Identifiers are deterministic 64-character SHA-256 hex hashes of the canonical (case-folded) path, computed by InMemoryFileIds via WopiResourceId.FromCanonicalPath. Consumers treat them as opaque. (The WOPI validator's test.wopitest file is the one exception — it's given the fixed id WOPITEST by every registration flow.) Lookup is O(1) in both directions; the in-memory map is rebuilt at startup and kept converged with the tree after that by FileIdMapSynchronizer, in three layers:
FileSystemWatcher(primary, on by default). Create/delete/rename events update the map as they happen. The rename event carries both paths, so the file's existing id is repointed to the new path — including every child when a directory is renamed — rather than a new id being derived for what looks like a new file.- Lazy registration. Enumeration and by-name lookups register on-disk entries the map hasn't seen; ids derive deterministically from paths, so every process derives the same id.
- Reconciliation sweep (recovery). An id absent from the map triggers a debounced full-tree sweep that registers the derived id of every entry — this covers events the watcher lost (buffer overflow, races) or never got (
WatchForExternalChanges=false). The sweep enumerates and hashes outside the map's write lock, so lookups and mutations aren't blocked while it runs, and malformed ids (anything that isn't a 64-char lower-hex digest) are rejected without touching the disk. Tree walks skip reparse points (junctions/symlinks): they can introduce cycles, and entries outside the root must not become addressable through a link inside it.
A consequence: because an id derives purely from the path, a file's id is stable across process restarts and across hosts pointing at the same tree, so long-lived WOPI URLs keep working without persisting a separate mapping. Renaming or moving a file changes its path-derived id, but the provider re-points the retained id on rename — and the watcher replays that same repoint in every other process over the tree — so an in-progress edit's URL keeps working everywhere and all processes keep addressing the file through one id, one lock domain. Only when the rename event is lost (or the watcher is off) does another process fall back to deriving a fresh id from the new path; both ids then resolve (the retained id stays canonical, the derived one registers as an alias on first use), at the cost that the two ids lock independently until the processes restart — acceptable for the dev/single-instance deployments this provider targets.
Customize
To layer behavior — versioning, audit, soft-delete — wrap the provider via composition. The concrete class' interface methods are non-virtual, so a decorator is the cleanest seam:
public class AuditingStorageProvider(IWopiWritableStorageProvider inner, IAuditLog audit)
: IWopiWritableStorageProvider
{
public async Task<bool> DeleteWopiFile(string id, CancellationToken ct = default)
{
var deleted = await inner.DeleteWopiFile(id, ct);
if (deleted) audit.Log($"deleted file {id}");
return deleted;
}
public async Task<bool> DeleteWopiContainer(string id, CancellationToken ct = default)
{
var deleted = await inner.DeleteWopiContainer(id, ct);
if (deleted) audit.Log($"deleted container {id}");
return deleted;
}
// Forward the rest of IWopiWritableStorageProvider to `inner`.
public int FileNameMaxLength => inner.FileNameMaxLength;
// ...
}
License
See the repo README.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Microsoft.AspNetCore.Authorization (>= 10.0.11)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.11)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.11)
- Microsoft.IdentityModel.Tokens (>= 8.22.0)
- System.IO.FileSystem.AccessControl (>= 5.0.0)
- WopiHost.Abstractions (>= 9.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 9.3.0 | 125 | 9/4/2026 |
| 9.2.0 | 990 | 8/14/2026 |
| 9.1.0 | 1,043 | 7/3/2026 |
| 9.0.0 | 783 | 6/5/2026 |
| 8.0.0 | 345 | 5/14/2026 |
| 7.0.0 | 270 | 5/6/2026 |
| 6.0.0 | 161 | 5/3/2026 |
| 5.1.0 | 110 | 4/30/2026 |
| 5.0.5 | 133 | 3/1/2026 |
| 5.0.3 | 115 | 3/1/2026 |
| 5.0.2 | 128 | 3/1/2026 |
| 4.0.1 | 401 | 2/24/2024 |
| 3.0.0 | 408 | 11/13/2021 |
| 3.0.0-beta1 | 447 | 3/21/2021 |