Storage.Vector 1.1.0

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

Storage.Vector

A portable .NET 8 storage provider abstraction and implementations for Azure Blob Storage (including local Azurite emulation) and Local Filesystem (supporting local directories, NAS, and SMB/NFS mounts). Supports secondary/backup storage mirroring, presigned download URLs, path traversal protection, and validation.

CI NuGet License: PolyForm Noncommercial


Features

  • 📁 Unified interface — swap between local directories, NAS, and cloud providers purely via configuration
  • ☁️ Azurite & Azure support — fully compatible with local Azurite emulator for dev/testing and cloud Azure Blob Storage
  • 🔒 Path traversal protection — local provider containment checks prevent directory breakout attacks
  • 👯 Keyed secondary mirroring — configure and inject independent backup/sync storage targets via keyed DI
  • 🔑 Presigned URLs — generate signed download URLs (HMAC-SHA256 signatures for LocalFile, SAS tokens for Azure)
  • ☁️ AWS S3 support — first-class S3 provider with single-bucket mode (container = key prefix), presigned URL generation, and LocalStack / MinIO compatibility via AwsServiceUrl
  • 🚀 High Performance & Zero-Allocation — optimized via thread-safe in-memory metadata caches, pre-computed path normalizations, and span-based zero-allocation url signing
  • ⚙️ Startup validation — throws clear errors on application boot if options or paths are missing
  • 📦 NuGet-ready — structured for dotnet pack with symbols (.snupkg)
  • 💉 DI-friendly — integrates with Microsoft.Extensions.DependencyInjection via AddStorageProvider()
  • 🛡️ Unified error handling — catches and translates underlying API exceptions into a structured StorageException

Quick Start

Install

dotnet add package Storage.Vector

Register with Dependency Injection

To register the primary storage provider:

// Program.cs / Startup.cs
builder.Services.AddStorageProvider(builder.Configuration);

Configure the provider options in your settings:

// appsettings.json
{
  "Storage": {
    "Provider": "LocalFile", // "LocalFile" or "AzureBlob"
    "Container": "uploads",
    "Local": {
      "RootPath": "C:\\ProgramData\\MyApp\\Storage",
      "PublicBaseUrl": "https://localhost:5001/api/v1/storage",
      "SigningKey": "your-hmac-sha256-signing-key-minimum-32-chars-long"
    }
  }
}

AWS S3

