AWS LambdaとActiveReports for .NETでつくる帳票生成API

.NET帳票コンポーネント「ActiveReports for .NET(アクティブレポート)」は18.0Jより、クラウド環境のサポートが拡充され、新たに大手クラウドベンダー4社の「FaaS(Function as a Service)」が利用可能となりました。今回は、「AWS Lambda」とActiveReportsを利用して、帳票機能をWeb APIとして作成する方法をご紹介します。

開発環境

ActiveReports for .NETの開発環境にはOSに「Windows」、統合開発環境(IDE)に「Visual Studio」が必要となります。事前に、ActiveReportsの必要システムに記載されている、開発環境をご準備ください。さらに、今回はAWS Lambdaのデプロイ先としてAWS環境もご用意ください。

今回の開発環境では、以下を使用します。

  • AWS 環境
  • OS:Windows 11(23H2)
  • IDE:Visual Studio 2022(Version 17.13.2)
  • AWS Toolkit for Visual Studio
    ※ 事前に、Toolkitを使用して用意したAWS環境アカウントへの接続を完了させる必要があります。
      接続方法については、こちらを参考に設定してください
  • ActiveReports:18.0J SP1(v18.1.1.0)

製品版の最新バージョンは以下より入手可能です。

トライアル版は無料で以下より入手可能です。

プロジェクトの作成

それでは、プロジェクトの作成を行っていきます。今回は次のようにAWS Toolkit for Visual Studioにて提供されている「AWS Serverless Application」プロジェクトテンプレートを利用します。

