C#でレポートを生成する; Crystal Reports(.NET 10)のように。

This article was translated from English: Does it need improvement?
Translated
View the article in English

HTMLからPDFへのレポート生成 in C# .NET with IronPDFは、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経由でIronPdfをインストールしてください(Install-Package IronPdf)。 わずか数行のコードで最初のレポートを作成します:

Nuget Icon今すぐ NuGet で PDF を作成してみましょう:

  1. NuGet パッケージ マネージャーを使用して IronPDF をインストールします

    PM > Install-Package IronPdf

  2. このコード スニペットをコピーして実行します。

    // 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");
  3. 実際の環境でテストするためにデプロイする

    今すぐ無料トライアルでプロジェクトに IronPDF を使い始めましょう
    arrow pointer

IronPDFを購入または30日間のトライアルにサインアップした後、アプリケーションの最初にライセンスキーを追加してください。

IronPdf.License.LicenseKey = "KEY";
IronPdf.License.LicenseKey = "KEY";
Imports IronPdf

IronPdf.License.LicenseKey = "KEY"
$vbLabelText   $csharpLabel

今日あなたのプロジェクトでIronPDFを無料トライアルで使用開始。

最初のステップ:
green arrow pointer
NuGet 購入の準備ができていませんか?

PM >  Install-Package IronPdf

IronPDFNuGet でチェックしてください。1000万回以上のダウンロードで、C#によるPDF開発を変革しています。 DLL または Windowsインストーラー をダウンロードすることもできます。

目次を参照してください。

C#レポートジェネレーター:HTML テンプレートから 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コンポーネントを使用しており、クロスプラットフォームの.NETとは互換性がありません。 最新の.NETをサポートするには、完全な書き換えが必要ですが、SAPはこれを拒否しています。

そのため、現行の .NET バージョンで新しいアプリケーションを開発するチームは Crystal Reports を使用できません。 .NET 8または.NET 10で標準化されている組織は統合できません。 既存のアプリケーションを最新の.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互換ホスティング、個別のデプロイ設定が必要です。 レポーティング・サービスは例外となり、標準化を阻む。

.NET 10 で C#num; レポート ジェネレーターをセットアップする。

IronPDFを使い始めるのは簡単です。 他の.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テンプレートでの作業が必要になった場合でも、スキルはそのまま引き継がれます。

C&numでデータ駆動型PDFレポートを作成する

このセクションでは、最初から最後まで、売上請求書レポートの完全な作成を示します。 データを構造化するモデルを定義し、データをフォーマットされた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
$vbLabelText   $csharpLabel

Subtotal、TaxAmount、GrandTotal の計算プロパティがモデルに含まれています。 これらの計算はテンプレートではなくモデルに属し、Razorビューをプレゼンテーションに集中させ、モデルがビジネスロジックを処理します。 この分離により、単体テストが容易になり、HTMLをレンダリングすることなく計算を検証できるようになります。

このモデルをプロフェッショナルな書式の請求書に変換するRazorビューを作成します。これをInvoiceTemplate.cshtmlとしてViewsフォルダに保存します:

@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>
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
$vbLabelText   $csharpLabel

コンソールアプリやバックグラウンドサービスのように、.NET Core MVCの完全なセットアップを必要としないシンプルなシナリオでは、動的な部分に補間とStringBuilderを使用したHTML文字列を使用できます。

サンプル出力

ヘッダー、フッター、ページ番号を追加する。

Professionalなレポートには通常、すべてのページで一貫したヘッダーとフッターが含まれ、会社のブランド名、文書のタイトル、生成日、ページ番号が表示されます。 IronPDFはこれらの要素を実装するために2つのアプローチを提供します: テキストベースのヘッダーは最小限のフォーマットでシンプルなコンテンツを、HTMLヘッダーはロゴやカスタムレイアウトで完全なスタイルコントロールを実現します。

テキストベースのヘッダーは、基本的な情報には適しており、HTMLの追加解析が不要なため、レンダリング速度も速くなります:

: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
$vbLabelText   $csharpLabel

利用可能なマージフィールドには、現在のページ番号の{page}、ドキュメントの総ページ数の{total-pages}、生成タイムスタンプの{date}{time}、ウェブページからレンダリングする場合のソースURLの{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
}
$vbLabelText   $csharpLabel

サンプル出力

動的なテーブルと繰り返しセクションを作成する

レポートは、複数のページにまたがるデータの集合を表示する必要があることがよくあります。 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
$vbLabelText   $csharpLabel

departmentクラスのCSSプロパティpage-break-inside: avoidは、可能な限りdepartmentセクションを1ページにまとめるようPDFレンダラーに指示します。 部門のコンテンツがセクションの途中で改ページされる場合は、レンダラがセクション全体を次のページに移動します。 .department:not(:first-child)page-break-before: alwaysのセレクタは、最初の部署以降の各部署を強制的に新しいページから開始させ、ディレクトリ全体できれいなセクション区切りを作成します。

サンプル出力

IronPDFによる高度なC#レポート生成

ビジネスレポートでは、静的な表やテキスト以上の機能が求められることがよくあります。 グラフは、表形式では理解するのが面倒なトレンドを視覚化します。 条件付き書式は、アクションを必要とする項目に注意を向けます。 サブレポートは、複数のソースからのデータを統合した文書です。 このセクションでは、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>
HTML

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)
$vbLabelText   $csharpLabel

サンプル出力

条件付き書式とビジネスロジックを適用する。

インベントリレポートは、アクションが必要な項目にすぐに注意を向けることができる視覚的なインジケータが役立ちます。 ユーザーが何百行もの行をスキャンして問題を探すのではなく、条件付き書式を使用することで、例外を視覚的に明らかにすることができます。 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>
}
HTML

サンプル出力

サブレポートとセクション区切りを作成する

別々に作成されたレポートを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
$vbLabelText   $csharpLabel

サンプル出力

目次の生成

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")
$vbLabelText   $csharpLabel

クリスタルレポートから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
$vbLabelText   $csharpLabel

サンプル出力

バッチ処理の例では、複数の請求書を並行して作成します。 生成されたバッチ請求書の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
$vbLabelText   $csharpLabel

完全なテスト プロジェクトをダウンロードする。

このチュートリアルのすべてのコード例は、すぐに実行できる.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と互換性があり、開発者はレポート生成のニーズに対して最新の.NET機能と改良を利用することができます。

IronPDFはどのようにレポート作成速度を向上させるのですか?

IronPdfはパフォーマンスのために最適化されており、HTML/CSSを効率的に処理し、高品質のPDFドキュメントにレンダリングすることで、レポートを素早く生成することができます。

カーティス・チャウ
テクニカルライター

Curtis Chauは、カールトン大学でコンピュータサイエンスの学士号を取得し、Node.js、TypeScript、JavaScript、およびReactに精通したフロントエンド開発を専門としています。直感的で美しいユーザーインターフェースを作成することに情熱を持ち、Curtisは現代のフレームワークを用いた開発や、構造の良い視覚的に魅力的なマニュアルの作成を楽しんでいます。

開発以外にも、CurtisはIoT(Internet of Things)への強い関心を持ち、ハードウェアとソフトウェアの統合方法を模索しています。余暇には、ゲームをしたりDiscordボットを作成したりして、技術に対する愛情と創造性を組み合わせています。

準備はできましたか?
Nuget ダウンロード 17,386,124 | バージョン: 2026.2 リリース