🌵
脱Crystal Reports
1. はじめに
通常、Windows Forms アプリケーションで帳票を作るときには「Crystal Reports」や「帳票専用ライブラリ」を使うことが多いですが、.NET 8.0 からは Windows Forms 上でも Razor(.razor
)コンポーネントを直接 HTML に変換できる API (HtmlRenderer
) が使えるようになりました。
これを利用すると、
- 帳票レイアウトを HTML/CSS でテンプレート化
- C# のコードからそのテンプレートをレンダリング(HTML 生成)
- 生成した HTML PDF 化
という流れで、Crystal Reports を使わずに、自由度の高い HTML/CSS ベースの帳票を作成できます。
2. 必要な環境
- .NET8.0
3. プロジェクトの作成と設定
3-1. Windows Forms プロジェクトの作成
Visual Studioで通常通り、Windows Formsのプロジェクトを作成してください。
その時には必ず、フレームワークを.NET8.0に指定しましょう。
プロジェクト名は「RazorReportDemo」とします。
.csproj
ファイルの編集
3-2. 作成した.csproj
は以下の編集してください。
//Microsoft.NET.Sdk.Razorを追加
- <Project Sdk="Microsoft.NET.Sdk">
+ <Project Sdk="Microsoft.NET.Sdk;Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
-
Microsoft.NET.Sdk.Razor
を追加すると、.razor
ファイルを自動的にビルド対象にしてくれます。
3-3. 必要なNuGet パッケージ
- Microsoft.AspNetCore.Components.Web (Version="8.0.0")
HtmlRenderer(Razor→HTML 生成)を提供 - Microsoft.Extensions.DependencyInjection (Version="8.0.0")
DI(依存性注入)コンテナを提供 - Microsoft.Extensions.Logging.Console (Version="8.0.0")
ログをコンソール表示できるようにする - PuppeteerSharp (Version="20.1.3")
HTML→PDF 変換
4. モデルクラスの準備
「帳票で表示したいデータ」をまとめるためのモデル(データ構造)を定義します。
ReportModels.cs
using System;
using System.Collections.Generic;
namespace RazorReportDemo
{
/// <summary>
/// 帳票全体をまとめたルートモデル
/// </summary>
public class ReportViewModel
{
public HeaderInfo Header { get; set; }
public List<DetailItem> Details { get; set; }
public FooterInfo Footer { get; set; }
}
/// <summary>
/// ヘッダー(会社名、帳票名、日付など)
/// </summary>
public class HeaderInfo
{
public string CompanyName { get; set; }
public DateTime ReportDate { get; set; }
public string Title { get; set; }
}
/// <summary>
/// 明細行(1レコード分)
/// </summary>
public class DetailItem
{
public int No { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
// 金額は数量 × 単価で計算プロパティとして用意
public decimal Amount => Quantity * UnitPrice;
}
/// <summary>
/// フッター(合計金額、備考など)
/// </summary>
public class FooterInfo
{
public decimal TotalAmount { get; set; }
public string Remarks { get; set; }
}
}
5. Razor コンポーネントの作成
次に、HTML/CSS で帳票のレイアウトを定義する .razor
ファイルを作ります。
ReportComponent.razor
@namespace RazorReportDemo
@using Microsoft.Extensions.Logging
@using Microsoft.AspNetCore.Components
@typeparam TModel
@inject ILogger<ReportComponent<TModel>> Logger
@code {
// 依存先(親コード)から渡されるモデル
[Parameter]
public RazorReportDemo.ReportViewModel Model { get; set; }
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
body { font-family: "Yu Gothic", sans-serif; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #333; padding: 4px; text-align: left; }
.header, .footer { margin-bottom: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>@Model.Header.CompanyName</h1>
<h2>@Model.Header.Title</h2>
<p>日付:@Model.Header.ReportDate.ToString("yyyy/MM/dd")</p>
</div>
<table>
<thead>
<tr>
<th>No</th>
<th>品名</th>
<th>数量</th>
<th>単価</th>
<th>金額</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Details)
{
<tr>
<td>@item.No</td>
<td>@item.ProductName</td>
<td>@item.Quantity</td>
<td>@item.UnitPrice.ToString("N0")</td>
<td>@item.Amount.ToString("N0")</td>
</tr>
}
</tbody>
</table>
<div class="footer">
<p>合計金額:@Model.Footer.TotalAmount.ToString("N0") 円</p>
<p>備考:@Model.Footer.Remarks</p>
</div>
</body>
</html>
-
@namespace RazorReportDemo
- 生成されるコンポーネントクラスが
RazorReportDemo
名前空間に属します。 - これにより、後述する
Form1.cs
側からReportComponent<ReportViewModel>
として参照できるようになります。
- 生成されるコンポーネントクラスが
-
@typeparam TModel
と[Parameter] public ReportViewModel Model { get; set; }
-
@typeparam TModel
:このコンポーネントが汎用的な「モデル型」を受け取れるようにします。 -
[Parameter] public ReportViewModel Model
:呼び出し側で渡す具体的なモデル(ReportViewModel
)を受け取るプロパティ。
-
-
@inject ILogger<ReportComponent<TModel>> Logger
- ロガーを注入するコード。コンポーネントのレンダリング中にログを出したい場合に利用します。
- DI(依存性注入)を使って、あらかじめ用意しておいたロギング機能を取り出せます。
Form1.cs
)側の実装
6. Windows Forms(続いて、Form1.cs のコードを編集し、Razor コンポーネントをレンダリングして PDF 化するロジックを記述します。
Form1.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PuppeteerSharp;
namespace RazorReportDemo
{
public partial class Form1 : Form
{
// DI(依存性注入)用のサービスプロバイダー
private readonly IServiceProvider _services;
// ロガーを生成するためのファクトリー
private readonly ILoggerFactory _loggerFactory;
// PuppeteerSharp が一度だけダウンロードした Chrome を扱うためのフェッチャー
private readonly BrowserFetcher _fetcher;
public Form1()
{
InitializeComponent();
//DI とロギングの設定
var services = new ServiceCollection();
//Console(標準出力)にログを出力するよう登録
services.AddLogging(builder => builder.AddConsole());
//サービスプロバイダーを構築
_services = services.BuildServiceProvider();
//ILoggerFactoryをDI から取得
_loggerFactory = _services.GetRequiredService<ILoggerFactory>();
//PuppeteerSharpのChromeダウンロードはここで一度だけ
var fetcher = new BrowserFetcher();
fetcher.DownloadAsync().GetAwaiter().GetResult();
_fetcher = fetcher;
}
private async void button1_Click(object sender, EventArgs e)
{
//モデル生成
var model = CreateSampleModel();
//RazorコンポーネントをHTMLにレンダリング
string html = await RenderReportHtmlAsync(model);
//HTMLをPDFに変換して保存ダイアログを表示
await ConvertHtmlToPdfAsync(html);
}
/// <summary>
/// サンプル用のモデルを作成するメソッド
/// </summary>
private ReportViewModel CreateSampleModel()
{
var header = new HeaderInfo
{
CompanyName = "ABC商事",
Title = "月次報告",
ReportDate = DateTime.Today
};
var details = new List<DetailItem>();
for (int i = 1; i <= 5; i++)
{
details.Add(new DetailItem
{
No = i,
ProductName = $"製品{i}",
Quantity = i * 2,
UnitPrice = 1000 + i * 100
});
}
var footer = new FooterInfo
{
TotalAmount = 0,
Remarks = "特になし"
};
foreach (var item in details)
{
footer.TotalAmount += item.Amount;
}
return new ReportViewModel
{
Header = header,
Details = details,
Footer = footer
};
}
/// <summary>
/// RazorコンポーネントをHTML文字列にレンダリングするメソッド
/// </summary>
private async Task<string> RenderReportHtmlAsync(ReportViewModel model)
{
//HtmlRendererを生成(IDisposableなので await using)
await using var renderer = new HtmlRenderer(_services, _loggerFactory);
// レンダリングに渡すパラメーターを辞書形式で準備
var parameters = ParameterView.FromDictionary(new Dictionary<string, object?>
{
["Model"] = model
});
//Dispatcher(Blazorの擬似UIスレッド)上で実行する必要がある
string html = await renderer.Dispatcher.InvokeAsync(async () =>
{
// Razorコンポーネントをレンダリング → HtmlRootComponent を取得
var rootComponent = await renderer.RenderComponentAsync<ReportComponent<ReportViewModel>>(parameters);
// HtmlRootComponentからHTML文字列を取得
return rootComponent.ToHtmlString();
});
return html;
}
/// <summary>
/// HTML文字列をPDF化し、ユーザーに保存先を選ばせるメソッド
/// </summary>
private async Task ConvertHtmlToPdfAsync(string html)
{
// HeadlessChromeを起動(先ほどフェッチした Chrome を使う)
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
// 新しいページを開き、HTMLを設定
var page = await browser.NewPageAsync();
await page.SetContentAsync(html);
// PDFバイト配列を取得(デフォルト設定で A4 ページ相当の PDF が生成される)
var pdfBytes = await page.PdfDataAsync();
// 保存先をユーザーに選ばせるダイアログを表示
using var sfd = new SaveFileDialog
{
Filter = "PDF ファイル (*.pdf)|*.pdf",
FileName = $"report_{DateTime.Now:yyyyMMdd}.pdf"
};
if (sfd.ShowDialog() == DialogResult.OK)
{
// 選択されたパスにバイト配列として書き出す
System.IO.File.WriteAllBytes(sfd.FileName, pdfBytes);
MessageBox.Show($"PDF を出力しました:{sfd.FileName}", "完了", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}
9. まとめ
- .NET 8.0 + Windows Forms 環境でも、わずかなコード追加で Razor コンポーネントを利用できるようになりました。
-
Razor → HTML を行う部分は
HtmlRenderer
(Microsoft.AspNetCore.Components.Web
名前空間)を使うだけなので、ASP.NET のサーバーは不要です。 - 生成した HTML を PuppeteerSharp で PDF 化することで、Crystal Reports のような外部ランタイムを使わずに電子帳票を作成できます。
- HTML/CSS ベースなので、帳票レイアウトの自由度が高く、CSS を使った細かなスタイル調整も容易です。
Discussion