検索ボックスに「AWS Serverless Application」と入力すると、「AWS Serverless Applicatio(.NET Core C#)」がフィルタリングされますので、こちらを選択して[次へ]ボタンを押します。

プロジェクトの作成1

続いて表示されるダイアログでは、プロジェクト名を「LambdaReportsApp」とします。保存場所は任意の場所を設定してください。

プロジェクトの作成2

最後に、次のようなBluePrint(作成するアプリケーションテンプレート)選択画面が表示されます。ここで「ASP.NET Core Web API」を選択し、[Finish]ボタンを押しプロジェクトを作成します。

プロジェクトの作成3

NuGetからパッケージをインストール

ActiveReport関連パッケージ

プロジェクトが作成されると次のような画面が表示されます。まずはActiveReportの関連パッケージをNuGetからインストールしていきます。

図のように、ソリューションエクスプローラー上からプロジェクトを右クリックし「NuGetパッケージの管理」を選択します。

NuGetからのインストール1

続いて、NuGetパッケージマネージャーの表示後、「参照」タブを選択、検索テキストボックスに「MESCIUS.ActiveReports.ja」を入力します。検索結果には次のように「MESCIUS.ActiveReports.ja」が表示されるので、選択し、インストールを行います。

NuGetからのActiveReportsをインストール

続いて、今回作成するWeb APIで利用するPDF出力に関するパッケージ「MESCIUS.ActiveReports.Export.Pdf.ja」もインストールします。

NuGetからのPDFパッケージインストール

NSwag

さらに、動作確認の際に利用するSwagger UIを追加する為のパッケージ「NSwag.Asp.NetCore」もあわせてインストールします。

NuGetからのNSwagパッケージをインストール

帳票レイアウト

パッケージのインストール後、帳票レイアウトの準備を行います。今回は以下のGitHubに公開されているレポートファイル「Invoice_bluegray.rdlx」を利用します。

今回のプログラムは、AWS LambdaのWeb APIとして実装します。APIのリクエストのタイミングでJSONデータを渡し、そのデータを帳票のデータソースとして利用して帳票を生成する構成として実装を行っていきます。

最初に、ダウンロードしたレポートファイルを次のようにソリューションエクスプローラーから追加します。追加の際、ファイルのビルドアクションを「埋め込みリソース」に変更します。

レポートファイルの追加

続けて、APIのリクエスト時に渡されるJSONデータを帳票のデータソースとして利用するために、データを格納する「レポートパラメータ」を追加していきます。

追加したファイルを選択し、Visual StudioのIDEデザイナで開きます。次に、レポートエクスプローラからパラメータを選択し、「パラメータの追加」を使用してレポートパラメータを追加します。

レポートファイルの編集1

レポートパラメータダイアログが表示後、パラメータ名称など変更可能です。今回は初期値のままにしておきます。

レポートファイルの編集2

続いて、レポートパラメータダイアログの「既定値」タブを選択します。「値を直接入力」を選び、「+」アイコンをクリックして既定値のリストを追加します。追加されたリスト内に、レポート表示データの初期値となるJSONデータを設定します。

レポートファイルの編集3
レポート表示初期値用JSONデータ
[
  {
    "ID": 1,
    "BillNo": "WS-DF502",
    "SlipNo": "GB465",
    "CustomerID": 1,
    "CustomerName": "長崎カントリーフーズ",
    "Products": "コーヒー 250 ml",
    "Number": 100,
    "UnitPrice": 100,
    "Date": "2020-01-05T00:00:00"
  },
  {
    "ID": 2,
    "BillNo": "WS-DF502",
    "SlipNo": "GB465",
    "CustomerID": 1,
    "CustomerName": "長崎カントリーフーズ",
    "Products": "紅茶 350 ml",
    "Number": 300,
    "UnitPrice": 120,
    "Date": "2020-01-05T00:00:00"
  },
  {
    "ID": 3,
    "BillNo": "WS-DF502",
    "SlipNo": "DK055",
    "CustomerID": 1,
    "CustomerName": "長崎カントリーフーズ",
    "Products": "炭酸飲料 (オレンジ) 350 ml",
    "Number": 200,
    "UnitPrice": 120,
    "Date": "2020-01-09T00:00:00"
  }
]

つづけて、追加したレポートパラメータを帳票のデータソースとして利用する為、以下のようにレポートエクスプローラの「データソース」から現在設定されている「DataSource1」を編集で開きます。

レポートファイルの編集4

レポートデータソースダイアログの表示後、次の画像のように「コンテンツ」タブ内の「JSONデータの形式の選択」を「式」に変更し、つづけて「式の入力」に="jsondata=" &[@ReportParameter1]を入力します。

レポートファイルの編集5

以上で、帳票レイアウトの設定は終了です。

コード実装

つづいて、コードの実装を行っていきます。まず、帳票描画処理を行うための次のようにControllerの追加を行います。

Controllerの追加

新しい項目の追加画面の表示後「APIコントローラー(空)」を選択。名前に「PdfExportController.cs」と設定し、[追加]ボタンを押し、ファイルを追加します。

Controllerの追加2

ファイルの追加後、以下コードの強調箇所を追加します。

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using GrapeCity.ActiveReports.Export.Pdf.Page;
using GrapeCity.ActiveReports.Extensibility.Rendering.IO;
using GrapeCity.ActiveReports.Rendering.IO;
using GrapeCity.ActiveReports;
using System.Xml;
using System.Reflection;
using static GrapeCity.Enterprise.Data.DataEngine.DataProcessing.DataProcessor;
using System.Text.Json;
using System.Text.Json.Serialization;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace LambdaReportsApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PdfExportController : ControllerBase
    {
        /// <summary>
        /// GETメソッド
        /// </summary>
        /// <param name="DataJson">レポートデータ</param>
        /// <returns>APIGatewayProxyResponse</returns>
        [HttpGet]
        public APIGatewayProxyResponse Get([FromQuery] string DataJson)
        {
            return ExportFunction(DataJson);
        }

        /// <summary>
        /// POSTメソッド
        /// </summary>
        /// <param name="DataJson">レポートデータ</param>
        /// <returns>APIGatewayProxyResponse</returns>
        [HttpPost]
        public APIGatewayProxyResponse Post([FromBody] string DataJson)
        {
            return ExportFunction(DataJson);
        }

        /// <summary>
        /// レポート出力処理
        /// </summary>
        /// <param name="DataJson">レポートデータ</param>
        /// <returns>APIGatewayProxyResponse</returns>
        private APIGatewayProxyResponse ExportFunction(string DataJson)
        {
            //戻り値を設定
            APIGatewayProxyResponse result;


            try
            {
                MemoryStream pdfResult = GetResult(DataJson);
                string base64Pdf = ConvertStreamPdfToBase64(pdfResult);
                result = new APIGatewayProxyResponse
                {
                    StatusCode = 200,
                    IsBase64Encoded = true,
                    Body = base64Pdf,
                    Headers = new Dictionary<string, string>
                    {
                        { "Content-Type", "application/pdf" },
                        { "Content-Disposition", "attachment; filename=\"report.pdf\"" }
                    }
                };

            }
            catch (Exception ex)
            {
                result = new APIGatewayProxyResponse
                {
                    StatusCode = 400,
                    IsBase64Encoded = false,
                    Body = ex.Message,
                    Headers = new Dictionary<string, string>
                    {
                        { "Content-Type", "text/plain" }
                    }
                };
            }

            return result;
        }

        private static MemoryStream GetResult(string DataJson)
        {
            MemoryStream? outputStream = null;
            try
            {
                //アセンブリ取得
                var assembly = Assembly.GetExecutingAssembly();

                //レポートファイルのストリームを取得
                var stream = assembly.GetManifestResourceStream("LambdaReportsApp.Invoice_bluegray.rdlx");
                var streamReader = new StreamReader(stream);

                //レポートファイルを読み込み
                GrapeCity.ActiveReports.PageReport pageReport = new PageReport(streamReader);

                //レポートにパラメータを設定
                pageReport.Document.Parameters[0].Values[0].Value = DataJson;

                //PDF出力設定
                GrapeCity.ActiveReports.Export.Pdf.Page.Settings pdfSetting = new GrapeCity.ActiveReports.Export.Pdf.Page.Settings();
                GrapeCity.ActiveReports.Export.Pdf.Page.PdfRenderingExtension pdfRenderingExtension = new GrapeCity.ActiveReports.Export.Pdf.Page.PdfRenderingExtension();
                GrapeCity.ActiveReports.Rendering.IO.MemoryStreamProvider outputProvider = new GrapeCity.ActiveReports.Rendering.IO.MemoryStreamProvider();

                //PDFレンダリング
                pageReport.Document.Render(pdfRenderingExtension, outputProvider, pdfSetting);

                //PDFファイルをMemoryStreamに変換
                System.IO.MemoryStream ms = new System.IO.MemoryStream();
                outputProvider.GetPrimaryStream().OpenStream().CopyTo(ms);

                outputStream = ms;
            }
            catch (Exception)
            {
                //エラー処理 エラー通知を返す
                throw;
            }
            return outputStream ?? new MemoryStream();
        }

        private string ConvertStreamPdfToBase64(MemoryStream pdfStream)
        {
            return Convert.ToBase64String(pdfStream.ToArray());
        }

    }
}

