エンジニアのはしがき

プログラミングの日々の知見を書き連ねているブログです

.NETライブラリのClosedXMLでExcelファイルを生成する

Excelは好きですか?
僕は嫌いです。大概の内容はマークダウンで済むと思っています。 しかしながら、最近バックエンド(ASP.NET Core)でExcel出力機能の実装を強いられましたのでその方法を記録しておきたいと思います。

f:id:tansantktk:20201118205539p:plain

↓フロントエンド(Node.js)でエクセル生成する方法をお探しの場合は以下の記事へどうぞ!

tm-progapp.hatenablog.com

↓ClosedXMLで既存Excelファイルを編集する方法は以下の記事で紹介してます。

tm-progapp.hatenablog.com

ClosedXMLとは

エクセルの読込・操作・書き込みの為の.NETライブラリです。 xlsx, xlsmに対応しています。

記述方法を直感的にするというコンセプトをもとに開発されたようで、可読性に優れ使い勝手の良いライブラリです。

使い方の詳細は公式のGitHubのWikiで詳しく解説されています。 github.com

当記事では、ASP.NET Core2.x環境でのエクセル生成を紹介していきたいと思います。

使う前の準備

まずはNuGetからClosedXMLをインストールします。

IDEにVisual Studioを使っている場合は、IDEのNuGetパッケージマネージャからインストール。

Visual Studio Codeを使っている場合は、拡張機能のNuGet Package Managerをインストールしてから、パッケージをインストールします。 (F1でコマンドパレットを展開 → NuGet Package Manager: Add Packageを選択 でNuGetパッケージをインストールできます)

xlsxを生成する

ID 社員番号 社員名
1 E10001 田中 太郎
2 E10002 山田 花子
3 E10003 佐藤 健太

今回は例として上記のようなある社員のリストをExcel出力することを想定します。 Excelにはカラム名を表記するヘッダ行と実際のデータ部となるボディ行の大きく分けて2つがあるとします。

以下のような社員用のクラスがあるとします。

public class Employee
{
    public int id { get; set; }
    public string employee_code { get; set; }
    public string name { get; set; }
}

エクセル生成処理は以下のようになります。

// エクセルを保存するパスを指定
string path = "***********.xlsx";
// カラム名
string[] columnNames = ["ID", "社員番号", "社員名"];
// 社員情報のリスト
List<Employee> employeeList = new List<Employee>()
{
    new Employee()
    {
        id = 1,
        employee_code = "E10001",
        name = "田中 太郎",
    },
    new Employee()
    {
        id = 2,
        employee_code = "E10002",
        name = "山田 花子",
    },
    new Employee()
    {
        id = 3,
        employee_code = "E10003",
        name = "佐藤 健太",
    },        
}

// ワークブック作成
var workbook = new XLWorkbook();

// ワークシート作成
IXLWorksheet worksheet = workbook.Worksheets.Add("sheet1");

// ヘッダ行生成
for (int index = 1; index <= columnNames.Count; index++)
{
    worksheet.Cell(1, index).SetValue(columnName[index]);
    worksheet.Column(index).Width = 20;
}

// ボディ行生成
for (int index = 1; index <= employeeList.Count; index++)
{
    worksheet.Cell(index + 1, 1).SetValue(employeeList[index - 1].id);
    worksheet.Cell(index + 1, 2).SetValue(employeeList[index - 1].employee_code);
    worksheet.Cell(index + 1, 3).SetValue(employeeList[index - 1].name);
}

// 指定パスにエクセル生成
workbook.SaveAs(path);

まずは、new XLWorkbook()で土台となるワークブックを作成します。

次にそのワークブックに対してworkbook.Worksheets.Add("sheet1")と書くことでシートを追加します。 追加したシート情報が戻り値ですので、一時変数で受けておき、後で編集できるように準備しておきます。

worksheet.Cell(1, index).SetValue(columnName[index])が実際にセルの値に代入する処理です。 Cell()の第1引数が行番号、第2引数が列番号を指します。 番号は1始まりで、1と指定した場合は最初の行または列を指定したことになります。(0ではないので注意)

ヘッダ行、ボディ行どちらにおいても、SetValue()でセルの値を指定しているだけです。

worksheet.Column(index).Width = 20は列幅を指定しています。 指定しない場合はデフォルトの幅のまま出力されるようです。 (ちなみに列幅を自動的に調整してくれる機能もあるのですが、私の環境ではうまく動作させることができませんでした…。)

セルのデータ型を指定する

生成前はただの文字列だったセルが、エクセル出力後に直接開くと意図せぬデータ型に変換されてしまうことがあります。 エクセルのお家芸ですね🤮

セルに対して、SetDataType({データ型})と記述することでデータ型を指定することができます。

例えば、

worksheet.Cell(index + 1, 2).SetValue(employeeList[index - 1].employee_code).SetDataType(XLDataType.Text)

と記述すると、社員番号は文字列のセルとして出力されます。

XLDataType.Textで文字列、
XLDataType.Numberで数値、
XLDataType.Booleanでブール値、
XLDataType.DateTimeで日付、
XLDataType.TimeSpanで時刻を指定できます。

Data Types · ClosedXML/ClosedXML Wiki · GitHub

なお、公式WikiではSetDataType(***)ではなくworksheet.Cell(*, *).DataType = XLDataType.***と記載されていたりするのですが、 何故かこれでは正しくデータ型が反映しませんでした。

余談

Excel絡みの記事の検索ヒット率が高いことに最近気づき、 やはり世のエンジニアはExcelの呪縛と日夜戦っているのだと実感…。