Excelsior
Excelsior is a Excel spreadsheet generation library with a distinctive data-driven approach.
Install / Use
/learn @SimonCropp/ExcelsiorREADME
<img src="/src/icon.png" height="30px"> Excelsior
Excelsior is a Excel spreadsheet generation library with a distinctive data-driven approach.
See Milestones for release notes.
Supported libraries
The architecture is designed to support multiple spreadsheet creation libraries.
Currently supported libraries include:
- ClosedXML via the ExcelsiorClosedXml nuget
- Aspose.Cells via the ExcelsiorAspose nuget
- Syncfusion XlsIO via the ExcelsiorSyncfusion nuget
- DocumentFormat.OpenXml via the ExcelsiorOpenXml nuget. Uses OpenXmlHtml for HTML cell rendering.
Usage
Model
Given an input class:
<!-- snippet: Employee.cs --><a id='snippet-Employee.cs'></a>
using Excelsior;
public class Employee
{
[Column(Heading = "Employee ID", Order = 1)]
public required int Id { get; init; }
[Column(Heading = "Full Name", Order = 2)]
public required string Name { get; init; }
[Column(Heading = "Email Address", Order = 3)]
public required string Email { get; init; }
[Column(Heading = "Hire Date", Order = 4)]
public Date? HireDate { get; init; }
[Column(Heading = "Annual Salary", Order = 5)]
public int Salary { get; init; }
public bool IsActive { get; init; }
public EmployeeStatus Status { get; init; }
}
<sup><a href='/src/Model/Employee.cs#L1-L23' title='Snippet source file'>snippet source</a> | <a href='#snippet-Employee.cs' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->[ColumnAttribute] is optional. If omitted:
- Order is based on the order of the properties defined in the class. Order can be programmatically controlled
- Heading text is based on the property names that is camel case split. Headings can be programmatically controlled
Builder
BookBuilder is the root entry point.
Once instantiated, the data for multiple sheets can be added.
<!-- snippet: Usage --><a id='snippet-Usage'></a>
var builder = new BookBuilder();
List<Employee> data =
[
new()
{
Id = 1,
Name = "John Doe",
Email = "john@company.com",
HireDate = new(2020, 1, 15),
Salary = 75000,
IsActive = true,
Status = EmployeeStatus.FullTime
},
new()
{
Id = 2,
Name = "Jane Smith",
Email = "jane@company.com",
HireDate = new(2019, 3, 22),
Salary = 120000,
IsActive = true,
Status = EmployeeStatus.FullTime
},
];
builder.AddSheet(data);
using var book = await builder.Build();
<sup><a href='/src/ExcelsiorAspose.Tests/UsageTests.cs#L7-L38' title='Snippet source file'>snippet source</a> | <a href='#snippet-Usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->The above sample builds an instance of the Workbook for the target library:
Aspose.Cells.Workbookfor AsposeClosedXML.Excel.IXLWorkbookfor ClosedXml.ExcelsiorSyncfusion.IDisposableBookfor Syncfusion. Which implements bothSyncfusion.XlsIO.IWorkbook, andSystem.IDisposableExcelsiorOpenXml.OpenXmlBookfor OpenXml.
Result:
<img src="/src/ExcelsiorClosedXml.Tests/UsageTests.Test_Sheet1.png">Worksheet Name
Worksheet defaults to SheetN, when N is a counter. So the first sheet is Sheet1, the second is Sheet2, etc.
The name can be controlled by passing an explicit value.
<!-- snippet: WorksheetName --><a id='snippet-WorksheetName'></a>
var builder = new BookBuilder();
builder.AddSheet(employees, "Employee Report");
<sup><a href='/src/ExcelsiorAspose.Tests/WorksheetName.cs#L9-L14' title='Snippet source file'>snippet source</a> | <a href='#snippet-WorksheetName' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->DataAnnotations Attributes
DisplayAttribute and DisplayNameAttribute from System.ComponentModel.DataAnnotations are supported.
DisplayAttribute and DisplayNameAttribute are support for scenarios where it is not convenient to reference Excelsior from that assembly.<!-- singleLineInclude: DisplayAttributeScenario. path: /docs/DisplayAttributeScenario.include.md -->
<a id='snippet-DataAnnotationsModel'></a>
public class Employee
{
[Display(Name = "Employee ID", Order = 1)]
public required int Id { get; init; }
[Display(Name = "Full Name", Order = 2)]
public required string Name { get; init; }
[Display(Name = "Email Address", Order = 3)]
public required string Email { get; init; }
[Display(Name = "Hire Date", Order = 4)]
public Date? HireDate { get; init; }
[Display(Name = "Annual Salary", Order = 5)]
public int Salary { get; init; }
[DisplayName("IsActive")]
public bool IsActive { get; init; }
public EmployeeStatus Status { get; init; }
}
public enum EmployeeStatus
{
[Display(Name = "Full Time")]
FullTime,
[Display(Name = "Part Time")]
PartTime,
[Display(Name = "Contract")]
Contract,
[Display(Name = "Terminated")]
Terminated
}
<sup><a href='/src/ExcelsiorAspose.Tests/DataAnnotationsTests.cs#L43-L83' title='Snippet source file'>snippet source</a> | <a href='#snippet-DataAnnotationsModel' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Result
<img src="/src/ExcelsiorClosedXml.Tests/DataAnnotationsTests.Simple_Sheet1.png">Saving to a stream
To save to a stream use ToStream().
<a id='snippet-ToStream'></a>
var builder = new BookBuilder();
builder.AddSheet(data);
var stream = new MemoryStream();
await builder.ToStream(stream);
<sup><a href='/src/ExcelsiorAspose.Tests/Saving.cs#L10-L18' title='Snippet source file'>snippet source</a> | <a href='#snippet-ToStream' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Custom Headings
The heading text for a column can be overridden:
Fluent
<!-- snippet: CustomHeadings --><a id='snippet-CustomHeadings'></a>
var builder = new BookBuilder();
builder.AddSheet(employees)
.Column(
_ => _.Name,
_ => _.Heading = "Employee Name");
<sup><a href='/src/ExcelsiorAspose.Tests/Headings.cs#L9-L17' title='Snippet source file'>snippet source</a> | <a href='#snippet-CustomHeadings' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->ColumnAttribute
public class Employee
{
[Column(Heading = "Employee Name")]
public required string Name { get; init; }
DisplayNameAttribute
public class Employee
{
[DisplayName("Employee Name")]
public required string Name { get; init; }
DisplayAttribute
public class Employee
{
[Display(Name = "Employee Name")]
public required string Name { get; init; }
Result:
<img src="/src/ExcelsiorClosedXml.Tests/Headings.Fluent_Sheet1.png">Order of precedence
- Fluent
ColumnAttributeDisplayAttributeDisplayNameAttribute
Column Ordering
The column order can be overridden:
<!-- snippet: ColumnOrdering --><a id='snippet-ColumnOrdering'></a>
var builder = new BookBuilder();
builder.AddSheet(employees)
.Column(_ => _.Email, _ => _.Order = 1)
.Column(_ => _.Name, _ => _.Order = 2)
.Column(_ => _.Salary, _ => _.Order = 3);
<sup><a href='/src/ExcelsiorAspose.Tests/ColumnOrdering.cs#L9-L17' title='Snippet source file'>snippet source</a> | <a href='#snippet-ColumnOrdering' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Result:
<img src="/src/ExcelsiorClosedXml.Tests/ColumnOrdering.Fluent_Sheet1.png">Heading Style
<!-- snippet: HeadingStyle --><a id='snippet-HeadingStyle'></a>
var builder = new BookBuilder(
headingStyle: style =>
{
style.Font.Bold = true;
style.Font.FontColor = XLColor.White;
style.Fill.BackgroundColor = XLColor.DarkBlue;
});
builder.AddSheet(data);
<sup><a href='/src/ExcelsiorClosedXml.Tests/StyleTests.cs#L10-L21' title='Snippet source file'>snippet source</a> | <a href='#snippet-HeadingStyle' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->Result:
<img src="/src/ExcelsiorClosedXml.Tests/StyleTests.HeadingStyle_Sheet1.png">Global Style
<!-- snippet: GlobalStyle --><a id='snippet-GlobalStyle'></a>
var builder = new BookBuilder(
globalStyle: style =>
{
style.Font.Bold = true;
style.Font.FontColor = XLColor.White;
style.Fill.BackgroundColor = XLColor.DarkBlue;
});
builder.AddSheet(data);
<sup><a href='/src/ExcelsiorClosedXml.Tests/StyleTests.cs#L33-L44' title='Snippet source file'>snippet source</a> | <a href='#snippet-GlobalStyle' title='Start of snippet'>a
Related Skills
node-connect
342.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
84.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
84.7kCommit, push, and open a PR
