C#×NPOIでExcelを読み書きする方法|インストールからサンプルコード・書式設定まで解説

はじめに

C#でExcelファイルを読み書きしたいとき、よく使われるライブラリのひとつがNPOIです。NPOIを使うと、Excelをインストールしていない環境でも、C#から.xls.xlsxファイルの作成、読み込み、更新、書式設定、数式設定などを行えます。

特にWebアプリケーションやバッチ処理で「一覧データをExcel出力したい」「アップロードされたExcelを読み取ってDBに登録したい」「既存のテンプレートExcelに値を差し込みたい」といったケースでは、C#×NPOIの組み合わせが役立ちます。

この記事では、NPOIのインストール方法から、Excelファイルの新規作成、読み込み、更新、書式設定、数式、ASP.NET Coreでのダウンロード処理まで、実務で使いやすいサンプルコード付きで解説します。

1. C#×NPOIでできることとExcel操作の全体像

1-1. NPOIとは?C#でExcelファイルを扱えるライブラリの特徴

NPOIは、Java向けのApache POIを.NET向けに移植したライブラリです。公式リポジトリでも、NPOIはOffice 2003/2007形式のファイルを読み書きできる.NETライブラリとして説明されています。

C#でNPOIを使うと、主に次のようなExcel操作ができます。

  • Excelファイルの新規作成

  • 既存Excelファイルの読み込み

  • セルへの文字列、数値、日付の書き込み

  • セル値の取得

  • 行や列の追加

  • セルの書式設定

  • フォント、背景色、罫線の設定

  • セル結合

  • 数式の設定

  • オートフィルタの設定

  • ウィンドウ枠の固定

  • テンプレートExcelへの差し込み

  • WebアプリからのExcelダウンロード

C#でExcelを操作する方法としては、Microsoft Office Interop、Open XML SDK、ClosedXML、EPPlusなどもありますが、NPOIは.xls.xlsxの両方を扱いたい場合に選択肢に入りやすいライブラリです。

1-2. NPOIで読み書きできるExcel形式(.xls/.xlsx)

NPOIでは、古いExcel形式である.xlsと、新しいExcel形式である.xlsxを扱えます。

主に使うクラスは次のとおりです。

Excel形式NPOIの主なクラス説明
.xlsHSSFWorkbookExcel 97-2003形式
.xlsxXSSFWorkbookExcel 2007以降の形式
自動判定WorkbookFactory.Create()拡張子やファイル内容からWorkbookを生成

新規でExcelファイルを作成する場合は、基本的に.xlsx形式を使うことが多いため、XSSFWorkbookを使えば問題ありません。一方、古いシステムとの連携で.xlsを読み込む必要がある場合は、HSSFWorkbookまたはWorkbookFactory.Create()を使います。

1-3. Microsoft OfficeなしでExcel操作できるメリット

NPOIの大きなメリットは、Microsoft ExcelやMicrosoft OfficeをインストールせずにExcelファイルを操作できることです。NPOIのリリース情報でも、Microsoft Officeなし、COM+なし、InteropなしでOffice形式を読み書きできるライブラリとして説明されています。

サーバーサイドでExcel出力を行う場合、Office Interopを使うと次のような問題が起こりがちです。

  • サーバーにExcelをインストールする必要がある

  • COM操作のため処理が不安定になりやすい

  • Webアプリやバッチ処理で権限問題が起きやすい

  • 複数同時実行に向かない

  • サーバー環境での利用が推奨されにくい

NPOIであれば、C#アプリケーション内でExcelファイルを直接生成・編集できるため、ASP.NET Core、Windowsサービス、コンソールアプリ、バッチ処理などでも使いやすいです。

1-4. 「c# npoi」で検索するユーザーが知りたいこと

「c# npoi」で検索するユーザーは、単にNPOIの概要を知りたいだけではなく、実際に動くコードを探しているケースが多いです。

特に知りたい内容は、次のようなものです。

  • NPOIをC#プロジェクトにインストールする方法

  • .xlsxファイルを作成するサンプルコード

  • Excelファイルを読み込む方法

  • セルの値を安全に取得する方法

  • 既存Excelに追記する方法

  • セルの書式設定を行う方法

  • 日付や数値の表示形式を設定する方法

  • ASP.NET CoreでExcelをダウンロードする方法

  • よくあるエラーの原因と対処法

以降では、これらを順番に解説します。

2. C#でNPOIを使うためのインストール手順

2-1. Visual Studioでプロジェクトを作成する

まずはVisual StudioでC#プロジェクトを作成します。学習用であれば、コンソールアプリを作るのが一番わかりやすいです。

手順は次のとおりです。

  1. Visual Studioを起動する

  2. 「新しいプロジェクトの作成」を選択する

  3. 「コンソール アプリ」を選択する

  4. プロジェクト名を入力する

  5. 使用する.NETのバージョンを選択する

  6. プロジェクトを作成する

ASP.NET Core MVCやWeb APIでExcel出力を行いたい場合も、基本的なNPOIの使い方は同じです。まずはコンソールアプリでExcel作成・読み込みの流れを理解してから、Webアプリに組み込むとスムーズです。

2-2. NuGetからNPOIをインストールする方法

NPOIはNuGetからインストールできます。NuGetは.NET向けのパッケージ管理システムで、Microsoft公式ドキュメントでも、パッケージを取得してプロジェクトに追加する仕組みとして説明されています。

Visual Studioからインストールする場合は、次の手順です。

  1. プロジェクトを右クリックする

  2. 「NuGet パッケージの管理」を選択する

  3. 「参照」タブでNPOIを検索する

  4. NPOIパッケージを選択する

  5. 「インストール」をクリックする

パッケージマネージャーコンソールを使う場合は、次のコマンドを実行します。

PowerShell
Install-Package NPOI

.NET CLIを使う場合は、次のコマンドです。

Bash
dotnet add package NPOI

NuGet Galleryでは、NPOIパッケージのインストール用PackageReferenceも公開されています。

XML
<PackageReference Include="NPOI" Version="2.8.0" />

バージョン番号は環境によって変わる可能性があるため、実際のプロジェクトではNuGet上の最新安定版を確認して導入してください。

2-3. 使用する主な名前空間と基本設定

NPOIでExcel操作を行うときは、主に次の名前空間を使います。

