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.Localon 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:
| Middleware | What it does |
|---|---|
| Validation | Reject files by size, extension, or content type before any bytes are processed |
| Compression | GZip on upload, transparent decompression on download |
| Encryption | AES-256-CBC on upload, transparent decryption on download |
| Content-Type Detection | Detect MIME type from magic bytes when content-type is missing |
| Deduplication | SHA-256 hashing — avoid storing the same file twice |
| Virus Scan | Pluggable IVirusScanner interface (ClamAV, VirusTotal, etc.) |
| Quota | Per-account or global storage limits with pluggable backend |
| Conflict Resolution | Replace, 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...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.
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 Targets | Notes |
|---|---|---|
| 1.0.0 | net8.0; net9.0 | Current 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
- Quick Start — Get your first upload working in 5 minutes.
- Packages Reference — Full table of every package and its dependencies.
- Pipeline Overview — Understand how the middleware pipeline works.
- StorageResult — Master the result type used by every operation.
- StoragePath — Learn the path-building utilities.