StorageProviders.FileSystem 1.0.2

dotnet add package StorageProviders.FileSystem --version 1.0.2
                    
NuGet\Install-Package StorageProviders.FileSystem -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="StorageProviders.FileSystem" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StorageProviders.FileSystem" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="StorageProviders.FileSystem" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add StorageProviders.FileSystem --version 1.0.2
                    
#r "nuget: StorageProviders.FileSystem, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package StorageProviders.FileSystem@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=StorageProviders.FileSystem&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=StorageProviders.FileSystem&version=1.0.2
                    
Install as a Cake Tool

Storage Providers

Lint Code Base CodeQL License: MIT

A collection of Storage Providers for various destinations.

How the library works

The package exposes the IStorageProvider abstraction, which offers a single asynchronous API for common file storage operations:

  • save content from byte[] or Stream
  • read content as Stream or byte[]
  • verify whether a file exists
  • enumerate files by prefix and extension
  • delete files
  • read file properties and metadata
  • update metadata
  • build the full file URI
  • generate a temporary read-only URI

The abstraction is designed so application code depends only on IStorageProvider, while the concrete provider can be registered through dependency injection.

IStorageProvider overview

The interface contains convenience overloads and stream-based methods:

  • SaveAsync supports uploads from both byte[] and Stream
  • ReadAsStreamAsync returns the file content as a stream
  • ReadAsByteArrayAsync is a convenience wrapper built on top of ReadAsStreamAsync
  • GetReadAccessUriAsync can optionally set a download file name when the provider supports it
  • EnumerateAsync returns an IAsyncEnumerable<string> so files can be streamed progressively
  • SetMetadataAsync updates the metadata associated with a file

Because the API is fully asynchronous, it works well in ASP.NET Core, background services, and other I/O-bound workloads.

File System Storage

NuGet Nuget

The StorageProviders.FileSystem implementation stores files and their metadata under a configured local directory. It targets .NET 8, .NET 9, and .NET 10.

Installation

The library is available on NuGet. Search for StorageProviders.FileSystem in the Package Manager GUI or run the following command in the .NET CLI:

dotnet add package StorageProviders.FileSystem

Registering File System Storage

The provider can be registered using settings known at startup:

builder.Services.AddFileSystemStorage(options =>
{
    options.RootDirectory = Path.Combine(builder.Environment.ContentRootPath, "attachments");
});

This registration adds:

  • FileSystemStorageSettings as a singleton
  • IStorageProvider mapped to the file-system provider as a singleton

Settings can also be resolved from the current service provider:

builder.Services.AddFileSystemStorage((serviceProvider, options) =>
{
    var environment = serviceProvider.GetRequiredService<IHostEnvironment>();
    var configuration = serviceProvider.GetRequiredService<IConfiguration>();
    var configuredPath = configuration.GetValue<string>("AppSettings:RootDirectory")!;

    options.RootDirectory = Path.GetFullPath(configuredPath, environment.ContentRootPath);
});

This overload registers both FileSystemStorageSettings and IStorageProvider as scoped services.

File System provider behavior

RootDirectory defines the storage root and security boundary. It can be absolute or relative to the application's working directory. The provider creates it when it does not exist.

All paths passed to IStorageProvider must be logical paths relative to that root:

documents/report.pdf
images/logo.png

The provider:

  • normalizes Windows and Unix directory separators
  • creates missing directories when saving files
  • rejects absolute paths and paths that resolve outside RootDirectory
  • rejects symbolic links and other reparse points in a target path
  • reserves the .storageproviders directory for internal data
  • prevents overwriting an existing file unless overwrite is true
  • filters enumeration by an optional path prefix and file extensions

Metadata is persisted as JSON sidecar data under <RootDirectory>/.storageproviders/metadata. Passing null or an empty dictionary to SetMetadataAsync removes the metadata associated with the file. Metadata files are also removed when their corresponding stored files are deleted.

GetFullPathAsync returns an absolute local file URI. Local file systems do not support delegated access, so GetReadAccessUriAsync validates the logical path and returns null.

File System usage

After registration, inject IStorageProvider and use the shared storage API:

var metadata = new Dictionary<string, string?>
{
    ["category"] = "invoice",
    ["customerId"] = "42"
};

await using var stream = File.OpenRead("report.pdf");
await storageProvider.SaveAsync("documents/report.pdf", stream, metadata, overwrite: false);

Files can then be read, inspected, enumerated, updated, and deleted:

await using var content = await storageProvider.ReadAsStreamAsync("documents/report.pdf");
var fileInfo = await storageProvider.GetPropertiesAsync("documents/report.pdf");
var fileUri = await storageProvider.GetFullPathAsync("documents/report.pdf");

await foreach (var path in storageProvider.EnumerateAsync("documents", [".pdf"]))
{
    Console.WriteLine(path);
}

await storageProvider.SetMetadataAsync("documents/report.pdf", new Dictionary<string, string?>
{
    ["category"] = "archived"
});

await storageProvider.DeleteAsync("documents/report.pdf");

The samples/FileSystemSample project demonstrates upload, download, enumeration, existence checks, metadata management, file information, deletion, and full-path resolution through ASP.NET Core Minimal APIs.

