Chd.Min.IO 8.5.9

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package Chd.Min.IO --version 8.5.9
                    
NuGet\Install-Package Chd.Min.IO -Version 8.5.9
                    
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="Chd.Min.IO" Version="8.5.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Chd.Min.IO" Version="8.5.9" />
                    
Directory.Packages.props
<PackageReference Include="Chd.Min.IO" />
                    
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 Chd.Min.IO --version 8.5.9
                    
#r "nuget: Chd.Min.IO, 8.5.9"
                    
#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 Chd.Min.IO@8.5.9
                    
#: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=Chd.Min.IO&version=8.5.9
                    
Install as a Cake Addin
#tool nuget:?package=Chd.Min.IO&version=8.5.9
                    
Install as a Cake Tool

MinIO Helper Library For .NET Core

Chd (Cleverly Handle Difficulty) library helps you cleverly handle difficulty, write code quickly, and keep your application stable.

NuGet License


πŸ“ Table of Contents


About

Chd.Library.MinIO is a modern, robust .NET 8+ integration for MinIO (or any S3-compatible) storage.
It offers developer-friendly, highly efficient, and production-tested tools for secure file, document, and image storage.
Thanks to advanced optimization features, it's perfect for user-generated content, SaaS, and media-rich web/mobile/cloud apps.


🎯 Why MinIO & Object Storage? (vs Traditional File Systems)

The Problem with Traditional File Storage

Many developers still rely on traditional file systems (NTFS, ext4, network shares) or basic cloud file services for storing user uploads, media, and documents. While simple to start with, these approaches quickly become problematic at scale:

Storage Type Key Limitations
Local File System (NTFS, ext4) ❌ No built-in replication or redundancy<br>❌ Hard to scale horizontally<br>❌ Server-bound (no multi-region)<br>❌ Complex backup/disaster recovery<br>❌ No native CDN integration
Network File Shares (SMB, NFS) ❌ Poor performance over WAN<br>❌ Single point of failure<br>❌ Limited concurrent access<br>❌ Complex permission management
Azure Blob Storage ⚠️ Vendor lock-in (Azure-only)<br>⚠️ Expensive at scale (egress fees)<br>⚠️ Complex pricing model<br>βœ… Good SDK support
AWS S3 ⚠️ Vendor lock-in (AWS-only)<br>⚠️ Expensive for large downloads (egress costs)<br>⚠️ Unpredictable bills at scale<br>βœ… Industry standard, reliable
Google Cloud Storage ⚠️ Vendor lock-in (GCP-only)<br>⚠️ Similar pricing concerns<br>⚠️ Less mature ecosystem than AWS
HP Cloud Object Storage ⚠️ Enterprise-focused, complex setup<br>⚠️ Limited community support<br>⚠️ Proprietary APIs (less portable)

Why MinIO is the Superior Choice

MinIO is a high-performance, open-source, S3-compatible object storage solution that solves all these problems:

βœ… 1. 100% Free & Open Source
  • No licensing fees, no hidden costs
  • Deploy on your own hardware or cloud
  • Full control over your data sovereignty
βœ… 2. S3-Compatible API (Industry Standard)
  • Works with AWS SDK, tools, and existing S3 code
  • Easy migration from/to AWS S3, Azure, or others
  • No vendor lock-inβ€”switch providers anytime
βœ… 3. Self-Hosted = Cost Savings

Cost Comparison Example (1TB storage, 500GB/month transfer):

Provider Monthly Cost (USD)
AWS S3 ~$60-120
Azure Blob (Hot) ~$50-100
Google Cloud Storage ~$50-110
MinIO (Self-hosted) ~$5-20 (server only)

πŸ’° Save 70-90% on storage costs by hosting MinIO on your own infrastructure!

βœ… 4. Superior Performance
  • 10x faster than traditional NFS/SMB shares
  • Built for parallel, concurrent access (handles 1000s of requests/sec)
  • Automatic erasure coding for data protection without RAID overhead
  • Native support for NVMe, SSD, and modern hardware
βœ… 5. Enterprise-Grade Features (Free!)
  • Built-in versioning and lifecycle management
  • Server-side encryption (AES-256)
  • Multi-tenancy (bucket-level isolation)
  • Distributed mode for high availability and scalability
  • Event notifications (Webhook, Kafka, NATS, etc.)
