Blazor Server から SSRS(.rdl)レポートを「REST API で呼び出して PDF を返したい」と思い、/ReportServer/api/v2.0/Reports までは取得できたのに、Export で HTTP 400…というケースは珍しくありません。結論は、SSRS の REST API はレンダリング用途ではなく、PDF 出力は URL Access(rs:Format=PDF)で行うのが現実解です。
結論:SSRS の REST API(v2.0)は「PDF をレンダリングして返す」用途に向かない
SSRS 2017 以降には REST API(/ReportServer/api/v2.0/…)が用意されていますが、主目的は「レポートサーバーのカタログ(フォルダー、レポート、データソース等)をプログラムから操作すること」です。つまり、一覧取得・作成・更新・削除・定義のダウンロードなどには使えても、Report Builder で作った .rdl を実行して PDF を返す“実行エンジン”としての API ではありません。
そのため、次のような URL を叩いて「PDF が返るはず」と期待しても、HTTP 400(Bad Request)に到達しやすく、Blazor 側で PDF をダウンロードできません。Microsoft Q&A でも、REST API での PDF レンダリング手段は用意されていない旨が案内されています。
https://localhost:440/ReportServer/api/v2.0/Reports(ItemPath='/BlazorSQL-Reports/Details')/Export(Format='PDF')
「/Reports が取れるのに /Export が 400」は、アプリ実装の問題というより“API の性格の違い”が原因です。ここを割り切ると、解決への最短ルートが見えてきます。
なぜレポート一覧は取得できるのに、Export で HTTP 400 になるのか
REST API は“カタログ管理 API”として設計されている
SSRS の REST API は、レポートサーバーカタログにあるオブジェクト(Reports / Folders / DataSources / Datasets / Subscriptions など)を CRUD するための仕組みとして説明されています。たとえば「フォルダーを辿る」「レポート定義をダウンロードする」「オブジェクトを作成・更新・削除する」といった用途です。
一方で、.rdl を“実行”して PDF・Excel に“レンダリング”するのは、別系統の仕組み(URL Access や SOAP の ReportExecution など)で実現する世界観です。REST API のエンドポイント名に Export が見えても、それが「従来のエクスポート(=レンダリング)」と一致するとは限りません。
「ReportServer」と「Reports」を取り違えると、URL が全部ズレる
SSRS には、よく似た URL が複数あります。ここを混同すると「一覧は見えるのに、実行ができない」「開くとポータルに飛ぶ」といった挙動になりがちです。
| URL の例 | 役割 | Blazor での使いどころ |
|---|---|---|
| https://<server>/ReportServer/… | レポート実行・URL Access・API の入口(本体) | PDF 出力や API 呼び出しで主に使う |
| https://<server>/Reports/… | Web ポータル(人が操作する UI) | 埋め込み用途としては原則非推奨(UI が変わりやすい) |
Export(Format=’PDF’) という形は、SSRS 側が想定していない可能性が高い
今回のように OData 風のパスで Export を呼ぶ場合、そもそもそのリソース・メソッドが“Report(.rdl)に対しては”提供されていない、あるいはパラメータの形が異なる、といった理由で 400 になり得ます。Microsoft 側からも「REST API で PDF をレンダリングする方法はない」旨の回答が出ているため、アプリ側でリトライやヘッダー調整を頑張っても、根本解決しないケースがほとんどです。
まずは「できること/できないこと」を表で整理する
| やりたいこと | REST API(/api/v2.0) | URL Access(rs:Format) | おすすめの選択 |
|---|---|---|---|
| レポート一覧を取得したい | 得意(GET /Reports 等) | 不得意(一覧用途ではない) | REST API |
| .rdl 定義をダウンロードしたい | 可能(カタログ操作の範囲) | 不向き | REST API |
| 特定レポートを実行して PDF を返したい | 基本的に不向き(その用途の仕組みが不足) | 得意(rs:Format=PDF で直接レンダリング) | URL Access |
PDF 出力の正攻法:URL Access(rs:Format=PDF)を使う
SSRS には昔から「URL Access」という仕組みがあり、URL にパラメータを付けるだけでレポートの表示・レンダリング形式の指定ができます。Microsoft Learn でも、rs:Format を使って PDF を含む各形式に出力できることが明記されています。
最小構成:PDF を返す URL 例(ネイティブモード想定)
http://localhost:440/ReportServer?/BlazorSQL-Reports/Details&rs:Command=Render&rs:Format=PDF
この URL をブラウザで開けるなら、Blazor Server 側も同じ URL を“開く”か“サーバー側で取得して中継する”だけで、PDF の表示・ダウンロードが実現できます。
ReportViewer.aspx を使うパターン(埋め込みで UI を調整したいとき)
環境によっては、次のように ReportViewer.aspx を経由した方が「パスの書き方が安定する」「rc:Toolbar など UI 系パラメータが効かせやすい」ことがあります。特にレポート名やフォルダー名にスペースが入る場合、%2f 形式(URL エンコード)を使うのが定石です。
http://localhost:440/ReportServer/Pages/ReportViewer.aspx?%2fBlazorSQL-Reports%2fDetails&rs:Command=Render&rs:Format=PDF&rc:Toolbar=false
rs:Format で指定できる主な形式
rs:Format は、PDF 以外にも Excel(xlsx)や Word(docx)など、サーバーに入っているレンダリング拡張に応じた形式を受け付けます。代表的な値として PDF、EXCELOPENXML、WORDOPENXML などが挙げられています。
Blazor Server での実装パターン(埋め込み+PDF 取得)
Blazor Server はサーバーサイドで .NET が動くため、ブラウザに直接 SSRS の認証情報を持たせずに実装しやすいのが強みです。要件(社内 AD 認証か、外部公開か、PDF を“表示”したいのか“ダウンロード”したいのか)に合わせて、次のパターンから選ぶと整理しやすくなります。
| パターン | 概要 | メリット | 注意点 |
|---|---|---|---|
| リンクで開く(新規タブ/同一タブ) | URL Access の PDF URL をそのまま開く | 実装が最小、SSRS 標準動作に乗れる | 認証が別ドメインだとユーザー体験が悪化しやすい |
| iframe で埋め込む | PDF URL または ReportViewer ページを iframe に設定 | 「画面内で完結」しやすい | SameSite/Cookie/認証/ヘッダー制限で詰まることがある |
| Blazor(ASP.NET Core)でプロキシして返す | サーバー側で SSRS から PDF を取得し、同一オリジンで返す | CORS・認証・URL 露出をコントロールしやすい | 実装量が増える。権限制御と負荷対策が必要 |
実装例:特定レポート(Details)を PDF として返す“中継エンドポイント”
「特定レポートだけを PDF で取得したい」という要件に最もハマるのが、Blazor Server(=ASP.NET Core)側に中継 API を作る方法です。ポイントは次の通りです。
- ユーザーには /ssrs/details.pdf のような“自アプリの URL”だけを見せる
- サーバー側で URL Access を呼び、PDF バイナリをそのままストリーミングする
- 任意のレポートパスを渡せないようにし、必要なレポートだけ許可する(重要)
Minimal API でのサンプル(概念実装)
下記は考え方を掴むための簡易例です。実運用では IHttpClientFactory を使う、SSRS 側の認証方式に合わせる、監査ログを残す等の補強を推奨します。
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// SSRS の ReportServer URL(例): https://localhost:440/ReportServer
var ssrsReportServerBase = "https://localhost:440/ReportServer";
// 「Details」だけを許可する(ユーザー入力で自由に変えられないようにする)
var detailsItemPath = "/BlazorSQL-Reports/Details";
app.MapGet("/ssrs/details.pdf", async (HttpContext context) =>
{
// 例:レポートパラメータをクエリから受け取る(必要なものだけ許可する)
var orderId = context.Request.Query["orderId"].ToString();
// URL Access の URL を組み立て(レポートパスは URL エンコードして安全に)
var encodedPath = Uri.EscapeDataString(detailsItemPath);
var sb = new StringBuilder();
sb.Append(ssrsReportServerBase);
sb.Append('?');
sb.Append(encodedPath);
sb.Append("&rs:Command=Render&rs:Format=PDF");
if (!string.IsNullOrWhiteSpace(orderId))
{
sb.Append("&OrderId=");
sb.Append(Uri.EscapeDataString(orderId));
}
var ssrsUrl = sb.ToString();
// Windows 認証(同一ドメイン内で SSRS にアクセスできる前提の例)
// 環境により、UseDefaultCredentials ではなく NetworkCredential が必要な場合もあります。
var handler = new HttpClientHandler
{
UseDefaultCredentials = true,
};
using var http = new HttpClient(handler);
using var resp = await http.GetAsync(ssrsUrl, HttpCompletionOption.ResponseHeadersRead);
if (!resp.IsSuccessStatusCode)
{
context.Response.StatusCode = (int)resp.StatusCode;
await context.Response.WriteAsync($"SSRS returned {(int)resp.StatusCode}");
return;
}
context.Response.ContentType = "application/pdf";
context.Response.Headers["Content-Disposition"] = "inline; filename=Details.pdf";
await resp.Content.CopyToAsync(context.Response.Body);
});
app.Run();
Blazor 側(ページ)から呼び出す例
同一オリジン(自アプリ内)の URL なので、PDF を “表示” したい場合は iframe、 “ダウンロード” したい場合はリンクや JS で開くだけです。
@page "/details-pdf"
<h1>Details レポート(PDF)</h1>
<p>
<a href="/ssrs/details.pdf?orderId=123" target="_blank" rel="noopener">
PDF を開く
</a>
</p>
<iframe src="/ssrs/details.pdf?orderId=123"
style="width:100%; height:80vh; border:1px solid #ccc;">
</iframe>
パラメータ付きレポートの PDF を“安全に”生成するコツ
URL Access では「レポートパラメータ」は通常のクエリとして渡します(例:&OrderId=123)。便利な反面、ユーザーが自由にパラメータを変えられると、意図しないデータ閲覧につながる可能性があります。中継エンドポイントを作る場合は、次の観点でガードすると安全です。
- 受け付けるパラメータを固定し、型(数値/日付/列挙)で検証する
- ログインユーザーの権限に応じて、許可された値だけに絞る(例:自部署の OrderId のみ)
- “レポートパス”を外部入力にしない(今回の Details のように固定する)
URL Access のパラメータ整理(rs: と rc: を使い分ける)
URL Access のパラメータは大きく rs:(レポートサーバー向け)と rc:(HTML Viewer 向け)に分かれます。Microsoft Learn のパラメータリファレンスでも、接頭辞の違いが説明されています。
| 接頭辞 | 例 | 用途 | メモ |
|---|---|---|---|
| rs: | rs:Command=Render | サーバー側の実行・レンダリング制御 | Render は表示/出力の基本コマンド |
| rs: | rs:Format=PDF | 出力形式の指定 | PDF のほか xlsx/docx なども指定可能 |
| rc: | rc:Toolbar=false | HTML Viewer の見た目制御 | 埋め込みで“見せたくない UI”を消すときに便利 |
| rc: | rc:Parameters=false | パラメータ領域の表示制御 | 埋め込みでパラメータ欄を隠したいときに使う |
よくある落とし穴と対処法(HTTP 400 以外も含む)
URL Access に切り替えると 400 問題は解消しやすい一方で、今度は認証や URL の組み立てで詰まりがちです。現場で遭遇しやすいポイントを、原因→対処でまとめます。
| 症状 | 主な原因 | 対処 |
|---|---|---|
| HTTP 400(URL Access 側でも 400) | レポートパスの書き方/URL エンコードが不正、必須パラメータ不足 | ReportServer の URL(/ReportServer と /Reports の違い)を再確認し、パスは %2f 形式でエンコードして試す |
| HTTP 401 / 403 | SSRS の認証(Windows 認証など)を通過できていない | 同一ドメイン/同一ゾーンでの統合認証、またはサーバー側プロキシで資格情報を管理する |
| PDF は返るが iframe で真っ白/表示されない | ブラウザの PDF ビューア設定、Mixed Content、iframe 制限 | https 統一、Content-Disposition を inline にする、新規タブ表示に切り替える |
| 特定のユーザーだけ表示できない | SSRS 側のフォルダー/レポート権限、データソース資格情報 | SSRS のセキュリティ(Browser/Content Manager 等)と、データソースの資格情報設定を見直す |
| 負荷が高く、タイムアウト/メモリ不足になりやすい | レポートが重い、同時実行が多い、サーバーリソース不足 | 中継側でアクセス制限(同時数/頻度)を入れる、必要ならキャッシュや非同期生成を検討する |
補足:将来的に REST API で“レンダリング”できるようになる可能性は?
SSRS は新バージョンで改善が続いており、REST API のサポート自体も「OpenAPI 準拠の RESTful API」として案内されています。また、SQL Server 2022 Reporting Services(SSRS 2022)の提供についても公式に触れられています。
ただし、現時点の公開情報や Q&A の回答を見る限り、REST API を “レポート実行→PDF 返却” の主ルートとして期待するよりも、URL Access を使う設計の方が堅実です。将来の方針は SQL Server/SSRS のリリースノートに依存するため、要件が長期運用前提なら「URL Access+アプリ側の中継」で吸収できる作りにしておくと、仕様変更にも追従しやすくなります。
まとめ:Blazor Server × SSRS で「特定レポートを PDF 取得」する最短ルート
- SSRS REST API(v2.0)はカタログ操作向けで、PDF レンダリング用途には向きにくい
- PDF を返したいなら URL Access(rs:Format=PDF)を使う
- Blazor Server では「中継エンドポイント」を作ると、認証・CORS・URL 露出をまとめて解決しやすい
- 特定レポートだけ許可(allowlist)し、必要なパラメータだけを受け付ける設計にすると安全

コメント