Katalyst.Library.FileStorage 1.0.0

dotnet add package Katalyst.Library.FileStorage --version 1.0.0
                    
NuGet\Install-Package Katalyst.Library.FileStorage -Version 1.0.0
                    
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="Katalyst.Library.FileStorage" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Katalyst.Library.FileStorage" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Katalyst.Library.FileStorage" />
                    
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 Katalyst.Library.FileStorage --version 1.0.0
                    
#r "nuget: Katalyst.Library.FileStorage, 1.0.0"
                    
#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 Katalyst.Library.FileStorage@1.0.0
                    
#: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=Katalyst.Library.FileStorage&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Katalyst.Library.FileStorage&version=1.0.0
                    
Install as a Cake Tool

Katalyst.Library.FileStorage

Overview

Library.FileStorage is a small abstraction over blob/object storage that lets you switch between a local filesystem, Amazon S3, or Azure Blob Storage using configuration only.

Capabilities

  • Unified API — Work against IFileStorage instead of wiring S3, Azure, or disk paths directly.
  • Configuration-driven provider — Choose Local, S3, or Azure via Storage:Provider (comparison is case-insensitive).
  • Optional key prefix — Each provider supports a Prefix so logical keys share a virtual folder under the root, bucket, or container.
  • Stream-based I/O — Upload and download use Stream; callers dispose streams returned from OpenReadAsync.

Configuration shape

{
  "Storage": {
    "Provider": "Local",
    "Local": {
      "RootPath": "C:\\app-data\\blobs",
      "Prefix": ""
    },
    "AmazonS3": {
      "BucketName": "my-app-bucket",
      "Region": "us-east-1",
      "Prefix": "uploads",
      "Credentials": {
        "Enabled": false,
        "AccessKeyId": "",
        "SecretAccessKey": "",
        "CredentialsEncrypted": false
      }
    },
    "AzureBlob": {
      "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net",
      "ContainerName": "my-container",
      "Prefix": "uploads"
    }
  }
}

Everything sits under the Storage section (see StorageSettings). Provider selects the implementation: Local, S3, or Azure. The value must be set; if it is missing or empty, registration throws InvalidOperationException. Only the subsection for the active provider must be valid—you can keep all three subsections in one file and switch Provider between environments; unused subsections are not read at runtime.

  • Local — Uses Storage:Local (RootPath, optional Prefix). Resolves relative RootPath under the consuming app’s project directory when an SDK project file can be found by walking up from AppContext.BaseDirectory; otherwise under AppContext.BaseDirectory (typical for published apps).
  • S3 — Uses Storage:AmazonS3 (BucketName, optional Region, Prefix, and optional Credentials). When Credentials:Enabled is false (default), IAmazonS3 is created with the AWS SDK default credential chain. When Credentials:Enabled is true, the client uses AccessKeyId and SecretAccessKey after optional decryption (see Amazon S3 provider section). AddAppFileStorage registers IStorageSecretResolver (default StorageSecretResolver) unless the host already registered one.
  • Azure — Uses Storage:AzureBlob (ConnectionString, ContainerName, optional Prefix).

Calling AddAppFileStorage registers IFileStorage as a singleton for the selected provider and binds that provider’s options with services.Configure<TOptions>(configuration.GetSection(...)). It also registers IStorageSecretResolver once (see the S3 section) so encrypted configuration values can be resolved when needed.

IFileStorage methods

Method Description
UploadAsync Writes or overwrites the object at key from content. Optional contentType is passed through when the provider supports it (S3, Azure).
OpenReadAsync Returns a readable stream; the caller must dispose it. Local throws FileNotFoundException if the key is missing; cloud providers surface their client errors similarly.
DeleteAsync Deletes the object if it exists; no error when the key is already absent.
ExistsAsync Returns whether an object exists at key.

Logical keys use / as a path separator where relevant; the local provider normalizes to the platform directory separator and rejects . / .. segments and paths that escape the configured root.


Local provider

When it runs

Selected when Storage:Provider is Local. Registers LocalFileStorage as IFileStorage.

How it works

  • Objects are files under RootPath, with optional Prefix applied to every key (see PrefixedObjectKey).
  • RootPath — If absolute, it is normalized and used as the root. If relative, it is combined with the resolved anchor directory (project folder containing *.csproj / *.vbproj / *.fsproj when found, else AppContext.BaseDirectory), so a value like "storage" often lands next to your project during dotnet run.
  • Prefix — Optional subdirectory prefix for all keys (like a folder).
  • Uploads create parent directories as needed. Keys that would resolve outside the root throw ArgumentException.

Configuration example

{
  "Storage": {
    "Provider": "Local",
    "Local": {
      "RootPath": "C:\\app-data\\blobs",
      "Prefix": ""
    }
  }
}

Paths that must not use the project-directory heuristic

For anchors such as ASP.NET content root instead of the project folder, set RootPath to an absolute path you compute at startup (for example Path.Combine(builder.Environment.ContentRootPath, "App_Data", "files")).


Amazon S3 provider

When it runs

Selected when Storage:Provider is S3. Registers IAmazonS3 (singleton client) and AmazonS3FileStorage as IFileStorage.

