NuvTools.Report.Pdf
10.2.2
dotnet add package NuvTools.Report.Pdf --version 10.2.2
NuGet\Install-Package NuvTools.Report.Pdf -Version 10.2.2
<PackageReference Include="NuvTools.Report.Pdf" Version="10.2.2" />
<PackageVersion Include="NuvTools.Report.Pdf" Version="10.2.2" />
<PackageReference Include="NuvTools.Report.Pdf" />
paket add NuvTools.Report.Pdf --version 10.2.2
#r "nuget: NuvTools.Report.Pdf, 10.2.2"
#:package NuvTools.Report.Pdf@10.2.2
#addin nuget:?package=NuvTools.Report.Pdf&version=10.2.2
#tool nuget:?package=NuvTools.Report.Pdf&version=10.2.2
NuvTools.Report
A .NET library suite for generating reports in PDF, Excel, and CSV formats, plus reading/importing CSV and fixed-length (positional) files. Build structured table-based reports with styling, company branding, and reflection-based data binding. Targets .NET 8, .NET 9, and .NET 10.
Libraries
Installation
# For PDF export
dotnet add package NuvTools.Report.Pdf
# For Excel/CSV export and CSV/fixed-length reading
dotnet add package NuvTools.Report.Sheet
# Or install the base library only (abstractions and models)
dotnet add package NuvTools.Report
Features
- Export: PDF (landscape A4), Excel (.xlsx), CSV with configurable delimiters
- Import: CSV and fixed-length (positional) file reading with attribute-based mapping
- Data binding: Automatic population from object collections via reflection
- Custom converters: Extensible field conversion with
IFieldConverter/FieldConverter<T> - Styling: Customizable colors, fonts, and formatting
- Branding: Company logos, names, and URLs in headers
- Metadata: Titles, filter descriptions, issue dates, user info
- Output: Base64-encoded output for API transmission
- Multi-table: Documents with separate worksheets/pages
- PDF merging: Combine multiple PDFs into one
- DI integration:
AddReportServices()andAddPdfReportServices()extensions
Architecture
Namespace Structure
The library follows an abstraction-implementation pattern:
| Abstraction (NuvTools.Report) | Implementation |
|---|---|
NuvTools.Report.Pdf |
NuvTools.Report.Pdf.* (QuestPDF + PDFsharp) |
NuvTools.Report.Sheet.Csv |
NuvTools.Report.Sheet.Csv (ClosedXML) |
NuvTools.Report.Sheet.Excel |
NuvTools.Report.Sheet.Excel (ClosedXML) |
NuvTools.Report.Sheet.FixedLength |
NuvTools.Report.Sheet.FixedLength |
NuvTools.Report.Sheet.Parsing |
Shared parsing utilities and converters |
Document Model
The library uses a document-table-component hierarchy:
Document
└── Tables (List<Table>)
├── Info (metadata: name, title, company info, issue date/user)
├── Style (formatting: colors, fonts)
└── Content (Body)
├── Header
│ └── Columns (List<Column>)
└── Rows (List<Row>)
└── Cells (List<Cell>)
Usage
Dependency Injection Setup
using NuvTools.Report.Sheet.Extensions;
using NuvTools.Report.Pdf.Extensions;
builder.Services.AddSheetReportServices(); // ICsvReader, ICsvExporter, IFixedLengthReader, IExcelExporter
builder.Services.AddPdfReportServices(); // IPdfExporter, IPdfMerger
Building a Document
using NuvTools.Report.Table.Models;
using NuvTools.Report.Table.Models.Components;
// Define columns
var columns = new List<Column>
{
new() { Name = "Id", Label = "ID", Order = 1, Format = "" },
new() { Name = "Name", Label = "Product Name", Order = 2, Format = "" },
new() { Name = "Price", Label = "Price", Order = 3, Format = "C2" },
new() { Name = "Date", Label = "Date", Order = 4, Format = "yyyy-MM-dd" }
};
// Create a table
var table = new Table
{
Info = new Info
{
Name = "Products",
Title = "Product Catalog",
CompanyAbbreviation = "ACME Corp",
CompanyUrl = "https://acme.com",
FilterDescription = "All Products",
IssueDate = DateTime.Now,
IssueUser = "john.doe"
},
Style = new Style
{
BackgroundHeaderColor = "#003366",
FontHeaderColor = "#FFFFFF"
},
Content = new Body()
};
// Populate rows from objects using reflection
table.SetRows(columns, products);
// Create document
var document = new Document
{
BackgroundDocumentHeaderColor = "#003366",
Tables = [table]
};
PDF Export
using NuvTools.Report.Pdf;
// Via DI
public class ReportService(IPdfExporter pdfExporter, IPdfMerger pdfMerger)
{
public string GeneratePdf(Document document)
{
// Export first table as base64 PDF
return pdfExporter.ExportFirstSheetToPdf(document);
}
public List<string> GenerateAllPdfs(Document document)
{
// Export all tables as separate base64 PDFs
return pdfExporter.ExportSheetToPdf(document);
}
public byte[] MergePdfs(IEnumerable<byte[]> pdfs)
{
return pdfMerger.Merge(pdfs);
}
}
Excel Export
using NuvTools.Report.Sheet.Excel;
// Via DI
public class ReportService(IExcelExporter excelExporter)
{
public string GenerateExcel(Document document)
{
// Returns base64-encoded .xlsx with styled headers, column definitions, and data rows
string base64 = excelExporter.ExportToExcel(document);
// Save to file
byte[] bytes = Convert.FromBase64String(base64);
File.WriteAllBytes("report.xlsx", bytes);
return base64;
}
}
CSV Export
using NuvTools.Report.Sheet.Csv;
// Via DI
public class ReportService(ICsvExporter csvExporter)
{
public void ExportCsv(Document document)
{
// Default delimiter is comma (,) per RFC 4180
// Header row (column labels) is included by default
List<string> csvFiles = csvExporter.ExportToCsv(document);
string csvBase64 = csvExporter.ExportFirstSheetToCsv(document);
// Use a different delimiter
string csvSemicolon = csvExporter.ExportFirstSheetToCsv(document, CsvDelimiter.Semicolon);
string csvTab = csvExporter.ExportFirstSheetToCsv(document, CsvDelimiter.Tab);
// Use a custom delimiter
string csvPipe = csvExporter.ExportFirstSheetToCsv(document, CsvDelimiter.Custom, "||");
// Export without the header row
string csvNoHeader = csvExporter.ExportFirstSheetToCsv(document, includeHeader: false);
// By default, delimiter occurrences in values/headers are removed to prevent CSV corruption
// Disable sanitization if you want to keep raw values
string csvRaw = csvExporter.ExportFirstSheetToCsv(document, sanitizeDelimiter: false);
}
}
CSV Reading / Importing
Map CSV content to strongly-typed objects using attributes:
using NuvTools.Report.Sheet.Csv;
using NuvTools.Report.Sheet.Csv.Attributes;
using NuvTools.Report.Sheet.Parsing;
// Define your record model
[CsvRecord(Delimiter = CsvDelimiter.Semicolon)]
public class ProductRecord
{
[CsvField(0, Caption = "ID")]
public int Id { get; set; }
[CsvField(1, Caption = "Name")]
public string Name { get; set; } = "";
[CsvField(2, Caption = "Price", Trim = TrimMode.Both)]
public decimal Price { get; set; }
[CsvField(3, Caption = "Date", Format = "yyyy-MM-dd")]
public DateTime Date { get; set; }
[CsvField(4)]
public string? OptionalNote { get; set; }
}
// Read CSV via DI
public class ImportService(ICsvReader csvReader)
{
public List<ProductRecord> ImportFromString(string csvContent)
{
return csvReader.ReadString<ProductRecord>(csvContent);
}
public List<ProductRecord> ImportFromBase64(string base64)
{
return csvReader.ReadBase64<ProductRecord>(base64);
}
public List<ProductRecord> ImportFromStream(Stream stream)
{
return csvReader.ReadStream<ProductRecord>(stream, new CsvReaderOptions
{
SkipHeader = true,
IgnoreEmptyLines = true,
HandleQuotedFields = true
});
}
public List<ProductRecord> ImportWithDelimiterOverride(string csvContent)
{
// Override the attribute-level delimiter at runtime
return csvReader.ReadString<ProductRecord>(csvContent, new CsvReaderOptions
{
Delimiter = CsvDelimiter.Comma
});
}
}
Generating CSV Headers
using NuvTools.Report.Sheet.Csv;
// Get ordered captions from CsvField attributes
IEnumerable<string> captions = typeof(ProductRecord).GetFieldCaptions();
// ["ID", "Name", "Price", "Date", "OptionalNote"]
// Get a formatted header line
string header = typeof(ProductRecord).GetCsvHeader(CsvDelimiter.Semicolon);
// "ID;Name;Price;Date;OptionalNote"
Fixed-Length (Positional) File Reading
Map fixed-width columns to objects using attributes:
using NuvTools.Report.Sheet.FixedLength;
using NuvTools.Report.Sheet.FixedLength.Attributes;
using NuvTools.Report.Sheet.Parsing;
// Define your record model
[FixedLengthRecord(AllowShorterLines = true)]
public class BankRecord
{
[FixedLengthField(0, 3)] // positions 0-2 (3 chars)
public string BankCode { get; set; } = "";
[FixedLengthField(1, 5, Trim = TrimMode.Both)] // positions 3-7 (5 chars)
public string BranchCode { get; set; } = "";
[FixedLengthField(2, 15, Trim = TrimMode.Both)] // positions 8-22 (15 chars)
public decimal Amount { get; set; }
[FixedLengthField(3, 8, Format = "yyyyMMdd")] // positions 23-30 (8 chars)
public DateTime TransactionDate { get; set; }
[FixedLengthField(4, 30, Optional = true, Trim = TrimMode.Right)] // positions 31-60 (optional)
public string? Description { get; set; }
}
// Read via DI
public class BankImportService(IFixedLengthReader reader)
{
public List<BankRecord> Import(string content)
{
return reader.ReadString<BankRecord>(content);
}
public List<BankRecord> ImportWithFilter(Stream stream)
{
return reader.ReadStream<BankRecord>(stream, new FixedLengthReaderOptions
{
IgnoreEmptyLines = true,
LineFilter = line => line.StartsWith("D") // only detail lines
});
}
public List<BankRecord> ImportFromBase64(string base64)
{
return reader.ReadBase64<BankRecord>(base64);
}
}
Custom Field Converters
Create custom converters for special parsing logic:
using NuvTools.Report.Sheet.Parsing.Converters;
// Implement IFieldConverter directly
public class BrazilianDecimalConverter : IFieldConverter
{
public object? Convert(string value, string? format)
{
// "000000150" with format "2" → 1.50
var decimalPlaces = int.Parse(format ?? "2");
var raw = long.Parse(value);
return raw / (decimal)Math.Pow(10, decimalPlaces);
}
}
// Or use the generic base class for type safety
public class PercentageConverter : FieldConverter<decimal>
{
public override decimal Convert(string value, string? format)
{
return decimal.Parse(value) / 100m;
}
}
// Use in attributes
public class FinancialRecord
{
[CsvField(0)]
public string Code { get; set; } = "";
[CsvField(1, Converter = typeof(BrazilianDecimalConverter), Format = "2")]
public decimal Amount { get; set; }
[FixedLengthField(0, 10, Converter = typeof(PercentageConverter))]
public decimal Rate { get; set; }
}
Built-in Type Conversions
The following types are supported out of the box (no custom converter needed):
| Type | Notes |
|---|---|
string |
Passed through as-is |
int, long, short |
Parsed with InvariantCulture |
decimal, double, float |
Parsed with InvariantCulture |
bool |
Supports true/false and 1/0 |
DateTime |
Supports optional format string (e.g. "yyyyMMdd") |
DateOnly |
Supports optional format string |
Guid |
Standard GUID parsing |
| Enums | Case-insensitive Enum.Parse |
| Nullable versions | All above types supported as T? (empty/whitespace → null) |
Dependencies
NuvTools.Report.Pdf
- QuestPDF
- PDFsharp
NuvTools.Report.Sheet
- ClosedXML
Building
This solution uses the .slnx (XML-based solution) format.
dotnet build NuvTools.Report.slnx
dotnet build NuvTools.Report.slnx -c Release
dotnet test NuvTools.Report.slnx
Project Structure
nuvtools-report/
├── src/
│ ├── NuvTools.Report/ # Core models and abstractions
│ │ ├── Pdf/ # IPdfExporter, IPdfMerger
│ │ ├── Sheet/
│ │ │ ├── Csv/ # ICsvReader, ICsvExporter, attributes, options
│ │ │ ├── Excel/ # IExcelExporter
│ │ │ ├── FixedLength/ # IFixedLengthReader, attributes, options
│ │ │ └── Parsing/ # TrimMode, ParseException, converters
│ │ └── Table/Models/ # Document, Table, Info, Style, Body, Row, Cell, Column
│ ├── NuvTools.Report.Pdf/ # PDF implementation (QuestPDF + PDFsharp)
│ └── NuvTools.Report.Sheet/ # Sheet implementation (ClosedXML)
├── tests/
│ ├── NuvTools.Report.Pdf.Tests/
│ └── NuvTools.Report.Sheet.Tests/
├── NuvTools.Report.slnx
└── README.md
License
This project requires license acceptance. See the LICENSE file for details.
Links
| 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
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- NuvTools.Report (>= 10.2.2)
- PDFsharp (>= 6.2.4 && < 6.3.0)
- QuestPDF (>= 2026.2.3)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- NuvTools.Report (>= 10.2.2)
- PDFsharp (>= 6.2.4 && < 6.3.0)
- QuestPDF (>= 2026.2.3)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- NuvTools.Report (>= 10.2.2)
- PDFsharp (>= 6.2.4 && < 6.3.0)
- QuestPDF (>= 2026.2.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.