このコードでは、GETメソッドのリクエスト時にクエリパラメータ「DataJson」を渡すか、POSTメソッドの場合はリクエストボディで設定されたパラメータ「DataJson」を渡します。これを帳票レイアウトで定義したレポートパラメーターを通じて帳票データソースとして利用し、最終的にPDFファイルとして帳票を作成しています。

作成したPDFファイルは、ファイルそのままではなく、Base64形式の文字列に変換して返却しています。これは、AWS Lambdaの戻り値であるAPIGatewayProxyResponseでバイナリデータを返却する場合、Base64エンコードし、isBase64Encoded: true を指定する必要があるためです。

Swagger UIの追加

ここまでで、帳票APIの実装を行ってきましたが、続いてAPIの動作を確認するために、Swagger UIを追加します。
Swagger UIは、OpenAPI仕様に基づいたインタラクティブなAPIドキュメントを提供するツールで、リクエストパラメータやレスポンスの形式を直感的に確認できるほか、APIを実際に呼び出して挙動をテストすることも可能です。これにより、APIの仕様理解やデバッグ作業がスムーズに行え、APIの検証に最適です。

アプリケーションの起動時の設定

Swagger UIを追加する為、アプリケーション起動時の設定が行える「Startup.cs」ファイルに以下のように設定を追加します。

using Microsoft.Extensions.Options;
using NSwag.AspNetCore;

namespace LambdaReportsApp;

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // このメソッドはランタイムによって呼び出されます。このメソッドを使用してコンテナにサービスを追加します
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddOpenApiDocument();
    }

    // このメソッドはランタイムによって呼び出されます。このメソッドを使用してHTTPリクエストパイプラインを構成します
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseOpenApi();
        app.UseSwaggerUi();

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Welcome to running ASP.NET Core on AWS Lambda");
            });
        });
    }
}

これで、基本的な設定の追加は完了です。「Web APIのURL/swagger」にアクセスして動作を確認してみます。

Swagger UIのカスタマイズ