C#
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.HSSF.UserModel;
using NPOI.SS.Util;

それぞれの役割は次のとおりです。

名前空間用途
NPOI.SS.UserModelWorkbook、Sheet、Row、Cellなどの共通インターフェース
NPOI.XSSF.UserModel.xlsx形式を扱うクラス
NPOI.HSSF.UserModel.xls形式を扱うクラス
NPOI.SS.Utilセル結合や範囲指定など

基本的には、IWorkbookISheetIRowICellといったインターフェースを使うと、.xls.xlsxの違いを吸収しやすくなります。

2-4. NPOI導入時によくあるエラーと対処法

NPOIをインストールした直後によくあるエラーは、名前空間の不足です。

たとえば、XSSFWorkbookが見つからない場合は、次のusingが不足している可能性があります。

C#
using NPOI.XSSF.UserModel;

IWorkbookICellが見つからない場合は、次のusingを追加します。

C#
using NPOI.SS.UserModel;

また、プロジェクトのターゲットフレームワークが古すぎると、最新のNPOIが使えない場合があります。その場合は、プロジェクトの.NETバージョンを上げるか、対応しているNPOIのバージョンを選んでください。

3. NPOIでExcelファイルを新規作成する方法

3-1. Workbook・Sheet・Row・Cellの基本構造

NPOIでExcelを操作するときは、Excelファイルの構造を次のように考えると理解しやすいです。

Excel上の要素NPOIのオブジェクト説明
Excelファイル全体IWorkbookブック
シートISheetワークシート
IRow1行分のデータ
セルICell1つのセル

たとえば、ExcelのA1セルに値を書き込む場合は、次の流れになります。

  1. Workbookを作る

  2. Sheetを作る

  3. Rowを作る

  4. Cellを作る

  5. Cellに値を設定する

  6. ファイルに保存する

NPOIでは、行番号と列番号は0始まりです。つまり、ExcelのA1セルはrowIndex = 0columnIndex = 0になります。

3-2. .xlsxファイルを作成するサンプルコード

以下は、C#とNPOIで.xlsxファイルを新規作成する基本サンプルです。

C#
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;

var filePath = "sample.xlsx";

IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("売上一覧");

// ヘッダー行を作成
IRow headerRow = sheet.CreateRow(0);
headerRow.CreateCell(0).SetCellValue("商品名");
headerRow.CreateCell(1).SetCellValue("単価");
headerRow.CreateCell(2).SetCellValue("数量");
headerRow.CreateCell(3).SetCellValue("売上日");

// データ行を作成
IRow dataRow = sheet.CreateRow(1);
dataRow.CreateCell(0).SetCellValue("ノートPC");
dataRow.CreateCell(1).SetCellValue(120000);
dataRow.CreateCell(2).SetCellValue(3);
dataRow.CreateCell(3).SetCellValue(DateTime.Today);

// ファイルに保存
using var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write);
workbook.Write(fs);

このコードを実行すると、プロジェクトの実行フォルダにsample.xlsxが作成されます。

3-3. セルに文字列・数値・日付を書き込む方法

NPOIでは、SetCellValue()に渡す値の型によって、文字列、数値、日付などを書き込めます。

C#
IRow row = sheet.CreateRow(0);

row.CreateCell(0).SetCellValue("文字列");
row.CreateCell(1).SetCellValue(12345);
row.CreateCell(2).SetCellValue(123.45);
row.CreateCell(3).SetCellValue(DateTime.Now);
row.CreateCell(4).SetCellValue(true);

ただし、日付をExcel上で日付らしく表示するには、セルの表示形式を設定する必要があります。

C#
ICellStyle dateStyle = workbook.CreateCellStyle();
IDataFormat dataFormat = workbook.CreateDataFormat();
dateStyle.DataFormat = dataFormat.GetFormat("yyyy/mm/dd");

ICell dateCell = row.CreateCell(3);
dateCell.SetCellValue(DateTime.Today);
dateCell.CellStyle = dateStyle;

日付は内部的には数値として扱われるため、表示形式を設定しないと、Excel上でシリアル値のように表示される場合があります。

3-4. 作成したExcelファイルを保存する方法

作成したExcelファイルは、FileStreamを使って保存します。

C#
using var fs = new FileStream("output.xlsx", FileMode.Create, FileAccess.Write);
workbook.Write(fs);

既存ファイルを上書きする場合も、FileMode.Createを使えば新しい内容で保存されます。

注意点として、読み込み用のFileStreamを開いたまま同じファイルに保存しようとすると、ファイルロックでエラーになることがあります。読み込みと保存は、ストリームを分けて処理するのが安全です。

4. NPOIで既存のExcelファイルを読み込む方法

4-1. Excelファイルを開いてWorkbookを取得する

既存のExcelファイルを読み込む場合は、WorkbookFactory.Create()を使うと便利です。.xls.xlsxのどちらでも、NPOI側で適切なWorkbookを生成してくれます。

C#
using NPOI.SS.UserModel;

var filePath = "sample.xlsx";

using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
IWorkbook workbook = WorkbookFactory.Create(fs);

.xlsxだけを扱うと決まっている場合は、XSSFWorkbookを直接使うこともできます。

C#
using NPOI.XSSF.UserModel;

using var fs = new FileStream("sample.xlsx", FileMode.Open, FileAccess.Read);
IWorkbook workbook = new XSSFWorkbook(fs);

ただし、実務では拡張子が.xls.xlsxかを意識せず読み込めるWorkbookFactory.Create()のほうが扱いやすいです。

4-2. シート・行・セルの値を取得するサンプルコード

次のコードは、Excelファイルの1枚目のシートから行とセルを順番に読み取る例です。

C#
using NPOI.SS.UserModel;

using var fs = new FileStream("sample.xlsx", FileMode.Open, FileAccess.Read);
IWorkbook workbook = WorkbookFactory.Create(fs);

ISheet sheet = workbook.GetSheetAt(0);

for (int rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++)
{
IRow? row = sheet.GetRow(rowIndex);
if (row == null)
{
continue;
}

for (int colIndex = row.FirstCellNum; colIndex < row.LastCellNum; colIndex++)
{
ICell? cell = row.GetCell(colIndex);
string value = GetCellValue(cell);

Console.Write($"{value}\t");
}

Console.WriteLine();
}