βœ… 6. Cloud-Native & Container-Ready
  • Deploy with Docker, Kubernetes, or bare metal
  • Scales horizontally (just add nodes)
  • Perfect for microservices and modern cloud architectures
βœ… 7. No Egress Fees or Bandwidth Surprises
  • Unlike AWS/Azure, no charges for downloads (you control bandwidth)
  • Predictable, transparent costs

Real-World Use Case Comparison

Scenario Traditional FS Azure Blob AWS S3 MinIO
Store 10TB of user photos ❌ Slow, no redundancy ⚠️ $200+/mo ⚠️ $230+/mo βœ… $50-80/mo
Serve 1M requests/day ❌ Bottleneck ⚠️ Egress fees ⚠️ Egress fees βœ… Free (self-hosted)
Multi-region replication ❌ Manual setup βœ… Yes βœ… Yes βœ… Yes (easy)
No vendor lock-in βœ… Yes ❌ Azure-only ❌ AWS-only βœ… Yes
Self-hosted (on-prem/hybrid) βœ… Yes ❌ Cloud-only ❌ Cloud-only βœ… Yes
Full data control βœ… Yes ❌ Limited ❌ Limited βœ… Yes

When to Choose MinIO Over Alternatives

βœ… Use MinIO if you:

  • Want cost-effective, scalable object storage
  • Need S3 compatibility without AWS vendor lock-in
  • Require self-hosted or hybrid cloud deployments
  • Have compliance/regulatory requirements (GDPR, HIPAA, etc.)
  • Want predictable costs (no surprise bills)
  • Need high performance for AI/ML workloads, media streaming, or backups

⚠️ Use AWS S3/Azure if you:

  • Already have deep integration with AWS/Azure services
  • Don't want to manage infrastructure (fully managed)
  • Need global edge CDN (AWS CloudFront integration)

Why This Library Matters

This library (Chd.Min.IO) makes MinIO even better by:

  • Abstracting away low-level S3 API complexity
  • Adding automatic image optimization (resize, compress, format conversion)
  • Providing production-ready error handling and retry logic
  • Offering developer-friendly APIs for .NET projects
  • Supporting both cloud and self-hosted MinIO deployments

TL;DR: Get the power of AWS S3 without the cost, vendor lock-in, or complexity!


Features

  • πŸ”Œ Plug-and-play .NET 8+ support with DI and builder extension
  • ⚑ Asynchronous upload/download for single files or batches
  • πŸ–ΌοΈ Auto format detection & conversion (JPEG/PNG/WebP) for images
  • πŸ“‰ Automatic resize, crop, compress, and strict memory/size limiting
  • 🧠 Preserves transparency, disallows "fake" image uploads, rejects corrupt files
  • 🚦 Automatic bucket management, multi-tenant/resource support
  • πŸ“ File versioning (update/delete operations preserve old versions)
  • πŸ–₯️ Support for both MinIO/S3 and local file servers (SMB/NFS)
  • πŸ† Production-tested for large enterprises and high-scale web/SaaS products

Installation & Setup

1. Add NuGet package

dotnet add package Chd.Min.IO

2. Register on Startup

Add to your Program.cs or startup routine:

using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

// Register MinIO (reads from appsettings.json)
app.UseMinIO();

app.Run();

Or manually:

using Chd.Min.IO.Containers;

var config = new MinIOConfig
{
    Host = "localhost:9000",
    AccessKey = "admin",
    SecretKey = "secret123",
    BucketName = "my-bucket"
};

app.UseMinIO(config);

Configuration

Add the following to your appsettings.json:

{
  "Minio": {
    "Host": "localhost:9000",
    "AccessKey": "admin",
    "SecretKey": "secret_key_for_min.io",
    "BucketName": "software",
    "Url": "http://localhost:9000"
  }
}

Configuration Options:

  • Host: MinIO or S3 endpoint (e.g., s3.amazonaws.com for AWS S3)
  • AccessKey: MinIO/S3 access key
  • SecretKey: MinIO/S3 secret key
  • BucketName: Default storage bucket (auto-created if not exists)
  • Url: Base URL for generating public links (optional)

Usage Overview