ここまでの設定でSwagger UIの追加とAPIの動作確認が行えました。しかし、Lambdaの戻り値であるAPIGatewayProxyResponseは、作成されたファイルを文字列として返却するため、作成されたファイルの内容を確認することができません。この問題を解消するために、Swagger UIをカスタマイズし、PDFファイルをダウンロードできる機能を実装します。

まず、次のようにプロジェクトに「wwwroot」フォルダを追加。さらにフォルダ内に「swagger-ui」というフォルダを追加します。

Swagger UIカスタマイズ

追加した「swagger-ui」フォルダ内に、以下の「custom.css」、「custom.js」を追加します。

.pdf-download-button {
    background-color: #4CAF50;
    border: none;
    color: white;
    padding: 10px 15px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 14px;
    margin: 10px 2px;
    cursor: pointer;
    border-radius: 4px;
}

    .pdf-download-button:hover {
        background-color: #45a049;
    }
// NSwag Swagger UIのPDFダウンロードボタン実装
(function () {
    // ページの読み込み後に実行
    window.addEventListener('load', function () {
        console.log("PDF Download Button Handler Loaded");

        // MutationObserverの設定
        setupResponseObserver();
    });

    // レスポンスの変更を監視
    function setupResponseObserver() {
        const observer = new MutationObserver(function (mutations) {
            mutations.forEach(function (mutation) {
                // 新しく追加されたノードがある場合
                if (mutation.addedNodes && mutation.addedNodes.length > 0) {
                    // レスポンスボディを検索
                    checkResponsesForPdf();
                }
            });
        });

        // ドキュメント全体を監視
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 実行ボタンのクリックイベントも監視
        document.addEventListener('click', function (e) {
            // 実行ボタンのクリック後にチェック
            if (e.target.classList.contains('nswag-execute-button') ||
                e.target.closest('.nswag-execute-button')) {
                setTimeout(checkResponsesForPdf, 1000);
            }
        });
    }

    // レスポンスボディをチェックしてPDF判定
    function checkResponsesForPdf() {
        // NSwagのレスポンスコンテナを取得
        const responseElements = document.querySelectorAll('.responses-table.live-responses-table pre');

        responseElements.forEach(element => {

            console.log("Checking response element:", element);
            // 既に処理済みの要素はスキップ
            if (element.dataset.pdfChecked === 'true') return;

            console.log("Checking response element:", element);
            // 処理済みとしてマーク
            element.dataset.pdfChecked = 'true';

            try {
                const responseText = element.textContent;
                if (!responseText) return;

                console.log("Response text:", responseText);
                const responseData = JSON.parse(responseText);
                console.log("Parsed response data:", responseData);

                // PDF応答かどうかチェック - プロパティ名の大文字小文字を両方チェック
                if (responseData) {
                    // statusCode/StatusCode チェック
                    const hasValidStatus = responseData.statusCode === 200 || responseData.StatusCode === 200;
                    
                    // headers/Headers チェック
                    const headers = responseData.headers || responseData.Headers || {};
                    const hasValidContentType = headers["Content-Type"] === "application/pdf" || 
                                               headers["content-type"] === "application/pdf";
                    
                    // isBase64Encoded/IsBase64Encoded チェック
                    const isBase64 = responseData.isBase64Encoded === true || responseData.IsBase64Encoded === true;
                    
                    // body/Body チェック
                    const hasBody = responseData.body || responseData.Body;

                    console.log("Status check:", hasValidStatus);
                    console.log("Content-Type check:", hasValidContentType);
                    console.log("Base64 check:", isBase64);
                    console.log("Body check:", !!hasBody);

                    if (hasValidStatus && hasValidContentType && isBase64 && hasBody) {
                        console.log("PDF response detected - adding download button");

                        // ボタンを作成して追加
                        addPdfDownloadButton(element, responseData);
                    }
                }
            } catch (e) {
                console.log("Not a valid JSON or not a PDF response", e);
            }
        });
    }

    // ダウンロードボタンを追加
    function addPdfDownloadButton(element, responseData) {
        // ボタンコンテナを作成
        const buttonContainer = document.createElement('div');
        buttonContainer.style.marginTop = '10px';

        // ダウンロードボタンを作成
        const downloadButton = document.createElement('button');
        downloadButton.className = 'pdf-download-button';
        downloadButton.textContent = 'Download PDF';
        downloadButton.onclick = function () {
            downloadPdf(responseData);
        };

        // ボタンを追加
        buttonContainer.appendChild(downloadButton);

        // レスポンス要素の後にボタンを挿入
        if (element.parentNode) {
            element.parentNode.insertBefore(buttonContainer, element.nextSibling);
        }
    }

    // PDF実際のダウンロード処理
    function downloadPdf(responseData) {
        try {
            // body または Body プロパティを取得
            const base64Content = responseData.body || responseData.Body;
            if (!base64Content) {
                throw new Error("PDF content not found in response");
            }

            // Base64文字列をデコード
            const binaryString = atob(base64Content);
            const bytes = new Uint8Array(binaryString.length);
            for (let i = 0; i < binaryString.length; i++) {
                bytes[i] = binaryString.charCodeAt(i);
            }

            // PDFのBlobを作成
            const blob = new Blob([bytes.buffer], { type: 'application/pdf' });

            // ファイル名を取得
            let filename = "report.pdf";  // デフォルト名
            const headers = responseData.headers || responseData.Headers || {};
            const contentDisposition = headers["Content-Disposition"] || headers["content-disposition"];
            
            if (contentDisposition) {
                const match = contentDisposition.match(/filename="([^"]+)"/);
                if (match && match[1]) {
                    filename = match[1];
                }
            }

            // ダウンロード用リンクを作成
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();

            // クリーンアップ
            setTimeout(function () {
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            }, 100);

            console.log("PDF download initiated for file:", filename);
        } catch (e) {
            console.error("PDF download failed:", e);
            alert("Failed to download PDF: " + e.message);
        }
    }
})();