GetCellValue()は、セルの型ごとに値を取り出すための自作メソッドです。

C#
static string GetCellValue(ICell? cell)
{
if (cell == null)
{
return string.Empty;
}

return cell.CellType switch
{
CellType.String => cell.StringCellValue,
CellType.Numeric => DateUtil.IsCellDateFormatted(cell)
? cell.DateCellValue?.ToString("yyyy/MM/dd") ?? string.Empty
: cell.NumericCellValue.ToString(),
CellType.Boolean => cell.BooleanCellValue.ToString(),
CellType.Formula => cell.CellFormula,
CellType.Blank => string.Empty,
CellType.Error => string.Empty,
_ => string.Empty
};
}

4-3. セルの型ごとに値を読み取る方法

Excelセルには、文字列、数値、日付、真偽値、数式、空白など複数の型があります。そのため、すべてのセルをStringCellValueで取得しようとすると、数値セルでエラーになることがあります。

安全に読み取るには、CellTypeで分岐します。

C#
switch (cell.CellType)
{
case CellType.String:
Console.WriteLine(cell.StringCellValue);
break;

case CellType.Numeric:
if (DateUtil.IsCellDateFormatted(cell))
{
Console.WriteLine(cell.DateCellValue?.ToString("yyyy/MM/dd"));
}
else
{
Console.WriteLine(cell.NumericCellValue);
}
break;

case CellType.Boolean:
Console.WriteLine(cell.BooleanCellValue);
break;

case CellType.Formula:
Console.WriteLine(cell.CellFormula);
break;

case CellType.Blank:
Console.WriteLine("");
break;
}

数式セルについては、数式そのものを取得したい場合はCellFormulaを使います。計算結果を取得したい場合は、IFormulaEvaluatorを使います。

C#
IFormulaEvaluator evaluator = workbook.GetCreationHelper().CreateFormulaEvaluator();

CellValue evaluatedValue = evaluator.Evaluate(cell);

switch (evaluatedValue.CellType)
{
case CellType.Numeric:
Console.WriteLine(evaluatedValue.NumberValue);
break;

case CellType.String:
Console.WriteLine(evaluatedValue.StringValue);
break;

case CellType.Boolean:
Console.WriteLine(evaluatedValue.BooleanValue);
break;
}

4-4. 空白セルやnull参照を安全に処理する方法

NPOIでExcelを読み込むときに多いエラーが、NullReferenceExceptionです。これは、行やセルが存在しない状態で値を取得しようとした場合に発生します。

たとえば、次のコードは危険です。

C#
string value = sheet.GetRow(1).GetCell(0).StringCellValue;

2行目やA列セルが存在しない場合、例外が発生します。

安全に処理するには、行とセルのnullチェックを行います。

C#
IRow? row = sheet.GetRow(1);
if (row == null)
{
return;
}

ICell? cell = row.GetCell(0);
string value = GetCellValue(cell);

また、空白セルをブランクセルとして取得したい場合は、MissingCellPolicyを指定できます。

C#
ICell cell = row.GetCell(0, MissingCellPolicy.CREATE_NULL_AS_BLANK);

Excel取込処理では、空白セルが含まれることを前提に実装するのが重要です。

5. NPOIでExcelファイルを更新・追記する方法

5-1. 既存セルの値を書き換える方法

既存のExcelファイルを読み込み、セルの値を書き換えて保存するサンプルです。

C#
using NPOI.SS.UserModel;

var filePath = "sample.xlsx";

IWorkbook workbook;

// 読み込み
using (var input = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
workbook = WorkbookFactory.Create(input);
}

ISheet sheet = workbook.GetSheetAt(0);

IRow row = sheet.GetRow(1) ?? sheet.CreateRow(1);
ICell cell = row.GetCell(0) ?? row.CreateCell(0);

cell.SetCellValue("更新後の商品名");

// 保存
using (var output = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
workbook.Write(output);
}

ポイントは、読み込み用のストリームを閉じてから保存用のストリームを開くことです。同じファイルを開いたまま上書きすると、ファイルがロックされることがあります。

5-2. 最終行を取得してデータを追記する方法

Excelにデータを追記する場合は、LastRowNumを使って最終行を取得します。

C#
ISheet sheet = workbook.GetSheetAt(0);

int nextRowIndex = sheet.LastRowNum + 1;
IRow row = sheet.CreateRow(nextRowIndex);

row.CreateCell(0).SetCellValue("マウス");
row.CreateCell(1).SetCellValue(3000);
row.CreateCell(2).SetCellValue(5);

ただし、LastRowNumは最後に使われている行番号を返すため、空のシートでも0になる場合があります。空シートかどうかを厳密に判定したい場合は、PhysicalNumberOfRowsも確認します。

C#
int nextRowIndex = sheet.PhysicalNumberOfRows == 0
? 0
: sheet.LastRowNum + 1;

5-3. 複数行・複数列のデータをループで書き込む方法

一覧データをExcelに出力する場合は、リストをループして行を作成します。

C#
public class Product
{
public string Name { get; set; } = "";
public int Price { get; set; }
public int Quantity { get; set; }
}

var products = new List<Product>
{
new Product { Name = "ノートPC", Price = 120000, Quantity = 3 },
new Product { Name = "マウス", Price = 3000, Quantity = 10 },
new Product { Name = "キーボード", Price = 8000, Quantity = 5 }
};

IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("商品一覧");

// ヘッダー
IRow header = sheet.CreateRow(0);
header.CreateCell(0).SetCellValue("商品名");
header.CreateCell(1).SetCellValue("単価");
header.CreateCell(2).SetCellValue("数量");
header.CreateCell(3).SetCellValue("金額");

// データ
for (int i = 0; i < products.Count; i++)
{
Product product = products[i];
IRow row = sheet.CreateRow(i + 1);

row.CreateCell(0).SetCellValue(product.Name);
row.CreateCell(1).SetCellValue(product.Price);
row.CreateCell(2).SetCellValue(product.Quantity);
row.CreateCell(3).SetCellValue(product.Price * product.Quantity);
}

using var fs = new FileStream("products.xlsx", FileMode.Create, FileAccess.Write);
workbook.Write(fs);

