Reading an XLSX file in C# is a routine integration task—finance exports, operations spreadsheets, legacy vendor feeds—but the .xlsx format is a ZIP archive of XML parts, not a flat CSV. If you searched c# read xlsx file, you want a reliable approach, library choices, and the edge cases that break production imports.
What you are actually parsing
An XLSX file is an Open Packaging Convention bundle: shared strings, sheet XML, styles, and relationships. You can unzip and parse XML manually; you should not in application code unless you enjoy maintenance pain.
In C#, teams typically choose:
| Approach | Pros | Cons |
|---|---|---|
| ClosedXML | Ergonomic API, good for business apps | Memory use on huge sheets |
| EPPlus (license varies by version/org) | Fast, widely used | Check license for commercial use |
| ExcelDataReader | Streaming read for large files | Read-focused; writing is separate |
| Interop / COM | Full Excel features | Requires Excel installed; server-hostile |
For most services, ClosedXML or EPPlus on .NET 6+ covers imports up to tens of thousands of rows. Switch to streaming readers when files exceed available RAM or arrive unpredictably large.
Quick start with ClosedXML
Install the NuGet package ClosedXML, then open a workbook and treat rows like a grid:
using ClosedXML.Excel;
public sealed class RowDto
{
public string Sku { get; init; } = "";
public decimal Price { get; init; }
public DateOnly EffectiveDate { get; init; }
}
public static IReadOnlyList<RowDto> ReadCatalog(string path)
{
using var workbook = new XLWorkbook(path);
var sheet = workbook.Worksheet(1);
var rows = new List<RowDto>();
foreach (var dataRow in sheet.RowsUsed().Skip(1)) // skip header
{
var sku = dataRow.Cell(1).GetString().Trim();
if (string.IsNullOrEmpty(sku))
continue;
var priceCell = dataRow.Cell(2);
if (!priceCell.TryGetValue(out decimal price))
throw new FormatException($"Invalid price on row {dataRow.RowNumber()}");
var dateCell = dataRow.Cell(3);
if (!dateCell.TryGetValue(out DateTime dt))
throw new FormatException($"Invalid date on row {dataRow.RowNumber()}");
rows.Add(new RowDto
{
Sku = sku,
Price = price,
EffectiveDate = DateOnly.FromDateTime(dt),
});
}
return rows;
}
Key ideas in that snippet:
RowsUsed()skips empty trailing rows but not blank lines inside the sheet—validate required columns.TryGetValueavoids exceptions on empty cells when you expect optional fields.- Header row handling belongs in one place; do not magic-number columns across methods.
EPPlus alternative (read-only mindset)
EPPlus uses a similar workbook model:
using OfficeOpenXml;
ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // set per your license
public static IEnumerable<string[]> ReadLines(string path)
{
using var package = new ExcelPackage(new FileInfo(path));
var sheet = package.Workbook.Worksheets[0];
var endRow = sheet.Dimension?.End.Row ?? 0;
var endCol = sheet.Dimension?.End.Column ?? 0;
for (var r = 2; r <= endRow; r++)
{
var line = new string[endCol];
for (var c = 1; c <= endCol; c++)
line[c - 1] = sheet.Cells[r, c].Text?.Trim() ?? "";
yield return line;
}
}
Use Text when you need displayed values; use Value when you need typed numeric/date values and understand Excel’s serial date numbers.
Large files and streaming
When imports scale to hundreds of thousands of rows:
- Prefer ExcelDataReader with
AsDataSet()disabled; read row-by-row. - Avoid loading entire sheets into
List<T>before validation—stream to database with batch inserts. - Set
CommandTimeoutand batch size on bulk copy operations.
Watch memory LOH pressure if you allocate massive strings per cell; reuse buffers where the library allows.
Validation and error reporting users understand
Spreadsheet imports fail in predictable ways:
- Merged cells shift perceived columns—reject files with merges in data regions or detect via API flags.
- Dates stored as text (
"01/02/2023") parse differently by culture—forceCultureInfo.InvariantCultureor ISO formats in templates. - Leading zeros on SKUs disappear when Excel treats columns as numbers—import SKU columns as text in templates and read as string.
- Formula cells may need calculated values; libraries often expose cached values only if the file was saved in Excel.
Return errors as row, column, message triples so operations teams can fix sources without reading stack traces.
Security notes
Treat uploads as untrusted input:
- Validate extension and MIME, but parse with a library—do not trust double extensions.
- Scan size limits before decompression; ZIP bombs target XLSX pipelines.
- Store files outside web roots; virus scan if users upload binaries.
- Do not enable macros (XLSM) unless you have an isolated macro analysis story—prefer XLSX without VBA.
Testing imports
Keep golden files in test assets:
valid_minimal.xlsxmissing_required_column.xlsxeuropean_decimal_comma.xlsxif you support multiple localesextra_blank_rows.xlsx
Run tests on Linux CI even if authors use Excel on Windows—date serial and locale differences surface quickly.
ASP.NET Core endpoint sketch
[HttpPost("catalog/import")]
public async Task<IActionResult> Import(IFormFile file, CancellationToken ct)
{
if (file.Length == 0 || file.Length > 20 * 1024 * 1024)
return BadRequest("File empty or too large");
var tempPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
await using (var fs = System.IO.File.Create(tempPath))
await file.CopyToAsync(fs, ct);
try
{
var rows = ReadCatalog(tempPath);
await _catalogService.UpsertAsync(rows, ct);
return Ok(new { imported = rows.Count });
}
finally
{
System.IO.File.Delete(tempPath);
}
}
Use IFormFile limits, virus scan hooks, and background processing if parsing exceeds HTTP timeouts.
FAQ
Should I use CSV instead of XLSX?
If you control both sides, CSV (with UTF-8 and clear headers) simplifies parsing. XLSX appears constantly from business users—support it at boundaries.
Why does my date shift by one day?
Timezone conversion on DateTime values. Prefer DateOnly for calendar dates without time, and document that templates must use true date cells.
Can I read XLSX without third-party packages?
Only with significant effort (ZIP + XML). NuGet libraries are the industry standard.
Does ClosedXML work on .NET 8?
Yes for current releases—pin versions in CI and read release notes for breaking changes.
How do I handle multiple sheets?
Iterate workbook.Worksheets, select by name (Worksheet("Prices")) rather than index when files come from humans who reorder tabs.
Reading XLSX in C# boils down to pick a maintained library, validate rows explicitly, and design for malformed business files instead of ideal demo spreadsheets. That keeps your import pipeline boring—in the best way—when finance sends the quarterly workbook again.
Mapping columns by header name
Hard-coding column indexes breaks when someone inserts a column. Read the header row into a dictionary:
var headerRow = sheet.Row(1);
var colMap = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
foreach (var cell in headerRow.CellsUsed())
colMap[cell.GetString().Trim()] = cell.Address.ColumnNumber;
int Col(string name) =>
colMap.TryGetValue(name, out var c) ? c : throw new InvalidDataException($"Missing column {name}");
Then reference Col("SKU") instead of magic numbers—operations teams can reorder columns without redeploying code.
Background jobs and idempotency
Large imports belong in a queue worker (Hangfire, Azure Functions, etc.) with progress reporting. Make imports idempotent with natural keys (SKU + EffectiveDate) so retries after partial failure do not duplicate rows. Store file checksums to reject duplicate uploads from impatient users clicking submit twice.
Accessibility and template distribution
Publish a locked template XLSX with data validation dropdowns and protected header rows. Users make fewer structural mistakes, and your parser sees consistent shapes. Document required columns in the sheet’s first tab and link to internal docs from the upload UI.
When stakeholders ask for “Excel support,” clarify whether they need one-time migration (CSV export may suffice) or ongoing self-service uploads. The engineering cost differs by an order of magnitude; XLSX parsers are maintainable when the upload volume and business value justify them.
Further Reading
Discover more articles on similar topics across our network
Comments
Loading comments…