Azure Functions 内でエクセルを作成してファイルダウンロードできるか試してみた

Azure Functions 内で CSV を作成してファイルダウンロードさせる場合、中身がテキストなのでシンプルで扱いやすい反面、文字列の処理が漏れていたりするとカンマやダブルクォーテーションが混ざり込んでセルがズレてしまいます。そこで CSV をやめてエクセル形式でファイルが生成できれば CSV の課題が解消されるのではと考え、Azure Functions 内でエクセルを作成してファイルダウンロードできるか試してみました。

## 検証用 Azure Functions の作成

```bash:bash
func init mnrfapp --dotnet

cd mnrfapp

func new --name crxlsx --template HttpTrigger

dotnet add package DocumentFormat.OpenXml
```

## エクセル作成コードに変更

```cs:crxlsx.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;

namespace mnrfapp
{
    public static class crxlsx
    {
        [FunctionName("crxlsx")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            // メモリストリームを作成
            var memoryStream = new MemoryStream();

            // SpreadsheetDocument を作成
            var spreadsheetDocument = SpreadsheetDocument.Create(memoryStream, SpreadsheetDocumentType.Workbook);

            // Workbook を作成
            var workbookPart = spreadsheetDocument.AddWorkbookPart();
            workbookPart.Workbook = new Workbook();

            // Worksheet を作成
            WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
            worksheetPart.Worksheet = new Worksheet(new SheetData());
            Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());

            // mySheet という名前を設定
            Sheet sheet = new Sheet() {
                Id = workbookPart.GetIdOfPart(worksheetPart),
                SheetId = 1,
                Name = "mySheet"
            };
            sheets.Append(sheet);

            // シートデータを取得
            var sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();

            // ヘッダ行を追加
            var headerRow = new Row();
            headerRow.Append(
                new Cell() { CellValue = new CellValue("Hello"), DataType = CellValues.String },
                new Cell() { CellValue = new CellValue("World"), DataType = CellValues.String }
            );
            sheetData.Append(headerRow);

            // データ行を追加
            var dataRow = new Row();
            dataRow.Append(
                new Cell() { CellValue = new CellValue("はろー"), DataType = CellValues.String },
                new Cell() { CellValue = new CellValue("わーるど"), DataType = CellValues.String }
            );
            sheetData.Append(dataRow);

            // SpreadsheetDocument を保存
            workbookPart.Workbook.Save();
            spreadsheetDocument.Dispose();

            // メモリストリームをシークして先頭へ戻す
            memoryStream.Seek(0, SeekOrigin.Begin);

            // ファイルをダウンロードとして提供
            return new FileContentResult(memoryStream.ToArray(), "application/octet-stream")
            {
                FileDownloadName = "sample.xlsx"
            };
        }
    }
}
```

## ダウンロードしたファイルを開いた結果

![azure-functions-download-xlsx-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675885/4cd1f086-98c2-525b-97d9-85bf72b46b70.png)

## 参考

https://learn.microsoft.com/ja-jp/office/open-xml/spreadsheet/structure-of-a-spreadsheetml-document?tabs=cs