This tutorial builds a quarterly sales report in XLSX using C# and Xceed Workbooks for .NET. You’ll load transaction data into a worksheet, apply number formats and conditional styling, add SUM and AVERAGE formulas, and save the file with a frozen header. The same patterns scale to multi-sheet financial packs, pivot-style summaries, and scheduled exports from ASP.NET Core. Alternatives in this space include EPPlus and ClosedXML; the trade-offs section compares when each fits. By the end you’ll have a runnable example and a checklist for production reports.
Finance teams ask for the same thing every quarter: a clean Excel file with totals, regional breakdowns, and formatting that survives a forward to the CFO. When you need to .NET create Excel file output that ships to real users, raw OpenXML feels like writing assembly. The Open XML SDK from Microsoft works, but it forces you to model parts, relationships, and shared strings by hand.
Xceed Workbooks for .NET handles that plumbing and exposes a cell-and-style API you can read at a glance. This walkthrough builds a working sales report from a list of transactions, applies number formatting and conditional bold highlighting, writes formula cells, and saves the result. The same code runs on .NET Framework 4.6.1+, .NET 6, .NET 8, and .NET 9.
Before writing any code, install the package from Xceed.Workbooks.NET on NuGet. The 45-day trial works without a license key, so you can run the full sample below as soon as the package restores.
Why Xceed Workbooks for .NET for sales reports
Sales reports have three properties that punish thin libraries: variable row counts, mixed data types per column, and stakeholders who reformat the file after delivery. Therefore the library you pick has to write real Excel objects, not CSV with an .xlsx extension.
Xceed Workbooks for .NET writes the OOXML SpreadsheetML format defined in ECMA-376, which means Excel, LibreOffice, Google Sheets, and Power BI all open the file without warnings. Cells keep their native types, formulas recalculate on open, and styles round-trip correctly. Alternatives in this space include EPPlus and ClosedXML; both are solid choices, and the section near the end of this article describes when each fits.
What the finished report contains
The sample produces a single worksheet named Q4 Sales. It contains a frozen header row, six data columns (Date, Region, Sales Rep, Product, Units, Revenue), one totals row with SUM formulas, an average revenue cell, and bold highlighting on rows where revenue exceeds a threshold. That covers about 80% of what business users request before they ask for charts.
Project setup and the data model
Create a console project targeting .NET 8 and add the package reference. The CLI commands are straightforward:
Terminal commands
cd SalesReport
dotnet add package Xceed.Workbooks.NET
Next, define the transaction record. Records are concise and immutable, which matches how reporting pipelines usually pass data around:
SalesTransaction record
using System.Collections.Generic;
using Xceed.Workbooks.NET;
public record SalesTransaction(
DateTime Date,
string Region,
string SalesRep,
string Product,
int Units,
decimal Revenue);
For the demo, generate transactions in memory. In production this list comes from EF Core, Dapper, or a service call; the report code below does not care where the rows originate.
XLSX C#: writing the header and data
The Workbooks API mirrors how a person describes a spreadsheet: open a workbook, grab a worksheet, set cell values by A1 reference. Here is the first half of the report builder, which writes the title, the column headers, and the data rows:
BuildReport: title, headers, rows
{
using (var workbook = Workbook.Create(path))
{
var sheet = workbook.Worksheets[0];
// Title row
sheet.Cells["A1"].Value = "Q4 Sales Report";
sheet.Cells["A1"].Style.Font.Bold = true;
sheet.Cells["A1"].Style.Font.Size = 16;
// Header row at row 3
string[] headers = { "Date", "Region", "Sales Rep", "Product", "Units", "Revenue" };
for (int c = 0; c < headers.Length; c++)
{
var cell = sheet.Cells[2, c];
cell.Value = headers[c];
cell.Style.Font.Bold = true;
}
// Data rows starting at row 4 (index 3)
for (int r = 0; r < data.Count; r++)
{
var tx = data[r];
int row = 3 + r;
sheet.Cells[row, 0].Value = tx.Date;
sheet.Cells[row, 1].Value = tx.Region;
sheet.Cells[row, 2].Value = tx.SalesRep;
sheet.Cells[row, 3].Value = tx.Product;
sheet.Cells[row, 4].Value = tx.Units;
sheet.Cells[row, 5].Value = tx.Revenue;
}
workbook.Save();
}
}
Why native types matter
DateTime and decimal values pass straight through. Excel sees them as native date and number types because the library writes the correct cell type token, not a string representation. Consequently, sorting and filtering inside Excel work as expected.
Formatting, totals, and styled output
Raw numbers without formatting fail the CFO test. The next block adds a totals row, currency formatting on the revenue column, a date format on column A, and a bold highlight on high-revenue rows. Append this section before the workbook.Save() call:
Formats + conditional bold + totals + average
int firstDataRow = 3;
int lastDataRow = 2 + data.Count;
for (int row = firstDataRow; row <= lastDataRow; row++)
{
sheet.Cells[row, 0].Style.CustomFormat = "yyyy-mm-dd";
sheet.Cells[row, 5].Style.CustomFormat = "$#,##0.00";
// Highlight rows where revenue > 5000
if (data[row - firstDataRow].Revenue > 5000m)
{
sheet.Cells[row, 5].Style.Font.Bold = true;
}
}
// Totals row
int totalsRow = lastDataRow + 2;
sheet.Cells[totalsRow, 3].Value = "Totals";
sheet.Cells[totalsRow, 3].Style.Font.Bold = true;
sheet.Cells[totalsRow, 4].Formula = $"=SUM(E{firstDataRow + 1}:E{lastDataRow + 1})";
sheet.Cells[totalsRow, 5].Formula = $"=SUM(F{firstDataRow + 1}:F{lastDataRow + 1})";
sheet.Cells[totalsRow, 5].Style.CustomFormat = "$#,##0.00";
// Average revenue cell below totals
sheet.Cells[totalsRow + 1, 3].Value = "Avg Revenue";
sheet.Cells[totalsRow + 1, 5].Formula = $"=AVERAGE(F{firstDataRow + 1}:F{lastDataRow + 1})";
sheet.Cells[totalsRow + 1, 5].Style.CustomFormat = "$#,##0.00";
Formulas stay correct after edits
The Formula property writes a real Excel formula. When the user opens the file, Excel computes the totals. Therefore the file stays correct even if a reviewer manually edits a revenue cell—the SUM updates live.
Running the sample
Wire up a small Main that builds demo data and calls the function. Run with dotnet run and open SalesReport.xlsx:
Program.Main
{
var data = new List<SalesTransaction>
{
new(new DateTime(2024, 10, 3), "East", "Alice", "Pro Plan", 12, 7200m),
new(new DateTime(2024, 10, 5), "West", "Ben", "Starter", 5, 1250m),
new(new DateTime(2024, 11, 12), "East", "Alice", "Enterprise", 3, 9450m)
};
BuildReport(data, "SalesReport.xlsx");
Console.WriteLine("Report written.");
}
Using Xceed Workbooks for .NET in ASP.NET Core
Most teams want this report served from an HTTP endpoint rather than a console app. The pattern is identical, but instead of a file path, write the workbook to a MemoryStream and return it as a file result. Refer to the ASP.NET Core file handling docs for response streaming guidance.
Controller endpoint: return sales-q4.xlsx
public IActionResult GetSalesReport()
{
var data = _service.GetQuarterlyTransactions();
var stream = new MemoryStream();
using (var workbook = Workbook.Create(stream))
{
// (same building logic as BuildReport)
workbook.Save();
}
stream.Position = 0;
return File(stream,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"sales-q4.xlsx");
}
Compatibility note
Because Xceed Workbooks targets .NET Standard 2.0 in addition to modern .NET, the same controller pattern works in legacy .NET Framework Web API projects as well as .NET 8 minimal APIs.
When to choose which library
Choosing a spreadsheet library is a fit problem, not a ranking problem. Here is a neutral summary of the most common options:
- Xceed Workbooks for .NET: commercial, supported, predictable cell/style API, paid license for production with a 45-day trial. Good when you need vendor support and a stable API surface.
- EPPlus: dual-licensed (commercial for business use since v5), mature feature set, large community.
- ClosedXML: MIT-licensed wrapper over the Open XML SDK, free, active community on GitHub.
- Other commercial libraries: several options exist, each with strengths in conversion fidelity, charting, and integrations.
Pick the library that matches your licensing model, your support expectations, and the breadth of Excel features you need. For Xceed customers already shipping DataGrid for WPF or Words for .NET, sharing a single vendor and license model often outweighs marginal feature differences.
Practical tips and pitfalls
A few habits keep production reports stable:
Production checklist
- Wrap workbook creation in a using block. The library buffers data until Save(); disposing flushes everything and releases the underlying stream.
- Set styles after writing values when looping. Style assignment is per-cell; setting it once per cell avoids repeated allocations.
- Prefer formulas over precomputed values for totals. Users edit reports; live formulas keep totals correct.
- Stream to MemoryStream for HTTP responses. Writing to disk and re-reading wastes IO and complicates cleanup.
- Guard against empty datasets. If the input list is empty, write a single “No data” row instead of producing a zero-row sheet that confuses Excel filters.
Ship the sales report this sprint
Download Xceed Workbooks for .NET and run the sample above with a 45-day trial—no license key required.
Frequently asked questions
How do I .NET create Excel file output without installing Office?
Xceed Workbooks for .NET writes the OOXML XLSX format directly, so the host machine does not need Microsoft Office, Excel Interop, or any COM components. The library works in Windows containers, Linux containers, and Azure App Service.
Does Xceed Workbooks for .NET support formulas?
Yes. Set the Formula property on a cell to any valid Excel formula string (for example, =SUM(F4:F23)). Excel evaluates the formula when the user opens the file, and standard functions like SUM, AVERAGE, IF, and VLOOKUP all work as written.
What .NET versions are supported?
The Xceed.Workbooks.NET package targets .NET Standard 2.0, which means it runs on .NET Framework 4.6.1 and later, .NET Core 2.0+, and all modern .NET versions including .NET 6, .NET 8, and .NET 9.
Can I create multiple worksheets in one XLSX C# file?
Yes. Call workbook.Worksheets.Add("SheetName") to add additional sheets. Each worksheet exposes the same Cells indexer, so the building code from this article applies unchanged to multi-sheet financial packs.
How does the trial license work?
The 45-day trial runs without any license key code. After purchase, set the license key once at application startup. The library otherwise behaves identically during and after the trial period.
How do I read an existing XLSX file?
Use Workbook.Load(path) instead of Workbook.Create. Then access workbook.Worksheets[0].Cells["A1"].Value to read values. The same API supports modify-and-save round trips on existing files.