Lyo.Xlsx
3.0.0
dotnet add package Lyo.Xlsx --version 3.0.0
NuGet\Install-Package Lyo.Xlsx -Version 3.0.0
<PackageReference Include="Lyo.Xlsx" Version="3.0.0" />
<PackageVersion Include="Lyo.Xlsx" Version="3.0.0" />
<PackageReference Include="Lyo.Xlsx" />
paket add Lyo.Xlsx --version 3.0.0
#r "nuget: Lyo.Xlsx, 3.0.0"
#:package Lyo.Xlsx@3.0.0
#addin nuget:?package=Lyo.Xlsx&version=3.0.0
#tool nuget:?package=Lyo.Xlsx&version=3.0.0
Lyo.Xlsx
Implements Lyo.Xlsx.Models. XlsxService composes an XlsxWriter (streaming DocumentFormat.OpenXml writer) and an XlsxReader
(ExcelDataReader / ClosedXML) to read and write XLSX workbooks from files, streams, and byte arrays, with helpers for converting to CSV, HTML, and Lyo.DataTable. Multi-targets
netstandard2.0;net10.0; async, custom-header, and formatter export overloads exist only on net10.0.
Export streams rows straight into the worksheet part via OpenXmlWriter, so memory stays bounded regardless of row count. Column widths are approximated from a bounded sample of
the leading rows rather than a full-workbook auto-fit pass.
Features
- Strongly-typed read/write through
IEnumerable<T>; onnet10.0,IAsyncEnumerable<T>export and forward-only streaming reads (ParseXlsx*RowsStreamingAsync/ typedParseXlsx*StreamingAsync) via ExcelDataReader. - Multi-sheet workbooks through
IReadOnlyDictionary<string, IEnumerable<T>>(sheet name → rows). - On read, control sheets with
ListSheetNames, parse one sheet by name or zero-based index (ParseXlsx*AsDictionary/ParseXlsx*AsDataTable/ParseXlsx*AsDataTableWithFormatting), or parse every sheet at once (ParseXlsx*AsAllSheets/…WithFormatting). - Thin vs formatted DataTable import:
AsDataTableskips ClosedXML style reads (values + spans only);AsDataTableWithFormattingfills the sparse(row,col) → DataTableCellFormatmap (absent key = no format; default black/white/theme colors and font size/name are stripped so unstyled sheets stay empty). Export writes styles for mapped cells (unique custom styles capped at 512). Skipping style reads does not remove ClosedXML workbook-load cost. - Parse-scoped value/format pooling through
XlsxOptions.Pooling(PoolValues,PoolFormats,PoolingCellThresholddefault 512; oneDataTableValueInternerper parse).Orderis Defined, Alphabetical, or Specified (OrderNamesand[MemberOrder]). A caller-supplied column list stays in that order. - Incremental multi-sheet write sessions through
CreateDocumentWriter/IXlsxDocumentWriter(typed rows, selected properties,DataTable, or row/column dictionary per sheet; dispose finalizes the workbook). - Cell spanning:
DataTablecells that carryColSpan/RowSpanround-trip as XLSX merged ranges (<mergeCells>on write,MergedRangeson read). - Export selected properties (
IReadOnlyList<PropertyInfo>). Onnet10.0, also custom-header (IReadOnlyDictionary<string, PropertyInfo>) and formatter (IReadOnlyDictionary<string, Func<T, string>>) exports. - Read and write a row/column dictionary (
IReadOnlyDictionary<int, IReadOnlyDictionary<int, string>>). - Round-trip
Lyo.DataTable.Models.DataTableincludingFooter(export always appends footer when present; import peels the last body row whenuseFooterRow: true; formats on map row-2) plus HTML table export (ExportToHtmlTable). - Convert XLSX → CSV to a file, stream, or byte array (
ConvertXlsxToCsv*) with optionalEncoding. - Batch parse helpers (
BatchParseFilesAsDataTable/…Async) that return oneResult<DataTable>per input path. - When wrapping failures in
Result<T>, usesXlsxErrorCodesconstants (XLSX_EXPORT_FAILED,XLSX_PARSE_FAILED,XLSX_OPERATION_CANCELLED,XLSX_FILE_OPERATION_FAILED,XLSX_CONVERT_TO_CSV_FAILED). XlsxService.Defaultuses a header row and the default pooling options. Concurrent calls on one instance are safe.SetExcelDataTableConfigurationpublishes a new configuration, so a call already running keeps the one it started with. Do not reconfigureDefault.
Examples
Add XlsxService in DI
using Lyo.Xlsx;
services.AddXlsxService();
services.AddXlsxService(o => {
o.Pooling.PoolValues = true;
o.Pooling.PoolFormats = true;
o.Pooling.PoolingCellThreshold = 512; // 0 = always when enabled
});
Export and import
public sealed class ReportingService(IXlsxService xlsx)
{
public async Task<byte[]> ExportAsync(IEnumerable<Person> rows, CancellationToken ct)
=> await xlsx.ExportToXlsxBytesAsync(rows, worksheetName: "People", ct: ct);
public async Task<Result<DataTable>> ImportAsync(string path, CancellationToken ct)
=> await xlsx.ParseXlsxFileAsDataTableAsync(path, useHeaderRow: true, ct: ct);
}
public sealed record Person(int Id, string Name, int Age);
Where exports can go
xlsx.ExportToXlsx(rows, "out.xlsx");
xlsx.ExportToXlsx(rows, stream);
byte[] bytes = xlsx.ExportToXlsxBytes(rows);
await xlsx.ExportToXlsxAsync(rows, "out.xlsx", worksheetName: "Data", ct: ct);
await xlsx.ExportToXlsxAsync(rows, stream, worksheetName: "Data", ct: ct);
byte[] bytesAsync = await xlsx.ExportToXlsxBytesAsync(rows, "Data", ct);
Pick and rename columns
IReadOnlyList<PropertyInfo> selected = [
typeof(Person).GetProperty(nameof(Person.Name))!,
typeof(Person).GetProperty(nameof(Person.Age))!,
];
xlsx.ExportToXlsx(rows, selected, "out.xlsx");
IReadOnlyDictionary<string, PropertyInfo> namedColumns = new Dictionary<string, PropertyInfo> {
["Full Name"] = typeof(Person).GetProperty(nameof(Person.Name))!,
["Years"] = typeof(Person).GetProperty(nameof(Person.Age))!,
};
await xlsx.ExportToXlsxAsync(rows, namedColumns, stream, ct: ct);
IReadOnlyDictionary<string, Func<Person, string>> formatters = new Dictionary<string, Func<Person, string>> {
["Display"] = p => $"{p.Name} ({p.Age})",
["Id"] = p => p.Id.ToString("D6"),
};
await xlsx.ExportToXlsxAsync(rows, formatters, stream, ct: ct);
DataTable, dictionary, and HTML
Result<DataTable> thin = xlsx.ParseXlsxFileAsDataTable("in.xlsx", useHeaderRow: true, useFooterRow: true);
Result<DataTable> styled = xlsx.ParseXlsxFileAsDataTableWithFormatting("in.xlsx", useHeaderRow: true, useFooterRow: true);
xlsx.ExportToXlsxFromDataTable(styled.ValueOrThrow(), "out.xlsx"); // writes Footer + formats when present
var grid = xlsx.ParseXlsxFileAsDictionary("in.xlsx");
xlsx.ExportToXlsxFromDictionary(grid, "out.xlsx", useHeaderRow: true, useFooterRow: true);
string html = xlsx.ExportToHtmlTable(File.ReadAllBytes("in.xlsx"), useHeaderRow: true, useFooterRow: true);
XLSX ↔ CSV
xlsx.ConvertXlsxToCsv("in.xlsx", "out.csv");
xlsx.ConvertXlsxToCsv(inputStream, outputStream, Encoding.UTF8);
byte[] csv = xlsx.ConvertXlsxToCsvBytes(File.ReadAllBytes("in.xlsx"));
await xlsx.ConvertXlsxToCsvAsync("in.xlsx", "out.csv", Encoding.UTF8, ct);
byte[] csvAsync = await xlsx.ConvertXlsxToCsvBytesAsync(inputStream, Encoding.UTF8, ct);
Batch parses
IReadOnlyList<Result<DataTable>> sync =
xlsx.BatchParseFilesAsDataTable(paths, useHeaderRow: true, useFooterRow: true);
IReadOnlyList<Result<DataTable>> async =
await xlsx.BatchParseFilesAsDataTableAsync(paths, useHeaderRow: true, useFooterRow: true, ct);
Benchmarks
Async workbook export of 100,000 rows finishes in under a second.
- Portfolio suite:
xlsx - XLSX async export
Dependency injection
AddXlsxService registers a singleton XlsxService and maps IXlsxService, IXlsxWriter, and IXlsxReader to that same instance. Overloads accept Action<XlsxOptions>, an
options instance, or AddXlsxServiceFromConfiguration (binds Xlsx and optional DataTablePooling sections).
Multi-sheet workbooks
var workbook = new Dictionary<string, IEnumerable<Person>> {
["Active"] = activePeople,
["Archived"] = archivedPeople,
};
xlsx.ExportToXlsx(workbook, "people.xlsx");
await xlsx.ExportToXlsxAsync(workbook, stream, ct);
For heterogeneous sheets (different row types or sources per sheet), open an
incremental writing session; each AddSheet* call streams one worksheet, and
disposing the session finalizes the workbook:
using (var doc = xlsx.CreateDocumentWriter("report.xlsx"))
{
doc.AddSheet("People", people); // typed rows
doc.AddSheet("Names", people, selectedProps); // selected properties
doc.AddSheetFromDataTable("Summary", summaryTable); // Lyo DataTable
doc.AddSheetFromDictionary("Raw", grid, useHeaderRow: true, useFooterRow: true);
}
Duplicate sheet names (case-insensitive) are rejected. CreateDocumentWriter(stream)
leaves the destination stream open for the caller; the file-path overload closes the
file on dispose.
Sheet control (read)
IReadOnlyList<string> names = xlsx.ListSheetNames("in.xlsx");
// Select a sheet by name or zero-based index.
var dict = xlsx.ParseXlsxBytesAsDictionary(bytes, "Archived");
Result<DataTable> dt = xlsx.ParseXlsxFileAsDataTable("in.xlsx", 1, useHeaderRow: true);
Result<DataTable> styled = xlsx.ParseXlsxFileAsDataTableWithFormatting("in.xlsx", 1, useHeaderRow: true);
// Or parse everything, keyed by sheet name in workbook order.
IReadOnlyDictionary<string, DataTable> all = xlsx.ParseXlsxStreamAsAllSheets(stream);
The no-arg parse methods keep their first-sheet behavior. AsDataTable is thin (no styles); use AsDataTableWithFormatting when you need the sparse format map. Async variants of
all sheet-control methods are available on net10.0.
Style export limits
Dynamic OpenXML styles cover the fields on DataTableCellFormat (RGB colors, common borders/align/numFmt). Theme colors do not round-trip on import (TryGetColorHex returns null
for theme). Unique custom cell formats cap at 512 per workbook; further formats fall back to the default style. FontSize/FontName are intentionally not imported (ClosedXML defaults
would fill the sparse map).
Types (generated)
ExtensionsXlsxErrorCodesXlsxService
License
Copyright © Lyo
Dependencies
Generated from ProjectReference / PackageReference (same model as docs/Lyo.ProjectGraph.html).
Lyo.Common.Core(direct, lyo)Lyo.Exceptions(direct, lyo)Lyo.Result(direct, lyo)Lyo.Xlsx.Models(direct, lyo)ClosedXML0.105.0(direct, third-party)DocumentFormat.OpenXml3.1.1(direct, third-party)ExcelDataReader3.9.0(direct, third-party)ExcelDataReader.DataSet3.9.0(direct, third-party)Microsoft.Extensions.Configuration10.0.5(direct, microsoft)Microsoft.Extensions.Configuration.Binder10.0.5(direct, microsoft)Microsoft.Extensions.DependencyInjection.Abstractions10.0.5(direct, microsoft)Microsoft.Extensions.Logging.Abstractions10.0.5(direct, microsoft)Microsoft.Extensions.Options10.0.5(direct, microsoft)System.Text.Encoding.CodePages10.0.5(direct, microsoft)Lyo.DataTable.Models(transitive, lyo)Microsoft.Bcl.AsyncInterfaces10.0.5(transitive, microsoft, netstandard2.0)System.Memory4.6.3(transitive, microsoft, netstandard2.0)System.Text.Json10.0.5(transitive, microsoft, netstandard2.0)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- ClosedXML (>= 0.105.0)
- DocumentFormat.OpenXml (>= 3.1.1 && < 4.0.0)
- ExcelDataReader (>= 3.9.0)
- ExcelDataReader.DataSet (>= 3.9.0)
- Lyo.Common.Core (>= 3.0.0)
- Lyo.Exceptions (>= 3.0.0)
- Lyo.Result (>= 3.0.0)
- Lyo.Xlsx.Models (>= 3.0.0)
- Microsoft.Extensions.Configuration (>= 10.0.5)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
- System.Text.Encoding.CodePages (>= 10.0.5)
-
net10.0
- ClosedXML (>= 0.105.0)
- DocumentFormat.OpenXml (>= 3.1.1 && < 4.0.0)
- ExcelDataReader (>= 3.9.0)
- ExcelDataReader.DataSet (>= 3.9.0)
- Lyo.Common.Core (>= 3.0.0)
- Lyo.Exceptions (>= 3.0.0)
- Lyo.Result (>= 3.0.0)
- Lyo.Xlsx.Models (>= 3.0.0)
- Microsoft.Extensions.Configuration (>= 10.0.5)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Lyo.Xlsx:
| Package | Downloads |
|---|---|
|
Lyo.Reporting.Postgres
PostgreSQL reporting data layer with EF Core, optional auto-migrations, and report generation orchestration. |
|
|
Lyo.FileStorage.Web.Components
Reusable Blazor components for file storage: upload/save, path tree browser, DEK/KEK migration and rotation, metadata/expected-storage browser grids. |
|
|
Lyo.Api.Export.Xlsx
XLSX export format handler for Lyo.Api export service. |
|
|
Lyo.Api.Import.Xlsx
XLSX import format handler for Lyo.Api.Import. |
GitHub repositories
This package is not used by any popular GitHub repositories.