Tigris.Storage
1.0.0
See the version list below for details.
dotnet add package Tigris.Storage --version 1.0.0
NuGet\Install-Package Tigris.Storage -Version 1.0.0
<PackageReference Include="Tigris.Storage" Version="1.0.0" />
<PackageVersion Include="Tigris.Storage" Version="1.0.0" />
<PackageReference Include="Tigris.Storage" />
paket add Tigris.Storage --version 1.0.0
#r "nuget: Tigris.Storage, 1.0.0"
#:package Tigris.Storage@1.0.0
#addin nuget:?package=Tigris.Storage&version=1.0.0
#tool nuget:?package=Tigris.Storage&version=1.0.0
Tigris.Storage
A reusable .NET 8 class library for uploading, deleting, and resolving URLs for images, videos, and documents using Tigris (S3-compatible object storage).
Installation
dotnet add package Tigris.Storage
Configuration
Add the following to your appsettings.json:
Single Bucket (simplest)
All three services share one bucket.
"Tigris": {
"EndpointUrl": "https://fly.storage.tigris.dev",
"AccessKeyId": "your-access-key-id",
"SecretAccessKey": "your-secret-access-key",
"BucketName": "my-bucket",
"CustomDomainUrl": "https://cdn.example.com"
}
CustomDomainUrlis optional. When omitted, the public URL is constructed fromEndpointUrlandBucketName.
Named Buckets (multiple buckets)
Each service maps to its own bucket.
"Tigris": {
"EndpointUrl": "https://fly.storage.tigris.dev",
"AccessKeyId": "your-access-key-id",
"SecretAccessKey": "your-secret-access-key",
"Buckets": {
"images": { "BucketName": "my-images", "CustomDomainUrl": "https://img.cdn.com" },
"videos": { "BucketName": "my-videos" },
"documents": { "BucketName": "my-docs" }
}
}
Public vs Private Buckets
Each bucket can be marked as public or private. This controls ACL and URL behaviour:
"Buckets": {
"images": { "BucketName": "my-images", "IsPublic": true },
"documents": { "BucketName": "my-docs", "IsPublic": false, "DefaultPresignedUrlExpiry": "01:00:00" }
}
| Setting | Default | Description |
|---|---|---|
IsPublic |
true |
Public buckets return a permanent URL. Private buckets return a presigned GET URL. |
DefaultPresignedUrlExpiry |
1 hour |
How long presigned URLs are valid (applies to private buckets and explicit presigned calls). |
Registration
Single Bucket
// Program.cs
builder.Services.AddTigrisStorage(builder.Configuration);
Named Buckets
// Program.cs
builder.Services.AddTigrisStorage(builder.Configuration, buckets =>
{
buckets.UseImageBucket("images");
buckets.UseVideoBucket("videos");
buckets.UseDocumentBucket("documents");
});
You only need to register the services you use. If your app does not handle documents, simply omit
UseDocumentBucket.
Usage
Inject the service interfaces via constructor injection.
Images
public class ProfileController(ITigrisImageService images) : ControllerBase
{
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
var result = await images.UploadFormFileAsync(file, folder: "avatars");
if (!result.IsSuccess)
return BadRequest(new { result.Message, result.ErrorCode });
// Store result.FileKey in your database — not FileUrl.
// For private buckets, FileUrl is a presigned URL that expires.
return Ok(new { result.FileKey, result.FileUrl });
}
[HttpPost("upload-base64")]
public async Task<IActionResult> UploadBase64([FromBody] string base64Image)
{
var result = await images.UploadBase64ImageAsync(base64Image, folder: "avatars");
if (!result.IsSuccess)
return BadRequest(new { result.Message, result.ErrorCode });
return Ok(new { result.FileKey, result.FileUrl });
}
[HttpDelete("{fileKey}")]
public async Task<IActionResult> Delete(string fileKey)
{
var deleted = await images.DeleteImageAsync(fileKey);
return deleted ? Ok() : StatusCode(500);
}
// Generate a fresh presigned URL from a stored FileKey
[HttpGet("{fileKey}/url")]
public IActionResult GetUrl(string fileKey)
{
var url = images.GetImageUrl(fileKey);
return Ok(new { url });
}
}
Videos
public class VideoController(ITigrisVideoService videos) : ControllerBase
{
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file, CancellationToken ct)
{
var result = await videos.UploadVideoAsync(file, folder: "clips", cancellationToken: ct);
if (!result.IsSuccess)
return BadRequest(new { result.Message, result.ErrorCode });
return Ok(new { result.FileKey, result.FileUrl });
}
[HttpDelete("{fileKey}")]
public async Task<IActionResult> Delete(string fileKey)
{
var deleted = await videos.DeleteVideoAsync(fileKey);
return deleted ? Ok() : StatusCode(500);
}
}
Documents
public class DocumentController(ITigrisDocumentService documents) : ControllerBase
{
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file, CancellationToken ct)
{
var result = await documents.UploadDocumentAsync(file, folder: "reports", cancellationToken: ct);
if (!result.IsSuccess)
return BadRequest(new { result.Message, result.ErrorCode });
return Ok(new { result.FileKey, result.FileUrl });
}
[HttpDelete("{fileKey}")]
public async Task<IActionResult> Delete(string fileKey)
{
var deleted = await documents.DeleteDocumentAsync(fileKey);
return deleted ? Ok() : StatusCode(500);
}
}
Presigned URLs
Use presigned URLs to grant temporary access to private files, or to let clients upload directly to Tigris without routing bytes through your server.
// Generate a time-limited GET URL for a private file
var downloadUrl = images.GetPresignedUrl("avatars/3f2a1b.jpg", expiry: TimeSpan.FromMinutes(15));
// Generate a presigned PUT URL for direct client-side upload
var uploadUrl = images.GetPresignedUploadUrl("avatars/3f2a1b.jpg", "image/jpeg", expiry: TimeSpan.FromMinutes(5));
API Reference
TigrisUploadResponse
All upload methods return TigrisUploadResponse.
| Property | Type | Description |
|---|---|---|
IsSuccess |
bool |
Whether the upload succeeded |
Message |
string |
Error message on failure, empty on success |
FileKey |
string? |
Storage key (e.g. avatars/3f2a1b.jpg). Always persist this — not FileUrl |
FileUrl |
string? |
URL at time of upload. Permanent for public buckets, presigned (expiring) for private buckets |
ErrorCode |
TigrisErrorCode |
Structured error code for programmatic handling |
TigrisErrorCode
| Value | Meaning |
|---|---|
None |
No error (success) |
InvalidFormat |
File extension is not allowed |
FileTooLarge |
File exceeds the size limit |
UploadFailed |
Upload failed due to a storage or network error |
Cancelled |
Upload was cancelled via CancellationToken |
ITigrisImageService
| Method | Description |
|---|---|
UploadFormFileAsync(file, fileName?, folder?, quality?, maxWidth?) |
Upload from IFormFile. Resizes and compresses automatically. Default quality: 75, max width: 1920px |
UploadBase64ImageAsync(base64, fileName?, folder?, quality?, maxWidth?) |
Upload from a base64 data URI. Max size: 100MB |
DeleteImageAsync(fileKey) |
Delete an image by its storage key |
GetImageUrl(fileKey, expiry?) |
Permanent URL for public buckets; presigned GET URL for private buckets |
GetPresignedUrl(fileKey, expiry?) |
Presigned GET URL regardless of bucket visibility |
GetPresignedUploadUrl(fileKey, contentType, expiry?) |
Presigned PUT URL for direct client-side uploads |
Supported formats: JPG, JPEG, PNG
ITigrisVideoService
| Method | Description |
|---|---|
UploadVideoAsync(file, fileName?, folder?, cancellationToken?) |
Upload a video using S3 multipart upload (10MB parts). Max size: 500MB |
DeleteVideoAsync(fileKey) |
Delete a video by its storage key |
GetVideoUrl(fileKey, expiry?) |
Permanent URL for public buckets; presigned GET URL for private buckets |
GetPresignedUrl(fileKey, expiry?) |
Presigned GET URL regardless of bucket visibility |
GetPresignedUploadUrl(fileKey, contentType, expiry?) |
Presigned PUT URL for direct client-side uploads |
Supported formats: MP4, WebM, MOV
ITigrisDocumentService
| Method | Description |
|---|---|
UploadDocumentAsync(file, fileName?, folder?, cancellationToken?) |
Upload a document. Max size: 50MB |
DeleteDocumentAsync(fileKey) |
Delete a document by its storage key |
GetDocumentUrl(fileKey, expiry?) |
Permanent URL for public buckets; presigned GET URL for private buckets |
GetPresignedUrl(fileKey, expiry?) |
Presigned GET URL regardless of bucket visibility |
GetPresignedUploadUrl(fileKey, contentType, expiry?) |
Presigned PUT URL for direct client-side uploads |
Supported formats: PDF, DOC, DOCX, PPT, PPTX, XLS, XLSX
File Keys
When you don't provide a fileName, a UUID is generated automatically (e.g. 3f2a1b.jpg).
The folder parameter prefixes the key: folder/fileName (e.g. avatars/3f2a1b.jpg).
Always store FileKey in your database — not FileUrl. For private buckets, FileUrl is a presigned URL that expires shortly after upload. Use GetImageUrl / GetVideoUrl / GetDocumentUrl with the stored key to regenerate a fresh URL on demand.
Requirements
- .NET 8
- ASP.NET Core (the package uses
IFormFile) - A Tigris account with a bucket and API credentials: tigrisdata.com
| Product | Versions 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. |
-
net8.0
- AWSSDK.S3 (>= 3.7.510.11)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
- SixLabors.ImageSharp (>= 3.1.12)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.