All primary APIs are accessible via FileUtils and ImageUtils classes.
Both async and batch workflows are supported.


πŸ“‚ File Operations (FileUtils)

1. Upload Single File
using Chd.Min.IO.Utils;

// Simple upload to default bucket
var result = await FileUtils.Upload(formFile);

// Upload to specific bucket and path
var result = await FileUtils.Upload(
    file: formFile,
    bucketName: "my-bucket",
    objectName: "documents/report.pdf"
);

// Upload with custom content type
var result = await FileUtils.Upload(
    file: formFile,
    objectName: "files/data.json",
    contentType: "application/json"
);

// Check result
if (result.IsFaulted)
    Console.WriteLine($"Error: {result.ErrorMessage}");
else
    Console.WriteLine($"Uploaded to: {result.Path}");
2. Upload Multiple Files
// Batch upload with prefix
List<IFormFile> files = GetFilesFromRequest();
var results = await FileUtils.Upload(
    files: files,
    prefix: "user-123",
    bucketName: "uploads",
    path: "documents"
);

// Check results
foreach (var result in results)
{
    if (result.IsFaulted)
        Console.WriteLine($"Error: {result.ErrorMessage}");
    else
        Console.WriteLine($"Uploaded: {result.Path}");
}
3. Download Files
// Download file from default bucket
var fileResult = await FileUtils.Get("documents/report.pdf");
if (!fileResult.IsFaulted)
{
    byte[] fileData = fileResult.Data.ToArray();
    string contentType = fileResult.ContentType;
    
    // Return as file download
    return File(fileData, contentType, "report.pdf");
}

// Download from specific bucket
var fileResult = await FileUtils.Get(
    bucketName: "my-bucket",
    objectName: "images/photo.jpg"
);
4. Update Existing File (with Versioning)
// Update file - old version is automatically saved
var result = await FileUtils.Update(
    file: newFile,
    bucketName: "my-bucket",
    oldobjectName: "documents/report.pdf"
);

// Old version is saved to: updated/20241215103045_report.pdf
// New file replaces the original path
5. Delete File (with Soft Delete)
// Delete file (moves to deleted/ folder with timestamp)
var result = await FileUtils.Delete("documents/report.pdf");

// Delete from specific bucket
var result = await FileUtils.Delete(
    bucketName: "my-bucket",
    fileFullPath: "documents/report.pdf"
);

// Old version is saved to: deleted/20241215103045_report.pdf
// Original file is removed
6. Upload to Network File Server (SMB/NFS)
// Upload to Windows network share or Linux NFS mount
var result = await FileUtils.UploadToFileServer(
    file: formFile,
    fileServerPath: @"\\server\share\uploads",
    prefix: "user-456"
);

// Or Linux NFS path
var result = await FileUtils.UploadToFileServer(
    file: formFile,
    fileServerPath: "/mnt/nfs/uploads",
    prefix: "backup"
);

Console.WriteLine($"Saved to: {result.Path}");

πŸ–ΌοΈ Image Operations (ImageUtils)

1. Simple Image Upload with Auto-Optimization
using Chd.Min.IO.Utils;

// Auto-resize and compress (transparent PNGs preserved)
var result = await ImageUtils.SimpleUpload(
    file: imageFile,
    bucketName: "images",
    folderName: "products",
    maxkb: 500,        // Max 500KB
    width: 1200,       // Max width 1200px
    height: 800        // Max height 800px
);

// Result: Image is resized, compressed, and saved
// PNG with transparency β†’ stays PNG
// PNG without transparency β†’ converted to JPEG
2. Upload with Format Conversion
using ImageMagick;

// Force convert to JPEG (great for photos)
var result = await ImageUtils.SimpleUpload(
    file: imageFile,
    fileName: "product-001.jpg",
    format: MagickFormat.Jpg,
    maxkb: 300,
    density: 96
);

// Convert to WebP (modern, smallest size)
var result = await ImageUtils.SimpleUpload(
    file: imageFile,
    format: MagickFormat.WebP,
    percentage: 85   // 85% quality
);

// Auto format (MagickFormat.A)
// - PNG with alpha β†’ PNG
// - PNG without alpha β†’ JPEG
var result = await ImageUtils.SimpleUpload(
    file: imageFile,
    format: MagickFormat.A  // Auto-detect
);
3. Batch Image Upload
List<IFormFile> productImages = GetProductImages();

var results = await ImageUtils.Upload(
    files: productImages,
    prefix: "product-batch-2024",
    bucketName: "catalog-images",
    folderName: "products",
    format: MagickFormat.Jpg,
    maxkb: 512,
    width: 1024,
    height: 768
);

foreach (var result in results)
{
    Console.WriteLine($"{result.FileName} β†’ {result.Path}");
}
4. Dynamic Image Resize on Download
// Get original image
var image = await ImageUtils.Get("products/chair.jpg");

// Get thumbnail (300x300, preserve aspect ratio)
var thumbnail = await ImageUtils.Get(
    objectName: "products/chair.jpg",
    width: 300,
    height: 300,
    ignoreAspectRatio: false  // Preserve aspect ratio
);

// Get square crop (force exact dimensions)
var square = await ImageUtils.Get(
    objectName: "products/chair.jpg",
    width: 500,
    height: 500,
    ignoreAspectRatio: true   // Force 500x500 square
);

// Get with aspect ratio (e.g., 16:9 for video thumbnails)
var videoThumb = await ImageUtils.Get(
    objectName: "videos/thumbnail.jpg",
    aspectRatio: "16/9",
    width: 1920
);
// Result: 1920x1080 image

// Reduce by percentage
var smallImage = await ImageUtils.Get(
    objectName: "products/chair.jpg",
    percentage: 50   // 50% of original size
);
5. Upload Image to Network File Server
// Upload optimized image to file server
var result = await ImageUtils.UploadImageToFileServer(
    file: imageFile,
    fileServerPath: @"\\server\images\products",
    prefix: "prod",
    maxkb: 800,
    width: 1600,
    height: 1200,
    format: MagickFormat.Jpg
);

Console.WriteLine($"Saved locally: {result.Path}");

πŸ” Result Object Structure

All upload/update/delete operations return a Result object:

public class Result
{
    public string FileName { get; set; }       // "report.pdf"
    public string Path { get; set; }           // "documents/report.pdf"
    public string ContentType { get; set; }    // "application/pdf"
    public string Message { get; set; }        // Success/error message
    public bool IsFaulted { get; set; }        // true if error occurred
    public string ErrorMessage { get; set; }   // Detailed error (if any)
}

// Example usage:
var result = await FileUtils.Upload(file);
if (result.IsFaulted)
{
    Log.Error($"Upload failed: {result.ErrorMessage}");
    return BadRequest(result.Message);
}
else
{
    Log.Info($"File saved to: {result.Path}");
    return Ok(new { path = result.Path, fileName = result.FileName });
}

Download operations return a RequestResult object:

public class RequestResult
{
    public MemoryStream Data { get; set; }     // File contents
    public string ContentType { get; set; }    // MIME type
    public bool IsFaulted { get; set; }
    public string ErrorMessage { get; set; }
}

// Example usage:
var result = await FileUtils.Get("documents/file.pdf");
if (!result.IsFaulted)
{
    return File(result.Data, result.ContentType, "download.pdf");
}

🎨 Advanced Image Processing Scenarios

Scenario 1: User Avatar Upload
// Accept user avatar, force square, compress, save as JPEG
var result = await ImageUtils.SimpleUpload(
    file: avatarFile,
    bucketName: "user-avatars",
    fileName: $"{userId}-avatar.jpg",
    format: MagickFormat.Jpg,
    width: 512,
    height: 512,
    ignoreAspectRetio: true,  // Force square
    maxkb: 200,               // Max 200KB
    density: 96
);

// Result: 512x512 JPEG, under 200KB
Scenario 2: Product Catalog with Transparency
// Upload product image (preserve PNG if transparent, else JPEG)
var result = await ImageUtils.SimpleUpload(
    file: productImage,
    bucketName: "products",
    folderName: "catalog",
    format: MagickFormat.A,   // Auto-detect (PNG if alpha, else JPEG)
    maxkb: 800,
    width: 1200
);

// Result:
// - Logo with transparency β†’ PNG (transparency preserved)
// - Product photo β†’ JPEG (smaller file size)
Scenario 3: Generate Multiple Thumbnail Sizes
var originalPath = "products/chair-original.jpg";

// Upload original (high quality)
var original = await ImageUtils.SimpleUpload(
    file: imageFile, 
    folderName: "originals",
    fileName: originalPath,
    maxkb: 2048  // 2MB max
);

// Generate and save thumbnail
var thumbFile = imageFile;  // Re-use same IFormFile
var thumb = await ImageUtils.SimpleUpload(
    thumbFile, 
    folderName: "thumbnails", 
    fileName: "chair-thumb.jpg",
    width: 150, 
    height: 150,
    ignoreAspectRetio: true
);

// Generate medium size
var medium = await ImageUtils.SimpleUpload(
    thumbFile, 
    folderName: "medium", 
    fileName: "chair-medium.jpg",
    width: 600, 
    height: 400
);

// Or dynamically resize on download:
var thumbOnDemand = await ImageUtils.Get(
    originalPath, 
    width: 150, 
    height: 150
);
Scenario 4: High-Quality Print Images
// Upload image for print (high density, minimal compression)
var result = await ImageUtils.SimpleUpload(
    file: printImage,
    bucketName: "print-ready",
    density: 300,       // 300 DPI for print
    depth: 16,          // 16-bit color depth
    percentage: 100,    // No quality loss
    format: MagickFormat.Tiff
);

πŸ—‚οΈ Bucket Management

  • Buckets are auto-created on first use (lazy-initialization)
  • You can change buckets per environment or logical tenant
  • Compatible with all MinIO and S3-compatible providers
// Each tenant can have their own bucket
await FileUtils.Upload(
    file: file,
    bucketName: $"tenant-{tenantId}",
    objectName: "documents/file.pdf"
);

// Environment-based buckets
var bucketName = env.IsDevelopment() 
    ? "dev-uploads" 
    : "prod-uploads";

await FileUtils.Upload(file, bucketName: bucketName);

⚠️ Error Handling & Robustness

try
{
    var result = await FileUtils.Upload(file);
    
    if (result.IsFaulted)
    {
        // Handle known errors (corrupt file, size limit, etc.)
        _logger.LogWarning($"Upload warning: {result.Message}");
        return BadRequest(new { error = result.ErrorMessage });
    }
    
    return Ok(new { 
        path = result.Path, 
        fileName = result.FileName,
        contentType = result.ContentType 
    });
}
catch (Exception ex)
{
    // Handle unexpected errors (network, permissions, etc.)
    _logger.LogError(ex, "Upload failed");
    return StatusCode(500, "Internal server error");
}

Built-in Safety Features:

  • βœ… Corrupt files are rejected with clear error messages
  • βœ… Over-sized images are auto-compressed or rejected
  • βœ… File extension spoofing is detected (magic byte validation)
  • βœ… Illegal filename characters are auto-removed
  • βœ… Automatic versioning on update/delete (no data loss)
  • βœ… Zero data loss in high-load environments
  • βœ… Transparent PNG preservation (no "white box" bugs)
  • βœ… Configurable memory limits prevent DoS attacks

πŸ“· Deep Dive: Advanced Image Processing, Storage, and Optimization

This library is not just a bridge to S3 or MinIO β€” it provides a full, robust, and production-tested media optimization pipeline
tailored for real-world .NET applications. Here's how it outclasses basic "file save" logic and why it makes operational, UX, and cost sense.


πŸš€ What Makes Image Handling in This Library Special?

  • Bandwidth and Storage Efficiency:
    Large camera/photo uploads are automatically resized, compressed, andβ€”where possibleβ€”reformatted to cut storage costs and page loads by 60–80%+.

  • Product-Grade Quality:
    Transparency (alpha channel) is never lost by accidental format downscaling: Avatars, logos, overlays always look crisp, no "white box" bugs!

  • Failsafe Security:
    Extension spoofing (e.g. JPG that's really a .doc) and broken/corrupt files are detectedβ€”your system is never DOS-ed by file upload attacks.

  • Image Trustworthiness:
    What the user sees is always what is deliveredβ€”pixel shape, background, sharpness preserved based on business rules.

  • Automation Simplicity:
    Devs don't have to handwrite format conversion, downsize, or edge-case filters; all this logic is handled automatically, tested in prod at scale!


πŸ§ͺ Supported Pipeline Features

1. Auto Format Selection & Conversion

  • Detects if uploaded image has an alpha channel (transparency)
  • Preserves PNG for transparent images (e.g. logos, overlays)
  • Converts PNGβ†’JPEG for non-transparent images (huge space savings)
  • Supports WebP for next-gen web clients
  • GIFs (animated/static) preserved unless explicit conversion requested

2. Gradual Memory & Dimension Downscaling

  • maxWidth, maxHeight restrict user uploads to maintain UX bounds
  • After resizing, applies compression so final file fits maxkb limit
  • If image can't be shrunk below limit, fails gracefully with error

3. Content-based File Validation

  • Validates magic bytes and decodes within bounds
  • Fakes (e.g. .jpg with PDF contents) are blocked with error
  • Rejects zero-length or broken/corrupted images

4. Bulk, Batch & Parallel Image Workflow

  • Async, parallel-safe; entire catalogs can be processed with high throughput

πŸ”„ Algorithm: "What Format Will My Image Be Saved As?"

Input Format Has Transparency? Output Format (auto) Notes
PNG Yes PNG Crisp overlays, logos preserved
PNG No JPEG Huge savings for photos
JPEG N/A JPEG Recompressed if too large
GIF N/A GIF Animation/static kept
WebP N/A WebP or JPEG Browser support dependent

Always checks actual binary, not just extension!

You can override format (format: MagickFormat.Jpg) or allow auto selection (format: MagickFormat.A).


πŸ’‘ Real-World Scenario Table

Case What Happens Result for User/UI
User uploads selfie from phone (7MB) Resized to maxWidth/maxHeight, down to 1MB as JPEG Fast page load, fits quota
Company logo (transparent PNG, 300KB) Saved as PNG, transparency preserved Overlay crisp, no white box
Fake .jpg (PDF file!) Blocked at validation step No File Spoof/Exploit Risk
Animated GIF meme Saved as GIF, animation preserved Animation works, no loss

⚠️ Complex Edge Cases & Warnings

  • Uploading "maxed-out" PNGs: If a user attempts to upload a 13MB lossless PNG with a 2MB limit, system downscales (pixels + quality) until threshold is met or returns "file too large" error

  • Batch uploads: All pipeline logic applies per-image, never "short-circuits"

  • Image caching: Downloaded images are cached for 20 seconds by default (configurable)


πŸ† Engineering and Business Value

  • Reduce bill shock: Prevents runaway storage costs via auto-compression
  • Improve time-to-first-paint: Users see media faster, CDNs cache efficiently
  • Bulletproof corrupt upload defense: No more bug tickets of blank images
  • Explicitly control user content: Out-of-range uploads rejected before save
  • Great for web, mobile, API and desktop clients

🌍 Real-World Production Examples

Example 1: E-Commerce Product Upload (Razor Pages)

// Pages/Products/Create.cshtml.cs
public class CreateModel : PageModel
{
    [BindProperty]
    public IFormFile ProductImage { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (ProductImage == null)
            return Page();

        // Upload optimized product image
        var result = await ImageUtils.SimpleUpload(
            file: ProductImage,
            bucketName: "product-images",
            folderName: $"products/{DateTime.UtcNow:yyyy/MM}",
            fileName: $"{Guid.NewGuid()}.jpg",
            format: MagickFormat.Jpg,
            maxkb: 800,
            width: 1200,
            height: 900
        );

        if (result.IsFaulted)
        {
            ModelState.AddModelError("", result.ErrorMessage);
            return Page();
        }

        // Save product with image path
        var product = new Product
        {
            Name = ProductName,
            ImagePath = result.Path
        };
        await _db.Products.AddAsync(product);
        await _db.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Example 2: User Avatar Upload (Web API)

// Controllers/UsersController.cs
[HttpPost("avatar")]
public async Task<IActionResult> UploadAvatar(IFormFile avatar)
{
    var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);

    // Delete old avatar if exists
    var user = await _db.Users.FindAsync(userId);
    if (!string.IsNullOrEmpty(user.AvatarPath))
    {
        await FileUtils.Delete(user.AvatarPath);
    }

    // Upload new avatar (square, compressed)
    var result = await ImageUtils.SimpleUpload(
        file: avatar,
        bucketName: "user-avatars",
        fileName: $"{userId}.jpg",
        format: MagickFormat.Jpg,
        width: 512,
        height: 512,
        ignoreAspectRetio: true,
        maxkb: 200
    );

    if (result.IsFaulted)
        return BadRequest(result.ErrorMessage);

    // Update user record

---

## πŸ“‹ Changelog

### Version 8.5.9 (2025-01-XX)
πŸ” **Security Update - Critical**

#### πŸ›‘οΈ Security Fixes
- **CRITICAL**: Updated `Magick.NET-Q8-AnyCPU` from 14.2.0 to 14.10.3
  - Fixed 8+ HIGH severity vulnerabilities
  - Fixed 20+ MODERATE severity vulnerabilities  
  - Fixed 12+ LOW severity vulnerabilities
  - **Affected advisories**: GHSA-6hjr-v6g4-3fm8, GHSA-72hf-fj62-w6j4, GHSA-543g-8grm-9cw6, and 40+ more
- Updated `Magick.NET.Core` from 14.2.0 to 14.10.3

#### πŸ“ Documentation
- Enhanced README with detailed security notes
- Added comprehensive image processing pipeline documentation

#### ⚠️ Breaking Changes
None - Safe to upgrade!

#### πŸ”„ Migration Guide
Simply update your package reference:
```bash
dotnet add package Chd.Library.MinIO --version 8.5.9

Recommendation: This is a security-critical update. Please upgrade immediately.


Version 8.5.8 (Previous)

  • Standard image processing features

  • MinIO integration improvements user.AvatarPath = result.Path; await _db.SaveChangesAsync();

    return Ok(new { avatarUrl = $"/api/users/{userId}/avatar" }); }

[HttpGet("{userId}/avatar")] public async Task<IActionResult> GetAvatar(string userId, [FromQuery] int? size) { var user = await _db.Users.FindAsync(userId); if (user == null || string.IsNullOrEmpty(user.AvatarPath)) return NotFound();

// Get avatar (optionally resized)
var result = size.HasValue
    ? await ImageUtils.Get(user.AvatarPath, width: (uint)size.Value, height: (uint)size.Value)
    : await ImageUtils.Get(user.AvatarPath);

if (result.IsFaulted)
    return NotFound();

return File(result.Data, result.ContentType);

}


### Example 3: Document Management with Versioning

```csharp
// Services/DocumentService.cs
public class DocumentService
{
    public async Task<Result> UploadDocument(IFormFile file, string category)
    {
        var fileName = $"{category}/{DateTime.UtcNow:yyyy/MM/dd}/{Guid.NewGuid()}_{file.FileName}";
        
        return await FileUtils.Upload(
            file: file,
            bucketName: "company-documents",
            objectName: fileName
        );
    }

    public async Task<Result> UpdateDocument(IFormFile newFile, string existingPath)
    {
        // Old version is automatically saved to updated/ folder
        return await FileUtils.Update(
            file: newFile,
            oldobjectName: existingPath
        );
    }

    public async Task<Result> DeleteDocument(string path)
    {
        // File is moved to deleted/ folder, not permanently removed
        return await FileUtils.Delete(path);
    }

    public async Task<RequestResult> DownloadDocument(string path)
    {
        return await FileUtils.Get(path);
    }
}

Example 4: Multi-Tenant SaaS File Storage

// Services/TenantStorageService.cs
public class TenantStorageService
{
    private string GetTenantBucket(string tenantId)
    {
        return $"tenant-{tenantId}-files";
    }

    public async Task<Result> UploadTenantFile(string tenantId, IFormFile file, string folder)
    {
        var bucketName = GetTenantBucket(tenantId);
        var objectName = $"{folder}/{Guid.NewGuid()}_{file.FileName}";

        return await FileUtils.Upload(
            file: file,
            bucketName: bucketName,
            objectName: objectName
        );
    }

    public async Task<RequestResult> GetTenantFile(string tenantId, string path)
    {
        var bucketName = GetTenantBucket(tenantId);
        return await FileUtils.Get(bucketName, path);
    }
}

Testing & Sample Code

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chd.Min.IO.Utils;
using Chd.Min.IO.Containers;
using Microsoft.AspNetCore.Http;

[TestClass]
public class MinIOTests
{
    static MinIOTests()
    {
        // Initialize MinIO
        var config = new MinIOConfig
        {
            Host = "localhost:9000",
            AccessKey = "minioadmin",
            SecretKey = "minioadmin",
            BucketName = "test-bucket"
        };
        MinIODependenyInjectionExtensions.UseMinIO(null, config);
    }

    [TestMethod]
    public async Task TestFileUpload()
    {
        using var fs = File.OpenRead("testfiles/sample.pdf");
        var formFile = new FormFile(fs, 0, fs.Length, "sample", "sample.pdf");
        
        var result = await FileUtils.Upload(formFile);
        
        Assert.IsFalse(result.IsFaulted);
        Assert.IsNotNull(result.Path);
        Console.WriteLine($"Uploaded to: {result.Path}");
    }

    [TestMethod]
    public async Task TestImageUploadWithOptimization()
    {
        using var fs = File.OpenRead("testfiles/photo.jpg");
        var formFile = new FormFile(fs, 0, fs.Length, "photo", "photo.jpg");
        
        var result = await ImageUtils.SimpleUpload(
            formFile,
            maxkb: 500,
            width: 1200,
            height: 800
        );
        
        Assert.IsFalse(result.IsFaulted);
        Assert.IsTrue(result.Path.EndsWith(".jpg"));
    }

    [TestMethod]
    public async Task TestImageDownloadWithResize()
    {
        var result = await ImageUtils.Get(
            "products/chair.jpg",
            width: 300,
            height: 300
        );
        
        Assert.IsFalse(result.IsFaulted);
        Assert.IsNotNull(result.Data);
        Assert.IsTrue(result.Data.Length < 100 * 1024); // Under 100KB
    }
}

Best Practices & Production Benefits

βœ… Do's

  • Always set size limits: Use maxkb, maxWidth, maxHeight for user uploads
  • Handle errors gracefully: Check result.IsFaulted and log errors
  • Use meaningful paths: Organize files by date, category, user ID
  • Enable format auto-detection: Use MagickFormat.A for images
  • Implement versioning: Use Update() and Delete() methods for audit trails
  • Cache aggressively: ImageUtils has built-in 20-second cache
  • Use bucket-per-tenant: For SaaS apps, isolate tenant data

❌ Don'ts

  • Don't skip validation: Always check IsFaulted before using results
  • Don't ignore transparency: Use auto-format to preserve PNG alpha
  • Don't store originals forever: Delete or archive old versions periodically
  • Don't hardcode bucket names: Use configuration or environment variables
  • Don't expose raw paths: Generate secure, time-limited URLs instead

πŸ’° Cost Savings

  • Automatic image optimization can reduce storage by 60-80%
  • Self-hosted MinIO saves 70-90% vs AWS S3
  • No egress fees = predictable monthly costs

FAQ

Q: Can I use with Amazon S3, Wasabi, or other S3-compatible providers?
A: Yes! Just update Host, AccessKey, and SecretKey in config. Works with any S3 API.

Q: Does it support animated GIFs?
A: Yes, animations are preserved unless you explicitly convert to another format.

Q: What happens if a user uploads a 12MB photo with maxkb: 2?
A: The image is progressively scaled down and compressed until under 2MB; if impossible, upload fails with error.

Q: Can I upload files in parallel?
A: Yes! All methods are async/await-friendly and thread-safe.

Q: How does file versioning work?
A: Update() saves old file to updated/{timestamp}_{filename}. Delete() saves to deleted/{timestamp}_{filename}. Original file is replaced/removed.

Q: Is there a file size limit?
A: MinIO supports files up to 5TB. This library doesn't impose hard limits, but you can set maxkb for images.

Q: Can I use this with local file servers (not MinIO)?
A: Yes! Use FileUtils.UploadToFileServer() and ImageUtils.UploadImageToFileServer() for SMB/NFS shares.

Q: Does it work with .NET 8 and .NET 9?
A: Yes, fully compatible with .NET 8 and .NET 9.


Authors

See also contributors on NuGet


Acknowledgements

  • Inspired by MinIO, AWS S3, and the .NET open-source community
  • Built with Minio SDK and Magick.NET
  • Kudos to real-world users for testing and feedback

For issues, feature requests, or contributions, please visit mehmet-yoldas/library-core

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