Canducci.FileUpload
1.0.0.3
See the version list below for details.
dotnet add package Canducci.FileUpload --version 1.0.0.3
NuGet\Install-Package Canducci.FileUpload -Version 1.0.0.3
<PackageReference Include="Canducci.FileUpload" Version="1.0.0.3" />
<PackageVersion Include="Canducci.FileUpload" Version="1.0.0.3" />
<PackageReference Include="Canducci.FileUpload" />
paket add Canducci.FileUpload --version 1.0.0.3
#r "nuget: Canducci.FileUpload, 1.0.0.3"
#:package Canducci.FileUpload@1.0.0.3
#addin nuget:?package=Canducci.FileUpload&version=1.0.0.3
#tool nuget:?package=Canducci.FileUpload&version=1.0.0.3
Canducci.FileUpload
A file upload library for ASP.NET Core with validation, flexible configuration, and support for synchronous and asynchronous operations.
Portuguese: README_PTBR.md
Version 1.0.0.3 (stable version)
Version 1.0.0.2 (stable version)
What's New
FileUploadStatusenum: Replaces the ambiguousSuccessboolean property with a clear status indicatorAllSucceeded- All files were uploaded successfullyPartialSuccess- Some files succeeded, some failedAllFailed- All files failed to uploadEmpty- No files were provided
SuccessCountproperty: Returns the count of successfully uploaded filesFailureCountproperty: Returns the count of failed uploads- Complete XML documentation in English for all public APIs
FileUploadValidationAttribute: Integration with ASP.NET Core Data Annotations- Multiple
FileUploadOptionsconstructors: Different ways to initialize with various constraints - Case-insensitive extension validation: Safe extension comparison
- .NET 8, 9, and 10 support: Compatibility with multiple .NET versions
- 91 comprehensive unit tests: Complete test coverage for reliability
Installation
Package Manager
Install-Package Canducci.FileUpload
.NET CLI
dotnet add package Canducci.FileUpload
PackageReference
<PackageReference Include="Canducci.FileUpload" Version="1.0.0.3" />
Configuration
Program.cs
Add the service in Program.cs:
using Canducci.FileUpload.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add FileUpload with default configuration (random filename)
builder.Services.AddFileUpload();
// OR with custom configuration (GUID filename)
builder.Services.AddFileUpload(config =>
{
config.FileUploadConfigurationName = FileUploadConfigurationName.Guid;
});
var app = builder.Build();
Configuration Options
| Option | Description |
|---|---|
FileUploadConfigurationName.Random |
Generates random filename (default) |
FileUploadConfigurationName.Guid |
Generates GUID-based filename |
Controller Usage
Basic Example
using Canducci.FileUpload;
using Canducci.FileUpload.Enuns;
using Canducci.FileUpload.Interfaces;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class UploadController : ControllerBase
{
[HttpPost]
public IActionResult Upload(IFileUpload fileUpload)
{
var result = fileUpload.Save("uploads", options =>
{
options.MaxFileSizeBytes = 5 * 1024 * 1024; // 5 MB
options.RejectEmptyFiles = true;
options.AllowedExtensions.Add(".jpg");
options.AllowedExtensions.Add(".png");
options.AllowedExtensions.Add(".pdf");
});
switch (result.Status)
{
case FileUploadStatus.AllSucceeded:
return Ok(new
{
Message = "Upload successful!",
SuccessCount = result.SuccessCount,
Files = result.Files.Select(f => new
{
f.FileNameSent,
f.FileNameSave,
f.Extension,
f.Length,
f.Path
})
});
case FileUploadStatus.PartialSuccess:
return Ok(new
{
Message = "Some files uploaded successfully",
SuccessCount = result.SuccessCount,
FailureCount = result.FailureCount,
Files = result.Files.Where(f => f.Success).Select(f => f.FileNameSave),
Errors = result.Files.Where(f => !f.Success).Select(f => new
{
f.FileNameSent,
Error = f.Exception?.Message
})
});
case FileUploadStatus.AllFailed:
return BadRequest(new
{
Message = "All files failed to upload",
FailureCount = result.FailureCount,
Errors = result.Files.Select(f => new
{
f.FileNameSent,
Error = f.Exception?.Message
})
});
case FileUploadStatus.Empty:
default:
return BadRequest(new { Message = "No files provided" });
}
}
}
Async Example
[HttpPost("async")]
public async Task<IActionResult> UploadAsync(IFileUpload fileUpload, CancellationToken cancellationToken)
{
var result = await fileUpload.SaveAsync("uploads", options =>
{
options.MaxFileSizeBytes = 10 * 1024 * 1024; // 10 MB
options.RejectEmptyFiles = true;
}, cancellationToken);
if (result.Status == FileUploadStatus.AllSucceeded)
{
return Ok(new { Message = "Async upload completed!", result.SuccessCount });
}
return BadRequest(new { Message = "Upload failed", result.FailureCount });
}
Multiple Files Example
[HttpPost("multiple")]
public IActionResult UploadMultiple(IFileUpload fileUpload)
{
if (fileUpload.Count == 0)
{
return BadRequest("No files uploaded");
}
var result = fileUpload.Save("documents", options =>
{
options.MaxFileSizeBytes = 20 * 1024 * 1024; // 20 MB
options.AllowedExtensions.Add(".docx");
options.AllowedExtensions.Add(".xlsx");
options.AllowedExtensions.Add(".pdf");
});
return Ok(new
{
Status = result.Status.ToString(),
TotalUploaded = fileUpload.Count,
Succeeded = result.SuccessCount,
Failed = result.FailureCount,
Files = result.Files.Where(f => f.Success).Select(f => f.FileNameSave),
Errors = result.Files.Where(f => !f.Success).Select(f => new { f.FileNameSent, Error = f.Exception?.Message })
});
}
Predefined Options Example
[HttpPost("images")]
public IActionResult UploadImages(IFileUpload fileUpload)
{
// Parameterized constructor with multiple extensions
var options = new FileUploadOptions(2 * 1024 * 1024, true, ".jpg", ".jpeg", ".png", ".gif", ".webp");
var result = fileUpload.Save("images", options);
return result.Status == FileUploadStatus.AllSucceeded
? Ok(result.Files.Select(f => f.Path))
: BadRequest($"Failed: {result.FailureCount} of {fileUpload.Count} images");
}
Fluent API Example
[HttpPost("fluent")]
public IActionResult UploadFluent(IFileUpload fileUpload)
{
var options = new FileUploadOptions();
options.AllowedExtensions
.AddImages() // All 10 image extensions
.AddDocuments() // All 10 document extensions
.AddArchives(); // All 5 archive extensions
var result = fileUpload.Save("uploads", options);
return Ok(result);
}
Data Annotations Example
using System.ComponentModel.DataAnnotations;
using Canducci.FileUpload.Attributes;
public class FileUploadModel
{
[FileUploadValidation(
MaxFileSizeBytes = 5242880,
RejectEmptyFiles = true,
AllowedExtensions = new[] { ".pdf", ".docx", ".xlsx" }
)]
public IFileUpload Files { get; set; }
}
[HttpPost("validated")]
public IActionResult ValidatedUpload(FileUploadModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = model.Files.Save("documents");
return Ok(result);
}
Validation Options
| Option | Type | Default | Description |
|---|---|---|---|
MaxFileSizeBytes |
long |
10485760 (10 MB) | Maximum file size in bytes |
RejectEmptyFiles |
bool |
true |
Reject empty files (0 bytes) |
AllowedExtensions |
HashSet<string> |
Empty | Allowed extensions (empty = all) |
FileUploadOptions Constructors
// Default constructor with default values
var options1 = new FileUploadOptions();
// Constructor with max size and empty file rejection
var options2 = new FileUploadOptions(5_000_000, false);
// Constructor with all constraints
var options3 = new FileUploadOptions(2_000_000, true, ".pdf", ".docx", ".xlsx");
Result Properties
IFileUploadResult
| Property | Type | Description |
|---|---|---|
Status |
FileUploadStatus |
Overall upload operation status |
SuccessCount |
int |
Number of successfully uploaded files |
FailureCount |
int |
Number of failed uploads |
Files |
IReadOnlyList<IFileItemResult> |
List of per-file results |
FileUploadStatus Enum
| Value | Description |
|---|---|
AllSucceeded |
All files were uploaded successfully |
PartialSuccess |
Some files succeeded, some failed |
AllFailed |
All files failed to upload |
Empty |
No files were provided for upload |
IFileItemResult
| Property | Type | Description |
|---|---|---|
Success |
bool |
Whether the file was saved successfully |
FileNameSent |
string |
Original filename sent by client |
FileNameSave |
string |
Filename saved on server |
Extension |
string |
File extension |
Length |
long |
File size in bytes |
Directory |
string |
Directory where file was saved |
Path |
string |
Full file path |
Exception |
Exception? |
Exception if error occurred |
Error Messages
| Message | Description |
|---|---|
File is Empty |
File is empty and RejectEmptyFiles = true |
File is TooLarge |
File exceeds MaxFileSizeBytes |
ExtensionNotAllowed |
Extension not in AllowedExtensions |
Complete Controller Example
using Canducci.FileUpload;
using Canducci.FileUpload.Enuns;
using Canducci.FileUpload.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Controllers;
[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
[HttpPost("upload")]
[RequestSizeLimit(50 * 1024 * 1024)] // 50 MB request limit
public async Task<IActionResult> Upload(
IFileUpload fileUpload,
CancellationToken cancellationToken)
{
if (fileUpload.Count == 0)
{
return BadRequest(new { Error = "No files uploaded" });
}
// Using parameterized constructor
var options = new FileUploadOptions(
maxFileSizeBytes: 10 * 1024 * 1024,
rejectEmptyFiles: true,
allowedExtensions: ".jpg", ".jpeg", ".png", ".pdf", ".docx");
var result = await fileUpload.SaveAsync("uploads", options, cancellationToken);
return result.Status switch
{
FileUploadStatus.AllSucceeded => Ok(new
{
Message = "All files saved successfully",
Count = result.SuccessCount,
Files = result.Files.Select(f => new { f.FileNameSave, f.Path })
}),
FileUploadStatus.PartialSuccess => Ok(new
{
Message = "Some files were saved",
Succeeded = result.SuccessCount,
Failed = result.FailureCount
}),
FileUploadStatus.AllFailed => BadRequest(new
{
Message = "Failed to save all files",
Errors = result.Files.Select(f => f.Exception?.Message)
}),
_ => BadRequest(new { Error = "No files provided" })
};
}
[HttpPost("images")]
public IActionResult UploadImages(IFileUpload fileUpload)
{
var options = new FileUploadOptions();
options.AllowedExtensions.AddImages(); // All image extensions
var result = fileUpload.Save("images", options);
return result.Status == FileUploadStatus.AllSucceeded
? Ok(result.Files)
: BadRequest(result.Files.Where(f => !f.Success));
}
[HttpPost("documents")]
public IActionResult UploadDocuments(IFileUpload fileUpload)
{
var options = new FileUploadOptions(5 * 1024 * 1024, true);
options.AllowedExtensions.AddDocuments(); // All document extensions
var result = fileUpload.Save("documents", options);
return Ok(result);
}
}
Security Features
- Filename sanitization: Removes dangerous characters and handles path traversal
- Case-insensitive extension validation: Protects against case-based bypasses
- Overwrite prevention: Generates unique names using UUID or random
- Atomic save: Saves to temporary file first, then renames atomically
- CancellationToken support: Safely cancels long-running operations
- Granular error handling: Returns detailed status per file
Supported File Extensions
The library provides constants and fluent methods for common extensions:
Images (10 extensions)
.jpg, .jpeg, .png, .gif, .webp, .svg, .bmp, .tiff, .tif, .heic
Documents (10 extensions)
.pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt, .csv, .odt
Archives (5 extensions)
.zip, .rar, .7z, .tar, .gz
Audio/Video (5 extensions)
.mp3, .wav, .mp4, .mkv, .avi
Library Highlights
Developed with focus on:
- ✅ Security: Rigorous validation and protection against common attacks
- ✅ Performance: Async operations and streaming for large files
- ✅ Usability: Simple and intuitive API with Fluent API support
- ✅ Documentation: Complete XML comments in English
- ✅ Testing: Comprehensive 91 unit test suite
- ✅ Compatibility: Supports .NET 8, 9, and 10
What's New
FileUploadStatusenum: Replaces the ambiguousSuccessboolean property with a clear status indicatorAllSucceeded- All files were uploaded successfullyPartialSuccess- Some files succeeded, some failedAllFailed- All files failed to uploadEmpty- No files were provided
SuccessCountproperty: Returns the count of successfully uploaded filesFailureCountproperty: Returns the count of failed uploads- Complete XML documentation in English for all public APIs
Installation
Package Manager
Install-Package Canducci.FileUpload
.NET CLI
dotnet add package Canducci.FileUpload
PackageReference
<PackageReference Include="Canducci.FileUpload" Version="1.0.0.1" />
Configuration
Program.cs
Add the service in Program.cs:
using Canducci.FileUpload.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add FileUpload with default configuration (random filename)
builder.Services.AddFileUpload();
// OR with custom configuration (GUID filename)
builder.Services.AddFileUpload(config =>
{
config.FileUploadConfigurationName = FileUploadConfigurationName.Guid;
});
var app = builder.Build();
Configuration Options
| Option | Description |
|---|---|
FileUploadConfigurationName.Random |
Generates random filename (default) |
FileUploadConfigurationName.Guid |
Generates GUID-based filename |
Controller Usage
Basic Example
using Canducci.FileUpload;
using Canducci.FileUpload.Enuns;
using Canducci.FileUpload.Interfaces;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class UploadController : ControllerBase
{
[HttpPost]
public IActionResult Upload(IFileUpload fileUpload)
{
var result = fileUpload.Save("uploads", options =>
{
options.MaxFileSizeBytes = 5 * 1024 * 1024; // 5 MB
options.RejectEmptyFiles = true;
options.AllowedExtensions.Add(".jpg");
options.AllowedExtensions.Add(".png");
options.AllowedExtensions.Add(".pdf");
});
switch (result.Status)
{
case FileUploadStatus.AllSucceeded:
return Ok(new
{
Message = "Upload successful!",
SuccessCount = result.SuccessCount,
Files = result.Files.Select(f => new
{
f.FileNameSent,
f.FileNameSave,
f.Extension,
f.Length,
f.Path
})
});
case FileUploadStatus.PartialSuccess:
return Ok(new
{
Message = "Some files uploaded successfully",
SuccessCount = result.SuccessCount,
FailureCount = result.FailureCount,
Files = result.Files.Where(f => f.Success).Select(f => f.FileNameSave),
Errors = result.Files.Where(f => !f.Success).Select(f => new
{
f.FileNameSent,
Error = f.Exception?.Message
})
});
case FileUploadStatus.AllFailed:
return BadRequest(new
{
Message = "All files failed to upload",
FailureCount = result.FailureCount,
Errors = result.Files.Select(f => new
{
f.FileNameSent,
Error = f.Exception?.Message
})
});
case FileUploadStatus.Empty:
default:
return BadRequest(new { Message = "No files provided" });
}
}
}
Async Example
[HttpPost("async")]
public async Task<IActionResult> UploadAsync(IFileUpload fileUpload, CancellationToken cancellationToken)
{
var result = await fileUpload.SaveAsync("uploads", options =>
{
options.MaxFileSizeBytes = 10 * 1024 * 1024; // 10 MB
options.RejectEmptyFiles = true;
}, cancellationToken);
if (result.Status == FileUploadStatus.AllSucceeded)
{
return Ok(new { Message = "Async upload completed!", result.SuccessCount });
}
return BadRequest(new { Message = "Upload failed", result.FailureCount });
}
Multiple Files Example
[HttpPost("multiple")]
public IActionResult UploadMultiple(IFileUpload fileUpload)
{
if (fileUpload.Count == 0)
{
return BadRequest("No files uploaded");
}
var result = fileUpload.Save("documents", options =>
{
options.MaxFileSizeBytes = 20 * 1024 * 1024; // 20 MB
options.AllowedExtensions.Add(".docx");
options.AllowedExtensions.Add(".xlsx");
options.AllowedExtensions.Add(".pdf");
});
return Ok(new
{
Status = result.Status.ToString(),
TotalUploaded = fileUpload.Count,
Succeeded = result.SuccessCount,
Failed = result.FailureCount,
Files = result.Files.Where(f => f.Success).Select(f => f.FileNameSave),
Errors = result.Files.Where(f => !f.Success).Select(f => new { f.FileNameSent, Error = f.Exception?.Message })
});
}
Predefined Options Example
[HttpPost("images")]
public IActionResult UploadImages(IFileUpload fileUpload)
{
var options = new FileUploadOptions
{
MaxFileSizeBytes = 2 * 1024 * 1024, // 2 MB
RejectEmptyFiles = true
};
options.AllowedExtensions.Add(".jpg");
options.AllowedExtensions.Add(".jpeg");
options.AllowedExtensions.Add(".png");
options.AllowedExtensions.Add(".gif");
options.AllowedExtensions.Add(".webp");
var result = fileUpload.Save("images", options);
return result.Status == FileUploadStatus.AllSucceeded
? Ok(result.Files.Select(f => f.Path))
: BadRequest($"Failed: {result.FailureCount} of {fileUpload.Count} images");
}
Validation Options
| Option | Type | Default | Description |
|---|---|---|---|
MaxFileSizeBytes |
long |
10485760 (10 MB) | Maximum file size in bytes |
RejectEmptyFiles |
bool |
true |
Reject empty files (0 bytes) |
AllowedExtensions |
HashSet<string> |
Empty | Allowed extensions (empty = all allowed) |
Result Properties
IFileUploadResult
| Property | Type | Description |
|---|---|---|
Status |
FileUploadStatus |
Overall status of the upload operation |
SuccessCount |
int |
Count of files uploaded successfully |
FailureCount |
int |
Count of files that failed to upload |
Files |
IReadOnlyList<IFileItemResult> |
List of results per file |
FileUploadStatus Enum
| Value | Description |
|---|---|
AllSucceeded |
All files were uploaded successfully |
PartialSuccess |
Some files succeeded, some failed |
AllFailed |
All files failed to upload |
Empty |
No files were provided for upload |
IFileItemResult
| Property | Type | Description |
|---|---|---|
Success |
bool |
Whether the file was saved successfully |
FileNameSent |
string |
Original filename sent by client |
FileNameSave |
string |
Filename saved on server |
Extension |
string |
File extension |
Length |
long |
File size in bytes |
Directory |
string |
Directory where file was saved |
Path |
string |
Full file path |
Exception |
Exception? |
Exception if error occurred |
Error Messages
| Message | Description |
|---|---|
File is Empty |
Empty file and RejectEmptyFiles = true |
File is TooLarge |
File exceeds MaxFileSizeBytes |
ExtensionNotAllowed |
Extension not in AllowedExtensions |
Complete Controller Example
using Canducci.FileUpload;
using Canducci.FileUpload.Enuns;
using Canducci.FileUpload.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Controllers;
[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
[HttpPost("upload")]
[RequestSizeLimit(50 * 1024 * 1024)] // 50 MB request limit
public async Task<IActionResult> Upload(
IFileUpload fileUpload,
CancellationToken cancellationToken)
{
if (fileUpload.Count == 0)
{
return BadRequest(new { Error = "No files uploaded" });
}
var result = await fileUpload.SaveAsync("uploads", options =>
{
options.MaxFileSizeBytes = 10 * 1024 * 1024;
options.RejectEmptyFiles = true;
options.AllowedExtensions.Add(".jpg");
options.AllowedExtensions.Add(".jpeg");
options.AllowedExtensions.Add(".png");
options.AllowedExtensions.Add(".pdf");
options.AllowedExtensions.Add(".docx");
}, cancellationToken);
switch (result.Status)
{
case FileUploadStatus.AllSucceeded:
return Ok(new
{
Message = "Upload completed successfully",
SuccessCount = result.SuccessCount,
Files = result.Files.Select(f => new
{
Original = f.FileNameSent,
Saved = f.FileNameSave,
Size = f.Length,
FullPath = f.Path
})
});
case FileUploadStatus.PartialSuccess:
return Ok(new
{
Message = "Some files uploaded successfully",
SuccessCount = result.SuccessCount,
FailureCount = result.FailureCount,
SuccessfulFiles = result.Files.Where(f => f.Success).Select(f => f.FileNameSave),
Errors = result.Files.Where(f => !f.Success).Select(f => new
{
File = f.FileNameSent,
Error = f.Exception?.Message ?? "Unknown error"
})
});
case FileUploadStatus.AllFailed:
return BadRequest(new
{
Message = "All files failed to upload",
FailureCount = result.FailureCount,
Errors = result.Files.Select(f => new
{
File = f.FileNameSent,
Error = f.Exception?.Message ?? "Unknown error"
})
});
case FileUploadStatus.Empty:
default:
return BadRequest(new { Error = "No files provided" });
}
}
}
Requirements
- .NET 8.0 or later
- ASP.NET Core
License
MIT License
| 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 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 is compatible. 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. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Canducci.FileUpload:
| Package | Downloads |
|---|---|
|
Canducci.FileUpload.FluentValidation
FluentValidation integration for Canducci.FileUpload. |
GitHub repositories
This package is not used by any popular GitHub repositories.