続いて、追加したjsファイルがSwagger UIで動作するように、「Startup.cs」ファイルの以下の強調箇所の設定を変更します。

・・・(中略)・・・
    // このメソッドはランタイムによって呼び出されます。このメソッドを使用してHTTPリクエストパイプラインを構成します
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseStaticFiles();
        app.UseOpenApi();
        app.UseSwaggerUi(config =>
        {
            // カスタムファイルを追加
            config.CustomStylesheetPath = "/swagger-ui/custom.css";
            config.CustomJavaScriptPath = "/swagger-ui/custom.js";
        });

・・・(中略)・・・

動作確認

最後に、カスタマイズした動作の確認を行います。

「custom.js」で追加されたPDFボタンにより、返却されたBase64文字列がバイナリデータに変換され、PDFファイルとしてダウンロード出来ることが確認できました。

AWS環境へデプロイ

ここまでで、帳票レイアウト作成、コード実装、ローカル環境での動作の確認までの、一連の作業が完了しましたので、AWS環境へのデプロイを行っていきます。

ライセンスの設定

AWS環境へデプロイする為に、まずアプリケーションへのライセンス設定を行っていきます。

「AWS Lambda」を含む、FaaS(Function as a Service)環境での実行には、通常の「.NETアプリケーション」と異なり、「gclm.exe」を利用したライセンスを付与する必要があります。
※ AWS Lambdaを利用する際は通常のコアサーバーライセンスでは利用できません。配布ライセンスの詳細については営業部へお問い合わせください。

まず、以下のように、Windowsのコマンドラインツールで「gclm.exe」が配置されているパスへ移動します。

cd C:\ProgramData\GrapeCity\gclm

続いて、次の形式のコマンドを実行し、任意のフォルダにライセンスファイルを生成します。

gclm.exe "[ActiveReports GUID]" -lc "[output dir]/[filename].gclicx" [DeployTarget].[assemblyName].dll
項目設定内容
[ActiveReports GUID]ActiveReports for .NET 18.0J 製品IDを設定します。
8145dedc-e6e6-4af3-917b-c3fb57853f6b
[output dir]ライセンスファイルの出力先フォルダを設定します。
[filename]任意のファイル名を設定します。
[DeployTarget]Amazon.Lambda.RuntimeSupport
[assemblyName]アプリケーションのアセンブリ名を指定します。今回は次のアセンブリ名を設定します。
LambdaReportsApp

上述のコマンドを利用して、以下のように、ライセンスの付与ファイル「.gclicx」を生成します。

ライセンスファイルの生成

続いて、コマンドにより生成されたライセンスファイル「.gclicx」をプロジェクト内に含め、ビルドアクションを「埋め込みリソース」に設定します。

ライセンスファイルの追加

