High-performance CSV and XLSX exporting library for .NET 8+ built on CsvHelper and SpreadCheetah. Convention-based with fluent ExportRule API, supports async streaming, multi-sheet XLSX (1M+ rows), auto-zip for large files, column formatting, decimal precision, enum handling, and minimal API integration. Zero-config with intelligent defaults.
$ dotnet add package PandaTech.FileExporterHigh-performance CSV and XLSX exporting library for .NET 8+ with convention-based defaults, fluent configuration, async streaming, multi-sheet support, and automatic compression.
dotnet add package PandaTech.FileExporter
using FileExporter.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Scan for ExportRule<T> configurations
builder.AddFileExporter(typeof(Program).Assembly);
using FileExporter.Extensions;
var products = await db.Products.ToListAsync();
// CSV export with automatic file naming and formatting
var csvFile = await products.ToFileFormatAsync(ExportFormat.Csv);
// XLSX export with multi-sheet support for large datasets
var excelFile = await products.ToFileFormatAsync(ExportFormat.Xlsx);
// Return from minimal API
return csvFile.ToFileResult();
That's it! The library uses conventions to:
CreatedDate → "Created Date")using FileExporter.Rules;
using FileExporter.Enums;
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public DateTime CreatedDate { get; set; }
public ProductStatus Status { get; set; }
}
public class ProductExportRule : ExportRule<Product>
{
public ProductExportRule()
{
// Custom file name (supports {DateTime} placeholder)
WithName("Product Report {DateTime}");
// Configure columns
RuleFor(x => x.Id)
.WriteToColumn("Product ID")
.HasOrder(1);
RuleFor(x => x.Name)
.WriteToColumn("Product Name")
.HasOrder(2)
.HasWidth(30);
RuleFor(x => x.Price)
.WriteToColumn("Price (USD)")
.HasFormat(ColumnFormatType.Currency)
.HasPrecision(2)
.HasOrder(3);
RuleFor(x => x.CreatedDate)
.WriteToColumn("Created")
.HasFormat(ColumnFormatType.DateTime)
.HasOrder(4);
RuleFor(x => x.Status)
.WriteToColumn("Status")
.WithEnumFormat(EnumFormatMode.Name) // Int, Name, or MixedIntAndName
.HasOrder(5);
}
}
Without configuration, the library automatically:
| Property Type | Format Applied | Column Width | Example Output |
|---|---|---|---|
string | Text | Based on header | "Product Name" |
int, long | Integer | 12 | 1234 |
decimal, double | Decimal (2 places) | 12 | 99.99 |
DateTime | DateTime | 19 | 2024-01-15 14:30:00 |
DateOnly | Date | 12 | 2024-01-15 |
bool | Yes/No | 8 | Yes |
enum | Mixed int + name | Based on header | 1 - Active |
| Method | Description | Example |
|---|---|---|
WriteToColumn(string) | Set column header | .WriteToColumn("Full Name") |
HasOrder(int) | Set column position | .HasOrder(1) |
HasWidth(int) | Set column width (chars) | .HasWidth(25) |
HasFormat(ColumnFormatType) | Set format type | .HasFormat(ColumnFormatType.Currency) |
HasPrecision(int) | Set decimal places | .HasPrecision(4) |
WithEnumFormat(EnumFormatMode) | Enum display mode | .WithEnumFormat(EnumFormatMode.Name) |
WithDefaultValue(string) | Default for nulls | .WithDefaultValue("N/A") |
Transform(Func) | Custom transformation | .Transform(x => x?.ToUpper()) |
Ignore() | Exclude from export | .Ignore() |
public enum ColumnFormatType
{
Default, // Auto-detect from property type
Text, // Force as text
Integer, // Whole numbers
Decimal, // Fixed decimal places
Currency, // Currency formatting with symbol
Percentage, // Percentage with % symbol
Date, // Date only (yyyy-MM-dd)
DateTime, // Date and time
Boolean // Yes/No
}
public enum EnumFormatMode
{
MixedIntAndName, // "1 - Active" (default)
Int, // "1"
Name // "Active"
}
Files with >1,048,575 rows automatically split into multiple sheets:
var hugeDataset = await db.Orders.ToListAsync(); // 3 million rows
var file = await hugeDataset.ToFileFormatAsync(ExportFormat.Xlsx);
// Creates single .xlsx with 3 sheets: "Orders", "Orders_2", "Orders_3"
Files >10MB automatically compress to ZIP:
var largeExport = await data.ToFileFormatAsync(ExportFormat.Csv);
// If >10MB: returns "Report 2024-01-15.zip" containing "Report 2024-01-15.csv"
// If <10MB: returns "Report 2024-01-15.csv" directly
IAsyncEnumerable<Product> GetProductsAsync()
{
await foreach (var product in db.Products.AsAsyncEnumerable())
{
yield return product;
}
}
var stream = GetProductsAsync();
var file = await stream.ToCsvAsync();
RuleFor(x => x.Email)
.Transform(email => email?.Contains("@") == true
? MaskEmail(email)
: "N/A");
RuleFor(x => x.Price)
.Transform(price => price * 1.20m) // Add 20% markup
.HasFormat(ColumnFormatType.Currency);
RuleFor(x => x.Tags)
.Transform(tags => string.Join(", ", tags)); // List<string> → "tag1, tag2"
app.MapGet("/export/products/csv", async (AppDbContext db) =>
{
var products = await db.Products.ToListAsync();
var file = await products.ToFileFormatAsync(ExportFormat.Csv);
return file.ToFileResult();
});
app.MapGet("/export/products/xlsx", async (AppDbContext db) =>
{
var products = await db.Products.ToListAsync();
var file = await products.ToFileFormatAsync(ExportFormat.Xlsx);
return file.ToFileResult();
});
Default naming:
typeof(Product) → "Product 2024-01-15 14:30:00"
Custom naming with placeholder:
WithName("Sales Report {DateTime}")
// Output: "Sales Report 2024-01-15 14:30:00"
Fixed name:
WithName("Monthly_Export")
// Output: "Monthly Export 2024-01-15 14:30:00" (DateTime still appended)
var file = await data.ToFileFormatAsync(ExportFormat.Csv);
var file = await data.ToFileFormatAsync(ExportFormat.Xlsx);
var file = await asyncData.ToCsvAsync();
var file = await asyncData.ToXlsxAsync();
var file = await asyncData.ToFileFormatAsync(ExportFormat.Csv);
var file = await data.ToFileFormatAsync(ExportFormat.Xlsx);
// Get file properties
string name = file.Name; // "Products 2024-01-15 14:30:00.xlsx"
string mimeType = file.MimeType; // "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
byte[] content = file.Content;
// Return from API
return file.ToFileResult();
Built on industry-standard libraries:
Benchmarks (1M rows, 10 columns):
| Feature | Limit | Behavior |
|---|---|---|
| XLSX rows per sheet | 1,048,575 | Auto-creates additional sheets |
| XLSX sheet name | 31 characters | Auto-truncates |
| File size before zip | 10 MB | Auto-compresses to ZIP |
// Model
public class Order
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public decimal TotalAmount { get; set; }
public DateTime OrderDate { get; set; }
public OrderStatus Status { get; set; }
public string? Notes { get; set; }
}
// Export configuration
public class OrderExportRule : ExportRule<Order>
{
public OrderExportRule()
{
WithName("Order Export {DateTime}");
RuleFor(x => x.OrderId)
.WriteToColumn("Order #")
.HasOrder(1);
RuleFor(x => x.CustomerName)
.WriteToColumn("Customer")
.HasWidth(30)
.HasOrder(2);
RuleFor(x => x.TotalAmount)
.WriteToColumn("Total")
.HasFormat(ColumnFormatType.Currency)
.HasPrecision(2)
.HasOrder(3);
RuleFor(x => x.OrderDate)
.WriteToColumn("Date")
.HasFormat(ColumnFormatType.DateTime)
.HasOrder(4);
RuleFor(x => x.Status)
.WriteToColumn("Status")
.WithEnumFormat(EnumFormatMode.Name)
.HasOrder(5);
RuleFor(x => x.Notes)
.WriteToColumn("Notes")
.WithDefaultValue("No notes")
.HasOrder(6);
}
}
// Usage
var orders = await db.Orders.ToListAsync();
var file = await orders.ToFileFormatAsync(ExportFormat.Xlsx);
return file.ToFileResult();
MIT