Skip to main content

Introduction to Vali-Blob

Vali-Blob is a provider-agnostic cloud storage abstraction library for .NET 8 and .NET 9. It gives you a single, unified API to work with Amazon S3, Azure Blob Storage, Google Cloud Storage, Oracle Cloud Infrastructure, Supabase, and even the local filesystem — without locking your application code to any specific vendor.

Whether you are building a multi-tenant SaaS platform that needs per-tenant storage isolation, a media pipeline that must compress and encrypt every upload, or a simple web API that just needs to store user avatars, Vali-Blob handles the complexity so you can focus on your business logic.


Why Vali-Blob?

Provider Agnostic

Your application code depends on IStorageProvider, not on AWS SDK or Azure SDK types. Swapping from S3 to Azure Blob is a one-line change in your DI configuration. This is invaluable for:

  • Portability — move between clouds without rewriting business logic.
  • Testing — use Vali-Blob.Testing (in-memory provider) in unit tests with zero infrastructure.
  • Multi-cloud — route uploads to different providers based on tenant, region, or content type.
  • Local development — use Vali-Blob.Local on a developer's machine, cloud provider in staging and production.

Pipeline Architecture

Every upload and download flows through a composable middleware pipeline. You can enable and combine:

MiddlewareWhat it does
ValidationReject files by size, extension, or content type before any bytes are processed
CompressionGZip on upload, transparent decompression on download
EncryptionAES-256-CBC on upload, transparent decryption on download
Content-Type DetectionDetect MIME type from magic bytes when content-type is missing
DeduplicationSHA-256 hashing — avoid storing the same file twice
Virus ScanPluggable IVirusScanner interface (ClamAV, VirusTotal, etc.)
QuotaPer-account or global storage limits with pluggable backend
Conflict ResolutionReplace, keep, rename, or fail on duplicate paths

The pipeline is entirely opt-in. Start with just validation and add features incrementally.

Resumable Uploads (TUS Protocol)

Large file uploads fail. Networks drop. Users close browser tabs. Vali-Blob implements the TUS resumable upload protocol so interrupted uploads resume from where they left off, not from the beginning.

Session state can be stored in Redis (Vali-Blob.Redis) or EF Core (Vali-Blob.EFCore), making resumable uploads production-ready in clustered deployments.

Encryption at the Application Layer

Unlike server-side encryption managed by the cloud provider, Vali-Blob encrypts file content in your application before bytes ever leave your process. Your cloud provider sees only ciphertext. AES-256-CBC with a random per-file IV is used, and the IV is stored in object metadata so transparent decryption works automatically on download.

Image Processing

Vali-Blob.ImageSharp adds upload-time image processing: resize, format conversion (JPEG, PNG, WebP, AVIF), and thumbnail generation. Images are transformed before they reach the storage provider, so you never store oversized originals unless you want to.

Observability

Vali-Blob emits structured events (UploadedEvent, DownloadedEvent, DeletedEvent, etc.) that you can subscribe to via IStorageEventHandler<T>. Build audit logs, analytics, or alerting without touching provider code. OpenTelemetry ActivitySource integration is also included for distributed tracing.

Health Checks

Vali-Blob.HealthChecks integrates with ASP.NET Core's IHealthCheck system so your /health endpoint reports the real-time status of every configured storage provider. This is essential for Kubernetes liveness and readiness probes.


Architecture Overview

The following diagram shows how a request flows from your application code through the pipeline and into the chosen storage provider.

loading...
info

The pipeline runs in the order you register middleware. Validation should always come first to reject bad files early. Encryption should come last (before the provider) so the compressed, deduplicated bytes are what gets encrypted — not the raw originals.

The IStorageFactory resolves named providers, allowing multiple providers to coexist in the same application. You can have a primary AWS provider and a secondary Azure provider for backups, each with their own pipeline configuration.


Package Ecosystem

Vali-Blob is distributed as 12 focused NuGet packages. Install only what you need.

