The simplest, fastest and low memory consumption library for writing Excel files to streams. Completely free to use.
$ dotnet add package Gooseberry.ExcelStreamingCreate Excel files with high performance and low memory allocations.
await using var file = new FileStream("myExcelReport.xlsx", FileMode.Create);
await using var writer = new ExcelWriter(file, token: cancellationToken);
// optional sheet configuration
var sheetConfig = new SheetConfiguration(
Columns: [new Column(Width: 10m), new Column(Width: 13m)], // column width
FrozenColumns: 1, // freeze pane: colums count
FrozenRows: 3, // freeze pane: rows count
ShowGridLines: true,
AutoFilter: "A1:G1"); // table headers filter. A1-style string or indexes AutoFilter: new((1,1), (7,1))
await writer.StartSheet("First sheet", sheetConfig);
writer.AddEmptyRows(3); // three empty rows
await foreach(var record in store.GetRecordsAsync(cancellationToken))
{
await writer.StartRow();
writer
.AddEmptyCell() // empty
.AddEmptyCells(5) // five empty cells
.AddCell(42) // int
.AddCell(999_999_999_999_999) // long
.AddCell(DateTime.Now) // DateTime
.AddCell(DateOnly.FromDateTime(DateTime.Now.Date)) // DateOnly
.AddCell(123.765M) // decimal
.AddCell(Math.PI) //double (note: very slow)
.AddCell("string") // string
.AddCell('#') // char
.AddCellUtf8String("string"u8) // utf8 string
.AddCellSharedString("shared"); // shared string
// hyperlink
writer.AddCell(new Hyperlink("https://[address]", "Label text"));
// cell with picture
writer.AddCellPicture(someStreamOrByteArray, PictureFormat.Jpeg, new Size(100, 130));
}
// Adding picture from stream to "First sheet" placed to
// cell (column 4, row 2, values are zero-based) with fixed size
writer.AddPicture(someStream, PictureFormat.Jpeg, new AnchorCell(3, 1), new Size(100, 130));
// Adding picture from byte array or ReadOnlyMemory<byte> to "First sheet"
// with top left corner placed in the cell (column 11:row 2, values are zero-based)
// and right bottom corner is placed in another cell (column 16:row 11)
writer.AddPicture(someByteArray, PictureFormat.Jpeg, new AnchorCell(10, 1), new AnchorCell(15, 10));
await writer.StartSheet("Second sheet");
for (var row = 0; row < 100; row++)
{
... //write rows
}
await writer.Complete(); // DisposeAsync method also will call Complete
More examples
Effective way to write standard and custom types to cell without string allocation.
//write any IUtf8SpanFormattable value as string cell or numeric cell
//format, format provider and styles are optional
writer.AddCellString(Guid.NewGuid(), ['N']);
float value = 0.3f;
writer.AddCellNumber(value);
Writing interpolated strings to cells without allocations (benchmark).
writer.AddCell($"{person.FirstName} {person.LastName}, age {person.Age}");
writer.AddCell($"Now is {DateTime.Now}");
The data isn't accumulated in memory. It is flushed to Http response streamingly.
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.Headers.Add("Content-Disposition", $"attachment; filename=fileName.xlsx");
await Response.StartAsync();
await using var writer = new ExcelWriter(Response.BodyWriter.AsStream(), token: cancellationToken);
await writer.StartSheet("Sheet name");
await writer.StartRow();
writer.AddCell(123);
await writer.Complete();
//add row with specified height
await writer.StartRow(height: 15);
//or specify optional row attributes
await writer.StartRow(new RowAttributes(
Height: 25,
OutlineLevel: 1,
IsCollapsed: true,
IsHidden: true));
// 1. Define styles
var styleBuilder = new StylesSheetBuilder();
staticSomeStyle = styleBuilder.GetOrAdd(
new Style(
Format: "0.00%", // or StandardFormat.PercentPrecision2
Font: new Font(Size: 24, Color: Color.DodgerBlue),
Fill: new Fill(Color: Color.Crimson, FillPattern.Gray125),
Borders: new Borders(
Left: new Border(BorderStyle.Thick, Color.BlueViolet),
Right: new Border(BorderStyle.MediumDashed, Color.Coral)),
Alignment: new Alignment(HorizontalAlignment.Center, VerticalAlignment.Center, false)));
// 2. Build styles. We can reuse single style sheet many times to increase performance.
// Style sheet is immutable and thread safe.
staticStyleSheet = styleBuilder.Build();
// 3. Using styles
await using var writer = new ExcelWriter(file, staticStyleSheet);
await writer.StartSheet("First sheet");
await writer.StartRow(15); //optional row height specified
writer.AddCell(123, staticSomeStyle); // all cells support style reference
await writer.Complete();
Format property can be string, int (any valid numFmtId) or StandardFormat enum value
Shared strings decrease the size of the resulting file when it contains repeated strings. It's implemented as unique list of strings, and cell contains only reference to the list index.
// 1. We can use global shared strings table
var tableBuilder = new SharedStringTableBuilder();
staticSharedStringRef1 = tableBuilder.GetOrAdd("String");
staticSharedStringRef2 = tableBuilder.GetOrAdd("Another String");
// 2. Build table
staticSharedStringTable = sharedStringTableBuilder.Build();
// 3. Using shared strings
await using var writer = new ExcelWriter(stream, sharedStringTable: staticSharedStringTable);
await writer.StartSheet("First sheet");
await writer.StartRow();
// using refernce from global table
writer.AddCell(staticSharedStringRef1);
writer.AddCell(staticSharedStringRef2);
// using local dictionary automatically maintained in the ExcelWriter instance
writer.AddCellSharedString("Some string from exteranal store");
writer.AddCellSharedString("Some string from exteranal store");
await writer.Complete();
Benchmarks results and source code.
Please note .net 9 can be 2x faster than .net 8 for some cases.
Benchmarks below use .net 9.0 runtime.
100 columns: numbers, dates, strings
| RowsCount | Mean | Gen0 | Allocated |
|---|---|---|---|
| 100 | 872.0 μs | 1.9531 | 13.01 KB |
| 1000 | 6,197.9 μs | - | 13.56 KB |
| 10000 | 55,191.2 μs | - | 26.76 KB |
| 100000 | 545,122.2 μs | - | 160.64 KB |
| 500000 | 2,851,101.2 μs | - | 814.27 KB |
| Method | RowsCount | Mean | Gen0 | Gen1 | Gen2 | Allocated |
|---|---|---|---|---|---|---|
| ExcelWriter | 100 | 999.6 μs | 1.9531 | - | - | 13.27 KB |
| SpreadCheetah | 100 | 1,344.5 μs | - | - | - | 7.02 KB |
| OpenXml | 100 | 3,583.9 μs | 187.5000 | 187.5000 | 187.5000 | 1216.56 KB |
| ExcelWriter | 1 000 | 6,506.4 μs | - | - | - | 14.11 KB |
| SpreadCheetah | 1 000 | 12,527.9 μs | - | - | - | 7.03 KB |
| OpenXml | 1 000 | 35,395.4 μs | 1133.3333 | 533.3333 | 466.6667 | 16631.97 KB |
| ExcelWriter | 10 000 | 62,054.4 μs | - | - | - | 27.39 KB |
| SpreadCheetah | 10 000 | 126,385.1 μs | - | - | - | 7.59 KB |
| OpenXml | 10 000 | 350,409.4 μs | 10000.0000 | 3000.0000 | 3000.0000 | 142223.66 KB |
| ExcelWriter | 100 000 | 582,107.2 μs | - | - | - | 171.54 KB |
| SpreadCheetah | 100 000 | 1,233,093.7 μs | - | - | - | 24.17 KB |
| OpenXml | 100 000 | 3,440,059.4 μs | 74000.0000 | 3000.0000 | 3000.0000 | 1226005.71 KB |