BlazorでRDLCレポートを表示・PDF生成する方法|ReportViewer代替とvbc.exe/チャートエラー対策

Blazor(最新の.NET / ASP.NET Core)でRDLC(.rdlc)レポートを「呼び出して表示できるのか?」は、実務で必ずぶつかる悩みです。結論としては、ReportViewerのようにブラウザ内でRDLCを直接表示するのは難しく、サーバー側でRDLCをPDF等にレンダリングしてBlazorに配信する構成が現実的です。

目次

BlazorでRDLCレポートを表示/生成できるか?結論

Blazor環境でRDLC(.rdlc)を扱う場合、「できる/できない」を最初に整理しておくと判断が早くなります。

やりたいことBlazorでの現実解難しい/注意が必要な理由
ブラウザ内でRDLCをそのまま閲覧(ReportViewer体験)基本的に非現実的(代替はPDF/画像の埋め込み)RDLCの実行・描画基盤が.NET Framework/Windows寄りで、Webブラウザ向けビューワーが標準で存在しない
RDLCを読み込んでPDFを生成可能(サーバー側でレンダリングし、Blazorへ配信)レンダリングエンジンや依存関係(GDI+、フォント、vbc.exe等)によりWindows前提になりやすい
サブレポート/複数データセットの取り回し可能だが設計・実装のコツが必要データソース名の一致、サブレポートのデータ供給、型付きDataSet依存などで詰まりやすい
Linuxホスティング(Docker含む)で安定運用RDLCに固執すると難易度が上がるRDLC描画はWindows依存が出やすい(チャート、ゲージ、System.Drawing、System.Windows.Forms等)

結論:「BlazorでRDLCを表示する」のではなく、サーバーでRDLCをPDF(または画像)にレンダリングして配信し、Blazor側はその成果物を表示する構成が定番の落としどころです。

なぜRDLCは.NET(Blazor)で扱いづらいのか

RDLCは「クライアント側でレポートをローカル処理する」文脈で発展してきた経緯があり、実行エンジンや描画がWindows由来のコンポーネントに寄るケースが多いのが特徴です。Blazor(特にWebAssembly)はブラウザ上で動作するため、RDLCのレンダリングをそのまま持ち込むのは構造的に難しくなります。

  • Blazor WebAssembly:ブラウザ内で.NETが動くが、RDLCレンダリングに必要な環境(GDI+相当、外部コンパイラ等)を満たしにくい
  • Blazor Server / Hosted(Server側):サーバーでRDLCをレンダリングできる余地はあるが、実行環境がWindows前提になりやすい

「ReportViewerのようにRDLCを読み込んで画面上でページングして見る」体験をWebで完全再現したい場合、RDLC自体よりもWeb向けレポート基盤(別方式)を検討した方が、結果的に短期間で安定します。

現実的な構成:サーバーでRDLC→PDF生成してBlazorへ配信

ここからは「BlazorアプリでRDLCを使い続ける」前提で、最も安定しやすい構成を具体化します。ポイントは、Blazor側は“表示”に徹し、RDLCの“生成”はサーバーAPIで完結させることです。

全体アーキテクチャ(おすすめ)

役割担当実装イメージ
RDLCの保管サーバー(ASP.NET Core)プロジェクト内のReportsフォルダに配置(配布時も含める)
RDLCのレンダリングサーバー(Controller / Minimal API)RDLCを読み込み、PDFをバイト配列で生成して返す
表示Blazor(UI)PDFをiframe/objectで埋め込む、またはダウンロードリンクを提供
データ供給サーバー(DB/サービス層)DTOのリストをDataSourceとして渡す。サブレポートもここで供給

RDLCの作成:デザイナーが使えるWindowsプロジェクトで作る