このように、C#のList<T>とNPOIを組み合わせると、DBから取得した一覧データも簡単にExcel出力できます。

5-4. DataTableやListのデータをExcelに出力する考え方

実務では、Excel出力元のデータがDataTableList<T>であることが多いです。

DataTableの場合は、列情報をヘッダーにし、行データをExcelに書き込みます。

C#
static void WriteDataTableToSheet(DataTable table, ISheet sheet)
{
// ヘッダー
IRow headerRow = sheet.CreateRow(0);

for (int col = 0; col < table.Columns.Count; col++)
{
headerRow.CreateCell(col).SetCellValue(table.Columns[col].ColumnName);
}

// データ
for (int rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++)
{
IRow row = sheet.CreateRow(rowIndex + 1);

for (int col = 0; col < table.Columns.Count; col++)
{
object value = table.Rows[rowIndex][col];
row.CreateCell(col).SetCellValue(value?.ToString() ?? "");
}
}
}

List<T>の場合は、プロパティを列として書き込む方法が一般的です。リフレクションを使えば汎用化できますが、実務では列順や表示名を制御したいことが多いため、明示的に列を指定する実装のほうが保守しやすいです。

6. NPOIでセルの書式設定を行う方法

6-1. フォント・文字色・太字を設定する

NPOIでは、IFontICellStyleを使ってフォントを設定します。

C#
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("書式サンプル");

IFont font = workbook.CreateFont();
font.FontName = "Meiryo UI";
font.FontHeightInPoints = 12;
font.IsBold = true;
font.Color = IndexedColors.White.Index;

ICellStyle style = workbook.CreateCellStyle();
style.SetFont(font);

IRow row = sheet.CreateRow(0);
ICell cell = row.CreateCell(0);
cell.SetCellValue("見出し");
cell.CellStyle = style;

スタイルはセルごとに大量作成するとファイルサイズや処理速度に影響します。実務では、見出し用、日付用、数値用など、用途ごとにスタイルを作成して使い回すのがおすすめです。

6-2. 背景色や罫線を設定する

背景色を設定するには、FillForegroundColorFillPatternを使います。

C#
ICellStyle headerStyle = workbook.CreateCellStyle();

headerStyle.FillForegroundColor = IndexedColors.DarkBlue.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;

headerStyle.BorderTop = BorderStyle.Thin;
headerStyle.BorderBottom = BorderStyle.Thin;
headerStyle.BorderLeft = BorderStyle.Thin;
headerStyle.BorderRight = BorderStyle.Thin;

フォントと組み合わせると、見出し行らしいデザインになります。

C#
IFont headerFont = workbook.CreateFont();
headerFont.IsBold = true;
headerFont.Color = IndexedColors.White.Index;

headerStyle.SetFont(headerFont);

セルに適用します。

C#
ICell cell = sheet.CreateRow(0).CreateCell(0);
cell.SetCellValue("商品名");
cell.CellStyle = headerStyle;

6-3. セル幅・行の高さ・折り返しを設定する

列幅はSetColumnWidth()で設定します。NPOIでは、列幅を文字数×256で指定します。

C#
sheet.SetColumnWidth(0, 20 * 256);
sheet.SetColumnWidth(1, 15 * 256);

行の高さはHeightInPointsで設定します。

C#
IRow row = sheet.CreateRow(0);
row.HeightInPoints = 24;

セル内で文字列を折り返す場合は、スタイルのWrapTextを有効にします。

C#
ICellStyle wrapStyle = workbook.CreateCellStyle();
wrapStyle.WrapText = true;

ICell cell = row.CreateCell(0);
cell.SetCellValue("長い文章をセル内で折り返して表示します。");
cell.CellStyle = wrapStyle;

列幅の自動調整をしたい場合は、AutoSizeColumn()を使います。

C#
sheet.AutoSizeColumn(0);

ただし、大量データに対して全列でAutoSizeColumn()を使うと処理が重くなることがあります。大量出力では固定幅を指定するほうが安定します。

6-4. 日付・数値・通貨の表示形式を設定する

日付、数値、通貨は、IDataFormatを使って表示形式を指定します。

C#
IDataFormat format = workbook.CreateDataFormat();

ICellStyle dateStyle = workbook.CreateCellStyle();
dateStyle.DataFormat = format.GetFormat("yyyy/mm/dd");

ICellStyle numberStyle = workbook.CreateCellStyle();
numberStyle.DataFormat = format.GetFormat("#,##0");

ICellStyle currencyStyle = workbook.CreateCellStyle();
currencyStyle.DataFormat = format.GetFormat("¥#,##0");

セルに適用します。

C#
IRow row = sheet.CreateRow(1);

ICell dateCell = row.CreateCell(0);
dateCell.SetCellValue(DateTime.Today);
dateCell.CellStyle = dateStyle;

ICell numberCell = row.CreateCell(1);
numberCell.SetCellValue(1234567);
numberCell.CellStyle = numberStyle;

ICell currencyCell = row.CreateCell(2);
currencyCell.SetCellValue(9800);
currencyCell.CellStyle = currencyStyle;

Excel上で「2026/06/11」「1,234,567」「¥9,800」のように表示できます。

6-5. セル結合と中央揃えを設定する

セル結合には、CellRangeAddressを使います。

C#
using NPOI.SS.Util;

sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, 4));

これは、1行目のA列からE列までを結合する指定です。

中央揃えは、スタイルで設定します。

C#
ICellStyle titleStyle = workbook.CreateCellStyle();
titleStyle.Alignment = HorizontalAlignment.Center;
titleStyle.VerticalAlignment = VerticalAlignment.Center;

IFont titleFont = workbook.CreateFont();
titleFont.IsBold = true;
titleFont.FontHeightInPoints = 16;
titleStyle.SetFont(titleFont);

IRow titleRow = sheet.CreateRow(0);
ICell titleCell = titleRow.CreateCell(0);
titleCell.SetCellValue("売上レポート");
titleCell.CellStyle = titleStyle;

セル結合した範囲に罫線を付けたい場合は、結合範囲内の各セルにスタイルを適用する必要があります。

7. NPOIで数式・オートフィルタ・ウィンドウ枠固定を使う方法

7-1. セルにExcel数式を設定する

NPOIでは、SetCellFormula()を使ってExcel数式を設定できます。