上記に加えて、プロジェクトファイルをエディタで開き、以下のように<PropertyGroup>内に<disablegclm>true</disablegclm>の記述を追加します。この設定は、通常の .NETアプリケーションをビルドする際に自動的に付与されているライセンスを無効化するものです。プロジェクトファイルを編集した後、再度プロジェクトを開き直してください。

自動付与ライセンスを無効化

フォント設定

つづいて、フォントの設定を行っていきます。今回使用している帳票レイアウトでは、フォントを「IPAゴシック」に設定しています。ローカル環境では該当フォントがインストールされているため、問題なく表示されていましたが、AWS環境上にはこのフォントがインストールされていないため、フォントファイルを含めてデプロイする必要があります。

まず、プロジェクト内に「Fonts」フォルダを作成し、そのフォルダ内にフォントファイルを格納し、ビルドアクションを「埋め込みリソース」に設定、さらに出力ディレクトリにコピーを「新しい場合はコピーする」に設定します。
※ IPAゴシックファイルはこちらからダウンロード可能です。

フォントファイルの追加

格納したフォントファイルを作成した帳票で利用するため、「EmbeddedFontResolver」クラスを追加します。

using GrapeCity.ActiveReports;
using GrapeCity.Documents.Text;
using System.Reflection;

internal sealed class EmbeddedFontResolver : IFontResolver
{
    internal static readonly IFontResolver Instance = new EmbeddedFontResolver();
    internal static readonly Font Ipag;
    static EmbeddedFontResolver()
    {
        Ipag = Font.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("LambdaReportsApp.Fonts.ipag.ttf"));
    }
    internal EmbeddedFontResolver() { }
    public FontCollection GetFonts(string familyName, bool isBold, bool isItalic)
    {
        var fonts = new FontCollection();
        fonts.AppendFonts(new Font[] { Ipag }, true);
        return fonts;
    }
}

さらに、下記コードで強調表示されている通り、実装済みの「PdfExportController.cs」内の「GetResult」関数において、追加したフォント埋込クラス「EmbeddedFontResolver」のインスタンスをレポートオブジェクトの「FontResolver」プロパティに設定し、フォントの埋め込みを実施します。

・・・(中略)・・・
        private static MemoryStream GetResult(string DataJson)
        {
            MemoryStream? outputStream = null;
            try
            {
                //アセンブリ取得
                var assembly = Assembly.GetExecutingAssembly();

                //レポートファイルのストリームを取得
                var stream = assembly.GetManifestResourceStream("LambdaReportsApp.Invoice_bluegray.rdlx");
                var streamReader = new StreamReader(stream);

                //レポートファイルを読み込み
                GrapeCity.ActiveReports.PageReport pageReport = new PageReport(streamReader);

                ////フォントの設定
                pageReport.FontResolver = new EmbeddedFontResolver();

                //レポートにパラメータを設定
                pageReport.Document.Parameters[0].Values[0].Value = DataJson;

                //PDF出力設定
                GrapeCity.ActiveReports.Export.Pdf.Page.Settings pdfSetting = new GrapeCity.ActiveReports.Export.Pdf.Page.Settings();
                GrapeCity.ActiveReports.Export.Pdf.Page.PdfRenderingExtension pdfRenderingExtension = new GrapeCity.ActiveReports.Export.Pdf.Page.PdfRenderingExtension();
                GrapeCity.ActiveReports.Rendering.IO.MemoryStreamProvider outputProvider = new GrapeCity.ActiveReports.Rendering.IO.MemoryStreamProvider();

                //PDFレンダリング
                pageReport.Document.Render(pdfRenderingExtension, outputProvider, pdfSetting);

                //PDFファイルをMemoryStreamに変換
                System.IO.MemoryStream ms = new System.IO.MemoryStream();
                outputProvider.GetPrimaryStream().OpenStream().CopyTo(ms);

                outputStream = ms;
            }
            catch (Exception)
            {
                //エラー処理 エラー通知を返す
                throw;
            }
            return outputStream ?? new MemoryStream();
        }

・・・(中略)・・・

デプロイ

ライセンス設定とフォント設定が完了し、デプロイの準備が整いましたので、アプリケーションをAWS上に発行します。

まず、ソリューションエクスプローラーからプロジェクトを選択し、コンテキストメニューから「Publish To AWS Lambda」をクリックします。

AWSへデプロイ1