PackageDescriptionNuGet
Vali-Blob.CoreCore abstractions, pipeline, DI, StorageResult<T>, StoragePathNuGet
Vali-Blob.AWSAmazon S3 providerNuGet
Vali-Blob.AzureAzure Blob Storage providerNuGet
Vali-Blob.GCPGoogle Cloud Storage providerNuGet
Vali-Blob.OCIOracle Cloud Infrastructure Object StorageNuGet
Vali-Blob.SupabaseSupabase Storage providerNuGet
Vali-Blob.LocalLocal filesystem provider (dev/testing/on-premise)NuGet
Vali-Blob.RedisRedis-backed resumable upload session storeNuGet
Vali-Blob.EFCoreEF Core resumable upload session storeNuGet
Vali-Blob.TestingIn-memory provider for unit testingNuGet
Vali-Blob.HealthChecksASP.NET Core health checks for storage providersNuGet
Vali-Blob.ImageSharpImage processing middleware (resize, convert, watermark)NuGet

All packages target net8.0 and net9.0.


Key Features at a Glance

Unified Result Type

Every operation returns StorageResult<T> — a discriminated union that forces you to handle both success and failure paths. No uncaught exceptions from network calls in production.

var result = await provider.UploadAsync(request);

if (result.IsSuccess)
return Ok(result.Value.Url);

return result.ErrorCode switch
{
StorageErrorCode.ValidationFailed => BadRequest(result.ErrorMessage),
StorageErrorCode.QuotaExceeded => StatusCode(429, result.ErrorMessage),
StorageErrorCode.VirusScanFailed => UnprocessableEntity(result.ErrorMessage),
_ => StatusCode(500, result.ErrorMessage)
};

Composable Path Builder

StoragePath builds cloud-safe storage paths from segments, with built-in helpers for date prefixes, hash suffixes, and sanitization.

var path = StoragePath
.From("users", userId, "avatars", fileName)
.WithDatePrefix() // 2026/03/18/users/42/avatars/photo.jpg
.WithHashSuffix(); // 2026/03/18/users/42/avatars/photo_a3f7b2.jpg

First-Class Cancellation

Every method on IStorageProvider accepts an optional CancellationToken, ensuring long-running uploads and downloads respect request timeouts and user cancellations.

Full IStorageProvider Interface

Task<StorageResult<UploadResult>>             UploadAsync(UploadRequest request, CancellationToken ct = default);
Task<StorageResult<Stream>> DownloadAsync(DownloadRequest request, CancellationToken ct = default);
Task<StorageResult> DeleteAsync(string path, CancellationToken ct = default);
Task<StorageResult> DeleteFolderAsync(string prefix, CancellationToken ct = default);
Task<StorageResult<bool>> ExistsAsync(string path, CancellationToken ct = default);
Task<StorageResult> CopyAsync(string src, string dst, CancellationToken ct = default);
Task<StorageResult<FileMetadata>> GetMetadataAsync(string path, CancellationToken ct = default);
Task<StorageResult> SetMetadataAsync(string path, Dictionary<string,string> metadata, CancellationToken ct = default);
Task<StorageResult<IReadOnlyList<FileEntry>>> ListFilesAsync(string prefix, CancellationToken ct = default);
Task<StorageResult<IReadOnlyList<string>>> ListFoldersAsync(string prefix, CancellationToken ct = default);
Task<StorageResult<string>> GetUrlAsync(string path, CancellationToken ct = default);

Typical DI Setup

builder.Services.AddVali-Blob(o => { o.DefaultProvider = "aws"; })
.AddProvider<AWSS3Provider>("aws", opts => {
opts.BucketName = "my-bucket";
opts.Region = "us-east-1";
})
.WithPipeline(p => p
.UseValidation(v => {
v.MaxFileSizeBytes = 100_000_000;
v.AllowedExtensions = [".jpg", ".png", ".pdf"];
})
.UseCompression()
.UseEncryption(e => { e.Key = config["Storage:EncryptionKey"]; })
.UseContentTypeDetection()
.UseDeduplication()
.UseVirusScan()
.UseQuota(q => { q.MaxTotalBytes = 10L * 1024 * 1024 * 1024; })
.UseConflictResolution(ConflictResolution.ReplaceExisting));

Target Frameworks

Version.NET TargetsNotes
1.0.0net8.0; net9.0Current stable release

All packages in the Vali-Blob ecosystem are versioned together. When you upgrade, upgrade all packages to the same version to avoid compatibility issues.


Next Steps