C#
IRow row = sheet.CreateRow(1);

row.CreateCell(0).SetCellValue("ノートPC");
row.CreateCell(1).SetCellValue(120000);
row.CreateCell(2).SetCellValue(3);

ICell amountCell = row.CreateCell(3);
amountCell.SetCellFormula("B2*C2");

注意点は、数式の先頭に=を付けないことです。Excel上では=B2*C2と表示されますが、NPOIでは"B2*C2"のように指定します。

合計行を作る場合は、次のように指定できます。

C#
ICell totalCell = sheet.CreateRow(10).CreateCell(3);
totalCell.SetCellFormula("SUM(D2:D10)");

7-2. 数式の再計算を有効にする

NPOIで数式を設定したあと、Excelで開いたときに計算結果を更新したい場合は、再計算を有効にします。

C#
workbook.ForceFormulaRecalculation = true;

また、C#側で計算結果を取得したい場合は、IFormulaEvaluatorを使います。

C#
IFormulaEvaluator evaluator = workbook.GetCreationHelper().CreateFormulaEvaluator();
evaluator.EvaluateFormulaCell(amountCell);

帳票出力では、NPOIで数式だけ設定しておき、Excelを開いたタイミングで再計算させる方法がよく使われます。

7-3. オートフィルタを設定する

オートフィルタを設定するには、SetAutoFilter()を使います。

C#
sheet.SetAutoFilter(new CellRangeAddress(0, sheet.LastRowNum, 0, 4));

この例では、1行目をヘッダーとして、A列からE列までにオートフィルタを設定しています。

一覧表をExcel出力する場合は、ヘッダー行にオートフィルタを付けておくと、利用者がExcel上で絞り込みや並び替えをしやすくなります。

7-4. ウィンドウ枠を固定する

ヘッダー行を固定したい場合は、CreateFreezePane()を使います。

C#
sheet.CreateFreezePane(0, 1);

これは、1行目を固定する指定です。

左側の列も固定したい場合は、次のように指定します。

C#
sheet.CreateFreezePane(1, 1);

この場合、1列目と1行目が固定されます。大量の一覧データを出力する場合は、ウィンドウ枠固定を設定しておくと見やすいExcelになります。

8. C#×NPOIの実践サンプルコード集

8-1. Excelに一覧データを書き出すサンプル

以下は、商品一覧をExcelに出力する実践的なサンプルです。

C#
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.SS.Util;

public class Product
{
public string Code { get; set; } = "";
public string Name { get; set; } = "";
public int Price { get; set; }
public int Quantity { get; set; }
}

var products = new List<Product>
{
new Product { Code = "P001", Name = "ノートPC", Price = 120000, Quantity = 3 },
new Product { Code = "P002", Name = "マウス", Price = 3000, Quantity = 10 },
new Product { Code = "P003", Name = "キーボード", Price = 8000, Quantity = 5 }
};

IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("商品一覧");

IDataFormat dataFormat = workbook.CreateDataFormat();

ICellStyle headerStyle = workbook.CreateCellStyle();
headerStyle.FillForegroundColor = IndexedColors.DarkBlue.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;
headerStyle.BorderBottom = BorderStyle.Thin;

IFont headerFont = workbook.CreateFont();
headerFont.Color = IndexedColors.White.Index;
headerFont.IsBold = true;
headerStyle.SetFont(headerFont);

ICellStyle numberStyle = workbook.CreateCellStyle();
numberStyle.DataFormat = dataFormat.GetFormat("#,##0");

// ヘッダー
string[] headers = { "商品コード", "商品名", "単価", "数量", "金額" };
IRow headerRow = sheet.CreateRow(0);

for (int i = 0; i < headers.Length; i++)
{
ICell cell = headerRow.CreateCell(i);
cell.SetCellValue(headers[i]);
cell.CellStyle = headerStyle;
}

// データ
for (int i = 0; i < products.Count; i++)
{
Product product = products[i];
IRow row = sheet.CreateRow(i + 1);

row.CreateCell(0).SetCellValue(product.Code);
row.CreateCell(1).SetCellValue(product.Name);

ICell priceCell = row.CreateCell(2);
priceCell.SetCellValue(product.Price);
priceCell.CellStyle = numberStyle;

ICell quantityCell = row.CreateCell(3);
quantityCell.SetCellValue(product.Quantity);
quantityCell.CellStyle = numberStyle;

ICell amountCell = row.CreateCell(4);
amountCell.SetCellFormula($"C{i + 2}*D{i + 2}");
amountCell.CellStyle = numberStyle;
}

// オートフィルタと固定
sheet.SetAutoFilter(new CellRangeAddress(0, products.Count, 0, headers.Length - 1));
sheet.CreateFreezePane(0, 1);

// 列幅
sheet.SetColumnWidth(0, 15 * 256);
sheet.SetColumnWidth(1, 25 * 256);
sheet.SetColumnWidth(2, 12 * 256);
sheet.SetColumnWidth(3, 12 * 256);
sheet.SetColumnWidth(4, 12 * 256);

workbook.ForceFormulaRecalculation = true;

using var fs = new FileStream("products.xlsx", FileMode.Create, FileAccess.Write);
workbook.Write(fs);

このサンプルでは、見出しの背景色、数値書式、数式、オートフィルタ、ウィンドウ枠固定までまとめて設定しています。

8-2. Excelテンプレートを読み込んで帳票を作成するサンプル

請求書や見積書などの帳票では、ゼロからExcelを作るよりも、テンプレートファイルを用意して値だけ差し込む方法が便利です。

C#
using NPOI.SS.UserModel;

var templatePath = "invoice_template.xlsx";
var outputPath = "invoice_output.xlsx";

IWorkbook workbook;

using (var input = new FileStream(templatePath, FileMode.Open, FileAccess.Read))
{
workbook = WorkbookFactory.Create(input);
}

ISheet sheet = workbook.GetSheetAt(0);

sheet.GetRow(1).GetCell(1).SetCellValue("株式会社サンプル");
sheet.GetRow(2).GetCell(1).SetCellValue(DateTime.Today.ToString("yyyy年MM月dd日"));
sheet.GetRow(5).GetCell(1).SetCellValue("システム開発費");
sheet.GetRow(5).GetCell(3).SetCellValue(500000);