How it works

  • Uses PutObject, GetObject, DeleteObject, and existence checks against the configured bucket.
  • Region — When omitted or empty, the client defaults to us-east-1 (RegionEndpoint.USEast1).
  • Prefix — Optional object key prefix applied to every logical key.
  • Credentials — Optional explicit IAM user keys:
    • Enabled — false: use the SDK default credential chain (environment variables, shared credentials file, IAM roles on AWS, and so on). true: use AccessKeyId and SecretAccessKey (both required).
    • CredentialsEncrypted — false: keys are plain text in configuration. true: values are ciphertext produced by StorageSecretCryptography.Encrypt; they are decrypted through IStorageSecretResolver when the IAmazonS3 singleton is created.

Configuration example

{
  "Storage": {
    "Provider": "S3",
    "AmazonS3": {
      "BucketName": "my-app-bucket",
      "Region": "us-east-1",
      "Prefix": "uploads",
      "Credentials": {
        "Enabled": false,
        "AccessKeyId": "",
        "SecretAccessKey": "",
        "CredentialsEncrypted": false
      }
    }
  }
}

Configuration example (explicit keys, plain text)

{
  "Storage": {
    "Provider": "S3",
    "AmazonS3": {
      "BucketName": "my-app-bucket",
      "Region": "us-east-1",
      "Credentials": {
        "Enabled": true,
        "AccessKeyId": "AKIA...",
        "SecretAccessKey": "...",
        "CredentialsEncrypted": false
      }
    }
  }
}

Requirements

  • A reachable bucket matching BucketName.
  • Either Credentials:Enabled is false and credentials are available to the default credential chain, or Enabled is true with non-empty AccessKeyId and SecretAccessKey (decrypted automatically when CredentialsEncrypted is true).

Azure Blob Storage provider

When it runs

Selected when Storage:Provider is Azure. Registers AzureBlobFileStorage as IFileStorage.

How it works

  • Uses Azure.Storage.Blobs with ConnectionString and ContainerName.
  • Prefix — Optional blob name prefix (virtual folder) applied to every logical key.

Configuration example

{
  "Storage": {
    "Provider": "Azure",
    "AzureBlob": {
      "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net",
      "ContainerName": "my-container",
      "Prefix": "uploads"
    }
  }
}

Requirements

  • A valid storage account connection string and an existing or creatable container name, as enforced by the Azure client at runtime.

Examples

ASP.NET Core (minimal hosting)

using Library.FileStorage.Contracts;
using Library.FileStorage.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAppFileStorage(builder.Configuration);

var app = builder.Build();

app.MapGet("/exists/{**key}", async (IFileStorage storage, string key, CancellationToken ct) =>
{
    var exists = await storage.ExistsAsync(key, ct);
    return Results.Ok(new { key, exists });
});

app.Run();

appsettings.json

{
  "Storage": {
    "Provider": "Local",
    "Local": {
      "RootPath": "storage",
      "Prefix": "app"
    }
  }
}

Switch to S3 or Azure by setting "Provider" to "S3" or "Azure" and filling in Storage:AmazonS3 or Storage:AzureBlob as in the provider sections above.

Class library or tests (manual Configuration + ServiceCollection)

Use this when you do not have WebApplicationBuilder but still want the same registration:

using Library.FileStorage.Contracts;
using Library.FileStorage.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

var configuration = new ConfigurationBuilder()
    .SetBasePath(AppContext.BaseDirectory)
    .AddJsonFile("appsettings.json", optional: false)
    .AddInMemoryCollection(new Dictionary<string, string?>
    {
        ["Storage:Provider"] = "Local",
        ["Storage:Local:RootPath"] = Path.Combine(Path.GetTempPath(), "file-storage-tests")
    })
    .Build();

var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddLogging();
services.AddAppFileStorage(configuration);

await using var provider = services.BuildServiceProvider();

var storage = provider.GetRequiredService<IFileStorage>();

await using var upload = new MemoryStream("Hello"u8.ToArray());
await storage.UploadAsync("documents/hello.txt", upload, contentType: "text/plain");

var exists = await storage.ExistsAsync("documents/hello.txt");
await using var readStream = await storage.OpenReadAsync("documents/hello.txt");
await storage.DeleteAsync("documents/hello.txt");

For S3 or Azure in tests, set the corresponding subsection and ensure credentials or emulators (for example Azurite, LocalStack) are reachable before calling AddAppFileStorage.

Custom implementation

Register your own type instead of calling AddAppFileStorage:

services.AddSingleton<IFileStorage, MyCustomFileStorage>();

Referenced packages

The project references AWSSDK.S3, Azure.Storage.Blobs, Microsoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.DependencyInjection.Abstractions, Microsoft.Extensions.Logging.Abstractions, and Microsoft.Extensions.Options.ConfigurationExtensions. Hosting apps typically already reference Microsoft.Extensions.Hosting / Microsoft.AspNetCore.App, which supply IServiceCollection extensions and IConfiguration. Unused providers still pull their cloud SDK dependencies transitively when you reference this library.

Product 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. 
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.0 153 5/11/2026