How to Read Open Office Excel Files in C# Without Excel Installed
Reading and processing OpenDocument Spreadsheet (ODS) and Excel files in C# is straightforward when you use the right library. With IronXL, you load any XLS, XLSX, ODS, or CSV file into a WorkBook object using a single method call -- no Microsoft Excel installation required, no COM registration, and no Interop headaches. This guide walks you through every step: installing the package, loading files, extracting typed cell values, working with named worksheets, handling merged cells, and deploying to Linux or containerized environments.
How Do You Install IronXL in a .NET Project?
NuGet Package Manager Installation
Add IronXL to your project through the NuGet Package Manager. Open the Package Manager Console in Visual Studio and run:
Install-Package IronXL
dotnet add package IronXL
Install-Package IronXL
dotnet add package IronXL
IronXL targets .NET 8, .NET 9, .NET 10, .NET Framework 4.6.2+, and .NET Standard 2.0, so it fits modern and legacy codebases alike. Once the package installs, add the using IronXL; directive at the top of any file that works with spreadsheets, and you are ready to load your first workbook.
For Azure Functions, Docker containers, or Linux-hosted APIs, no additional runtime configuration is required. The library bundles everything it needs internally and does not call out to Excel automation components.
How Do You Load an OpenOffice or Excel File in C#?
IronXL treats ODS, XLS, XLSX, and CSV files identically through the WorkBook.Load method. You pass an absolute or relative file path, and the library detects the format from the file header -- not just the extension. This means a file renamed from .ods to .xlsx is still read correctly.
using IronXL;
// Load an OpenDocument Spreadsheet (.ods) produced by LibreOffice Calc or OpenOffice
WorkBook workbook = WorkBook.Load("quarterly_report.ods");
// Access the first worksheet by index
WorkSheet sheet = workbook.WorkSheets[0];
// Read a cell value
string companyName = sheet["A1"].StringValue;
int recordCount = sheet["B1"].IntValue;
Console.WriteLine($"Company : {companyName}");
Console.WriteLine($"Records : {recordCount}");
using IronXL;
// Load an OpenDocument Spreadsheet (.ods) produced by LibreOffice Calc or OpenOffice
WorkBook workbook = WorkBook.Load("quarterly_report.ods");
// Access the first worksheet by index
WorkSheet sheet = workbook.WorkSheets[0];
// Read a cell value
string companyName = sheet["A1"].StringValue;
int recordCount = sheet["B1"].IntValue;
Console.WriteLine($"Company : {companyName}");
Console.WriteLine($"Records : {recordCount}");
Imports IronXL
' Load an OpenDocument Spreadsheet (.ods) produced by LibreOffice Calc or OpenOffice
Dim workbook As WorkBook = WorkBook.Load("quarterly_report.ods")
' Access the first worksheet by index
Dim sheet As WorkSheet = workbook.WorkSheets(0)
' Read a cell value
Dim companyName As String = sheet("A1").StringValue
Dim recordCount As Integer = sheet("B1").IntValue
Console.WriteLine($"Company : {companyName}")
Console.WriteLine($"Records : {recordCount}")
The same call works for XLSX and XLS files -- just change the path. WorkBook.Load returns the same strongly-typed object regardless of source format, so the rest of your code stays identical whether the file originates from Microsoft Excel, LibreOffice, or any other ODF-compliant application.
How Do You Read Every Row and Cell in a Worksheet?
Iterating the Rows Collection
Iterating over all rows and cells is the most common Excel processing task -- whether you are validating import data, transforming records, or feeding a reporting pipeline. IronXL exposes a Rows collection on every WorkSheet:
using IronXL;
WorkBook workbook = WorkBook.Load("customers.xlsx");
WorkSheet worksheet = workbook.WorkSheets[0];
Console.WriteLine($"Total rows : {worksheet.RowCount}");
Console.WriteLine($"Total columns : {worksheet.ColumnCount}");
Console.WriteLine();
foreach (var row in worksheet.Rows)
{
foreach (var cell in row)
{
if (!cell.IsEmpty)
Console.Write($"{cell.StringValue,-20}");
}
Console.WriteLine();
}
using IronXL;
WorkBook workbook = WorkBook.Load("customers.xlsx");
WorkSheet worksheet = workbook.WorkSheets[0];
Console.WriteLine($"Total rows : {worksheet.RowCount}");
Console.WriteLine($"Total columns : {worksheet.ColumnCount}");
Console.WriteLine();
foreach (var row in worksheet.Rows)
{
foreach (var cell in row)
{
if (!cell.IsEmpty)
Console.Write($"{cell.StringValue,-20}");
}
Console.WriteLine();
}
Imports IronXL
Dim workbook As WorkBook = WorkBook.Load("customers.xlsx")
Dim worksheet As WorkSheet = workbook.WorkSheets(0)
Console.WriteLine($"Total rows : {worksheet.RowCount}")
Console.WriteLine($"Total columns : {worksheet.ColumnCount}")
Console.WriteLine()
For Each row In worksheet.Rows
For Each cell In row
If Not cell.IsEmpty Then
Console.Write($"{cell.StringValue,-20}")
End If
Next
Console.WriteLine()
Next
The RowCount and ColumnCount properties return only the populated range -- empty trailing rows and columns are not included. Checking cell.IsEmpty before reading prevents unnecessary processing on sparse sheets.
For large files, consider using range-based access (worksheet["A1:D500"]) instead of full-sheet iteration. This limits the cells loaded into memory and speeds up processing when you only need a subset of the data.
How Do You Extract Typed Values from Cells?
Typed Property Accessors
Cells in a real-world spreadsheet contain strings, integers, decimals, booleans, and dates. IronXL exposes dedicated typed properties so you never have to parse raw strings manually:
using IronXL;
WorkBook workbook = WorkBook.Load("inventory.xlsx");
WorkSheet sheet = workbook.GetWorkSheet("Products");
// Typed accessors handle conversion automatically
string productName = sheet["A2"].StringValue;
int quantity = sheet["B2"].IntValue;
decimal unitPrice = sheet["C2"].DecimalValue;
bool inStock = sheet["D2"].BoolValue;
DateTime? lastAudit = sheet["E2"].DateTimeValue;
Console.WriteLine($"Product : {productName}");
Console.WriteLine($"Qty : {quantity}");
Console.WriteLine($"Price : {unitPrice:C}");
Console.WriteLine($"In Stock : {inStock}");
Console.WriteLine($"Audited : {lastAudit?.ToString("d") ?? "Never"}");
using IronXL;
WorkBook workbook = WorkBook.Load("inventory.xlsx");
WorkSheet sheet = workbook.GetWorkSheet("Products");
// Typed accessors handle conversion automatically
string productName = sheet["A2"].StringValue;
int quantity = sheet["B2"].IntValue;
decimal unitPrice = sheet["C2"].DecimalValue;
bool inStock = sheet["D2"].BoolValue;
DateTime? lastAudit = sheet["E2"].DateTimeValue;
Console.WriteLine($"Product : {productName}");
Console.WriteLine($"Qty : {quantity}");
Console.WriteLine($"Price : {unitPrice:C}");
Console.WriteLine($"In Stock : {inStock}");
Console.WriteLine($"Audited : {lastAudit?.ToString("d") ?? "Never"}");
Imports IronXL
Dim workbook As WorkBook = WorkBook.Load("inventory.xlsx")
Dim sheet As WorkSheet = workbook.GetWorkSheet("Products")
' Typed accessors handle conversion automatically
Dim productName As String = sheet("A2").StringValue
Dim quantity As Integer = sheet("B2").IntValue
Dim unitPrice As Decimal = sheet("C2").DecimalValue
Dim inStock As Boolean = sheet("D2").BoolValue
Dim lastAudit As DateTime? = sheet("E2").DateTimeValue
Console.WriteLine($"Product : {productName}")
Console.WriteLine($"Qty : {quantity}")
Console.WriteLine($"Price : {unitPrice:C}")
Console.WriteLine($"In Stock : {inStock}")
Console.WriteLine($"Audited : {If(lastAudit?.ToString("d"), "Never")}")
When a cell holds a formula, IronXL evaluates it and returns the computed result through the same typed properties. You do not need to call a separate evaluation method. For cells that may be empty or contain an incompatible type, the library returns the default value for that type rather than throwing an exception, which simplifies input validation logic.
| Property | .NET Type | Returns When Empty | Notes |
|---|---|---|---|
StringValue |
string |
Empty string | Always safe; converts any cell to text |
IntValue |
int |
0 |
Truncates decimals |
DecimalValue |
decimal |
0m |
Preserves precision for financial data |
DoubleValue |
double |
0.0 |
Use for scientific or statistical values |
BoolValue |
bool |
false |
Reads Excel TRUE/FALSE cells |
DateTimeValue |
DateTime? |
null |
Nullable -- check before use |
How Do You Work with Multiple Named Worksheets?
Access by Name, Index, or Iteration
Enterprise spreadsheets often contain several named sheets -- one per month, one per region, or one per product line. IronXL gives you multiple ways to access them:
using IronXL;
WorkBook workbook = WorkBook.Load("annual_sales.xlsx");
// Option 1: access by name (throws if the sheet does not exist)
WorkSheet januarySheet = workbook.GetWorkSheet("January");
// Option 2: iterate all sheets in the workbook
foreach (WorkSheet ws in workbook.WorkSheets)
{
Console.WriteLine($"Sheet: {ws.Name} | Rows: {ws.RowCount}");
// Read the header row from each sheet
string header = ws["A1"].StringValue;
Console.WriteLine($" Header: {header}");
}
// Option 3: access by zero-based index
WorkSheet lastSheet = workbook.WorkSheets[workbook.WorkSheets.Count - 1];
Console.WriteLine($"Last sheet: {lastSheet.Name}");
using IronXL;
WorkBook workbook = WorkBook.Load("annual_sales.xlsx");
// Option 1: access by name (throws if the sheet does not exist)
WorkSheet januarySheet = workbook.GetWorkSheet("January");
// Option 2: iterate all sheets in the workbook
foreach (WorkSheet ws in workbook.WorkSheets)
{
Console.WriteLine($"Sheet: {ws.Name} | Rows: {ws.RowCount}");
// Read the header row from each sheet
string header = ws["A1"].StringValue;
Console.WriteLine($" Header: {header}");
}
// Option 3: access by zero-based index
WorkSheet lastSheet = workbook.WorkSheets[workbook.WorkSheets.Count - 1];
Console.WriteLine($"Last sheet: {lastSheet.Name}");
Imports IronXL
Dim workbook As WorkBook = WorkBook.Load("annual_sales.xlsx")
' Option 1: access by name (throws if the sheet does not exist)
Dim januarySheet As WorkSheet = workbook.GetWorkSheet("January")
' Option 2: iterate all sheets in the workbook
For Each ws As WorkSheet In workbook.WorkSheets
Console.WriteLine($"Sheet: {ws.Name} | Rows: {ws.RowCount}")
' Read the header row from each sheet
Dim header As String = ws("A1").StringValue
Console.WriteLine($" Header: {header}")
Next
' Option 3: access by zero-based index
Dim lastSheet As WorkSheet = workbook.WorkSheets(workbook.WorkSheets.Count - 1)
Console.WriteLine($"Last sheet: {lastSheet.Name}")
When the sheet name is known at design time, GetWorkSheet is the clearest option. For dynamic processing -- where sheet names come from user input or configuration -- iterating the WorkSheets collection prevents hard-coded assumptions and handles workbooks with varying numbers of sheets.
How Do You Handle Merged Cells and Formatted Regions?
Reports and dashboards frequently use merged cells for headings, grouped labels, and summary rows. IronXL reads the value from the top-left cell of a merged region, exactly as Excel displays it:
using IronXL;
WorkBook workbook = WorkBook.Load("report_with_merges.xlsx");
WorkSheet sheet = workbook.DefaultWorkSheet;
// The merged region A1:D1 stores its value in cell A1
string reportTitle = sheet["A1"].StringValue;
Console.WriteLine($"Report title : {reportTitle}");
// Read cell formatting metadata
var titleCell = sheet["A1"];
Console.WriteLine($"Bold : {titleCell.Style.Font.Bold}");
Console.WriteLine($"Font size : {titleCell.Style.Font.Height}");
// Scan an entire column for non-empty section headers
foreach (var cell in sheet["A1:A100"])
{
if (!cell.IsEmpty && cell.Style.Font.Bold)
Console.WriteLine($"Section header at {cell.AddressString}: {cell.StringValue}");
}
using IronXL;
WorkBook workbook = WorkBook.Load("report_with_merges.xlsx");
WorkSheet sheet = workbook.DefaultWorkSheet;
// The merged region A1:D1 stores its value in cell A1
string reportTitle = sheet["A1"].StringValue;
Console.WriteLine($"Report title : {reportTitle}");
// Read cell formatting metadata
var titleCell = sheet["A1"];
Console.WriteLine($"Bold : {titleCell.Style.Font.Bold}");
Console.WriteLine($"Font size : {titleCell.Style.Font.Height}");
// Scan an entire column for non-empty section headers
foreach (var cell in sheet["A1:A100"])
{
if (!cell.IsEmpty && cell.Style.Font.Bold)
Console.WriteLine($"Section header at {cell.AddressString}: {cell.StringValue}");
}
Imports IronXL
Dim workbook As WorkBook = WorkBook.Load("report_with_merges.xlsx")
Dim sheet As WorkSheet = workbook.DefaultWorkSheet
' The merged region A1:D1 stores its value in cell A1
Dim reportTitle As String = sheet("A1").StringValue
Console.WriteLine($"Report title : {reportTitle}")
' Read cell formatting metadata
Dim titleCell = sheet("A1")
Console.WriteLine($"Bold : {titleCell.Style.Font.Bold}")
Console.WriteLine($"Font size : {titleCell.Style.Font.Height}")
' Scan an entire column for non-empty section headers
For Each cell In sheet("A1:A100")
If Not cell.IsEmpty AndAlso cell.Style.Font.Bold Then
Console.WriteLine($"Section header at {cell.AddressString}: {cell.StringValue}")
End If
Next
The Style property tree mirrors the structure of the OOXML SpreadsheetML specification, so property names feel familiar if you have worked with the Open XML SDK. However, IronXL wraps all of that complexity in a clean API that does not require any XML manipulation on your part.
How Do You Import CSV Files with the Same API?
CSV files produced by database exports, CRM systems, and legacy applications can be read through the same WorkBook.Load call. IronXL infers the delimiter from the file content:
using IronXL;
// Load a comma-separated values file -- same method, same API
WorkBook csvWorkbook = WorkBook.Load("export.csv");
WorkSheet csvSheet = csvWorkbook.DefaultWorkSheet;
Console.WriteLine($"CSV rows loaded: {csvSheet.RowCount}");
// Process rows exactly like any other worksheet
foreach (var row in csvSheet.Rows)
{
string id = row[0].StringValue;
string name = row[1].StringValue;
Console.WriteLine($"{id,-10} {name}");
}
using IronXL;
// Load a comma-separated values file -- same method, same API
WorkBook csvWorkbook = WorkBook.Load("export.csv");
WorkSheet csvSheet = csvWorkbook.DefaultWorkSheet;
Console.WriteLine($"CSV rows loaded: {csvSheet.RowCount}");
// Process rows exactly like any other worksheet
foreach (var row in csvSheet.Rows)
{
string id = row[0].StringValue;
string name = row[1].StringValue;
Console.WriteLine($"{id,-10} {name}");
}
Imports IronXL
' Load a comma-separated values file -- same method, same API
Dim csvWorkbook As WorkBook = WorkBook.Load("export.csv")
Dim csvSheet As WorkSheet = csvWorkbook.DefaultWorkSheet
Console.WriteLine($"CSV rows loaded: {csvSheet.RowCount}")
' Process rows exactly like any other worksheet
For Each row In csvSheet.Rows
Dim id As String = row(0).StringValue
Dim name As String = row(1).StringValue
Console.WriteLine($"{id,-10} {name}")
Next
After loading, you can save the data as XLSX using csvWorkbook.SaveAs("output.xlsx"). This is a common pattern for CSV-to-Excel conversion pipelines -- receive a CSV file, enrich it with calculated columns or formatting, and return a formatted XLSX report to the user.
For tab-separated files or custom delimiters, use WorkBook.LoadCSV("file.tsv", fileFormat: ExcelFileFormat.TSV) to specify the format explicitly.
| Format | Extension | Produced By | Notes |
|---|---|---|---|
| XLSX | .xlsx |
Excel 2007+, LibreOffice | Default modern format; XML-based |
| XLS | .xls |
Excel 97--2003 | Binary format; full read/write support |
| ODS | .ods |
LibreOffice, OpenOffice | OpenDocument Spreadsheet standard |
| CSV | .csv |
Any application | Delimiter auto-detected; no formatting |
| TSV | .tsv |
Database exports | Tab-delimited; specify format explicitly |
How Does IronXL Compare to Microsoft.Office.Interop.Excel?
Interop, Open XML SDK, and IronXL Side by Side
The most common alternative to IronXL for Excel automation in .NET is Microsoft.Office.Interop.Excel. Understanding the trade-offs helps you choose the right tool for your project.
Microsoft Interop wraps the Excel COM object model. This means Excel must be installed on every machine that runs your code -- including web servers, build agents, and cloud VMs. COM object lifecycle management is manual: you must release every Range, Worksheet, and Workbook object explicitly, or Excel processes accumulate in the background and consume memory until the server restarts. Licensing is also a concern: the Office EULA prohibits server-side automation in many scenarios.
IronXL avoids all of these constraints. It is a pure managed library with no COM dependency. The WorkBook object is a standard .NET class; the garbage collector handles cleanup. You can run the same code on a developer laptop, an Azure App Service, a Docker container, or a Raspberry Pi running Linux.
The Open XML SDK from Microsoft is another alternative. It provides direct access to the OOXML file format without requiring Excel, but it operates at a very low level -- you manipulate XML elements directly. Reading a single cell value requires navigating shared string tables, cell references, and style indices. IronXL wraps all of that into the single-line sheet["A1"].StringValue call shown throughout this guide.
How Do You Deploy Excel Processing to Linux and Docker?
Server deployments are where IronXL's independence from Excel becomes most valuable. The same code you write on Windows runs unchanged on Ubuntu, Alpine Linux, or macOS. For containerized deployments, your Dockerfile requires no special configuration:
# Standard .NET runtime image -- no Office packages needed
FROM mcr.microsoft.com/dotnet/runtime:10.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "ExcelProcessor.dll"]
# Standard .NET runtime image -- no Office packages needed
FROM mcr.microsoft.com/dotnet/runtime:10.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "ExcelProcessor.dll"]
For Azure Functions and AWS Lambda, IronXL works within the managed runtime without any additional configuration. The cold-start overhead is minimal because there is no COM initialization step.
Thread safety is built in: multiple threads can open different WorkBook instances concurrently without coordination. If you need to process thousands of files in parallel -- for example, in a background job that ingests user-uploaded spreadsheets -- you can use Parallel.ForEach or Task.WhenAll across a pool of WorkBook instances without risk of corruption.
Memory usage stays predictable because IronXL loads only the requested worksheet into memory, not the entire workbook on initialization. For very large files, this distinction matters: a workbook with ten 50MB sheets does not require 500MB of RAM just to read a single sheet. The IronXL performance documentation covers additional strategies for handling high-volume file processing scenarios.
What Are Your Next Steps?
The quickest way to validate IronXL in your project is to install the NuGet package and run the examples in this guide against a real file from your environment. A free trial license unlocks all features with no code changes required when you are ready for production.
Explore related IronXL capabilities that complement file reading:
- Write and create Excel files in C# -- build reports and data exports programmatically
- Format cells, rows, and columns -- apply colors, fonts, and borders in code
- Work with Excel formulas in C# -- read and write formula cells
- Convert Excel to PDF in .NET -- generate PDF reports from spreadsheet data
- IronXL API documentation -- full reference for all classes and methods
- IronXL licensing and pricing -- options for individual developers and enterprise teams
- IronXL NuGet page -- installation instructions for all project types
- IronXL tutorials -- step-by-step guides for common use cases
- Compare IronXL to EPPlus -- side-by-side feature and licensing comparison
- IronXL product home -- feature overview and getting-started resources
For questions about licensing, cross-platform support, or specific file format requirements, the IronXL support team is available through live chat and email.
Frequently Asked Questions
How can I read Open Office Excel files in C# without having Excel installed?
You can use the IronXL library to read Open Office Excel files in C# without needing Excel installed on your server. It allows you to load XLS, XLSX, ODS, and CSV files efficiently using a single WorkBook.Load method call.
What types of Excel files can be processed using IronXL?
IronXL supports processing various Excel file formats including XLS, XLSX, ODS (OpenDocument Spreadsheet), and CSV files, which makes it versatile for different spreadsheet applications including LibreOffice and OpenOffice.
Why should developers use IronXL instead of Microsoft Interop for reading Excel files?
IronXL provides a simpler and more efficient way to handle Excel files without the need for Microsoft Excel or COM Interop, reducing the complexity and overhead on production servers and enabling Linux and Docker deployments.
Can IronXL handle files created in spreadsheet applications other than MS Excel?
Yes, IronXL can read and process files from other spreadsheet applications such as OpenOffice Calc and LibreOffice Calc, making it a flexible tool for developers working with ODS and ODF-compliant formats.
Is it necessary to install Excel on production servers to use IronXL?
No, with IronXL, you do not need to install Excel on production servers, which simplifies deployment and reduces maintenance requirements. IronXL runs on Windows, Linux, macOS, and inside Docker containers.
Does IronXL support processing of CSV files?
Yes, IronXL fully supports reading and processing CSV files along with other Excel formats like XLS, XLSX, and ODS using the same WorkBook.Load API.
What are the benefits of using IronXL for .NET developers?
IronXL offers .NET developers an easy-to-use library for reading, writing, and manipulating Excel files without needing Microsoft Excel, enhancing productivity and reducing development time with a clean, strongly-typed API.
How does IronXL improve efficiency in handling Excel files?
IronXL enhances efficiency by eliminating the need for Excel software, providing typed cell value accessors, thread-safe concurrent processing, and a lightweight solution that processes Excel files directly within .NET applications.




