Generate Reports in C# Like Crystal Reports (.NET 10)
C# .NET での IronPDF による HTML から PDF へのレポート生成は、Crystal Reports の独自デザイナー(.rpt)を標準的な HTML、CSS、および Razor テンプレートに置き換えることで、.NET 開発者が既に持っている Web 開発スキルを活用して、データ駆動型のビジネスレポートを構築できるようにします。 これには、動的なテーブル、JavaScriptを使用したチャート、条件付き書式設定、複数ドキュメントのバッチ処理、および.NETを実行するあらゆる環境でのクロスプラットフォーム展開の完全なサポートが含まれます。
TL;DR: クイックスタート ガイド
このチュートリアルでは、基本的なテンプレートからバッチ処理、スケジュール生成まで、C# .NET での HTML-to-PDF レポート生成による Crystal Reports の置き換えについて説明します。
- 対象者: .NET開発者がCrystal Reportsを置き換えたり、ゼロから新しいレポートシステムを構築したりする場合。
- 構築するもの 3つの完全なレポート実装(売上請求書、従業員名簿、在庫レポート)、およびChart.jsによる視覚化、ブランド化されたヘッダー/フッター、目次の生成、サブレポートのマージ、並列バッチ処理。
- Where it runs: .NET 10、.NET 8 LTS、.NET Framework 4.6.2+、.NET Standard 2.0。WindowsのみのCOM依存はありません。
- このアプローチを使用する場合 Crystal Reports の .NET Core サポートの欠如、Windows ロックイン、複雑なライセンスがボトルネックになっている場合。
- 技術的に重要な理由 HTML/CSSは、プラットフォーム間で同じようにレンダリングされ、CI/CDと統合され、チャートのためにJavaScriptを実行します。
コード例を実際に試すには、NuGet (Install-Package IronPdf) 経由で IronPdf をインストールしてください。 わずか数行のコードで最初のレポートを作成します:
-
IronPDF をNuGetパッケージマネージャでインストール
PM > Install-Package IronPdf -
このコード スニペットをコピーして実行します。
// Install-Package IronPdf var pdf = new IronPdf.ChromePdfRenderer() .RenderHtmlAsPdf("<h1>Sales Report</h1><table><tr><td>Q1</td><td>$50,000</td></tr></table>") .SaveAs("sales-report.pdf"); -
実際の環境でテストするためにデプロイする
今日プロジェクトで IronPDF を使い始めましょう無料トライアル
IronPDFを購入または30日間のトライアルにサインアップした後、アプリケーションの最初にライセンスキーを追加してください。
IronPdf.License.LicenseKey = "KEY";
IronPdf.License.LicenseKey = "KEY";
Imports IronPdf
IronPdf.License.LicenseKey = "KEY"
今日あなたのプロジェクトでIronPDFを無料トライアルで使用開始。
目次
- TL;DR:クイックスタートガイド
- HTMLテンプレートからPDFアーキテクチャへ
- .NETアプリケーションでCrystal Reportsを置き換える理由
- .NET10でC#レポートジェネレータをセットアップする
- C#でデータ駆動型PDFレポートを作成する
- IronPDFによる高度なC#レポート生成
- CrystalレポートからIronPDFへ移行する
- .NETでのバッチレポート生成とスケジューリング
- 完全なテストプロジェクトをダウンロードする。
C# Report Generator: HTML Templates to PDF
HTMLからPDFへの生成は、線形アーキテクチャのパイプラインに依存しています。独自のファイルフォーマットの代わりに、アプリケーションは標準データモデルを使用してRazorビューまたはHTMLテンプレートを作成します。 生成されたHTML文字列は、IronPdfのようなレンダリングエンジンに渡され、そこで視覚的な出力がPDFドキュメントとしてキャプチャされます。 このアプローチでは、レポートのデザインをホスティング環境から切り離し、.NETをサポートするあらゆるプラットフォームでまったく同じコードを実行できるようにします。
このワークフローは、標準的なウェブ開発を反映しています。 フロントエンド開発者は、CSSを使ってレイアウトを作成し、どのブラウザでもすぐにプレビューできます。 バックエンド開発者は、C#を使用してデータをバインドします。 この分離により、チームは既存のバージョン管理、コードレビュー、継続的デプロイのプロセスを、アプリケーションの他の部分と同じようにレポートに使用することができます。
HTML を使用することで、Crystal Reports では利用できない機能(インタラクティブなチャート、レスポンシブなテーブル、一貫したブランディングのための共有スタイルなど)を実現できます。
.NETアプリケーションでCrystal Reportsを置き換える理由
Crystal Reports からの移行は、致命的な問題や、SAP による突然の放棄によるものではありません。むしろ、摩擦要因が積み重なった結果、このプラットフォームは新規プロジェクトでは正当化されにくくなり、既存ソリューションでは維持が難しくなっているのです。 これらのペインポイントを特定することで、多くのチームが代替案を探している理由と、代替案を評価する際に最も重要な基準が明確になります。
.NET 8または.NET Coreのサポートはありません
Crystal Reports は .NET Core または .NET 5-10 をサポートしていません。SAP はフォーラムで、サポートを追加する予定はないと述べています。 SDKはCOMコンポーネントを使用しており、クロスプラットフォーム for .NETとは互換性がありません。 最新 for .NETをサポートするには、完全な書き換えが必要ですが、SAPはこれを拒否しています。
そのため、現行の .NET バージョンで新しいアプリケーションを開発するチームは Crystal Reports を使用できません。 .NET 8または.NET 10で標準化されている組織は統合できません。 既存のアプリケーションを最新 for .NETランタイムにアップグレードするには、まずレポートシステムを置き換える必要があります。
複雑なライセンスと隠れたコスト
Crystal Reports のライセンスは、デザイナーライセンス、ランタイムライセンス、サーバー展開、組み込み用途を区別しています。 ルールは、デスクトップ、ウェブ、ターミナルサービスによって異なります。 あるセットアップでコンプライアンスを遵守しても、別のセットアップでは余分なライセンスが必要になることがあります。 導入後にギャップが生じると、予期せぬコストが発生します。多くの組織は、ライセンスが明確なソリューションに移行するよりも、不確実性の方が悪いと判断します。
Windowsのみのプラットフォーム ロックイン
Crystal Reports は、レガシーの .NET Framework を搭載した Windows 上でのみ動作します。 これらのアプリケーションをLinuxコンテナ、Azure App Service on Linux、AWS Lambda、Google Cloud Runにデプロイすることはできません。 組織がコンテナ化、プラットフォーム非依存、サーバーレスシステムを使用するにつれて、これらの制限はより重要になります。
マイクロサービスを構築する開発チームは、さらなる課題に直面しています。 9つのサービスが軽量のLinuxコンテナで実行され、1つのサービスがCrystal ReportsのためにWindowsを必要とする場合、デプロイはより複雑になります。 Windowsコンテナイメージ、Windows互換ホスティング、個別のデプロイ設定が必要です。 レポーティング・サービスは例外となり、標準化を阻む。
Set Up a C# Report Generator in .NET 10
IronPDFを使い始めるのは簡単です。 他 for .NET依存ライブラリと同様に、NuGetを通してライブラリをインストールしてください。 追加のソフトウェアをダウンロードしたり、本番サーバー用に別のランタイム・インストーラーをインストールしたりする必要はありません。
テンプレートアプローチの選択:Razor、HTML、ハイブリッド
IronPDFはレポートテンプレートを作成するための3つの異なるアプローチをサポートしています。 各アプローチは、チームの構成、プロジェクトの要件、長期的なメンテナンスの考慮事項に応じて、特定の利点を提供します。
Razor Views は、すでに .NET エコシステムで作業しているチームに最も豊かな開発体験を提供します。 Visual StudioおよびVS Codeのインテリセンス、コンパイル時チェック、ループ、条件分岐、NULL処理、文字列フォーマットなど、C#の全機能をサポートする強力な型付けモデルが使用可能です。 Razorのシンタックスは、ASP.NET Coreアプリケーションを構築したことがある人にはなじみがあり、他のエコシステムのテンプレートエンジンに関連する学習曲線を排除します。 テンプレートは、他のソースファイルと一緒にプロジェクトに存在し、リファクタリング作業に参加し、通常のビルドプロセスの一部としてコンパイルされます。
文字列補間によるプレーンHTMLは、よりシンプルなレポートや、テンプレートを.NETコードから完全に分離することを好むチームには効果的です。 HTMLテンプレートは、アセンブリにコンパイルされた埋め込みリソースとして、アプリケーションと共にデプロイされる外部ファイルとして、あるいは実行時にデータベースやコンテンツ管理システムから取得される形式で保存できます。基本的なデータバインディングには、単一の値に対してstring.Replace()を使用するか、より高度なシナリオではScribanやFluidのような軽量なテンプレートライブラリを使用します。 このアプローチは移植性を最大限に高め、デザイナーが.NETツールをインストールせずに、テキストエディタとプレビュー用のWebブラウザだけでテンプレートを編集できるようにします。
ハイブリッドアプローチは、柔軟性を必要とするシナリオのために、両方のテクニックを組み合わせたものです。 例えば、Razorのビューは、メインのHTML構造を生成するためにレンダリングされ、ビューモデルにきれいに収まらない動的要素のために文字列置換を追加して後処理されるかもしれません。 また、開発者以外がデザインしたHTMLテンプレートを読み込み、Razorのパーシャルビューを使用して、複雑なデータ駆動部分のみをレンダリングしてから、すべてを結合することもできます。 HTMLからPDFへの変換は、HTMLソースに依存しないため、各レポートのニーズに応じてアプローチを組み合わせることができます。
これらの選択肢の中で、このチュートリアルは主にRazorビューに焦点を当てます。なぜなら、Razorビューは典型的なビジネスレポーティングシナリオにおいて、型の安全性、保守性、機能の豊富さのバランスが最も優れているからです。 どちらの方法でもHTML文字列が生成されるため、将来的にプレーンなHTMLテンプレートでの作業が必要になった場合でも、スキルはそのまま引き継がれます。
Build a Data-Driven PDF Report in C
このセクションでは、最初から最後まで、売上請求書レポートの完全な作成を示します。 データを構造化するモデルを定義し、データをフォーマットされたHTMLに変換するRazorテンプレートを作成し、そのテンプレートをHTML文字列にレンダリングし、HTMLを閲覧、電子メール送信、またはアーカイブに適したPDFドキュメントに変換します。
HTML/CSSレポートテンプレートの作成
最初のステップは、データモデルを定義することです。 実際の請求書には、顧客情報、説明と価格を含む行項目、計算された合計、税処理、会社のブランディング要素が必要です。 モデルクラスは、これらのグループ分けを反映した構造になっている必要があります:
// Invoice data model with customer, company, and line item details
public class InvoiceModel
{
public string InvoiceNumber { get; set; } = string.Empty;
public DateTime InvoiceDate { get; set; }
public DateTime DueDate { get; set; }
public CompanyInfo Company { get; set; } = new();
public CustomerInfo Customer { get; set; } = new();
public List<LineItem> Items { get; set; } = new();
// Computed totals - business logic stays in the model
public decimal Subtotal => Items.Sum(x => x.Total);
public decimal TaxRate { get; set; } = 0.08m;
public decimal TaxAmount => Subtotal * TaxRate;
public decimal GrandTotal => Subtotal + TaxAmount;
}
// Company details for invoice header
public class CompanyInfo
{
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string LogoPath { get; set; } = string.Empty;
}
// Customer billing information
public class CustomerInfo
{
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
// Individual invoice line item
public class LineItem
{
public string Description { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total => Quantity * UnitPrice;
}
// Invoice data model with customer, company, and line item details
public class InvoiceModel
{
public string InvoiceNumber { get; set; } = string.Empty;
public DateTime InvoiceDate { get; set; }
public DateTime DueDate { get; set; }
public CompanyInfo Company { get; set; } = new();
public CustomerInfo Customer { get; set; } = new();
public List<LineItem> Items { get; set; } = new();
// Computed totals - business logic stays in the model
public decimal Subtotal => Items.Sum(x => x.Total);
public decimal TaxRate { get; set; } = 0.08m;
public decimal TaxAmount => Subtotal * TaxRate;
public decimal GrandTotal => Subtotal + TaxAmount;
}
// Company details for invoice header
public class CompanyInfo
{
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string LogoPath { get; set; } = string.Empty;
}
// Customer billing information
public class CustomerInfo
{
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
// Individual invoice line item
public class LineItem
{
public string Description { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total => Quantity * UnitPrice;
}
Imports System
Imports System.Collections.Generic
Imports System.Linq
' Invoice data model with customer, company, and line item details
Public Class InvoiceModel
Public Property InvoiceNumber As String = String.Empty
Public Property InvoiceDate As DateTime
Public Property DueDate As DateTime
Public Property Company As New CompanyInfo()
Public Property Customer As New CustomerInfo()
Public Property Items As New List(Of LineItem)()
' Computed totals - business logic stays in the model
Public ReadOnly Property Subtotal As Decimal
Get
Return Items.Sum(Function(x) x.Total)
End Get
End Property
Public Property TaxRate As Decimal = 0.08D
Public ReadOnly Property TaxAmount As Decimal
Get
Return Subtotal * TaxRate
End Get
End Property
Public ReadOnly Property GrandTotal As Decimal
Get
Return Subtotal + TaxAmount
End Get
End Property
End Class
' Company details for invoice header
Public Class CompanyInfo
Public Property Name As String = String.Empty
Public Property Address As String = String.Empty
Public Property City As String = String.Empty
Public Property Phone As String = String.Empty
Public Property Email As String = String.Empty
Public Property LogoPath As String = String.Empty
End Class
' Customer billing information
Public Class CustomerInfo
Public Property Name As String = String.Empty
Public Property Address As String = String.Empty
Public Property City As String = String.Empty
Public Property Email As String = String.Empty
End Class
' Individual invoice line item
Public Class LineItem
Public Property Description As String = String.Empty
Public Property Quantity As Integer
Public Property UnitPrice As Decimal
Public ReadOnly Property Total As Decimal
Get
Return Quantity * UnitPrice
End Get
End Property
End Class
GrandTotalの計算プロパティがモデルに含まれています。 これらの計算はテンプレートではなくモデルに属し、Razorビューをプレゼンテーションに集中させ、モデルがビジネスロジックを処理します。 この分離により、単体テストが容易になり、HTMLをレンダリングすることなく計算を検証できるようになります。
次に、このモデルを Professional 書式の請求書に変換する Razor ビューを作成します。これを Views フォルダに InvoiceTemplate.cshtml として保存してください:
@model InvoiceModel
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* Reset and base styles */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; color: #333; line-height: 1.5; }
.invoice-container { max-width: 800px; margin: 0 auto; padding: 40px; }
/* Header with company info and invoice title */
.header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #3498db; }
.company-info h1 { font-size: 24px; color: #2c3e50; margin-bottom: 10px; }
.company-info p { color: #7f8c8d; font-size: 11px; }
.invoice-title { text-align: right; }
.invoice-title h2 { font-size: 32px; color: #3498db; margin-bottom: 10px; }
.invoice-title p { font-size: 12px; color: #7f8c8d; }
/* Address blocks */
.addresses { display: flex; justify-content: space-between; margin-bottom: 30px; }
.address-block { width: 45%; }
.address-block h3 { font-size: 11px; text-transform: uppercase; color: #95a5a6; margin-bottom: 8px; letter-spacing: 1px; }
.address-block p { font-size: 12px; }
/* Line items table */
.items-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
.items-table th { background-color: #3498db; color: white; padding: 12px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
.items-table th:last-child, .items-table td:last-child { text-align: right; }
.items-table td { padding: 12px; border-bottom: 1px solid #ecf0f1; }
.items-table tr:nth-child(even) { background-color: #f9f9f9; }
/* Totals section */
.totals { float: right; width: 300px; }
.totals-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #ecf0f1; }
.totals-row.grand-total { border-bottom: none; border-top: 2px solid #3498db; font-size: 16px; font-weight: bold; color: #2c3e50; padding-top: 12px; }
/* Footer */
.footer { clear: both; margin-top: 60px; padding-top: 20px; border-top: 1px solid #ecf0f1; text-align: center; color: #95a5a6; font-size: 10px; }
</style>
</head>
<body>
<div class="invoice-container">
<div class="header">
<div class="company-info">
<h1>@Model.Company.Name</h1>
<p>@Model.Company.Address</p>
<p>@Model.Company.City</p>
<p>@Model.Company.Phone | @Model.Company.Email</p>
</div>
<div class="invoice-title">
<h2>INVOICE</h2>
<p>Invoice #: @Model.InvoiceNumber</p>
<p>Date: @Model.InvoiceDate.ToString("MMMM dd, yyyy")</p>
<p>Due Date: @Model.DueDate.ToString("MMMM dd, yyyy")</p>
</div>
</div>
<div class="addresses">
<div class="address-block">
<h3>Bill To</h3>
<p>@Model.Customer.Name</p>
<p>@Model.Customer.Address</p>
<p>@Model.Customer.City</p>
<p>@Model.Customer.Email</p>
</div>
</div>
<table class="items-table">
<thead>
<tr><th>Description</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
</thead>
<tbody>
@foreach (var item in Model.Items)
{
<tr>
<td>@item.Description</td>
<td>@item.Quantity</td>
<td>@item.UnitPrice.ToString("C")</td>
<td>@item.Total.ToString("C")</td>
</tr>
}
</tbody>
</table>
<div class="totals">
<div class="totals-row"><span>Subtotal:</span><span>@Model.Subtotal.ToString("C")</span></div>
<div class="totals-row"><span>Tax (@(Model.TaxRate * 100)%):</span><span>@Model.TaxAmount.ToString("C")</span></div>
<div class="totals-row grand-total"><span>Total Due:</span><span>@Model.GrandTotal.ToString("C")</span></div>
</div>
<div class="footer">
<p>Thank you for your business!</p>
<p>Payment is due within 30 days. Please include invoice number with your payment.</p>
</div>
</div>
</body>
</html>
@model InvoiceModel
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* Reset and base styles */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; color: #333; line-height: 1.5; }
.invoice-container { max-width: 800px; margin: 0 auto; padding: 40px; }
/* Header with company info and invoice title */
.header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #3498db; }
.company-info h1 { font-size: 24px; color: #2c3e50; margin-bottom: 10px; }
.company-info p { color: #7f8c8d; font-size: 11px; }
.invoice-title { text-align: right; }
.invoice-title h2 { font-size: 32px; color: #3498db; margin-bottom: 10px; }
.invoice-title p { font-size: 12px; color: #7f8c8d; }
/* Address blocks */
.addresses { display: flex; justify-content: space-between; margin-bottom: 30px; }
.address-block { width: 45%; }
.address-block h3 { font-size: 11px; text-transform: uppercase; color: #95a5a6; margin-bottom: 8px; letter-spacing: 1px; }
.address-block p { font-size: 12px; }
/* Line items table */
.items-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
.items-table th { background-color: #3498db; color: white; padding: 12px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
.items-table th:last-child, .items-table td:last-child { text-align: right; }
.items-table td { padding: 12px; border-bottom: 1px solid #ecf0f1; }
.items-table tr:nth-child(even) { background-color: #f9f9f9; }
/* Totals section */
.totals { float: right; width: 300px; }
.totals-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #ecf0f1; }
.totals-row.grand-total { border-bottom: none; border-top: 2px solid #3498db; font-size: 16px; font-weight: bold; color: #2c3e50; padding-top: 12px; }
/* Footer */
.footer { clear: both; margin-top: 60px; padding-top: 20px; border-top: 1px solid #ecf0f1; text-align: center; color: #95a5a6; font-size: 10px; }
</style>
</head>
<body>
<div class="invoice-container">
<div class="header">
<div class="company-info">
<h1>@Model.Company.Name</h1>
<p>@Model.Company.Address</p>
<p>@Model.Company.City</p>
<p>@Model.Company.Phone | @Model.Company.Email</p>
</div>
<div class="invoice-title">
<h2>INVOICE</h2>
<p>Invoice #: @Model.InvoiceNumber</p>
<p>Date: @Model.InvoiceDate.ToString("MMMM dd, yyyy")</p>
<p>Due Date: @Model.DueDate.ToString("MMMM dd, yyyy")</p>
</div>
</div>
<div class="addresses">
<div class="address-block">
<h3>Bill To</h3>
<p>@Model.Customer.Name</p>
<p>@Model.Customer.Address</p>
<p>@Model.Customer.City</p>
<p>@Model.Customer.Email</p>
</div>
</div>
<table class="items-table">
<thead>
<tr><th>Description</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
</thead>
<tbody>
@foreach (var item in Model.Items)
{
<tr>
<td>@item.Description</td>
<td>@item.Quantity</td>
<td>@item.UnitPrice.ToString("C")</td>
<td>@item.Total.ToString("C")</td>
</tr>
}
</tbody>
</table>
<div class="totals">
<div class="totals-row"><span>Subtotal:</span><span>@Model.Subtotal.ToString("C")</span></div>
<div class="totals-row"><span>Tax (@(Model.TaxRate * 100)%):</span><span>@Model.TaxAmount.ToString("C")</span></div>
<div class="totals-row grand-total"><span>Total Due:</span><span>@Model.GrandTotal.ToString("C")</span></div>
</div>
<div class="footer">
<p>Thank you for your business!</p>
<p>Payment is due within 30 days. Please include invoice number with your payment.</p>
</div>
</div>
</body>
</html>
このテンプレートに埋め込まれたCSSは、色、フォント、スペーシング、テーブルのフォーマットなど、視覚的なスタイリングをすべて処理します。 IronPDFはフレックスボックス、グリッドレイアウト、CSS変数のような最新のCSS機能もサポートしています。 レンダリングされたPDFは、Chromeの印刷プレビューと完全に一致します。そのため、デバッグが簡単です。PDFで何か問題があるように見える場合は、ブラウザでHTMLを開き、開発者ツールを使用してスタイルを検査し、調整します。
テンプレートにデータをバインドする
モデルとテンプレートが準備できたら、PDFを生成するには、IronPDFのChromePdfRendererを通じてそれらを接続する必要があります。 重要なステップは、Razor のビューを HTML 文字列に変換し、その文字列をレンダラーに渡すことです:
using IronPdf;
// Service class for generating invoice PDFs from Razor views
public class InvoiceReportService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
public InvoiceReportService(
IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
// Generate PDF from invoice model
public async Task<byte[]> GenerateInvoicePdfAsync(InvoiceModel invoice)
{
// Render Razor view to HTML string
string html = await RenderViewToStringAsync("InvoiceTemplate", invoice);
// Configure PDF renderer with margins and paper size
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.MarginTop = 10;
renderer.RenderingOptions.MarginBottom = 10;
renderer.RenderingOptions.MarginLeft = 10;
renderer.RenderingOptions.MarginRight = 10;
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
// Convert HTML to PDF and return bytes
var pdfDocument = renderer.RenderHtmlAsPdf(html);
return pdfDocument.BinaryData;
}
// Helper method to render a Razor view to string
private async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using var stringWriter = new StringWriter();
var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
if (!viewResult.Success)
throw new InvalidOperationException($"View '{viewName}' not found.");
var viewDictionary = new ViewDataDictionary<TModel>(
new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model };
var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
stringWriter, new HtmlHelperOptions());
await viewResult.View.RenderAsync(viewContext);
return stringWriter.ToString();
}
}
using IronPdf;
// Service class for generating invoice PDFs from Razor views
public class InvoiceReportService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
public InvoiceReportService(
IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
// Generate PDF from invoice model
public async Task<byte[]> GenerateInvoicePdfAsync(InvoiceModel invoice)
{
// Render Razor view to HTML string
string html = await RenderViewToStringAsync("InvoiceTemplate", invoice);
// Configure PDF renderer with margins and paper size
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.MarginTop = 10;
renderer.RenderingOptions.MarginBottom = 10;
renderer.RenderingOptions.MarginLeft = 10;
renderer.RenderingOptions.MarginRight = 10;
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
// Convert HTML to PDF and return bytes
var pdfDocument = renderer.RenderHtmlAsPdf(html);
return pdfDocument.BinaryData;
}
// Helper method to render a Razor view to string
private async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using var stringWriter = new StringWriter();
var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
if (!viewResult.Success)
throw new InvalidOperationException($"View '{viewName}' not found.");
var viewDictionary = new ViewDataDictionary<TModel>(
new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model };
var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
stringWriter, new HtmlHelperOptions());
await viewResult.View.RenderAsync(viewContext);
return stringWriter.ToString();
}
}
Imports IronPdf
Imports Microsoft.AspNetCore.Mvc.Razor
Imports Microsoft.AspNetCore.Mvc.ViewFeatures
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.AspNetCore.Http
Imports Microsoft.AspNetCore.Mvc
Imports Microsoft.AspNetCore.Routing
Imports System.IO
Imports System.Threading.Tasks
' Service class for generating invoice PDFs from Razor views
Public Class InvoiceReportService
Private ReadOnly _razorViewEngine As IRazorViewEngine
Private ReadOnly _tempDataProvider As ITempDataProvider
Private ReadOnly _serviceProvider As IServiceProvider
Public Sub New(razorViewEngine As IRazorViewEngine, tempDataProvider As ITempDataProvider, serviceProvider As IServiceProvider)
_razorViewEngine = razorViewEngine
_tempDataProvider = tempDataProvider
_serviceProvider = serviceProvider
End Sub
' Generate PDF from invoice model
Public Async Function GenerateInvoicePdfAsync(invoice As InvoiceModel) As Task(Of Byte())
' Render Razor view to HTML string
Dim html As String = Await RenderViewToStringAsync("InvoiceTemplate", invoice)
' Configure PDF renderer with margins and paper size
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.MarginTop = 10
renderer.RenderingOptions.MarginBottom = 10
renderer.RenderingOptions.MarginLeft = 10
renderer.RenderingOptions.MarginRight = 10
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter
' Convert HTML to PDF and return bytes
Dim pdfDocument = renderer.RenderHtmlAsPdf(html)
Return pdfDocument.BinaryData
End Function
' Helper method to render a Razor view to string
Private Async Function RenderViewToStringAsync(Of TModel)(viewName As String, model As TModel) As Task(Of String)
Dim httpContext As New DefaultHttpContext With {.RequestServices = _serviceProvider}
Dim actionContext As New ActionContext(httpContext, New RouteData(), New ActionDescriptor())
Using stringWriter As New StringWriter()
Dim viewResult = _razorViewEngine.FindView(actionContext, viewName, False)
If Not viewResult.Success Then
Throw New InvalidOperationException($"View '{viewName}' not found.")
End If
Dim viewDictionary As New ViewDataDictionary(Of TModel)(
New EmptyModelMetadataProvider(), New ModelStateDictionary()) With {.Model = model}
Dim viewContext As New ViewContext(actionContext, viewResult.View, viewDictionary,
New TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
stringWriter, New HtmlHelperOptions())
Await viewResult.View.RenderAsync(viewContext)
Return stringWriter.ToString()
End Using
End Function
End Class
コンソールアプリやバックグラウンドサービスなど、完全な ASP.NET Core MVC のセットアップを必要としないシンプルなシナリオでは、動的な部分に対して、補間機能と StringBuilder を使用した HTML 文字列をそのまま使用できます。
サンプル出力
ヘッダー、フッター、ページ番号を追加する
Professionalなレポートには通常、すべてのページで一貫したヘッダーとフッターが含まれ、会社のブランド名、文書のタイトル、生成日、ページ番号が表示されます。 IronPDFはこれらの要素を実装するために2つのアプローチを提供します: テキストベースのヘッダーは最小限のフォーマットでシンプルなコンテンツを、HTMLヘッダーはロゴやカスタムレイアウトで完全なスタイルコントロールを実現します。
Text-based headers work well for basic information and render faster since they don't require additional HTML parsing:
:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/text-headers-footers.cs
using IronPdf;
using IronSoftware.Drawing;
// Configure text-based headers and footers
var renderer = new ChromePdfRenderer();
// Set starting page number
renderer.RenderingOptions.FirstPageNumber = 1;
// Add centered header with divider line
renderer.RenderingOptions.TextHeader = new TextHeaderFooter
{
CenterText = "CONFIDENTIAL - Internal Use Only",
DrawDividerLine = true,
Font = FontTypes.Arial,
FontSize = 10
};
// Add footer with date on left, page numbers on right
renderer.RenderingOptions.TextFooter = new TextHeaderFooter
{
LeftText = "{date} {time}",
RightText = "Page {page} of {total-pages}",
DrawDividerLine = true,
Font = FontTypes.Arial,
FontSize = 9
};
// Set margins to accommodate header/footer
renderer.RenderingOptions.MarginTop = 25;
renderer.RenderingOptions.MarginBottom = 20;
Imports IronPdf
Imports IronSoftware.Drawing
' Configure text-based headers and footers
Dim renderer As New ChromePdfRenderer()
' Set starting page number
renderer.RenderingOptions.FirstPageNumber = 1
' Add centered header with divider line
renderer.RenderingOptions.TextHeader = New TextHeaderFooter With {
.CenterText = "CONFIDENTIAL - Internal Use Only",
.DrawDividerLine = True,
.Font = FontTypes.Arial,
.FontSize = 10
}
' Add footer with date on left, page numbers on right
renderer.RenderingOptions.TextFooter = New TextHeaderFooter With {
.LeftText = "{date} {time}",
.RightText = "Page {page} of {total-pages}",
.DrawDividerLine = True,
.Font = FontTypes.Arial,
.FontSize = 9
}
' Set margins to accommodate header/footer
renderer.RenderingOptions.MarginTop = 25
renderer.RenderingOptions.MarginBottom = 20
利用可能なマージフィールドには、現在のページ番号を表す {page}、ドキュメントの総ページ数を表す {total-pages}、生成日時を表す {date} および {time}、Web ページからレンダリングする場合のソース URL 用の {url}(Webページからレンダリングする場合のソースURL)、および {html-title} と {pdf-title}(ドキュメントのタイトル)が含まれます。 これらのプレースホルダーは、レンダリング中に自動的に置き換えられます。
ロゴ、カスタムフォント、複雑な複数カラムレイアウトのヘッダーには、完全なCSSスタイリングをサポートするHTMLヘッダーを使用してください:
:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/html-headers-footers.cs
using IronPdf;
using System;
var renderer = new ChromePdfRenderer();
// Configure HTML header with logo and custom layout
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
MaxHeight = 30,
HtmlFragment = @"
<div style='display: flex; justify-content: space-between; align-items: center;
width: 100%; font-family: Arial; font-size: 10px; color: #666;'>
<img src='logo.png' style='height: 25px;'>
<span>Company Name Inc.</span>
<span>Invoice Report</span>
</div>",
BaseUrl = new Uri(@"C:\assets\images\").AbsoluteUri
};
// Configure HTML footer with page info and generation date
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
MaxHeight = 20,
HtmlFragment = @"
<div style='text-align: center; font-size: 9px; color: #999;
border-top: 1px solid #ddd; padding-top: 5px;'>
Page {page} of {total-pages} | Generated on {date}
</div>",
DrawDividerLine = false
};
Imports IronPdf
Imports System
Dim renderer As New ChromePdfRenderer()
' Configure HTML header with logo and custom layout
renderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooter With {
.MaxHeight = 30,
.HtmlFragment = "
<div style='display: flex; justify-content: space-between; align-items: center;
width: 100%; font-family: Arial; font-size: 10px; color: #666;'>
<img src='logo.png' style='height: 25px;'>
<span>Company Name Inc.</span>
<span>Invoice Report</span>
</div>",
.BaseUrl = New Uri("C:\assets\images\").AbsoluteUri
}
' Configure HTML footer with page info and generation date
renderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooter With {
.MaxHeight = 20,
.HtmlFragment = "
<div style='text-align: center; font-size: 9px; color: #999;
border-top: 1px solid #ddd; padding-top: 5px;'>
Page {page} of {total-pages} | Generated on {date}
</div>",
.DrawDividerLine = False
}
サンプル出力
動的なテーブルと繰り返しセクションを作成する
レポートは、複数のページにまたがるデータの集合を表示する必要があることがよくあります。 Razorのループ構造は、コレクションを繰り返し処理し、各項目についてテーブルの行やカードの要素を生成することで、この処理を自然に行います。
以下は、部門ごとにグループ化されたデータ表示を示す、完全な従業員名簿の例です:
// Employee directory data models
public class EmployeeDirectoryModel
{
public List<Department> Departments { get; set; } = new();
public DateTime GeneratedDate { get; set; } = DateTime.Now;
}
// Department grouping with manager info
public class Department
{
public string Name { get; set; } = string.Empty;
public string ManagerName { get; set; } = string.Empty;
public List<Employee> Employees { get; set; } = new();
}
// Individual employee details
public class Employee
{
public string Name { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string PhotoUrl { get; set; } = string.Empty;
public DateTime HireDate { get; set; }
}
// Employee directory data models
public class EmployeeDirectoryModel
{
public List<Department> Departments { get; set; } = new();
public DateTime GeneratedDate { get; set; } = DateTime.Now;
}
// Department grouping with manager info
public class Department
{
public string Name { get; set; } = string.Empty;
public string ManagerName { get; set; } = string.Empty;
public List<Employee> Employees { get; set; } = new();
}
// Individual employee details
public class Employee
{
public string Name { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string PhotoUrl { get; set; } = string.Empty;
public DateTime HireDate { get; set; }
}
' Employee directory data models
Public Class EmployeeDirectoryModel
Public Property Departments As List(Of Department) = New List(Of Department)()
Public Property GeneratedDate As DateTime = DateTime.Now
End Class
' Department grouping with manager info
Public Class Department
Public Property Name As String = String.Empty
Public Property ManagerName As String = String.Empty
Public Property Employees As List(Of Employee) = New List(Of Employee)()
End Class
' Individual employee details
Public Class Employee
Public Property Name As String = String.Empty
Public Property Title As String = String.Empty
Public Property Email As String = String.Empty
Public Property Phone As String = String.Empty
Public Property PhotoUrl As String = String.Empty
Public Property HireDate As DateTime
End Class
departmentクラスに指定されたCSSプロパティpage-break-inside: avoidは、PDFレンダラーに対し、可能な限りdepartmentセクションを1ページにまとめて表示するよう指示します。 部門のコンテンツがセクションの途中で改ページされる場合は、レンダラがセクション全体を次のページに移動します。 セレクタ .department:not(:first-child) と page-break-before: always を使用することで、最初のセクション以降の各セクションを新しいページから開始させ、ディレクトリ全体を通じてセクションの区切りを明確にします。
サンプル出力
Advanced C# Report Generation With IronPDF
ビジネスレポートでは、静的な表やテキスト以上の機能が求められることがよくあります。 グラフは、表形式では理解するのが面倒なトレンドを視覚化します。 条件付き書式は、アクションを必要とする項目に注意を向けます。 サブレポートは、複数のソースからのデータを統合した文書です。 このセクションでは、IronPDFのChromiumレンダリングエンジンを使ってそれぞれの機能を実装することについて説明します。
PDFレポートにチャートとグラフを追加する
JavaScriptはレンダリング中に実行されるため、任意のクライアント側チャートライブラリを使用して、レポートに直接ビジュアライゼーションを生成することができます。 図表はページの一部としてラスタライズされ、最終的なPDFには画面とまったく同じように表示されます。 Chart.jsは、ほとんどのレポーティングニーズに対して、シンプルさ、機能、ドキュメントの優れたバランスを提供します。
CDNからChart.jsをインクルードし、C#モデルからシリアライズされたデータでチャートを構成します:
@model SalesReportModel
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="salesChart"></canvas>
<script>
// Initialize bar chart with data from C# model
const ctx = document.getElementById('salesChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
// Serialize model data to JavaScript arrays
labels: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthLabels)),
datasets: [{
label: 'Monthly Sales',
data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthlySales)),
backgroundColor: 'rgba(52, 152, 219, 0.7)'
}]
}
});
</script>
@model SalesReportModel
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="salesChart"></canvas>
<script>
// Initialize bar chart with data from C# model
const ctx = document.getElementById('salesChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
// Serialize model data to JavaScript arrays
labels: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthLabels)),
datasets: [{
label: 'Monthly Sales',
data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthlySales)),
backgroundColor: 'rgba(52, 152, 219, 0.7)'
}]
}
});
</script>
JavaScriptで生成されたコンテンツを含むページをレンダリングする場合は、スクリプトの実行が完了するまで待ってからページをキャプチャするようにレンダラーを設定してください:
:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/javascript-wait-rendering.cs
using IronPdf;
string html = "<h1>Report</h1>";
// Configure renderer to wait for JavaScript execution
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.WaitFor.JavaScript(500); // Wait 500ms for JS to complete
var pdf = renderer.RenderHtmlAsPdf(html);
Imports IronPdf
Dim html As String = "<h1>Report</h1>"
' Configure renderer to wait for JavaScript execution
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.WaitFor.JavaScript(500) ' Wait 500ms for JS to complete
Dim pdf = renderer.RenderHtmlAsPdf(html)
サンプル出力
条件付き書式とビジネスロジックを適用する
インベントリレポートは、アクションが必要な項目にすぐに注意を向けることができる視覚的なインジケータが役立ちます。 ユーザーが何百行もの行をスキャンして問題を探すのではなく、条件付き書式を使用することで、例外を視覚的に明らかにすることができます。 Razor のインライン式を使用して、データ値に基づいて CSS クラスを適用します:
@foreach (var item in Model.Items.OrderBy(x => x.Quantity))
{
// Apply CSS class based on stock level thresholds
var rowClass = item.Quantity <= Model.CriticalStockThreshold ? "stock-critical" :
item.Quantity <= Model.LowStockThreshold ? "stock-low" : "";
<tr class="@rowClass">
<td>@item.SKU</td>
<td>@item.ProductName</td>
<td class="text-right">
<span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
@item.Quantity
</span>
</td>
</tr>
}
@foreach (var item in Model.Items.OrderBy(x => x.Quantity))
{
// Apply CSS class based on stock level thresholds
var rowClass = item.Quantity <= Model.CriticalStockThreshold ? "stock-critical" :
item.Quantity <= Model.LowStockThreshold ? "stock-low" : "";
<tr class="@rowClass">
<td>@item.SKU</td>
<td>@item.ProductName</td>
<td class="text-right">
<span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
@item.Quantity
</span>
</td>
</tr>
}
サンプル出力
サブレポートとセクション区切りを作成する
別々に作成されたレポートを1つのドキュメントにまとめるには、IronPDFのマージ機能を使用してください:
using IronPdf;
// Combine multiple reports into a single PDF document
public byte[] GenerateCombinedReport(SalesReportModel sales, InventoryReportModel inventory)
{
var renderer = new ChromePdfRenderer();
// Render each report section separately
var salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales));
var inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory));
// Merge PDFs into one document
var combined = PdfDocument.Merge(salesPdf, inventoryPdf);
return combined.BinaryData;
}
using IronPdf;
// Combine multiple reports into a single PDF document
public byte[] GenerateCombinedReport(SalesReportModel sales, InventoryReportModel inventory)
{
var renderer = new ChromePdfRenderer();
// Render each report section separately
var salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales));
var inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory));
// Merge PDFs into one document
var combined = PdfDocument.Merge(salesPdf, inventoryPdf);
return combined.BinaryData;
}
Imports IronPdf
' Combine multiple reports into a single PDF document
Public Function GenerateCombinedReport(sales As SalesReportModel, inventory As InventoryReportModel) As Byte()
Dim renderer As New ChromePdfRenderer()
' Render each report section separately
Dim salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales))
Dim inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory))
' Merge PDFs into one document
Dim combined = PdfDocument.Merge(salesPdf, inventoryPdf)
Return combined.BinaryData
End Function
サンプル出力
目次の生成
IronPDFはHTMLの見出し要素に基づいて目次を自動的に生成することができます:
:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/table-of-contents.cs
using IronPdf;
// Generate PDF with automatic table of contents
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers;
var pdf = renderer.RenderHtmlFileAsPdf("report.html");
Imports IronPdf
' Generate PDF with automatic table of contents
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers
Dim pdf = renderer.RenderHtmlFileAsPdf("report.html")
クリスタルレポートからIronPDFへ移行する
確立されたレポートシステムを移行するには、混乱を最小限に抑えつつ、近代化と簡素化の機会を捉えるための慎重な計画が必要です。 Crystal Reports のコンセプトを HTML ベースのアプローチにどのように対応させるかを理解することで、元のレポートの機能を忠実に再現したり、癖を残したりする必要がなくなります。
クリスタルレポートの概念をIronPDFにマップする
概念マッピングを理解することで、既存のレポートを体系的に翻訳することができます:
| Crystal Reports | IronPDF 同等物 |
|---|---|
| レポートセクション | HTML divs with CSS page-break プロパティ |
| パラメータフィールド | Razor ビューに渡されるモデルプロパティ |
| 数式フィールド | C# モデルクラスの計算プロパティ |
| 合計 | LINQ集約 |
| サブレポート | 部分的なビューまたはマージされたPDFドキュメント |
| グループ化/ソート | テンプレートにデータを渡す前のLINQ操作 |
| クロスタブレポート | ネストされたループを使用したHTMLテーブル |
| 条件付き書式設定 | CSSクラスによるRazor @ifブロック |
.rptテンプレートを変換するための最善の戦略
.rptファイルをプログラムで解析しようとしないでください。 その代わりに、既存のPDF出力を視覚的な仕様書として扱い、体系的な4つのステップ戦略を使用してロジックを再構築します:
1.インベントリ:すべての.rptファイルを、その目的、データソース、使用頻度とともにカタログ化します。 古いレポートを削除し、移行範囲を縮小する。
2.優先順位: 頻度の高いレポートを最初に移行します。シンプルなレイアウトのレポートや、メンテナンスに問題があるレポートを対象とします。
3.参考資料 既存のCrystal ReportsをPDFとしてエクスポートする。 開発者向けの視覚的な仕様書として使用してください。
4.検証:本番のデータ量でテストします。 10行で瞬時にレンダリングされるテンプレートは、10,000行では遅くなる可能性があります。
.NETにおけるレポートのバッチ生成とスケジューリング
プロダクションシステムでは、多くのレポートを同時に生成したり、スケジュールに従ってレポートジョブを実行したりする必要があります。 IronPDFのスレッドセーフデザインは両方のシナリオを効率的にサポートします。
複数のレポートを並行して生成する
バッチ処理については、Parallel.ForEachAsync または Task.WhenAll を使用した非同期パターンを使用してください:
using IronPdf;
using System.Collections.Concurrent;
// Generate multiple invoices in parallel using async processing
public async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices)
{
var results = new ConcurrentBag<ReportResult>();
// Process invoices concurrently with controlled parallelism
await Parallel.ForEachAsync(invoices,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
async (invoice, token) =>
{
// Each thread gets its own renderer instance
var renderer = new ChromePdfRenderer();
string html = BuildInvoiceHtml(invoice);
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
// Save individual invoice PDF
string filename = $"Invoice_{invoice.InvoiceNumber}.pdf";
await pdf.SaveAsAsync(filename);
results.Add(new ReportResult { InvoiceNumber = invoice.InvoiceNumber, Success = true });
});
return results.ToList();
}
using IronPdf;
using System.Collections.Concurrent;
// Generate multiple invoices in parallel using async processing
public async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices)
{
var results = new ConcurrentBag<ReportResult>();
// Process invoices concurrently with controlled parallelism
await Parallel.ForEachAsync(invoices,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
async (invoice, token) =>
{
// Each thread gets its own renderer instance
var renderer = new ChromePdfRenderer();
string html = BuildInvoiceHtml(invoice);
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
// Save individual invoice PDF
string filename = $"Invoice_{invoice.InvoiceNumber}.pdf";
await pdf.SaveAsAsync(filename);
results.Add(new ReportResult { InvoiceNumber = invoice.InvoiceNumber, Success = true });
});
return results.ToList();
}
Imports IronPdf
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
' Generate multiple invoices in parallel using async processing
Public Async Function GenerateInvoiceBatchAsync(invoices As List(Of InvoiceModel)) As Task(Of List(Of ReportResult))
Dim results As New ConcurrentBag(Of ReportResult)()
' Process invoices concurrently with controlled parallelism
Await Task.Run(Async Function()
Await Parallel.ForEachAsync(invoices,
New ParallelOptions With {.MaxDegreeOfParallelism = Environment.ProcessorCount},
Async Function(invoice, token)
' Each thread gets its own renderer instance
Dim renderer As New ChromePdfRenderer()
Dim html As String = BuildInvoiceHtml(invoice)
Dim pdf = Await renderer.RenderHtmlAsPdfAsync(html)
' Save individual invoice PDF
Dim filename As String = $"Invoice_{invoice.InvoiceNumber}.pdf"
Await pdf.SaveAsAsync(filename)
results.Add(New ReportResult With {.InvoiceNumber = invoice.InvoiceNumber, .Success = True})
End Function)
End Function)
Return results.ToList()
End Function
サンプル出力
バッチ処理の例では、複数の請求書を並行して作成します。 生成されたバッチ請求書の1つです:
レポート生成をASP.NET Coreバックグラウンドサービスと統合する
スケジュールされたレポート生成は、.NET Coreのホストされたサービスインフラに自然に適合します:
// Background service for scheduled report generation
public class DailyReportService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Calculate next run time (6 AM daily)
var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6);
await Task.Delay(nextRun - DateTime.Now, stoppingToken);
// Create scoped service for report generation
using var scope = _serviceProvider.CreateScope();
var reportService = scope.ServiceProvider.GetRequiredService<IReportGenerationService>();
// Generate and distribute daily report
var salesReport = await reportService.GenerateDailySalesSummaryAsync();
// Email or save reports as needed
}
}
}
// Background service for scheduled report generation
public class DailyReportService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Calculate next run time (6 AM daily)
var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6);
await Task.Delay(nextRun - DateTime.Now, stoppingToken);
// Create scoped service for report generation
using var scope = _serviceProvider.CreateScope();
var reportService = scope.ServiceProvider.GetRequiredService<IReportGenerationService>();
// Generate and distribute daily report
var salesReport = await reportService.GenerateDailySalesSummaryAsync();
// Email or save reports as needed
}
}
}
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports Microsoft.Extensions.DependencyInjection
' Background service for scheduled report generation
Public Class DailyReportService
Inherits BackgroundService
Private ReadOnly _serviceProvider As IServiceProvider
Protected Overrides Async Function ExecuteAsync(stoppingToken As CancellationToken) As Task
While Not stoppingToken.IsCancellationRequested
' Calculate next run time (6 AM daily)
Dim nextRun = DateTime.Now.Date.AddDays(1).AddHours(6)
Await Task.Delay(nextRun - DateTime.Now, stoppingToken)
' Create scoped service for report generation
Using scope = _serviceProvider.CreateScope()
Dim reportService = scope.ServiceProvider.GetRequiredService(Of IReportGenerationService)()
' Generate and distribute daily report
Dim salesReport = Await reportService.GenerateDailySalesSummaryAsync()
' Email or save reports as needed
End Using
End While
End Function
End Class
完全なテスト プロジェクトをダウンロードする
このチュートリアルのすべてのコード例は、すぐに実行できる.NET 10テストプロジェクトで利用できます。 ダウンロードには、完全なソースコード、データモデル、HTMLテンプレート、および上記のすべてのサンプルPDFを生成するテストランナーが含まれています。
次のステップ
本ガイドの例では、IronPDFがあらゆるビジネスレポーティングのニーズに対応できることを実証しています:シンプルな行項目と合計を含む請求書、グループ化されたデータと写真を含む複雑な従業員名簿、条件付き書式とグラフを含む在庫レポート、数百のドキュメントの並列バッチ処理、およびバックグラウンドサービスによるスケジュール生成などです。
既存の Crystal Reports の代替案を検討する場合は、価値の高いレポートを 1 つ作成することから始めます。 ここに示したHTML-to-PDFパターンを使って再構築し、開発経験と出力品質を比較し、そこから拡張してください。 多くのチームは、最初に変換したレポートは、パターンと基本テンプレートを確立するのに数時間かかり、その後のレポートは、Razorテンプレートとスタイリングを再利用するため、数分で組み立てることができます。 レイアウトの精度については、ピクセルパーフェクトレンダリングガイドで、CSS を使用して Crystal Reports の出力を正確に一致させる方法を説明しています。
構築開始の準備はできましたか? IronPDFをダウンロードして、無料トライアルで試してください。 同じライブラリで、単一のレポート レンダリングから、.NET 環境間での 大容量バッチ生成まで、すべてを処理します。 レポートの移行について質問がある場合、またはアーキテクチャのガイダンスが必要な場合は、当社のエンジニアリング・サポート・チームにお問い合わせください。
よくある質問
IronPDFとは?
IronPDFは開発者がプログラムでPDFドキュメントを作成、編集、生成することを可能にするC#ライブラリで、Crystal Reportsのような伝統的なレポートツールに代わる最新のツールを提供します。
IronPDFはどのようにCrystal Reportsの代替となるのですか?
IronPDFは、Crystal Reportsの堅苦しい構造とは対照的に、スタイリングや変更が容易なHTML/CSSテンプレートを開発者が使用できるようにすることで、レポート生成に柔軟でモダンなアプローチを提供します。
IronPDFを使って請求書を作成できますか?
IronPDFのHTML/CSSテンプレートを使って、詳細でカスタマイズされた請求書を作成することができます。
IronPDFで従業員名簿を作成することはできますか?
もちろんです。IronPDFを使えば、ダイナミックデータとHTML/CSSを活用して、わかりやすく整理された包括的な従業員名簿を作成することができます。
IronPDFはインベントリーレポートでどのようにお役に立てますか?
IronPDFはHTML/CSSテンプレートを使用することでインベントリレポートの作成を効率化し、動的にデータを入力することで、最新かつ視覚的に魅力的なレポートを提供します。
IronPDFでHTML/CSSテンプレートを使う利点は何ですか?
IronPDFでHTML/CSSテンプレートを使用することで、デザインの柔軟性、更新のしやすさ、ウェブテクノロジーとの互換性を提供し、レポートレイアウトの維持と拡張を容易にします。
IronPDF は .NET 10 をサポートしていますか?
IronPDFは.NET 10と互換性があり、開発者はレポート生成のニーズに対して最新 for .NET機能と改良を利用することができます。
IronPDFはどのようにレポート作成速度を向上させるのですか?
IronPDFはパフォーマンスのために最適化されており、HTML/CSSを効率的に処理し、高品質のPDFドキュメントにレンダリングすることで、レポートを素早く生成することができます。