{
  "Storage": {
    "Provider": "S3",
    "Container": "my-app-bucket",
    "AwsRegion": "eu-west-2",
    "AwsAccessKeyId": "AKIAIOSFODNN7EXAMPLE",
    "AwsSecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  }
}

LocalStack / MinIO: Add "AwsServiceUrl": "http://localhost:4566" and "AwsForcePathStyle": true to target a local S3-compatible endpoint. Omit AwsAccessKeyId / AwsSecretAccessKey entirely to use the ambient IAM credential chain (ECS task role, EC2 instance profile, etc.).

Object keys are namespaced as {container}/{key} within the configured S3 bucket, so a single bucket can serve multiple logical containers.

Upload and Download Files

Inject IStorageProvider into your services:

public class DocumentService(IStorageProvider storage)
{
    public async Task SaveFileAsync(string key, Stream data, CancellationToken ct)
    {
        await storage.PutObjectAsync("documents", key, data, "application/pdf", ct);
    }

    public async Task<Stream> ReadFileAsync(string key, CancellationToken ct)
    {
        return await storage.GetObjectAsync("documents", key, ct);
    }
}

Fluent Scoped Operations

To avoid repeating the container name or key in consecutive actions, you can scope your operations using the fluent API:

public class DocumentService(IStorageProvider storage)
{
    public async Task ProcessInvoiceAsync(string key, Stream data, CancellationToken ct)
    {
        // 1. Scope to a container
        var container = storage.Container("documents");
        await container.EnsureExistsAsync(ct);

        // 2. Scope to a file
        var file = container.File(key);

        // 3. Perform actions on that file fluently
        await file.UploadAsync(data, "application/pdf", ct);
        
        var downloadUrl = await file.GetPresignedUrlAsync(TimeSpan.FromMinutes(15), ct);
        
        using var downloadStream = await file.DownloadAsync(ct);
    }
}

Register a Keyed Secondary Provider (Backup Sync)

// Register Primary IStorageProvider
builder.Services.AddStorageProvider(builder.Configuration);

// Register Secondary Keyed IStorageProvider ("secondary")
builder.Services.AddSecondaryStorageProvider(builder.Configuration);

Configure both in settings:

{
  "Storage": {
    "Provider": "LocalFile",
    "Container": "uploads",
    "Local": {
      "RootPath": "C:\\Storage\\Primary",
      "PublicBaseUrl": "https://localhost:5001/storage",
      "SigningKey": "primary-key"
    },
    "Secondary": {
      "Provider": "AzureBlob",
      "Container": "backups",
      "Azure": {
        "ConnectionString": "UseDevelopmentStorage=true"
      }
    }
  }
}

Resolve the secondary provider using the SecondaryProviderKey constant:

public class SyncService(
    IStorageProvider primary,
    [FromKeyedServices(StorageServiceCollectionExtensions.SecondaryProviderKey)] IStorageProvider secondary)
{
    public async Task MirrorAsync(string key, CancellationToken ct)
    {
        using var data = await primary.GetObjectAsync("documents", key, ct);
        await secondary.PutObjectAsync("documents", key, data, "application/octet-stream", ct);
    }
}

Without DI (direct use)

// LocalFile
var localOptions = Options.Create(new StorageOptions
{
    Provider = "LocalFile",
    RootPath = "C:\\Storage",
    PublicBaseUrl = "https://localhost:5001/storage",
    SigningKey = "secret-signing-key"
});
IStorageProvider localProvider = new LocalFileStorageProvider(localOptions);

// AzureBlob
var azureOptions = Options.Create(new StorageOptions
{
    Provider = "AzureBlob",
    Container = "media",
    ConnectionString = "UseDevelopmentStorage=true"
});
var client = new BlobServiceClient(azureOptions.Value.ConnectionString);
IStorageProvider azureProvider = new AzureBlobStorageProvider(client, azureOptions);

Detailed API & Options Reference

Generating and Validating Presigned URLs

Generate a URL that routes download requests through your local endpoint and validates them with a signature:

// Generate
var url = await storage.GetPresignedUrlAsync("documents", "invoice.pdf", TimeSpan.FromMinutes(15), ct);

// Validate (in your Controller/Endpoint)
var signer = new LocalFileUrlSigner(options.Value.SigningKey);
var requestUrl = $"{Request.Path}{Request.QueryString}";

if (!signer.VerifyUrl(requestUrl))
{
    return Forbid("Presigned URL is expired or has an invalid signature.");
}

Unified Exception Handling

All underlying filesystem or Azure SDK network/authorization errors are mapped into a StorageException containing a StorageErrorKind enum:

try
{
    await storage.GetObjectAsync("documents", "missing.pdf", ct);
}
catch (StorageException ex)
{
    switch (ex.ErrorKind)
    {
        case StorageErrorKind.NotFound:
            Console.WriteLine("File or container not found.");
            break;
        case StorageErrorKind.AccessDenied:
            Console.WriteLine("Unauthorized access to storage path.");
            break;
        case StorageErrorKind.Transient:
            Console.WriteLine("Transient network issue. Retry later.");
            break;
        default:
            Console.WriteLine($"Storage operation failed: {ex.Message}");
            break;
    }
}

Configuration

Option Type Default Description
Storage:Provider string (None) Storage engine selection: "LocalFile" or "AzureBlob"
Storage:Container string (None) Default container/folder name to build roots in
Storage:Local:RootPath string (None) Directory containing storage containers (LocalFile only)
Storage:Local:PublicBaseUrl string (None) Base URL to route signed requests (LocalFile only)
Storage:Local:SigningKey string (None) Secret key used to sign URLs (LocalFile only)
Storage:Azure:ConnectionString string (None) Storage Account Connection String (AzureBlob only)
Storage:Azure:PublicBlobEndpoint string (None) Optional CDN public blob endpoint overlay (AzureBlob only)

License

This project is licensed under the PolyForm Noncommercial License 1.0.0.

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 was computed.  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 was computed.  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.1.0 114 8/1/2026
1.0.5 163 7/21/2026
1.0.4 109 7/21/2026
1.0.3 399 7/13/2026
1.0.2 105 7/13/2026
1.0.1 114 7/13/2026
1.0.0 122 7/13/2026