RDLCのデザイン編集は、環境によってはVisual Studioの拡張やテンプレートが必要だったり、プロジェクト種別によって操作性が変わります。実務では次のどちらかが多いです。

  • Windows FormsなどのプロジェクトでRDLCを作成し、完成した.rdlcをBlazor(Server側)へコピーして使う
  • Visual StudioのRDLCデザイナー拡張を使い、編集環境を整えて同一ソリューション内で管理する

重要なのは、Blazorプロジェクト側では「デザイン編集」よりも「確実に読み込み・配布できる配置」を優先することです。

実装例:ASP.NET CoreのControllerでRDLC→PDFを返す

ここでは、Blazor(ServerでもWASM Hostedでも可)のサーバー側にController(API)を用意して、PDFを返す基本形を紹介します。サンプルでは「AspNetCore.Reporting」を利用する想定です(同系統のRDLCレンダリングライブラリでも考え方は同じです)。

Program.cs:Controllersの追加とMapControllersを忘れない

Blazor側の構成によっては、Controllerを追加したのにルーティングされず「404」になったり、エンドポイントが生えないことがあります。特に多いのがControllers追加とMapControllersの設定漏れです。

using System.Text;
using System.Text.Encoding.CodePages;

var builder = WebApplication.CreateBuilder(args);

// Blazor(例:Server)
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();

// ★Controllerを使うなら追加
builder.Services.AddControllers();

var app = builder.Build();

// 文字コード(必要なケースあり)
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

app.UseStaticFiles();
app.UseRouting();

// ★Controllerのマッピング
app.MapControllers();

// Blazorのマッピング(Serverの例)
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

ハマりポイント:BlazorのFallback(MapFallbackToPage / MapFallbackToFile)が先に効いてしまうと、APIが意図通りに当たらないことがあります。基本はMapControllersを先に置く運用が安全です。

Controller:RDLCを読み込み、PDFをFileで返す

RDLCの読み込み方はライブラリや構成で変わりますが、現場で安定しやすいのは「配布物として.rdlcを出力し、パスで読み込む」方式です。

using AspNetCore.Reporting;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/reports")]
public class ReportsController : ControllerBase
{
    private readonly IWebHostEnvironment _env;

    public ReportsController(IWebHostEnvironment env)
    {
        _env = env;
    }

    [HttpGet("invoice/{orderId:int}")]
    public IActionResult Invoice(int orderId)
    {
        // RDLCの配置例:ServerプロジェクトのReportsフォルダ
        var reportPath = Path.Combine(_env.ContentRootPath, "Reports", "Invoice.rdlc");

        // RDLCロード
        var localReport = new LocalReport(reportPath);

        // データソース名はRDLC側のDataSet名に合わせる(ここが最重要)
        var header = GetInvoiceHeader(orderId);     // 1件のDTOでもList化する方が無難
        var lines  = GetInvoiceLines(orderId);      // 明細のList

        localReport.AddDataSource("HeaderDataSet", new[] { header });
        localReport.AddDataSource("LineDataSet", lines);

        // パラメータ(RDLC側でReportParameterを定義している場合)
        var parameters = new Dictionary<string, string>
        {
            ["PrintedAt"] = DateTime.Now.ToString("yyyy/MM/dd HH:mm"),
            ["OrderId"]   = orderId.ToString()
        };

        // PDF生成(シグネチャはライブラリのバージョンで差異があるため調整)
        string mimeType;
        var result = localReport.Execute(RenderType.Pdf, 1, parameters, out mimeType);

        // ブラウザ表示したい場合はファイル名やヘッダを調整(必要に応じて)
        return File(result.MainStream, mimeType, $"invoice_{orderId}.pdf");
    }

    private InvoiceHeaderDto GetInvoiceHeader(int orderId)
    {
        // DBやサービス層から取得する想定
        return new InvoiceHeaderDto
        {
            OrderId = orderId,
            CustomerName = "サンプル商事",
            TotalAmount = 123456
        };
    }