「Publish AWS Serverless Application」ダイアログ表示されたら、「AWS Credenrials」にデプロイしたいプロファイルを設定し、「Region」にデプロイ先となるリージョンを指定。さらに「StackName」に「LambdaReportsApp」を入力。「S3 Bucket」には、アプリケーションの格納先となるS3バケットを設定し、[Publish]ボタンを押して発行を行います。

AWSへデプロイ2

発行中は、ダイアログ内に以下のように作業ステータスが表示され、完了すると、ダイアログは自動的に閉じます。

AWSへデプロイ3

ダイアログが閉じられると、AWS環境側で発行されたアプリケーションを利用可能にするために、「IAMロールの作成」、「Lambda関数のデプロイと設定」、「API Gatewayの作成」、「パーミッションの設定」、「API Gatewayのステージ設定」などの作業が実行されます。すべて完了すると、Statusが「CREATE_COMPLETE」に更新され、「AWS Serverless URL」に設定されたリンクを開くことでWeb APIを使用できるようになります。

AWSへデプロイ4

動作確認

最後に、動作確認を行います。デプロイ時に表示された「AWS Serverless URL」のリンク、または以下の画像のようにAWSマネジメントコンソールでデプロイしたLambdaアプリケーションのトリガーのエンドポイントにアクセスします。

AWSマネジメントコンソール

アクセス後、次のように「Web APIのURL/swagger」を開き、Web APIの動作の確認を行っていきます。

GETメソッド

GETメソッドには次のテストデータを設定し、動作を確認します。

[{"ID":1,"BillNo":"WS-DF502","SlipNo":"GB465","CustomerID":1,"CustomerName":"AWSGETメソッドテスト","Products":"コーヒー 250 ml","Number":100,"UnitPrice":100,"Date":"2020-01-05T00:00:00"},{"ID":2,"BillNo":"WS-DF502","SlipNo":"GB465","CustomerID":1,"CustomerName":"AWSGETメソッドテスト","Products":"紅茶 350 ml","Number":300,"UnitPrice":120,"Date":"2020-01-05T00:00:00"},{"ID":3,"BillNo":"WS-DF502","SlipNo":"DK055","CustomerID":1,"CustomerName":"AWSGETメソッドテスト","Products":"炭酸飲料 (オレンジ) 350 ml","Number":200,"UnitPrice":120,"Date":"2020-01-09T00:00:00"}]

POSTメソッド

POSTメソッドには次のテストデータを設定し、動作を確認します。

"[{\"ID\":1,\"BillNo\":\"WS-DF502\",\"SlipNo\":\"GB465\",\"CustomerID\":1,\"CustomerName\":\"AWSPOSTメソッドテスト\",\"Products\":\"コーヒー 250 ml\",\"Number\":100,\"UnitPrice\":100,\"Date\":\"2020-01-05T00:00:00\"},{\"ID\":2,\"BillNo\":\"WS-DF502\",\"SlipNo\":\"GB465\",\"CustomerID\":1,\"CustomerName\":\"AWSPOSTメソッドテスト\",\"Products\":\"紅茶 350 ml\",\"Number\":300,\"UnitPrice\":120,\"Date\":\"2020-01-05T00:00:00\"},{\"ID\":3,\"BillNo\":\"WS-DF502\",\"SlipNo\":\"DK055\",\"CustomerID\":1,\"CustomerName\":\"AWSPOSTメソッドテスト\",\"Products\":\"炭酸飲料 (オレンジ) 350 ml\",\"Number\":200,\"UnitPrice\":120,\"Date\":\"2020-01-09T00:00:00\"}]"

AWS環境においても、Swagger UIを利用して、「GET」、「POST」それぞれのメソッドの動作の確認ができました。

さいごに

今回の記事では、ActiveReports for .NET 18.0Jでサポートされた「AWS Lambda」を活用し、帳票機能をWeb APIとして構築する方法をご紹介しました。Web APIとして帳票機能を提供することで、従来のプラットフォームに加えて、クラウドサービスやモバイルアプリケーションなど、多様な環境から利用できるようになり、使用シーンが大きく広がります。さまざまなプラットフォームで帳票アプリを活用したいとお考えの方は、ぜひ今回の方法をご参考にしていただければ幸いです。

今回の内容について動作を確認されたい方は、是非トライアル版ダウンロードの上、GitHub上のコードをお試しください。

製品の機能を手軽に体験できるデモアプリケーションも公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /