One writeable IFileProvider for .NET, over the local disk and six cloud stores: read, write, list, move and sign
links with the same code everywhere. Native AOT compatible, except Google Cloud Storage and Box.
| Package | Store | Native AOT | Downloads |
|---|---|---|---|
| Physical | A folder on the local disk | ✓ | |
| AzureBlob | Azure Blob Storage | ✓ | |
| AmazonS3 | Amazon S3, or an S3-compatible server | ✓ | |
| Minio | MinIO | ✓ | |
| GoogleCloudStorage | Google Cloud Storage | ||
| Box | Box | ||
| SharePoint | SharePoint, through Microsoft Graph | ✓ | |
| AspNetCore | Signed links for every store, served by your app | ✓ | |
| Common | The interface, the base class and configuration | ✓ |
Each package is WriteableFileProviders.<name> on NuGet, all at the same version, and brings
WriteableFileProviders.Common.
Another store? Derive from WriteableFileProvider<TOptions> in Common and implement five members. The base class
handles paths, the root, conditions, copies, moves and errors (Writing a provider).
dotnet add package WriteableFileProviders.AzureBlob
One package per store. Add WriteableFileProviders.AspNetCore for signed links on every store. .NET 10 only; on
.NET 8, stay on 2026.10.10.x: it is unlisted on nuget.org, so reference it by version.
public interface IWriteableFileProvider : IFileProvider, IDisposable, IAsyncDisposable
{
// Look and list
Task<IWriteableFileInfo> GetFileInfoAsync(ProviderPath path);
Task<IDirectoryContents> GetDirectoryContentsAsync(ProviderPath path);
IAsyncEnumerable<IWriteableFileInfo> EnumerateAsync(ProviderPath path, bool recursive = false);
// Read: the whole file, a byte range, or a stream you can seek
Task<Stream> ReadFileAsync(ProviderPath path);
Task<Stream> ReadRangeAsync(ProviderPath path, long offset, long? length, ReadOptions? options = null);
Task<Stream> OpenReadSeekableAsync(ProviderPath path, SeekableReadOptions? options = null);
// Write (overwrite or append; only if unchanged, IfMatch, or absent, IfNotExists), delete, move, copy
Task UpdateFileAsync(ProviderPath path, Stream contents, UpdateFileOptions? options = null);
Task<bool> DeleteAsync(ProviderPath path);
Task MoveAsync(ProviderPath source, ProviderPath destination);
Task CopyAsync(ProviderPath source, ProviderPath destination);
// Edit through a local copy, uploaded when it is disposed; a signed link to download or upload the file
Task<LocalFileStream> CreateLocalFileStreamAsync(ProviderPath path, bool readOnly);
Task<SignedUrl> GetSignedUrlAsync(ProviderPath path, SignedUrlOptions options);
}Every member also takes a CancellationToken. A path is a ProviderPath relative to the provider's root; a string
converts to one. Writes are all or nothing, no path reaches outside the root, and failures are the same few
exceptions on every store. Register a provider:
builder.Services.AddWriteableFileProvider(builder.Configuration.GetSection("Files"));{ "Files": { "Type": "WriteableAzureBlobFileProvider", "AccountName": "contoso", "ContainerName": "files",
"UseDefaultAzureCredential": true } }Then inject IWriteableFileProvider wherever you need files. Another store is another configuration, not new code.
Every package works in trimmed and native AOT apps except GoogleCloudStorage and Box, whose SDKs read and write JSON through reflection. CI publishes an app over the others with native AOT and runs it: on the local disk, Azurite, MinIO and an in-memory Microsoft Graph.
Building a provider from configuration by its Type uses reflection too, so AddWriteableFileProvider(section) and
WriteableFileProviderSerializer warn in an AOT app. Bind the options and build the provider in code instead:
var options = builder.Configuration.GetSection("Files").Get<AzureBlobFileProviderOptions>()!;
builder.Services.AddWriteableFileProvider(_ => new WriteableAzureBlobFileProvider(options));In an ASP.NET Core AOT app, SharePoint adds one warning, IL2104 for Microsoft.IO.RecyclableMemoryStream, which
Microsoft Graph uses. It is harmless: that package's event logging passes only primitives.
GetSignedUrlAsync returns the store's own link where it has one: an Azure SAS, an S3 or MinIO presigned URL, a Google
Cloud Storage V4 URL. Elsewhere, the gateway in WriteableFileProviders.AspNetCore serves it from your app, and
IWriteableFileUrlService picks the right one (Signed URLs).
- Getting started: local files, download links, then a cloud store.
- Providers compared: what each store can do.
- Paths and listings: the root, folders, case, listings.
- Reading and writing: ranges, appends, conditions, copies, local streams.
- Signed URLs: native links, the gateway, uploads, security.
- Dependency injection and configuration: one provider or several.
- Errors: what each member throws.
- Writing a provider for another store.
- Moving from 2026 and the changelog.
All packages ship together at one SemVer version: use the same version of each. 2027.x adds features and
fixes; 2028.0.0 is the next breaking release. The major is a sequence number, not a date
(policy).
Issues and pull requests are welcome: see CONTRIBUTING.md, and AGENTS.md for the rules.
Report vulnerabilities privately, never in an issue: see SECURITY.md.
MIT. Copyright (c) 2018-2026 the WriteableFileProviders contributors.