workbook.ForceFormulaRecalculation = true;

using (var output = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
{
workbook.Write(output);
}

テンプレート方式のメリットは、デザインや罫線、印刷設定をExcel側で管理できることです。C#側では値の差し込みに集中できるため、保守性が高くなります。

8-3. Excelファイルを読み込んでCSVやDB登録に使うサンプル

アップロードされたExcelを読み込み、CSV出力やDB登録に使う場合は、まずExcelの内容を行データとして取り出します。

C#
using NPOI.SS.UserModel;

var rows = new List<string[]>();

using var fs = new FileStream("import.xlsx", FileMode.Open, FileAccess.Read);
IWorkbook workbook = WorkbookFactory.Create(fs);
ISheet sheet = workbook.GetSheetAt(0);

for (int rowIndex = 1; rowIndex <= sheet.LastRowNum; rowIndex++)
{
IRow? row = sheet.GetRow(rowIndex);
if (row == null)
{
continue;
}

string code = GetCellValue(row.GetCell(0));
string name = GetCellValue(row.GetCell(1));
string price = GetCellValue(row.GetCell(2));

if (string.IsNullOrWhiteSpace(code))
{
continue;
}

rows.Add(new[] { code, name, price });
}

static string GetCellValue(ICell? cell)
{
if (cell == null)
{
return "";
}

return cell.CellType switch
{
CellType.String => cell.StringCellValue.Trim(),
CellType.Numeric => DateUtil.IsCellDateFormatted(cell)
? cell.DateCellValue?.ToString("yyyy/MM/dd") ?? ""
: cell.NumericCellValue.ToString(),
CellType.Boolean => cell.BooleanCellValue.ToString(),
CellType.Formula => cell.ToString() ?? "",
_ => ""
};
}

DB登録に使う場合は、Excelから読み取った値をそのまま登録するのではなく、必ず入力チェックを行います。

  • 必須項目が空でないか

  • 数値項目に文字列が入っていないか

  • 日付形式が正しいか

  • コードが重複していないか

  • 文字数制限を超えていないか

Excel取込はユーザー入力と同じ扱いにし、バリデーションとエラーメッセージを丁寧に実装することが重要です。

8-4. ASP.NET CoreでExcelをダウンロードするサンプル

ASP.NET CoreでNPOIを使うと、ブラウザからExcelファイルをダウンロードできます。

C#
using Microsoft.AspNetCore.Mvc;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;

[ApiController]
[Route("api/[controller]")]
public class ExcelController : ControllerBase
{
[HttpGet("download")]
public IActionResult Download()
{
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("売上一覧");

IRow header = sheet.CreateRow(0);
header.CreateCell(0).SetCellValue("商品名");
header.CreateCell(1).SetCellValue("金額");

IRow row = sheet.CreateRow(1);
row.CreateCell(0).SetCellValue("ノートPC");
row.CreateCell(1).SetCellValue(120000);

using var ms = new MemoryStream();
workbook.Write(ms, true);

byte[] bytes = ms.ToArray();

return File(
bytes,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"sales.xlsx"
);
}
}

Content-Typeには、.xlsx用のMIMEタイプを指定します。

application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

WebアプリでExcelをダウンロードさせる場合、サーバー上に一時ファイルを作らず、MemoryStreamで生成してそのまま返す実装がよく使われます。

9. C#×NPOIでよくあるエラーと解決策

9-1. ファイルが開けない・壊れる原因

NPOIで作成したExcelファイルが開けない場合、よくある原因は次のとおりです。

  • .xlsxなのにHSSFWorkbookで作成している

  • .xlsなのにXSSFWorkbookで作成している

  • ファイル拡張子とWorkbookの種類が一致していない

  • workbook.Write()が正しく実行されていない

  • FileStreamが閉じられていない

  • Webダウンロード時のMIMEタイプが誤っている

  • 既存ファイルを開いたまま上書きしている

.xlsxを作成する場合は、基本的にXSSFWorkbookを使います。

C#
IWorkbook workbook = new XSSFWorkbook();

.xlsを作成する場合は、HSSFWorkbookを使います。

C#
IWorkbook workbook = new HSSFWorkbook();

読み込み時は、WorkbookFactory.Create()を使うと形式の違いを吸収しやすくなります。

9-2. 日本語が文字化けする場合の確認ポイント

NPOIで.xlsxを作成する場合、通常の文字列セルで日本語が文字化けすることは多くありません。文字化けが発生する場合は、次の点を確認します。

  • CSV出力とExcel出力を混同していないか

  • フォントが日本語に対応しているか

  • 古い.xls形式で特殊な文字を使っていないか

  • Webダウンロード時にExcelではなくテキストとして返していないか

  • ファイル拡張子と実際の形式が一致しているか

フォントを明示的に設定する場合は、次のようにします。

C#
IFont font = workbook.CreateFont();
font.FontName = "Meiryo UI";

ICellStyle style = workbook.CreateCellStyle();
style.SetFont(font);

CSVで文字化けする場合は、NPOIではなくCSVの文字コード問題です。UTF-8 BOM付きで出力するなど、CSV側の対応が必要です。

9-3. セルの値が取得できない場合の対処法

セルの値が取得できない場合は、次の原因が考えられます。

  • 行が存在しない

  • セルが存在しない

  • セルが空白

  • セルの型が想定と違う

  • 数式セルを文字列として取得しようとしている

  • 結合セルの左上以外を読んでいる

特に、次のようなコードは例外の原因になります。

C#
string value = row.GetCell(0).StringCellValue;

安全に取得するには、共通メソッドを用意します。

C#
static string SafeGetCellValue(IRow? row, int columnIndex)
{
if (row == null)
{
return "";
}

ICell? cell = row.GetCell(columnIndex);
return GetCellValue(cell);
}

結合セルの場合、値は基本的に結合範囲の左上セルに入っています。結合範囲内の別セルを読みに行くと空になることがあるため注意してください。

9-4. 日付や数値の表示がおかしい場合の対処法

日付が数値のように表示される場合は、表示形式が設定されていない可能性があります。

C#
ICellStyle dateStyle = workbook.CreateCellStyle();
IDataFormat format = workbook.CreateDataFormat();
dateStyle.DataFormat = format.GetFormat("yyyy/mm/dd");

ICell cell = row.CreateCell(0);
cell.SetCellValue(DateTime.Today);
cell.CellStyle = dateStyle;

数値にカンマを付けたい場合は、数値書式を設定します。

C#
ICellStyle numberStyle = workbook.CreateCellStyle();
numberStyle.DataFormat = format.GetFormat("#,##0");

金額表示の場合は、次のようにします。

C#
ICellStyle currencyStyle = workbook.CreateCellStyle();
currencyStyle.DataFormat = format.GetFormat("¥#,##0");

Excelでは、値そのものと表示形式は別です。C#側で数値や日付を書き込んだだけでは、期待通りの表示にならないことがあるため、表示形式もセットで設定しましょう。

9-5. 大量データ出力で処理が重い場合の改善策

大量データをExcel出力する場合、NPOIの使い方によっては処理が重くなります。

改善ポイントは次のとおりです。

  • セルスタイルを使い回す

  • セルごとにスタイルを新規作成しない

  • AutoSizeColumn()を多用しない

  • 不要な罫線や装飾を減らす

  • 出力列を必要最小限にする

  • 一度に巨大なWorkbookを作らない

  • 可能であればCSV出力も検討する

悪い例は、ループ内で毎回スタイルを作ることです。

C#
for (int i = 0; i < 10000; i++)
{
ICellStyle style = workbook.CreateCellStyle();
// 毎回スタイルを作るため重くなりやすい
}

良い例は、ループの外でスタイルを作り、使い回すことです。

C#
ICellStyle numberStyle = workbook.CreateCellStyle();
numberStyle.DataFormat = workbook.CreateDataFormat().GetFormat("#,##0");

for (int i = 0; i < 10000; i++)
{
ICell cell = sheet.CreateRow(i).CreateCell(0);
cell.SetCellValue(i);
cell.CellStyle = numberStyle;
}

大量データを扱う場合は、Excelの見た目よりも、出力速度とメモリ使用量を優先して設計することが大切です。

10. NPOIと他のExcel操作ライブラリの違い

10-1. NPOIとClosedXMLの違い

ClosedXMLは、Excel 2007以降の.xlsx.xlsmを読み書きする.NETライブラリで、OpenXML APIを扱いやすくすることを目的としています。

NPOIとClosedXMLの違いを簡単に整理すると、次のようになります。

比較項目NPOIClosedXML
対応形式.xls.xlsx主に.xlsx.xlsm
APIのわかりやすさやや低レベル比較的直感的
古いExcel形式扱いやすい基本的に不向き
書式設定細かく設定可能書きやすい
用途既存Excel処理、.xls対応、細かい制御.xlsx中心の帳票・一覧出力

.xlsxだけを簡単に出力したい場合は、ClosedXMLのほうがコードが読みやすいことがあります。一方、.xlsも扱いたい場合や、Apache POIに近い考え方で細かく制御したい場合は、NPOIが向いています。

10-2. NPOIとEPPlusの違い

EPPlusは、Excelファイルを扱う高機能な.NETライブラリです。ただし、EPPlus 8では非商用向けのコミュニティライセンスと商用ライセンスのデュアルライセンスモデルが採用されています。商用ビジネスで使う場合は商用ライセンスが必要と説明されています。

NPOIとEPPlusの違いは次のとおりです。

比較項目NPOIEPPlus
ライセンスApache-2.0非商用・商用で条件が異なる
対応形式.xls.xlsx主に.xlsx
APIやや低レベル高機能で使いやすい
商用利用時の確認比較的シンプルライセンス確認が重要
向いている用途汎用的なExcel読み書き高機能なExcel帳票作成

EPPlusは便利な機能が多い一方、商用利用ではライセンス条件の確認が必須です。社内システムや受託開発で使う場合は、導入前にライセンスを確認しましょう。

10-3. NPOIが向いているケース

NPOIが向いているのは、次のようなケースです。

  • C#で.xls.xlsxの両方を扱いたい

  • Microsoft OfficeなしでExcelを操作したい

  • 既存Excelファイルを読み込んで更新したい

  • Excelテンプレートに値を差し込みたい

  • セル単位で細かく制御したい

  • サーバーサイドでExcel出力したい

  • ライセンス面をシンプルにしたい

特に、古い業務システムでは.xlsファイルが残っていることがあります。このような環境では、NPOIが有力な選択肢になります。

10-4. 他のライブラリを検討したほうがよいケース

一方で、次のような場合は他のライブラリも検討するとよいです。

  • .xlsxだけを簡単に出力したい

  • コードの読みやすさを最優先したい

  • 複雑な帳票を短いコードで作りたい

  • グラフや高度なExcel機能を多用したい

  • 大量データを高速に出力したい

  • ライブラリのサポート体制を重視したい

NPOIは高機能ですが、APIはやや低レベルです。シンプルなExcel出力だけなら、ClosedXMLのほうが短く書ける場合もあります。プロジェクトの要件に合わせて選ぶことが大切です。

11. C#×NPOIでExcel操作するときのベストプラクティス

11-1. usingでファイルストリームを安全に扱う

NPOIでファイルを読み書きするときは、usingを使ってFileStreamを確実に閉じます。

C#
using var fs = new FileStream("sample.xlsx", FileMode.Create, FileAccess.Write);
workbook.Write(fs);

既存ファイルを読み込んで上書き保存する場合は、読み込み用と保存用のストリームを分けます。

C#
IWorkbook workbook;

using (var input = new FileStream("sample.xlsx", FileMode.Open, FileAccess.Read))
{
workbook = WorkbookFactory.Create(input);
}

// workbookを編集する

using (var output = new FileStream("sample.xlsx", FileMode.Create, FileAccess.Write))
{
workbook.Write(output);
}

ファイルが壊れる、保存できない、アクセス拒否になるといった問題の多くは、ストリームの扱いを見直すことで解決できます。

11-2. セルスタイルの使い回しでパフォーマンスを改善する

NPOIでは、セルスタイルを大量に作るとパフォーマンスに悪影響があります。スタイルは用途ごとに作成して使い回しましょう。

C#
ICellStyle headerStyle = CreateHeaderStyle(workbook);
ICellStyle numberStyle = CreateNumberStyle(workbook);

for (int i = 0; i < 1000; i++)
{
IRow row = sheet.CreateRow(i + 1);

ICell cell = row.CreateCell(0);
cell.SetCellValue(i);
cell.CellStyle = numberStyle;
}

スタイル作成処理はメソッド化しておくと、コードの見通しもよくなります。

C#
static ICellStyle CreateNumberStyle(IWorkbook workbook)
{
ICellStyle style = workbook.CreateCellStyle();
IDataFormat format = workbook.CreateDataFormat();
style.DataFormat = format.GetFormat("#,##0");
return style;
}

11-3. 例外処理とログ出力を実装する

Excel操作では、ファイル破損、形式違い、ロック、権限不足、予期しないセル型など、さまざまな例外が発生します。

実務では、例外処理とログ出力を必ず実装しましょう。

C#
try
{
using var fs = new FileStream("import.xlsx", FileMode.Open, FileAccess.Read);
IWorkbook workbook = WorkbookFactory.Create(fs);

ISheet sheet = workbook.GetSheetAt(0);

// 読み込み処理
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"ファイルが見つかりません: {ex.Message}");
}
catch (IOException ex)
{
Console.WriteLine($"ファイルの読み書きに失敗しました: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Excel処理中にエラーが発生しました: {ex.Message}");
}