    private List<InvoiceLineDto> GetInvoiceLines(int orderId)
    {
        return new List<InvoiceLineDto>
        {
            new InvoiceLineDto { ItemName = "商品A", UnitPrice = 1000, Quantity = 2 },
            new InvoiceLineDto { ItemName = "商品B", UnitPrice = 500,  Quantity = 3 }
        };
    }
}

public class InvoiceHeaderDto
{
    public int OrderId { get; set; }
    public string CustomerName { get; set; } = "";
    public int TotalAmount { get; set; }
}

public class InvoiceLineDto
{
    public string ItemName { get; set; } = "";
    public int UnitPrice { get; set; }
    public int Quantity { get; set; }
}

この方式のメリットは、BlazorのUIは「PDFの表示」だけを担当し、RDLC実行の依存関係がUIに漏れないことです。RDLC周りで問題が起きても、API側だけを見れば切り分けできます。

Blazor側:PDFを表示する(iframe / object / ダウンロード)

Blazorでの表示はシンプルに割り切るのがコツです。例えば、PDFを新しいタブで開くリンクを出すだけでも実務上は十分なケースが多いです。

<!-- 新しいタブで開く -->
<a href="/api/reports/invoice/123" target="_blank" rel="noopener">請求書PDFを開く</a>

<!-- ページ内に埋め込み(ブラウザのPDFビューアに依存) -->
<iframe src="/api/reports/invoice/123" style="width:100%; height:80vh; border:1px solid #ddd;"></iframe>

補足:「ReportViewerのような検索・ページング・拡大縮小」をWebでやりたい場合は、PDFビューア(ブラウザ標準またはJavaScriptのPDFビューア)に寄せるのが現実的です。RDLCを“表示する”のではなく、“出力物を表示する”設計にする方が安定します。

.csproj設定:RDLCを配布物に含める(ファイル方式/埋め込み方式)

開発機では動くのに本番で「RDLCが見つからない」問題はかなり多いです。原因の大半は、ビルド出力やデプロイ成果物にRDLCが含まれていないことです。

おすすめ:RDLCをContentとして出力にコピー(パス読み込み向け)

RDLCをファイルパスで読み込むライブラリの場合、これが一番分かりやすいです。

<ItemGroup>
  <Content Include="Reports\**\*.rdlc">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

埋め込み方式:EmbeddedResourceにしてアセンブリへ内包(ライブラリ対応が必要)

配布ミスを減らすという観点では埋め込みが強い一方、ライブラリがストリーム読み込みに対応していないと扱いづらくなります。採用する場合は「そのライブラリが埋め込み読み込みに対応しているか」を先に確認してください。

<ItemGroup>
  <EmbeddedResource Include="Reports\**\*.rdlc" />
</ItemGroup>
方式メリットデメリット
ファイル方式(Content + CopyToOutputDirectory)読み込みが単純、トラブルシュートしやすいデプロイ漏れがあると本番で落ちる
埋め込み方式(EmbeddedResource)配布ミスを減らせる、単一成果物に寄せられる対応ライブラリが必要、ストリーム処理の実装が増える

サブレポートとデータセット:詰まりやすいポイントと対処

RDLCで「サブレポート」「複数データセット」「パラメータ」を使い始めると、急に難易度が上がります。ここでは、実務でよく出る“詰まりポイント”を先に潰しておきます。

データセット名は「RDLCで定義した名前」と完全一致させる

RDLCは、DataSourceに「名前」を持ちます。C#側でAddDataSourceする際の名前が1文字でも違うと、空表示・例外・NullReferenceにつながります。

  • RDLC側:DataSet名(例:HeaderDataSet、LineDataSet)
  • C#側:AddDataSource(“HeaderDataSet”, …)

「型付きDataSet(.xsd)」をRDLCに紐づけている場合、.NET(ASP.NET Core)側でそのまま再現しようとすると、設計や依存が重くなりがちです。可能なら、DTOのListをそのままDataSourceにする運用に寄せると管理が楽になります。

