C# のLamda関数でExcelファイルをS3から読み込む方法


はじめに

個人プロジェクトで C# の Lamda 関数を使う予定なのですが、いやぁ情報が少ないですね。

今回も S3 にアップロードした Excel ファイルを読み込もうとしたのですが、そもそも S3 のファイルを C# で扱う方法がほとんど見つからない。💦

まあ、そんなこんなですが、一応動くようになったので、C# の Lambda 関数で Excel ファイルを S3 から読み込む方法をご紹介します。

Excel の読み込みに使うライブラリ

C# で Excle ファイルを読み込むライブラリには ClosedXML と NPOI がありますが、今回は NPOI で試してみます。

理由は、ClosedXML は使いやすいようですが、xls 形式に対応していないのですよね。

一方、NPOI は古くからあるプロジェクトなので、xls, xlsx 両形式に対応しています。

個人プロジェクトは xls, xlsx 両形式をサポートする予定なので、NPOI を選択した次第です。

ClosedXML, NPOI については以下の記事が詳しいので参照してください。

qiita.com

C# で Lambda 関数を作成するために

C# で Lambda 関数を作成するために、Visual Studio 2017 Community Edition と AWS Toolkit for Visual Studio を使用しました。

NPOI は NuGet からインストールすることが可能です。

今回は S3 を使用するので、NuGet で AWSSDK.S3 というライブラリをインストールする必要があります。

NPOI と AWSSDK.S3 をインストールしたプロジェクトになりますので、関連ライブラリを ZIP ファイルにまとめてサーバーにアップロードする必要があります。こちらについては、以下の記事を参照してください。

www.aruse.net

S3 に Excel ファイルをアップロードする

まず、Excel ファイルを S3 にアップロードする必要があります。

東京リージョンにバケットを作成して、Excel ファイルは test.xlsx という名前でアップロードします。

Excel ファイルの中身は以下の通りです。

f:id:fnyablog:20190310112224p:plain

ここでバケットを他のリージョンに作成すると、以下のようなエラーが発生して頭を抱えることになるのでご注意ください。

The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.

C# のソースコード

いよいよ C# のソースコードになります。

以下のように Lambda 関数を記述します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using NPOI.SS.UserModel;
using Amazon.Lambda.Core;
using Amazon.S3;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
namespace AWSLambda1
{
    public class Function
    {
        public async Task FunctionHandler(object input, ILambdaContext context)
        {
            // S3のリージョンを指定する
            AmazonS3Client s3 = new AmazonS3Client(Amazon.RegionEndpoint.APNortheast1);
            
            var res = await s3.GetObjectAsync(
                bucketName: "BucketName", 
                key:"test.xlsx"
                );

            // 日時をファイル名につけるの重要!
            var filePath = "/tmp/" + (DateTime.Now).ToString("yyyyMMdd_HHmmss") + "_test.xlsx";
            await res.WriteResponseStreamToFileAsync(filePath, false,System.Threading.CancellationToken.None);

            // Create で xls,xlsx に対応
            IWorkbook workbook = WorkbookFactory.Create(filePath);
            ISheet worksheet = workbook.GetSheetAt(0);

            // 最終行数を取得
            int lastRow = worksheet.LastRowNum;

            for (int i = 0; i <= lastRow; i++)
            {
                // 行取得
                IRow row = worksheet.GetRow(i);

                // セルの値取得
                ICell cell1 = row?.GetCell(0);
                ICell cell2 = row?.GetCell(1);

                // ログに出力
                LambdaLogger.Log(cell1?.StringCellValue);
                LambdaLogger.Log((cell2?.NumericCellValue).ToString());
            }

            // ファイル削除
            File.Delete(filePath);
        }

    }
}

基本的にコメントに書いてある通りなのですが、いくつか補足をします。

AmazonS3Client をインスタンスするときに、リージョンの指定をしています。必ずしも必要な訳ではないですが、エラーが発生する場合は指定してください。

/tmp にファイルをコピーする時にファイル名に日時を追加していますが、これは/tmpがコンテナで共通のディレクトリになるからです。ファイル名固定で処理を行うと、同時に他のユーザーが実行しているとエラーが発生する可能性があります。

なお、この/tmp ディレクトリは上限が512MBになっているのでご注意ください。

workbook をインスタンスする際にWorkbookFactory.Createを使用していますが、これで xls, xlsx 両形式に対応することができます。

最後に/tmpにコピーしたファイルを削除しています。これをやらないと処理を行う度に/tmpディレクトリにファイルが増えていってしまいます。

実行結果

この Lambda 関数をサーバーにアップロードした結果は成功で、CloudWatch に以下のログが出力されたので想定通りになります。

START RequestId: b9bc1eca-36e2-417b-a899-d7a6ef705f57 Version: $LATEST
AAAAA
11111
BBBBB
22222
CCCCC
33333
END RequestId: b9bc1eca-36e2-417b-a899-d7a6ef705f57

おわりに

毎度ながら情報が少なく苦労させられるのですが、この情報がお役に立てば幸いです。

参考サイト

www.bokukoko.info

AWS : S3 Storage | Sacha's Blog