WebアプリでExcelアップロードを受け付ける場合は、ユーザー向けのエラーメッセージと、開発者向けの詳細ログを分けて管理すると運用しやすくなります。

11-4. テンプレートファイルを使って保守性を高める

帳票のデザインをすべてC#コードで作ると、罫線、列幅、印刷設定、ヘッダー、フッターなどのコードが増え、保守が大変になります。

請求書、見積書、納品書、集計レポートなどは、Excelテンプレートを使う方法がおすすめです。

テンプレート方式では、次のように役割を分けます。

担当内容
Excelテンプレートレイアウト、罫線、印刷設定、固定文言
C#コードDB取得、値の差し込み、ファイル保存
NPOIテンプレート読み込み、セル更新、出力

この方式にすると、レイアウト変更が発生しても、C#コードを大きく変更せずに対応できます。

12. C#×NPOIに関するよくある質問

12-1. NPOIは無料で使える?

NPOIはApache-2.0ライセンスのオープンソースライブラリとして公開されています。GitHubのリポジトリでもApache-2.0 licenseと表示されています。

ただし、ライセンスの解釈や利用可否はプロジェクトや会社のルールによって異なる場合があります。商用プロジェクトで利用する場合は、社内のライセンス確認ルールに従ってください。

12-2. .xlsと.xlsxはどちらを使うべき?

