アプリケーション開発において、ローコード・ノーコードテクノロジーが急速に広がっています。特にMicrosoftの「Power Apps」は、提携関係のあるOpenAI社の生成AIテクノロジーを取り入れ、コードが不要となる機能「Copilot」を追加し、さらに一段上のローコード・ノーコードプラットフォームへと進化を遂げています。
このPower Appsを利用したアプリケーション開発の動きは、企業の業務システム開発分野でも広がっており、これまで作成してきた業務システムのマイグレーションニーズも増加しています。こうしたニーズに対しても、Power Appsは、同じく「Power Platform」ファミリである「Power Automate」、「Dataverse」など複数製品の機能を連携して利用することで、大規模なシステム移行にも対応が可能となっています。
こうした動きがある一方で、全ての日本の業務システムをカバーすることが可能かというと、日本の特有の要件などを満たすことは難しく、特に業務帳票の開発においては、既存の帳票を移行するのは困難な状況となっています。
このような状況の中で、当社は帳票開発コンポーネント「ActiveReports for .NET(アクティブレポート)」を活用することで、既存帳票の移行の課題を解決できるのではないかと考え、Power Apps上での利用方法について調査を行いました。今回の記事では、調査結果によって得られた、Power Apps上でのActiveReports for .NETを利用した帳票開発の具体的な方法についてご紹介いたします。
目次
Power AppsでActiveReportsの帳票を利用する方法はいくつかありますが、今回は、ActiveReportsのJSビューワをPower Appsのコードコンポーネントとして実装し利用します。この方法では、ASP.NET CoreのWebアプリ上にある帳票エンジンで生成されるドキュメントを、ビューワコンポーネントを介して、Power Apps上に帳票機能を提供することが可能となります。
事前準備
今回の開発では以下の環境を使用します。
- Visual Studio Code
- Npm or Node.JS ※ LTS (長期サポート) バージョンのご利用をお勧め
- Power Platform CLI(Power Apps Component Frameworkを利用する為のツール)
- OS:Windows 11(23H2)
- IDE:Visual Studio 2022(Version 17.9.5)
- ActiveReports:18.0J (v18.0.0.0)
製品版の最新バージョンは以下より入手可能です。
トライアル版は無料で以下より入手可能です。
Power Appsで利用可能なActiveReportsビューワコンポーネントの作成
それでは、ActiveReportsのJSビューワをPower Appsのコードコンポーネントとして実装を行っていきます。コードコンポーネントの作成方法については、以下の記事でも詳しく解説しています。こちらもあわせてご覧ください。
プロジェクト作成
コードコンポーネントのプロジェクトを作成します。プロジェクトの作成あたり、事前準備にある、「Visual Studio Code」、「Npm or Node.JS」、「Power Platform CLI」のインストールを行ったうえで、さらに任意のフォルダを作成しておきます。今回は「ARdotNetViewerPCF」というフォルダとして作成しました。
作成したフォルダをVisual Studio Codeで開き、ターミナルを起動します。ターミナル上で以下のコマンドを実行しPCFプロジェクトを作成します。
pac pcf init -n ARdotNetViewer -ns ARdotNet -t field -fw react -npm
コマンドを実行すると、プロジェクトフォルダが作成され、コマンドオプション「-npm」に基づいて、npm installが実行され、プロジェクトが作成されます。
ActiveReports.NET JSビューワのインストール
続いて、以下のコマンドを実行し、ActiveReportsのJSビューワをインストールします。
npm install @mescius/activereportsnet-viewer-ja
CSSファイルの追加
ここまでの手順で、プロジェクトの作成が終わりましたので、続いて必要なフォルダとファイルをプロジェクト上に追加していきます。
まず、以下のコマンドで、作成したプロジェクトフォルダへ移動します。
cd ARdotNetViewer
VSCode上より、ARdotNetViewerの配下にCSSフォルダを追加します。
作成したフォルダ内に以下の「App.css」を追加します。
#viewer-host {
width: 100%;
height: 100vh;
}
.flexbox {
display:block;
}
さらに、先ほどインストールしたActiveReportsのJSビューワのパッケージフォルダから以下のファイルをコピーし、CSSフォルダへ追加します。
ARdotNetViewerPCF\node_modules\@mescius\activereportsnet-viewer-ja\dist\jsViewer.min.css
ARdotNetViewerPCF\node_modules\@mescius\activereportsnet-viewer-ja\dist\jsViewer.chart.min.css
マニフェストファイルの更新
続いて、コンポーネントの名前、参照するcssなどのリソースや、プロパティ定義などを行う為のマニフェストファイルを、以下コードの強調箇所のように更新します。
※ 以下のコードでは、既存のコメントを理解しやすいように翻訳しています。
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="ARdotNet" constructor="ARdotNetViewer" version="0.0.1" display-name-key="ARdotNetViewer" description-key="ARdotNetViewer description" control-type="virtual" >
<!--external-service-usageノードは、このサードパーティPCFコントロールが外部サービスを使用しているかどうかを宣言します。もし使用している場合、このコントロールはプレミアムと見なされ、使用している外部ドメインも追加してください。
外部サービスを使用していない場合、enabled="false"に設定し、以下にドメインを追加しないでください。"enabled"はデフォルトでfalseになります。
例1:
<external-service-usage enabled="true">
<domain>www.Microsoft.com</domain>
</external-service-usage>
例2:
<external-service-usage enabled="false">
</external-service-usage>
-->
<external-service-usage enabled="true">
<domain>http://example.com/</domain>
<!--外部ドメインを追加するにはコメントを解除してください
<domain></domain>
<domain></domain>
-->
</external-service-usage>
<!-- propertyノードは、コントロールがCDSから期待する特定の設定可能なデータを識別します -->
<!-- <property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" /> -->
<property name="Server_Url" display-name-key="Server_Url" description-key="ActiveReport.Netのバックエンドサーバーを指定します。" of-type="SingleLine.Text" pfx-default-value ='"http://example.com/"' usage="input" required="true" />
<property name="Reports_ID" display-name-key="Reports_ID" description-key="表示するレポートを指定します。" of-type="SingleLine.Text" pfx-default-value ='"Invoice.rdlx"' usage="input" required="true"/>
<property name="Reports_Loading" display-name-key="Reports_Loading" description-key="レポートの読み込みを行うかを指定します。" of-type="TwoOptions" pfx-default-value ='false' usage="input" required="true"/>
<property name="Style_Width" display-name-key="Style_Width" description-key="ビューワの横幅を指定します。" of-type="SingleLine.Text" pfx-default-value ='"100%"' usage="input" required="true"/>
<property name="Style_Height" display-name-key="Style_Height" description-key="ビューワの高さを指定します。" of-type="SingleLine.Text" pfx-default-value ='"100vh"' usage="input" required="true"/>
<!--
プロパティノードの of-type 属性は of-type-group 属性にすることができます。
例:
<type-group name="numbers">
<type>Whole.None</type>
<type>Currency</type>
<type>FP</type>
<type>Decimal</type>
</type-group>
<property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type-group="numbers" usage="bound" required="true" />
-->
<resources>
<code path="index.ts" order="1"/>
<platform-library name="React" version="16.8.6" />
<!-- <platform-library name="Fluent" version="8.29.0" /> -->
<css path="css/App.css" order="1" />
<css path="css/jsViewer.min.css" order="2" />
<css path="css/jsViewer.chart.min.css" order="3" />
<!-- 追加のリソースを追加するにはコメントを解除してください
<css path="css/ARdotNetViewer.css" order="1" />
<resx path="strings/ARdotNetViewer.1033.resx" version="1.0.0" />
-->
</resources>
<!-- 指定されたAPIを有効にするにはコメントを解除してください
<feature-usage>
<uses-feature name="Device.captureAudio" required="true" />
<uses-feature name="Device.captureImage" required="true" />
<uses-feature name="Device.captureVideo" required="true" />
<uses-feature name="Device.getBarcodeValue" required="true" />
<uses-feature name="Device.getCurrentPosition" required="true" />
<uses-feature name="Device.pickFile" required="true" />
<uses-feature name="Utility" required="true" />
<uses-feature name="WebAPI" required="true" />
</feature-usage>
-->
</control>
</manifest>
今回作成するコンポーネントでは、各ノードに対して以下の設定を追加しています。
ノード | プロパティ | 用途 |
---|---|---|
<external-service-usage> | – | ASP.NET CoreのWebアプリとの接続を行う為、外部ドメインを利用する設定を追加 します。通常は利用するドメインを指定しますが、今回は「http://example.com/」としています。 ※ external-service-usageの機能を利用する場合は、Power Appsのプレミアムコントロールを利用できるライセンスが必要となります。詳細についてはPower Appsのライセンス、価格をご確認ください。 |
<property> | Server_Url | ActiveReport.Netのバックエンドサーバーを設定 |
Reports_ID | 表示するレポートを設定 | |
Reports_Loading | レポートの読み込みを行うかを設定 | |
Style_Width | ビューワの横幅を設定 | |
Style_Height | ビューワの高さを設定 | |
<resources> | – | JSビューワで利用するStyleSheet(cssファイル)のパスを設定 |
マニフェストファイルの更新が終わりましたら、「ManifestTypes.d.ts」の自動生成を行う為、一度以下のコマンドでビルドしておきます。
npm run build
tsxファイルの更新
続いて、ActiveReportsのJSビューワを実装する「tsx」ファイルを更新していきます。まずファイル名を「HelloWorld」から「App」へ変更します。その後ファイル内を以下のように書き換えます。
import * as React from 'react';
// 型定義
interface GrapeCity {
ActiveReports: {
JSViewer: {
create: (config: { element: string; reportService: { url: string } }) => ViewerInstance;
};
};
}
// ViewerInstance の型定義
interface ViewerInstance {
openReport: (report: string | null) => void;
}
interface Window {
GrapeCity: GrapeCity;
}
declare let window: Window;
// 追加するプロパティ
export interface IViewerProps {
Server_Url?: string | null;
Reports_ID?: string | null;
Reports_Loading?: boolean | false;
Style_Width?: string;
Style_Height?: string;
}
export class Viewer extends React.Component<IViewerProps> {
viewer: ViewerInstance | null = null;
constructor(props: IViewerProps) {
super(props);
}
componentDidMount() {
if (window.GrapeCity && window.GrapeCity.ActiveReports) {
this.viewer = window.GrapeCity.ActiveReports.JSViewer.create({
element: '#viewer-host',
reportService: {
url: (this.props.Server_Url ?? '') + 'api/reporting',
},
});
if (this.props.Reports_Loading) {
this.viewer.openReport(this.props.Reports_ID ?? null);
}
}
}
componentDidUpdate() {
if (this.viewer && this.props.Reports_Loading) {
this.viewer.openReport(this.props.Reports_ID ?? null);
}
}
render() {
const viewerStyle = {
width: this.props.Style_Width || '100%',
height: this.props.Style_Height || '100vh'
};
return (
<div>
<div id="viewer-host" style={viewerStyle} />
</div>
);
}
}
このコードでは、コンポーネントが画面に追加された際にJSビューワを初期化し、コンポーネントの「Reports_Loading」が更新された場合にレポートをオープンするように実装しています。
index.tsファイルの更新
「tsx」ファイルの変更後、「index.ts」ファイルも以下の強調表示箇所を更新します。
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import { Viewer, IViewerProps } from "./App";
import * as React from "react";
import "@mescius/activereportsnet-viewer-ja/dist/jsViewer.min.js";
export class ARdotNetViewer implements ComponentFramework.ReactControl<IInputs, IOutputs> {
private theComponent: ComponentFramework.ReactControl<IInputs, IOutputs>;
private notifyOutputChanged: () => void;
private props: IViewerProps = { Server_Url: 'http://example.com/', Reports_ID: 'Invoice.rdlx', Style_Width: '100%', Style_Height: '100vh' };
/**
* 空のコンストラクタ。
*/
constructor() { }
/**
* コントロールのインスタンスを初期化するために使用されます。ここでリモートサーバー呼び出しや他の初期化アクションを開始できます。
* データセットの値はここでは初期化されません。updateViewを使用してください。
* @param context コンテキストオブジェクトを介して制御できるプロパティバッグ全体。これは、マニフェストで定義されたプロパティ名にマッピングされたカスタマイザによって設定された値やユーティリティ関数を含みます。
* @param notifyOutputChanged コントロールに新しい出力が非同期で取得可能であることをフレームワークに通知するコールバックメソッド。
* @param state 単一ユーザーの1つのセッション内で持続するデータ。Modeインターフェイスで 'setControlState' を呼び出すことにより、コントロールのライフサイクルの任意の時点で設定できます。
*/
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary
): void {
this.notifyOutputChanged = notifyOutputChanged;
this.props.Server_Url = context.parameters.Server_Url.raw || 'http://example.com/';
this.props.Reports_ID = context.parameters.Reports_ID.raw || 'Invoice.rdlx';
this.props.Reports_Loading = context.parameters.Reports_Loading.raw.valueOf() || false;
this.props.Style_Width = context.parameters.Style_Width?.raw || '100%';
this.props.Style_Height = context.parameters.Style_Height?.raw || '100vh';
}
/**
* プロパティバッグ内の任意の値が変更されたときに呼び出されます。これには、フィールド値、データセット、コンテナの高さや幅などのグローバル値、オフラインステータス、ラベル、可視性などのコントロールメタデータ値が含まれます。
* @param context コンテキストオブジェクトを介して制御できるプロパティバッグ全体。これは、マニフェストで定義された名前にマッピングされたカスタマイザによって設定された値やユーティリティ関数を含みます。
* @returns ReactElement コントロールのルートReact要素を返します。
*/
public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
this.props.Server_Url = context.parameters.Server_Url.raw || 'http://example.com/';
this.props.Reports_ID = context.parameters.Reports_ID.raw || 'Invoice.rdlx';
this.props.Reports_Loading = context.parameters.Reports_Loading.raw.valueOf() || false;
this.props.Style_Width = context.parameters.Style_Width?.raw || '100%';
this.props.Style_Height = context.parameters.Style_Height?.raw || '100vh';
return React.createElement(
Viewer, this.props
);
}
/**
* コントロールが新しいデータを受け取る前に、フレームワークによって呼び出されます。
* @returns マニフェストで定義された命名法に基づくオブジェクトを返します。「bound」または「output」とマークされたプロパティに対するオブジェクトを期待します。
*/
public getOutputs(): IOutputs {
return { };
}
/**
* コントロールがDOMツリーから削除されるときに呼び出されます。コントロールはこの呼び出しを使用してクリーンアップを行うべきです。
* 例: 保留中のリモート呼び出しのキャンセル、リスナーの削除など。
*/
public destroy(): void {
// 必要に応じてコントロールをクリーンアップするコードを追加
}
}
「init」、「updateView」それぞれのメソッドにて追加したプロパティを受け取り、内部変数へセットしたのちに、「updateView」メソッドにて HTML 要素の生成を行います。
ここまでで、コード実装は完了となりますので、以下のコマンドでデバッグを行います。
npm start
コマンドが実行すると、以下のようにブラウザが立ち上がり、ActiveReportsのJSビューワが表示され、Power Appsで利用可能なコードコンポーネントとしての動作確認が行えます。
AcitveReports ASP.NET Core Webアプリの作成
つづいて、作成したビューワコードコンポーネントに、帳票機能を提供する為のサーバー機能をもつASP.NET Core Webアプリの作成を行っていきます。今回はGitHub上で公開しているサンプルプロジェクトを利用していきます。
今回は、「WebSample/JSViewer_CORS_Core/CORS.Server」のサンプルを利用して、Webアプリケーションを構築します。GitHubよりサンプルプロジェクトのリポジトリをZip形式もしくはGitコマンドでローカルフォルダへ展開後、以下のようにソリューションファイルを開きます。
Visual Studio上でプロジェクトが開かれたのちに、以下のようにデバッグ実行を行っていきます。
デバッグ実行を行うと、以下のようにブラウザが起動します。
ビューワコードコンポーネントとASP.NET Core Webアプリのデバッグ
Webアプリのデバッグを実行を行ったままの状態で、先ほど作成した「ビューワコードコンポーネント」をデバッグ実行し、以下の動画のように、プロパティに値を設定すると、帳票が表示されることが確認できました。
デバッグ実行によって、「ビューワコードコンポーネント」、「ASP.NET Core Webアプリ」それぞれが正しく動作することが確認できました。
ASP.NET Core WebアプリのCORS設定
続いて「ASP.NET Core Webアプリ」のCORS設定を行う為、サンプルプロジェクトの「Startup.cs」を以下のように変更します。
using System;
using System.IO;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using GrapeCity.ActiveReports.Aspnetcore.Viewer;
using System.Text;
using System.Linq;
using System.Text.RegularExpressions;
namespace JSViewer_CORS_Core
{
public class Startup
{
private static readonly string CurrentDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? String.Empty;
public static readonly DirectoryInfo ReportsDirectory = new DirectoryInfo(Path.Combine(CurrentDir, "Reports"));
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
services
.AddLogging(config =>
{
// Disable the default logging configuration
config.ClearProviders();
// Enable logging for debug mode only
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development)
{
config.AddConsole();
}
})
.AddReportViewer()
.AddMvc(options => options.EnableEndpointRouting = false);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseFileServer();
// Configure CORS
//app.UseCors(cors => cors.SetIsOriginAllowed(origin => new Uri(origin).Host == "localhost")
// .AllowAnyMethod().AllowAnyHeader().AllowCredentials().WithExposedHeaders("Content-Disposition"));
app.UseCors(cors =>
cors.SetIsOriginAllowed(origin =>
{
var allowedPatterns = new[]
{
@"^localhost$", // localhost
@"^make.powerapps.com$", // make.powerapps.com
@"^.*\.powerapps\.com$", // *.powerapps.com
@"^.*\.*\.*\.*\.*\.powerapps\.com$", // *.powerapps.com
@"^.*\.azureedge\.net", // *.azureedge.net
@"^.*\.powerplatform\.com$", // *.powerplatform.com
@"^.*\.api\.powerplatformusercontent\.com$" // *.api.powerplatformusercontent.com
};
var requestHost = new Uri(origin).Host;
return allowedPatterns.Any(pattern => Regex.IsMatch(requestHost, pattern));
}
)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithExposedHeaders("Content-Disposition")
);
app.UseReportViewer(settings =>
{
settings.UseFileStore(ReportsDirectory);
});
app.UseMvc();
}
}
}
上記のようにCORSを許可するドメインを設定することで、Power Apps上からWebアプリケーションがアクセスされた際でもエラーとならず正しく機能します。対象となるドメインについては、こちらにて解説されています。必要に応じて追加ください。
Webサーバへデプロイ
Webアプリケーションの実装が終わりましたので、Power Appsから参照できるようにWebサーバ上へデプロイしていきます。インターネット上に公開されているWebサーバであれば基本的に問題ありません。今回はAzure上にデプロイします。
次のように、ソリューションエクスプローラー上で右クリック後[発行]を選択します。
ダイアログ表示後に以下の手順で、必要な項目を入力してきます。
ダイアログでプロファイルが作成できましたら、以下のように[発行]ボタンを押してAzure上へデプロイを行います。
デプロイが完了しましたら、次のようにWebブラウザ上よりWebアプリケーションへのアクセスが可能となります。
デプロイしたWebアプリケーションのURLを、ビューワコードコンポーネントの「Server_Url」プロパティに設定すると、ASP.NET Core Webアプリ上に作成された帳票を表示することが可能となりました。
さいごに
今回の記事では、Power Apps上でActiveReportsを利用する為、ActiveReportsのJSビューワをPower Appsのコードコンポーネントとして実装する方法と、ビューワーに帳票機能を提供するASP.NET Core Webアプリの実装について解説いたしました。
次回の記事では、作成したコードコンポーネントをキャンバスアプリ上に配置して、実際にPower Apps上で帳票を開発する方法についてもご紹介しています。こちらも是非ご覧ください。
製品の機能を手軽に体験できるデモアプリケーションも公開しておりますので、こちらもご確認ください。
また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。