StashPup.Core
0.1.0
See the version list below for details.
dotnet add package StashPup.Core --version 0.1.0
NuGet\Install-Package StashPup.Core -Version 0.1.0
<PackageReference Include="StashPup.Core" Version="0.1.0" />
<PackageVersion Include="StashPup.Core" Version="0.1.0" />
<PackageReference Include="StashPup.Core" />
paket add StashPup.Core --version 0.1.0
#r "nuget: StashPup.Core, 0.1.0"
#:package StashPup.Core@0.1.0
#addin nuget:?package=StashPup.Core&version=0.1.0
#tool nuget:?package=StashPup.Core&version=0.1.0
StashPup ๐ถ
<p align="center"> <img src="stashpup.png" alt="StashPup Logo" width="200" style="border-radius: 20px;"/> </p>
A flexible, provider-agnostic file storage library for .NET with built-in ASP.NET Core integration.
StashPup simplifies file storage in .NET applications by providing a unified interface for local filesystem, AWS S3, and Azure Blob Storage. It includes advanced features like thumbnail generation, signed URLs, metadata management, and powerful search capabilities.
โจ Features
Core Capabilities
- โ Multi-Provider Support - Seamlessly switch between Local, S3, and Azure Blob Storage
- โ Railway-Oriented Error Handling - Type-safe Result<T> pattern instead of exceptions
- โ Advanced Search - Filter by name patterns, content type, size, dates, and custom metadata
- โ Thumbnail Generation - Automatic image thumbnail creation with configurable sizes
- โ File Metadata - Store and query custom key-value metadata with each file
- โ Bulk Operations - Upload or delete multiple files in a single operation
- โ Signed URLs - Generate time-limited secure download links
- โ Content Type Detection - Magic byte detection for accurate MIME types
- โ Pagination - Built-in paginated listing and search results
- โ SHA-256 Hashing - Optional file integrity verification
ASP.NET Core Integration
- โ Dependency Injection - First-class DI support with fluent configuration
- โ Minimal API Endpoints - Pre-built REST endpoints for common operations
- โ File Serving Middleware - Serve local files via HTTP with optional signed URL validation
- โ IFormFile Support - Direct upload from ASP.NET Core forms
- โ Configuration-Driven - Configure via appsettings.json or code
๐ฆ Installation
# Core library (required)
dotnet add package StashPup.Core
# Choose your storage provider(s)
dotnet add package StashPup.Storage.Local
dotnet add package StashPup.Storage.S3
dotnet add package StashPup.Storage.Azure
# ASP.NET Core integration (optional)
dotnet add package StashPup.AspNetCore
๐ Quick Start
1. Basic Setup (Minimal API)
var builder = WebApplication.CreateBuilder(args);
// Add StashPup with local storage
builder.Services.AddStashPup(stash => stash
.UseLocalStorage(options =>
{
options.BasePath = "./uploads";
options.BaseUrl = "/files";
options.MaxFileSizeBytes = 10 * 1024 * 1024; // 10MB
options.AllowedExtensions = [".jpg", ".png", ".pdf"];
}));
var app = builder.Build();
// Add pre-built endpoints
app.MapStashPupEndpoints("/api/files");
app.Run();
2. Upload a File
app.MapPost("/upload", async (IFormFile file, StashPupService service) =>
{
var result = await service.UploadAsync(
file,
folder: "documents",
metadata: new Dictionary<string, string>
{
["uploaded-by"] = "john@example.com",
["category"] = "invoice"
});
return result.Success
? Results.Ok(result.Data)
: Results.BadRequest(result.ErrorMessage);
});
3. Download a File
app.MapGet("/download/{id:guid}", async (Guid id, IFileStorage storage) =>
{
var result = await storage.GetAsync(id);
var metadata = await storage.GetMetadataAsync(id);
return result.Success
? Results.File(result.Data!, metadata.Data!.ContentType, metadata.Data.Name)
: Results.NotFound();
});
๐ฏ Core Concepts
Result Pattern
StashPup uses a railway-oriented programming approach. All operations return Result<T>:
public class Result<T>
{
public bool Success { get; }
public T? Data { get; }
public string? ErrorMessage { get; }
public string? ErrorCode { get; }
}
Usage:
var result = await storage.SaveAsync(stream, "photo.jpg");
if (result.Success)
{
Console.WriteLine($"File saved with ID: {result.Data!.Id}");
}
else
{
Console.WriteLine($"Error: {result.ErrorMessage} (Code: {result.ErrorCode})");
}
// Or use implicit bool conversion
if (result)
{
// Success!
}
File Records
Every stored file has an associated FileRecord with rich metadata:
public class FileRecord
{
public Guid Id { get; set; } // Unique identifier
public string Name { get; set; } // Current name
public string OriginalName { get; set; } // Original upload name
public string Extension { get; set; } // File extension
public string ContentType { get; set; } // MIME type
public long SizeBytes { get; set; } // File size
public DateTime CreatedAtUtc { get; set; } // Creation timestamp
public DateTime UpdatedAtUtc { get; set; } // Last modified
public string? Hash { get; set; } // SHA-256 hash (optional)
public string? Folder { get; set; } // Folder/prefix
public string StoragePath { get; set; } // Provider-specific path
public Dictionary<string, string>? Metadata { get; set; } // Custom metadata
}
๐ง Storage Providers
Local Filesystem
builder.Services.AddStashPup(stash => stash
.UseLocalStorage(options =>
{
options.BasePath = "./uploads";
options.BaseUrl = "/files";
options.OverwriteExisting = false;
options.AutoCreateDirectories = true;
options.EnableSignedUrls = true;
options.SigningKey = "your-secret-key-here";
}));
// Enable file serving middleware
app.UseStashPup();
AWS S3
builder.Services.AddStashPup(stash => stash
.UseS3(options =>
{
options.BucketName = "my-bucket";
options.Region = "us-east-1";
options.AccessKeyId = "AKIA...";
options.SecretAccessKey = "secret";
options.PublicRead = false;
options.EnableEncryption = true;
options.StorageClass = "STANDARD";
}));
Azure Blob Storage
builder.Services.AddStashPup(stash => stash
.UseAzureBlob(options =>
{
options.ConnectionString = "DefaultEndpointsProtocol=https;...";
options.ContainerName = "uploads";
options.AccessTier = "Hot";
options.PublicAccess = false;
options.CreateContainerIfNotExists = true;
}));
Configuration via appsettings.json
{
"StashPup": {
"Provider": "S3",
"S3": {
"BucketName": "my-bucket",
"Region": "us-east-1",
"EnableEncryption": true
}
}
}
builder.Services.AddStashPup(builder.Configuration);
๐ Common Operations
Search Files
var searchParams = new SearchParameters
{
NamePattern = "invoice*.pdf",
ContentType = "application/pdf",
MinSizeBytes = 1024,
CreatedAfter = DateTime.UtcNow.AddDays(-30),
Metadata = new Dictionary<string, string>
{
["category"] = "invoice"
},
SortBy = SearchSortField.CreatedAt,
SortDirection = SearchSortDirection.Descending,
Page = 1,
PageSize = 20
};
var result = await storage.SearchAsync(searchParams);
Generate Thumbnails
var thumbnailResult = await storage.GetThumbnailAsync(
fileId,
ThumbnailSize.Medium);
if (thumbnailResult.Success)
{
return Results.File(thumbnailResult.Data!, "image/jpeg");
}
Move Files Between Folders
var result = await storage.MoveAsync(
id: fileId,
newFolder: "archive/2024");
Copy Files
var result = await storage.CopyAsync(
id: fileId,
newFolder: "backups");
// Returns a new FileRecord with a new ID
Console.WriteLine($"Copied to new ID: {result.Data!.Id}");
Bulk Operations
// Bulk upload
var items = new[]
{
new BulkSaveItem(stream1, "file1.jpg", "images"),
new BulkSaveItem(stream2, "file2.jpg", "images"),
new BulkSaveItem(stream3, "file3.jpg", "images")
};
var results = await storage.BulkSaveAsync(items);
// Bulk delete
var idsToDelete = new[] { id1, id2, id3 };
await storage.BulkDeleteAsync(idsToDelete);
๐ Signed URLs
Generate Signed URLs (S3 & Azure)
// Time-limited download URL
var urlResult = await storage.GetSignedUrlAsync(
fileId,
expiry: TimeSpan.FromHours(1));
if (urlResult.Success)
{
// Share this URL with users
var downloadUrl = urlResult.Data;
}
Local Storage Signed URLs
// Configure
options.EnableSignedUrls = true;
options.SigningKey = "your-secret-key";
// Generate
var url = storage.GetSignedUrl(fileId, TimeSpan.FromMinutes(30));
// Returns: /files/{id}?expires=...&signature=...
// Middleware automatically validates signatures
app.UseStashPup();
๐จ Validation & Security
Configure File Restrictions
options.MaxFileSizeBytes = 50 * 1024 * 1024; // 50MB
options.AllowedExtensions = [".jpg", ".png", ".gif", ".webp"];
options.AllowedContentTypes = ["image/*"]; // Supports wildcards
options.ComputeHash = true; // Enable SHA-256 hashing
Content Type Detection
StashPup automatically detects content types using magic byte analysis:
// Detects based on file signature, not just extension
var result = await storage.SaveAsync(stream, "photo.jpg");
// result.Data.ContentType = "image/jpeg" (verified via magic bytes)
๐ ASP.NET Core Integration
Pre-built Endpoints
app.MapStashPupEndpoints("/api/files", options =>
{
options.RequireAuthorization = true;
options.EnableUpload = true;
options.EnableDownload = true;
options.EnableDelete = true;
options.EnableMetadata = true;
options.EnableList = false; // Disabled by default for security
});
Available Endpoints:
POST /api/files- Upload fileGET /api/files/{id}- Download fileDELETE /api/files/{id}- Delete fileGET /api/files/{id}/metadata- Get metadataGET /api/files?folder=...&page=1&pageSize=20- List files (opt-in)
Custom Endpoints
app.MapPost("/api/upload-avatar", async (
IFormFile file,
StashPupService service,
ClaimsPrincipal user) =>
{
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var result = await service.UploadAsync(
file,
folder: $"avatars/{userId}",
metadata: new Dictionary<string, string>
{
["user-id"] = userId!,
["upload-date"] = DateTime.UtcNow.ToString("O")
});
return result.Success
? Results.Ok(new { fileId = result.Data!.Id, url = $"/files/{result.Data.Id}" })
: Results.BadRequest(new { error = result.ErrorMessage });
});
๐ Advanced Usage
Custom Naming Strategy
options.NamingStrategy = (originalFileName) =>
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var extension = Path.GetExtension(originalFileName);
return $"{timestamp}_{Guid.NewGuid()}{extension}";
};
Custom Subfolder Strategy
options.SubfolderStrategy = (fileRecord) =>
{
// Organize by year/month
var now = DateTime.UtcNow;
return $"{now.Year}/{now.Month:D2}";
};
Direct IFileStorage Usage
public class DocumentService
{
private readonly IFileStorage _storage;
public DocumentService(IFileStorage storage)
{
_storage = storage;
}
public async Task<Result<FileRecord>> SaveInvoice(
Stream pdfStream,
string customerId)
{
return await _storage.SaveAsync(
content: pdfStream,
fileName: $"invoice_{customerId}.pdf",
folder: $"invoices/{DateTime.UtcNow.Year}",
metadata: new Dictionary<string, string>
{
["customer-id"] = customerId,
["document-type"] = "invoice",
["processed"] = "false"
});
}
}
๐งช Testing
StashPup's interface-based design makes testing easy:
public class MockFileStorage : IFileStorage
{
private readonly Dictionary<Guid, FileRecord> _files = new();
public Task<Result<FileRecord>> SaveAsync(...)
{
var record = new FileRecord { Id = Guid.NewGuid(), ... };
_files[record.Id] = record;
return Task.FromResult(Result<FileRecord>.Ok(record));
}
// Implement other methods...
}
๐ Performance Tips
- Use Bulk Operations for multiple files to reduce round-trips
- Enable Hashing Selectively - only when integrity verification is needed
- Cache Thumbnails - they're automatically cached by storage providers
- Use Pagination - don't load all files at once
- Consider Storage Classes - use S3 GLACIER or Azure Cool tier for archives
- Stream Large Files - don't buffer entire files in memory
๐ Error Handling
var result = await storage.SaveAsync(stream, fileName);
if (!result.Success)
{
switch (result.ErrorCode)
{
case FileStorageErrors.MaxFileSizeExceeded:
return Results.BadRequest("File too large");
case FileStorageErrors.InvalidFileExtension:
return Results.BadRequest("File type not allowed");
case FileStorageErrors.FileAlreadyExists:
return Results.Conflict("File already exists");
default:
logger.LogError("Upload failed: {ErrorMessage}", result.ErrorMessage);
return Results.StatusCode(500);
}
}
๐ License
MIT License - see LICENSE file for details
๐ค Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
๐ Additional Documentation
- Full Usage Guide - Detailed examples for every feature
- Integration Guide - Step-by-step project integration
- API Reference - Complete API documentation
- Examples - Real-world code examples
๐ Support
For questions, issues, or feature requests, please open an issue on GitHub.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. 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. |
-
net9.0
- No dependencies.
NuGet packages (4)
Showing the top 4 NuGet packages that depend on StashPup.Core:
| Package | Downloads |
|---|---|
|
StashPup.Storage.S3
AWS S3 storage provider for StashPup. Implements IFileStorage for Amazon S3 with pre-signed URLs, server-side encryption, and storage classes. Supports thumbnail caching and metadata storage in S3 object metadata. |
|
|
StashPup.Storage.Local
Local filesystem storage provider for StashPup. Implements IFileStorage for local disk storage with folder organization, metadata files, and thumbnail generation. Perfect for development, testing, or self-hosted scenarios. |
|
|
StashPup.Storage.Azure
Azure Blob Storage provider for StashPup. Implements IFileStorage for Azure Blob Storage with SAS tokens, access tiers, and public access support. Supports thumbnail caching and metadata storage in blob metadata. |
|
|
StashPup.AspNetCore
ASP.NET Core integration for StashPup. Provides pre-built minimal API endpoints, middleware for file serving, and IFormFile support. Includes dependency injection extensions for easy configuration. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version 0.1.0 - Initial Release
- Core IFileStorage interface with 15 methods
- Result pattern for error handling
- FileRecord model with complete metadata
- SearchParameters for advanced filtering
- Support for pagination, sorting, and bulk operations
- Thumbnail size enumeration
- Validation and error handling