新規作成するExcelファイルであれば、基本的には.xlsxを使うのがおすすめです。

.xlsxを使うメリットは次のとおりです。

  • 現在のExcelで一般的に使われている

  • 最大行数・列数が多い

  • ファイル構造が新しい

  • Webアプリや帳票出力で扱いやすい

一方、古いシステムとの連携で.xlsが必要な場合は、.xlsを使います。ただし、.xlsは古い形式であり、行数や列数の制限もあるため、新規開発では.xlsxを選ぶケースが多いです。

12-3. Excelがインストールされていないサーバーでも使える?

はい、NPOIはExcelがインストールされていないサーバーでも使用できます。NPOIはMicrosoft Officeなし、COM+なし、InteropなしでOffice形式を扱えるライブラリとして公開されています。

そのため、ASP.NET Coreアプリケーション、Linuxサーバー、Docker環境、バッチサーバーなどでも利用しやすいです。

ただし、フォントや日本語表示、ファイルの保存先権限など、サーバー環境特有の問題には注意が必要です。

12-4. マクロ付きExcelファイルは扱える?

NPOIでマクロ付きExcelファイルを扱う場合は、慎重に検証が必要です。

.xlsmファイルを読み込んで一部のセルを更新できるケースはありますが、マクロの編集や実行を目的とするライブラリではありません。マクロ付きファイルをテンプレートとして使う場合は、出力後にマクロが保持されるか、Excelで開いたときに問題がないかを必ず確認してください。

マクロの実行やVBAコードの編集が必要な場合は、NPOIではなく別の方法を検討する必要があります。

12-5. 大量データのExcel出力にNPOIは向いている?

NPOIでも大量データのExcel出力は可能ですが、実装方法に注意が必要です。

大量データ出力で意識すべきポイントは次のとおりです。

  • セルスタイルを使い回す

  • 不要な装飾を避ける

  • AutoSizeColumn()を多用しない

  • メモリ使用量を確認する

  • 出力件数が多すぎる場合はCSVも検討する

  • 必要な列だけ出力する

  • Webアプリではタイムアウトに注意する

Excelは表計算ソフトであり、巨大データの格納先としては向かない場合もあります。数十万行以上のデータを扱う場合は、Excel出力が本当に適切か、CSVやDB検索画面のほうがよいかも検討しましょう。

まとめ

C#でExcelを読み書きするなら、NPOIは実務でも使いやすいライブラリです。NPOIを使うことで、Microsoft Officeをインストールしていない環境でも、Excelファイルの作成、読み込み、更新、書式設定、数式設定、テンプレート出力、Webダウンロードなどを実装できます。

基本的な流れは、次のとおりです。

  1. NuGetからNPOIをインストールする

  2. XSSFWorkbookまたはWorkbookFactory.Create()でWorkbookを用意する

  3. ISheetIRowICellを使って値を読み書きする

  4. ICellStyleIFontで書式を設定する

  5. FileStreamMemoryStreamで保存・ダウンロードする

新規でExcelを作成する場合は.xlsx形式を選び、XSSFWorkbookを使うのが基本です。既存ファイルを読み込む場合は、.xls.xlsxの両方に対応しやすいWorkbookFactory.Create()が便利です。

また、C#×NPOIで安定したExcel処理を作るには、nullチェック、セル型の判定、ストリーム管理、スタイルの使い回し、例外処理が重要です。

NPOIはAPIが少し低レベルな分、セル単位で細かく制御できます。Excelテンプレートと組み合わせれば、見た目の整った帳票も効率よく作成できます。C#でExcel出力やExcel取込を実装する際は、この記事のサンプルコードをベースに、自分のプロジェクトに合わせて拡張してみてください。