To read an Excel file in C#, use the ExcelDataReader NuGet package. It streams rows from .xlsx and .xls files, runs on Windows, macOS, and Linux, and needs no Microsoft Office or COM Interop. If you also write spreadsheets, ClosedXML or ExcelMapper cover both directions.
dotnet add package ExcelDataReaderusing ExcelDataReader;
using var stream = File.Open("Movies.xlsx", FileMode.Open, FileAccess.Read);using var reader = ExcelReaderFactory.CreateReader(stream);
reader.Read(); // skip the header rowwhile (reader.Read()){ var id = Convert.ToInt32(reader.GetValue(0)); var name = reader.GetString(1); var service = reader.GetString(2);
Console.WriteLine($"{id}: {name} ({service})");}1: Dune: Part Two (max)2: Oppenheimer (peacock)That’s the quickest way to read Excel files. The rest of this post covers the other options, including how to read an .xlsx with no library at all. Every sample below uses the same Movie record from my post on writing Excel files in C#, and I compiled and ran each one on .NET 10 against a small Movies.xlsx (Id, Name, StreamingService).
public record Movie(int Id, string Name, string StreamingService);Which C# Library Should I Use to Read an Excel File?
| Library | Best for |
|---|---|
| ExcelDataReader | Fast, forward-only reading of .xlsx and .xls. Read-only. Free and OSS. |
| ExcelMapper | Reading rows straight into a List<T>, and writing them back. Built on NPOI. Free and OSS. |
| ClosedXML | Cell-by-cell access with a friendly API, plus writing and formatting. .xlsx only. Free and OSS. |
| NPOI | Lower-level control and the legacy .xls format. Free and OSS (recent versions ask you to accept a license notice in the project file). |
| Open XML SDK | Microsoft’s official, strongly typed wrapper over the file format. .xlsx only. Free and OSS. |
ZipArchive + XDocument |
Zero dependencies. You parse the XML yourself. .xlsx only. |
| EPPlus | Feature-rich, but commercially licensed since version 5. I wouldn’t start here. |
My rule of thumb: reach for ExcelMapper when you want objects, ExcelDataReader for raw cells or huge files, and ClosedXML when you’ll read and then edit. Can’t add a dependency at all? Keep reading.
How to Read an .xlsx File with ExcelDataReader
ExcelDataReader is a forward-only reader, similar in spirit to SqlDataReader. It never loads the whole workbook into a tree of objects, which is why it’s the quick option for large files. The example at the top is all you need for the .xlsx case.
A few things worth knowing:
- Columns are zero-based, and
GetValuereturnsobject?. Numbers come back asdouble, soConvert.ToInt32is safer than a cast. - A workbook with several sheets? Call
reader.NextResult()to move to the next one, and read the current sheet name fromreader.Name. - For
.xlsfiles on .NET Core and .NET 5+, register the code pages encoding provider once at startup, or you’ll get an exception about encoding 1252:
using System.Text;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);ExcelReaderFactory.CreateReader detects whether the stream is .xls or .xlsx, so the same reading code handles both formats.
using var stream = File.Open("Movies.xls", FileMode.Open, FileAccess.Read);using var reader = ExcelReaderFactory.CreateReader(stream);
do{ while (reader.Read()) { Console.WriteLine($"{reader.GetValue(0)} {reader.GetValue(1)}"); }} while (reader.NextResult());How to Read Excel Rows into Objects with ExcelMapper
When you want a List<Movie> and nothing else, ExcelMapper is the least code. It maps header names to property names.
dotnet add package ExcelMapperusing Ganss.Excel;
var movies = (await new ExcelMapper().FetchAsync<Movie>("Movies.xlsx")).ToList();
foreach (var movie in movies){ Console.WriteLine(movie);}
public record Movie(int Id, string Name, string StreamingService);Movie { Id = 1, Name = Dune: Part Two, StreamingService = max }Movie { Id = 2, Name = Oppenheimer, StreamingService = peacock }It also reads .xls, and the same library writes files back out. I go deeper on that in writing Excel files in C#.
How to Read Cell Values with ClosedXML
If you want to pick specific cells or edit the workbook afterward, ClosedXML is a pleasant API.
dotnet add package ClosedXMLusing ClosedXML.Excel;
using var workbook = new XLWorkbook("Movies.xlsx");var sheet = workbook.Worksheet("Movies");
foreach (var row in sheet.RowsUsed().Skip(1)) // skip the header{ var id = row.Cell(1).GetValue<int>(); var name = row.Cell(2).GetValue<string>(); var service = row.Cell(3).GetValue<string>();
Console.WriteLine($"{id}: {name} ({service})");}GetValue<T>() converts the cell for you, and cells are 1-based. RowsUsed() skips empty rows, so you don’t need to find the last row yourself. You can also address a cell by A1 notation: sheet.Cell("B2").GetValue<string>().
How to Read an .xls (Legacy) File with NPOI
The older binary .xls format isn’t OpenXML, so the ZIP tricks below won’t work on it. ExcelDataReader and ExcelMapper handle it, and so does NPOI directly, which is a good choice if you want a low-level API.
dotnet add package NPOIusing NPOI.HSSF.UserModel;using NPOI.SS.UserModel;
using var stream = File.OpenRead("Movies.xls");IWorkbook workbook = new HSSFWorkbook(stream); // XSSFWorkbook for .xlsxvar sheet = workbook.GetSheetAt(0);
for (var i = 1; i <= sheet.LastRowNum; i++){ var row = sheet.GetRow(i); Console.WriteLine($"{row.GetCell(0).NumericCellValue}: {row.GetCell(1).StringCellValue}");}HSSFWorkbook is for .xls, and XSSFWorkbook is for .xlsx. NPOI’s cell API is typed by cell kind, so reading a numeric cell as a string throws an exception. Check cell.CellType when you don’t control the input to ensure proper conversion is possible. Note that recent NPOI versions print a build warning asking you to accept their license terms with <AcceptNPOIOSMFLicense>true</AcceptNPOIOSMFLicense> in your project file.
How to Read an .xlsx File Without a Library
An .xlsx file is a ZIP archive of XML files, defined by the OpenXML (SpreadsheetML) standard. Rename Movies.xlsx to Movies.zip, open it, and you’ll see the elements that make up the original Excel file. The two files that matter are:
xl/worksheets/sheet1.xml: the cells of the first sheet.xl/sharedStrings.xml: a table of every distinct string in the workbook.
That second file is the classic gotcha. A text cell in sheet1.xml stores an index into the shared strings table, marked with t="s", not the text itself. Numbers are different: they sit inline in the <v> element. If you skip the lookup, your names come out as 0, 1, 2.
Option 1: The Open XML SDK
Microsoft’s DocumentFormat.OpenXml package gives you typed access to those parts. It’s still just a NuGet package, with no Office and no Interop.
dotnet add package DocumentFormat.OpenXmlusing DocumentFormat.OpenXml.Packaging;using DocumentFormat.OpenXml.Spreadsheet;
using var document = SpreadsheetDocument.Open("Movies.xlsx", isEditable: false);var workbookPart = document.WorkbookPart!;
var sharedStrings = workbookPart.SharedStringTablePart?.SharedStringTable .Elements<SharedStringItem>() .Select(s => s.InnerText) .ToList() ?? [];
var sheetPart = workbookPart.WorksheetParts.First();
foreach (var row in sheetPart.Worksheet.Descendants<Row>().Skip(1)){ var values = row.Elements<Cell>().Select(cell => { var raw = cell.CellValue?.Text ?? ""; return cell.DataType?.Value == CellValues.SharedString ? sharedStrings[int.Parse(raw)] : raw; });
Console.WriteLine(string.Join(" | ", values));}1 | Dune: Part Two | max2 | Oppenheimer | peacockOne caveat: First() isn’t necessarily the sheet named “Movies”. If you need a specific sheet, look it up through workbookPart.Workbook.Sheets and GetPartById. Also, empty cells are omitted from the XML, so row.Elements<Cell>() can skip columns. For ragged data, read each cell’s CellReference (like C2) to work out its column.
Option 2: Zero Dependencies with ZipArchive and XDocument
If you can’t add a package, the BCL has what you need. Open the file as a ZIP, as in my post on creating a ZIP file in .NET, and parse the XML with LINQ to XML.
using System.IO.Compression;using System.Xml.Linq;
XNamespace ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
using var zip = ZipFile.OpenRead("Movies.xlsx");
// 1. load the shared strings table (it's absent if the workbook has no text)var shared = new List<string>();var sharedEntry = zip.GetEntry("xl/sharedStrings.xml");if (sharedEntry is not null){ using var sharedStream = sharedEntry.Open(); shared = XDocument.Load(sharedStream) .Descendants(ns + "si") .Select(si => string.Concat(si.Descendants(ns + "t").Select(t => t.Value))) .ToList();}
// 2. read the first worksheetusing var sheetStream = zip.GetEntry("xl/worksheets/sheet1.xml")!.Open();var sheetXml = XDocument.Load(sheetStream);
foreach (var row in sheetXml.Descendants(ns + "row").Skip(1)){ var values = row.Elements(ns + "c").Select(c => { var v = c.Element(ns + "v")?.Value ?? ""; return (string?)c.Attribute("t") == "s" ? shared[int.Parse(v)] : v; });
Console.WriteLine(string.Join(" | ", values));}// output// 1 | Dune: Part Two | max// 2 | Oppenheimer | peacockI join the <t> elements inside each <si> because rich-text strings are split into several runs. Just be mindful about the limits, though. This version ignores dates (stored as serial numbers plus a style), inline strings (t="inlineStr"), formulas, and skipped empty cells. It’s great for a known, simple file, such as a template your own system generates, but for unfamiliar files, use a library.
FAQ
How do I read an Excel file in C# without Interop?
Use a managed library such as ExcelDataReader, ClosedXML, or ExcelMapper, or read the .xlsx yourself with the Open XML SDK or ZipArchive and XDocument. None require Excel, COM, or Office, and all work on Windows, macOS, and Linux.
How do I read an .xlsx file in C#?
Add ExcelDataReader, call ExcelReaderFactory.CreateReader(stream), and loop with reader.Read(). For objects instead of raw cells, call await new ExcelMapper().FetchAsync<T>("file.xlsx").
Can I read an Excel file without Microsoft Office installed?
Yes. An .xlsx is a ZIP of XML files, and these libraries parse it directly. Microsoft.Office.Interop.Excel is the only approach that needs Excel installed, and I’d avoid it, especially on servers.
How do I read Excel data into a list of objects?
Make a record or class whose property names match the header row, then call FetchAsync<Movie> with ExcelMapper. You get one Movie per row. If you need more control, loop over ExcelDataReader or ClosedXML rows and construct the objects yourself.
What’s the fastest way to read a large Excel file in C#?
ExcelDataReader, because it streams rows forward-only instead of building the whole workbook in memory. A hand-rolled XmlReader over the sheet XML can go lower on memory still, at the cost of doing everything yourself. ClosedXML and ExcelMapper load the workbook, so expect more memory use on big files.
Can I read an .xls file in C#?
Yes. ExcelDataReader, ExcelMapper, and NPOI all read the legacy .xls format. On .NET Core and .NET 5+, call Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) first for ExcelDataReader. ClosedXML, the Open XML SDK, and the ZIP approach only handle .xlsx.
Reading a spreadsheet is the easy half. If you need to produce one too, see writing Excel files in C#. For the bigger picture, check out reading files in C#, and if the data should leave as CSV or another format, converting C# objects to almost any format.
Thanks for reading, and please leave a comment below. Also, follow me on Mastodon at @khalidabuhakmeh to get the latest .NET news and tutorials.
Written by Khalid Abuhakmeh. If this guide helped your workflow, feel free to buy me a coffee.