サブレポートは「親から子へ渡すキー」と「子側のデータ供給」がセット

サブレポートがある場合、親レポートをレンダリングしている最中に「子(サブ)レポート用のデータをください」と呼ばれる流れになります。ライブラリによってイベント名や書き方は違いますが、考え方は共通です。

サブレポート対応の基本:

  • 親レポートでサブレポートを配置し、必要なキー(例:OrderId)を渡す
  • サーバー側で「そのキーに紐づく明細や関連情報」を取得し、サブレポートのDataSource名で供給する

サブレポートが絡む場合は、次のチェックが有効です。

チェック項目よくある症状対処の方向性
親→子に渡すパラメータ名サブレポートが真っ白/一部だけ欠けるRDLCのサブレポート設定で、パラメータ名と型を一致させる
子(サブ)側DataSource名「データセットが見つからない」系のエラー子RDLCのDataSet名とC#側の供給名を完全一致
キーの型(int/stringなど)条件に合うはずなのに0件になる渡す値の型と変換を見直す(文字列化の有無など)

よくあるエラーと対処:vbc.exe、チャート描画、NullReferenceなど

RDLCレンダリングで厄介なのは「環境依存でエラーが変わる」点です。ここでは、相談が多いエラーを原因別に整理し、対処の現実解をまとめます。

エラー/症状典型原因現実的な対処
vbc.exe cannot be found
(例:Framework64\v8.0.0\vbc.exeがない)
RDLCの式評価などでVBコンパイラが必要になり、外部のvbc.exeを探しに行くWindows環境前提で運用し、.NET Framework側のvbc.exeが使える状態にする 事例として、.NET Frameworkのv4.0.30319配下のvbc.exeを、探索されるフォルダ相当に配置して回避できたケースがある(ただし運用設計が必要) 長期的には、vbc依存の少ないレポート基盤へ移行も検討
チャート/ゲージ/データバー等で描画エラー
System.Windows.Formsが見つからない等
RDLCの一部描画がWinForms/GDI+寄りで、実行環境が要求を満たさないWindows前提のホスティングに寄せる(IIS/Windows Server) プロジェクトをnetX.0-windowsに寄せる必要が出る場合がある(=Linuxを捨てる判断) RDLCの表現を見直し、チャートを画像化して差し替える/別方式にする
データセット追加でNullReference
(オブジェクト参照エラー)
DataSource名の不一致、期待する列/プロパティが存在しない、データがnullなどRDLCのDataSet名とAddDataSource名を一致させる DTOのプロパティ名・型がRDLCの参照と一致しているか確認 データが0件の場合の挙動を想定し、空Listを渡す
文字化け(日本語が□になる/????になる)コードページ未登録、フォント不足、レンダリング側の環境差CodePagesEncodingProviderを登録する(Encoding.RegisterProvider) サーバーに日本語フォントを用意(Windowsなら比較的容易) 帳票フォントを標準フォントに寄せる
本番だけRDLCが見つからないデプロイ成果物に.rdlcが含まれていない(CopyToOutput漏れ)csprojでContentとして出力コピーする コンテナ/CI/CDで成果物に含まれているか確認 埋め込み方式を検討(対応ライブラリ前提)

vbc.exe問題の考え方:その場しのぎと運用設計を分けて考える

vbc.exeの問題は「ローカルで動いたからOK」では終わりません。デプロイ先でも同じ探索・配置が必要になりやすく、台数が増えるほど運用コストが上がります。

  • 開発機だけ回避できた:本番で再発しやすい。デプロイ手順に組み込むか、方式転換を検討
  • 本番もWindows固定で、セットアップを標準化できる:RDLC継続の現実味が増す
  • Linux/Docker前提で横展開したい:RDLCに固執すると不確定要素が増えるので、代替案の検討価値が高い