Azure Storage

NuGet Nuget

Installation

The library is available on NuGet. Search for StorageProviders.AzureStorage in the Package Manager GUI or run the following command in the .NET CLI:

dotnet add package StorageProviders.AzureStorage

Registering Azure Storage

The Azure implementation is provided by StorageProviders.AzureStorage and can be registered in two ways.

Static configuration

Use this overload when the Azure settings are known at startup:

builder.Services.AddAzureStorage(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString("AzureStorageConnection")!;
    options.ContainerName = builder.Configuration.GetValue<string>("AppSettings:ContainerName");
});

This registration adds:

  • AzureStorageSettings as a singleton
  • IStorageProvider mapped to AzureStorageProvider as a singleton
Configuration resolved from the service provider

Use this overload when the storage settings depend on other registered services:

builder.Services.AddAzureStorage((serviceProvider, options) =>
{
    options.ConnectionString = serviceProvider
        .GetRequiredService<IConfiguration>()
        .GetConnectionString("AzureStorageConnection")!;

    options.ContainerName = serviceProvider
        .GetRequiredService<IConfiguration>()
        .GetValue<string>("AppSettings:ContainerName");
});

This registration adds:

  • AzureStorageSettings as scoped
  • IStorageProvider mapped to AzureStorageProvider as scoped

Azure provider behavior

AzureStorageProvider uses Azure Blob Storage and requires:

  • ConnectionString: the Azure Storage connection string
  • ContainerName: optional default container name

If ContainerName is configured, paths are treated as blob paths inside that container:

documents/report.pdf
images/logo.png

If ContainerName is not configured, the first segment of the path is interpreted as the container name:

documents/report.pdf   -> container: documents, blob: report.pdf
images/logo.png        -> container: images, blob: logo.png

Backslashes are normalized to forward slashes, so Windows-style paths are also accepted.

Main operations

Upload a file
using var stream = file.OpenReadStream();
await storageProvider.SaveAsync(file.FileName, stream, overwrite: false);

You can also upload metadata:

var metadata = new Dictionary<string, string>
{
    ["category"] = "invoice",
    ["customerId"] = "42"
};

using var stream = file.OpenReadStream();
await storageProvider.SaveAsync(file.FileName, stream, metadata, overwrite: true);

When overwrite is false, the Azure provider throws an IOException if the blob already exists.

Read a file
await using var stream = await storageProvider.ReadAsStreamAsync("documents/report.pdf");

Or read it as a byte array:

var content = await storageProvider.ReadAsByteArrayAsync("documents/report.pdf");
Check whether a file exists
var exists = await storageProvider.ExistsAsync("documents/report.pdf");
Enumerate files
await foreach (var path in storageProvider.EnumerateAsync("documents", [".pdf", ".docx"]))
{
    Console.WriteLine(path);
}

This method supports:

  • an optional prefix
  • filtering by extension
  • asynchronous streaming of results
Get file information
var fileInfo = await storageProvider.GetPropertiesAsync("documents/report.pdf");

The returned StorageFileInfo contains:

  • file name
  • inferred content type
  • size
  • creation date
  • last modification date
  • metadata
Update metadata
await storageProvider.SetMetadataAsync("documents/report.pdf", new Dictionary<string, string>
{
    ["category"] = "archived"
});

Passing null clears the existing metadata for the file.

Get the full blob URI
var uri = await storageProvider.GetFullPathAsync("documents/report.pdf");
Generate a temporary read URI
var uri = await storageProvider.GetReadAccessUriAsync(
    "documents/report.pdf",
    expirationDate: DateTime.UtcNow.AddMinutes(30),
    fileName: "Report.pdf");

For Azure Blob Storage this produces a SAS URI with read permissions. If a file name is provided, the provider also sets the Content-Disposition header so the browser can suggest a download name.

Delete a file
await storageProvider.DeleteAsync("documents/report.pdf");

Example with ASP.NET Core Minimal APIs

The sample project in samples/AzureStorageSample shows how to inject IStorageProvider in endpoints and use it for:

  • upload
  • upload with metadata
  • file existence checks
  • file listing
  • file download
  • metadata updates
  • file deletion
  • generating full and temporary read URIs

Example registration:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAzureStorage(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString("AzureStorageConnection")!;
    options.ContainerName = builder.Configuration.GetValue<string>("AppSettings:ContainerName");
});

Example endpoint:

app.MapGet("/api/attachments/full-path", async (IStorageProvider storageProvider, string fileName) =>
{
    var fullPath = await storageProvider.GetFullPathAsync(fileName);
    return Results.Ok(fullPath);
});

Notes

  • The Azure provider automatically creates the target container when saving a file, if it does not exist.
  • Uploaded blobs use a content type inferred from the file name.
  • ReadAsStreamAsync and SetMetadataAsync throw when the target blob does not exist.
  • The API is storage-oriented and does not depend on ASP.NET Core, so it can also be used in console apps, workers, and class libraries.

Contribute

The project is constantly evolving. Contributions are welcome. Feel free to file issues and pull requests in the repository, and we'll address them as we can.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
1.0.2 0 9/9/2026