Skip to footer content
USING IRONXL

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
SHELL

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}")
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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")}")
$vbLabelText   $csharpLabel

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.

IronXL Cell Value Properties and Their Equivalent .NET Types
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}")
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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.

Supported Input File Formats in IronXL
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"]
SHELL

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:

For questions about licensing, cross-platform support, or specific file format requirements, the IronXL support team is available through live chat and email.

Get stated with IronXL now.
green arrow pointer

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.

Jordi Bardia
Software Engineer
Jordi is most proficient in Python, C# and C++, when he isn’t leveraging his skills at Iron Software; he’s game programming. Sharing responsibilities for product testing, product development and research, Jordi adds immense value to continual product improvement. The varied experience keeps him challenged and engaged, and he ...
Read More

Iron Support Team

We're online 24 hours, 5 days a week.
Chat
Email
Call Me