運用で差がつく:RDLC→PDF生成APIの設計ポイント

実装が動いた後、運用で困りがちな点を先回りしておくと、後々のトラブルが減ります。

1リクエスト1生成は重い:キャッシュや非同期生成も検討

RDLCレンダリングは、帳票の複雑さやページ数によってCPU負荷が上がりやすい処理です。以下のような工夫が効きます。

  • 同一条件で何度も同じPDFを作るなら、生成結果をキャッシュする(メモリ/Redis/Blobなど)
  • 大量印刷・一括出力があるなら、ジョブ化して非同期生成し、完了後にダウンロードさせる
  • 帳票の画像・チャートが重い場合、RDLC側の表現を軽くする(解像度や要素数の見直し)

パラメータはバリデーション必須:帳票APIは情報漏えいの入口になりやすい

「/api/reports/invoice/123」のようなURLは分かりやすい反面、IDを変えるだけで他人の帳票が見えてしまう事故が起きがちです。

  • 認証(ログイン必須)・認可(そのユーザーが参照できる帳票か)を必ず入れる
  • クエリパラメータをそのままSQLに渡さない(当然ですが帳票は狙われやすい)
  • 生成したPDFに個人情報が入るなら、ログや例外メッセージに出さない

フォントと地域設定:本番サーバーで“文字が崩れる”を防ぐ

RDLCはフォントの影響を受けます。開発PCに入っているフォントが本番サーバーに無いだけで、レイアウトがズレたり、文字が豆腐になることがあります。

観点チェック内容対策例
フォントRDLCで指定しているフォントが本番にもあるかWindows標準フォントに寄せる/サーバーへフォントを追加
カルチャ日付・数値の書式が環境で変わらないかパラメータで文字列化して渡す/CultureInfoを明示
文字コード特定文字で化けないかCodePagesEncodingProviderの登録、出力確認

「RDLCに固執しない」選択肢も、実はコスト削減になる

RDLCは既存資産があるほど捨てづらい一方で、Blazor/クラウド/コンテナ時代の運用要件と噛み合わない局面もあります。特に「Linuxで動かしたい」「スケールアウトしたい」「描画依存で詰まっている」場合は、移行も含めて冷静に検討する価値があります。

選択肢向いているケース考慮点
RDLC継続(サーバーPDF化)既存RDLC資産が大きい/Windows運用で問題ないvbc.exeやチャートなど環境依存のケアが必要
別レポート基盤へ移行Linux/クラウド前提で安定運用したい移行コストはかかるが、将来の運用が軽くなる可能性
HTMLテンプレート→PDF(別レンダラー)帳票デザインをWeb技術で持ちたい印刷レイアウトの作り込みが必要(余白/改ページ等)

「今すぐ移行は無理」でも、少なくとも新規帳票だけ別方式にする、またはチャートだけ別方式に逃がすなど、段階的に混在させると現実的に前へ進めます。

まとめ:BlazorでRDLCを扱うなら“表示”ではなく“生成”に寄せる

BlazorでRDLCを扱うときの最適解は、ブラウザ内でRDLCを直接表示することではなく、サーバー側でRDLCをレンダリングしてPDF(または画像)を返す構成に寄せることです。これにより、ReportViewer相当の体験を無理に再現しようとして沼にハマるリスクを下げられます。

  • RDLCはWindows依存が出やすいため、Linux運用にこだわるほど難易度が上がる
  • Blazor側はPDFを表示するだけにして、RDLCレンダリングはController/APIへ隔離する
  • MapControllersの設定漏れ、RDLCの配布漏れ、DataSet名不一致が三大ハマりどころ
  • vbc.exeやチャート周りは「その場しのぎ」だけでなく、デプロイ手順まで含めて設計する

まずは「RDLC→PDF生成API」を小さく作って安定稼働させ、サブレポートや複雑なチャートは段階的に適用していくと、トラブルを最小化しながら移行できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次