ASP.NET WebForms GridViewをPDF/Excelにエクスポートする方法(VB.NET)|Response設定とiTextSharp手順

ASP.NET WebForms の GridView で一覧を作っていると、画面に表示した内容をそのまま Excel や PDF としてダウンロードさせたい場面がよくあります。この記事では VB.NET を例に、Response の設定・ページングへの対応・iTextSharp を使った PDF 生成まで、つまずきやすい点も含めて実装手順をまとめます。

目次

ASP.NET WebForms の GridView を「ファイルとしてダウンロード」させる考え方

WebForms のエクスポートは、最終的に HTTP レスポンスを「ファイル」扱いにして返すのが基本です。つまり、Response に対して次の要素を正しく設定し、コンテンツをそのまま書き出します。

設定項目役割よく使う値・ポイント
ContentTypeブラウザに「これは何のファイルか」を伝えるExcel: application/vnd.ms-excel / PDF: application/pdf
Content-Dispositionダウンロード扱いにしてファイル名を指定するattachment; filename=...(日本語ファイル名は別途対策推奨)
Encoding文字化け対策(特に Excel の簡易方式)UTF-8 を基本にしつつ、メタタグや BOM を併用すると安定
出力内容Excel なら HTML、PDF なら PDF バイナリGridView を HTML として描画、PDF はライブラリで生成して BinaryWrite

また、WebForms 特有の制約として、GridView を Response に直接レンダリングする場合は “おまじない” が必要です。これを忘れると、実行時に例外で止まります。

共通で押さえるべきポイント

VerifyRenderingInServerForm を空実装でオーバーライドする

GridView を RenderControl で直接書き出す場合、WebForms のランタイム検証を回避するために VerifyRenderingInServerForm をオーバーライドします。ページ(.aspx のコードビハインド)に次を追加してください。

Public Overrides Sub VerifyRenderingInServerForm(ByVal control As Control)
    ' GridView を Response に直接描画するためのおまじない
End Sub

ページング中なら、エクスポート前に全件を再バインドする

GridView でページングを有効にしている場合、そのまま出力すると 表示中のページ分だけが出力されます。全件出したい場合は、エクスポート直前に以下の流れにします。

  • AllowPaging = False にする
  • 同じ条件でデータを取り直して DataBind()(もしくは既存のバインドメソッド)
  • 出力(RenderControl / PDF 生成)
  • 必要なら AllowPaging = True に戻して再バインド

Response.End は避け、CompleteRequest を使うと安定しやすい

サンプルでよく見かける Response.End() は、内部的にスレッド中断が発生して例外(ThreadAbortException)が出ることがあります。動作自体は問題ないケースも多いですが、ログが汚れたり運用で嫌われがちです。可能なら次のように CompleteRequest を使う構成にしておくと安心です。

HttpContext.Current.ApplicationInstance.CompleteRequest()

UpdatePanel 配下では「非同期ポストバックだとダウンロードできない」ことがある

ボタンが UpdatePanel 内にあると、クリックが非同期ポストバックになり、ファイルダウンロードが失敗することがあります。その場合は、エクスポートボタンを PostBackTrigger にします。

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
  <ContentTemplate>
    <asp:Button ID="Export2Excel" runat="server" Text="Export to Excel" OnClick="Export2Excel_Click" />
  </ContentTemplate>
  <Triggers>
    <asp:PostBackTrigger ControlID="Export2Excel" />
  </Triggers>
</asp:UpdatePanel>

GridView を Excel にエクスポートする方法(HTML 方式)

WebForms で一番手軽なのが、GridView を HTML として描画し、Excel に「Excel っぽく開かせる」方法です。厳密には .xls のバイナリではありませんが、社内ツールや簡易出力としては今でも使われます。

HTML 方式のメリット・デメリット

観点内容
メリット実装が軽い/GridView の見た目(ある程度)をそのまま出せる/ライブラリ不要
デメリット拡張子は .xls が多いが中身は HTML/Excel で「ファイル形式と拡張子が一致しない」警告が出ることがある/高度な書式や .xlsx の厳密出力は不向き

ボタン配置(ASPX)

<asp:Button Text="Export to Excel"
            ID="Export2Excel"
            runat="server"
            OnClick="Export2Excel_Click" />

クリックイベント実装(VB.NET)

以下は実務向けに少し安定化した例です。ポイントは、文字化け対策と、全件出力(ページング解除)、そして 余計な HTML が混ざらないよう Response をクリアすることです。

Imports System.IO
Imports System.Text
Imports System.Web

Protected Sub Export2Excel_Click(sender As Object, e As EventArgs)
    ' 1) 全件出力したい場合はページングを解除して再バインド
    Dim originalPaging As Boolean = GV1.AllowPaging
    GV1.AllowPaging = False
    BindGrid()

    ' 2) テンプレート列など、セル内コントロールを文字に置き換える(必要に応じて)
    PrepareGridViewForExport(GV1)

    ' 3) Response 設定
    Dim fileName As String = "GridViewExport.xls"

    Response.Clear()
    Response.ClearContent()
    Response.ClearHeaders()
    Response.Buffer = True

    ' Excel が文字化けしにくいように UTF-8 を明示
    Response.Charset = "UTF-8"
    Response.ContentEncoding = Encoding.UTF8

    Response.ContentType = "application/vnd.ms-excel"

    ' 日本語ファイル名を使う場合は filename* を検討(対応ブラウザ向け)
    Dim encodedFileName As String = HttpUtility.UrlEncode(fileName, Encoding.UTF8)
    Response.AddHeader("Content-Disposition", "attachment; filename*=UTF-8''" & encodedFileName)

    ' 4) HTML として GridView を描画して書き出す
    Using sw As New StringWriter()
        Using hw As New HtmlTextWriter(sw)

            ' Excel 向けの最低限の HTML ヘッダー
            Response.Write("<html><head>")
            Response.Write("<meta http-equiv=""Content-Type"" content=""text/html; charset=utf-8"" />")

            ' 数値が勝手に指数表記/日付変換されるのを抑えたい場合の例(必要に応じて)
            Response.Write("<style> .text { mso-number-format:\@; } </style>")
            Response.Write("</head><body>")

            GV1.RenderControl(hw)

            Response.Write("</body></html>")

            Response.Output.Write(sw.ToString())
            Response.Flush()
        End Using
    End Using

    ' 5) 後処理(元に戻す)
    GV1.AllowPaging = originalPaging
    BindGrid()

    HttpContext.Current.ApplicationInstance.CompleteRequest()
End Sub

Private Sub PrepareGridViewForExport(gv As GridView)
    For Each row As GridViewRow In gv.Rows
        For Each cell As TableCell In row.Cells
            For i As Integer = cell.Controls.Count - 1 To 0 Step -1
                Dim c As Control = cell.Controls(i)

                ' よくあるコントロールは Text を拾って Literal に差し替え
                Dim text As String = ExtractText(c)
                If text IsNot Nothing Then
                    cell.Controls.RemoveAt(i)
                    cell.Controls.Add(New LiteralControl(HttpUtility.HtmlEncode(text)))
                End If
            Next
        Next
    Next
End Sub

Private Function ExtractText(c As Control) As String
    If TypeOf c Is ITextControl Then
        Return CType(c, ITextControl).Text
    End If

    ' 例:CheckBox を「TRUE/FALSE」や「〇/空」に変換したい場合
    If TypeOf c Is CheckBox Then
        Dim cb = CType(c, CheckBox)
        Return If(cb.Checked, "TRUE", "FALSE")
    End If

    ' 子コントロールを掘る(テンプレート列で Panel 等に入っているケース)
    If c.HasControls() Then
        Dim sb As New StringBuilder()
        For Each child As Control In c.Controls
            Dim t As String = ExtractText(child)
            If Not String.IsNullOrEmpty(t) Then
                If sb.Length > 0 Then sb.Append(" ")
                sb.Append(t)
            End If
        Next
        If sb.Length > 0 Then Return sb.ToString()
    End If

    Return Nothing
End Function

Excel 側で警告が出る・表示がおかしいときの対策

  • 「ファイル形式と拡張子が一致しません」:HTML を .xls として開く方式の仕様です。社内運用で許容されない場合は、後述の Open XML 方式(.xlsx)へ移行します。
  • 文字化け:UTF-8 の指定に加え、<meta charset> を出力する/必要なら BOM 付与(BinaryWrite で UTF-8 BOM を先頭に書く)を検討します。
  • 先頭ゼロが消える、勝手に日付になる:CSS の mso-number-format を使う、または出力前に文字列化しておきます(例:郵便番号、社員番号など)。

.xlsx の「本格的な Excel」を出したい場合の現実的な選択肢

HTML 方式は手軽な反面、厳密な Excel ファイルではありません。帳票に近い用途(関数・書式・複数シート・正しい .xlsx)なら、次の方向性が候補になります。

方式特徴向いているケース
Open XML SDKMicrosoft 公式系の低レベル API。自由度は高いが記述量が増えやすい完全に .xlsx を組み立てたい/依存を抑えたい
ClosedXML / EPPlus / NPOI など高レベル API で書きやすい(ただしライセンスや配布条件は必ず確認)開発工数を抑えて整った Excel を出したい

GridView を PDF にエクスポートする方法(iTextSharp 方式)

PDF は Excel のように「HTML を開かせる」手は効きにくいため、PDF 生成ライブラリで 表データを PDF のテーブルとして組み立てるのが定番です。ここでは iTextSharp(iText 5 系)を使う例を紹介します。

事前準備:iTextSharp の導入(NuGet)

Visual Studio の [ツール] → [NuGet パッケージ マネージャー] → [パッケージ マネージャー コンソール] を開き、次を実行します。

Install-Package iTextSharp

説明文に書かれている「Execute command …」まで含めて入力してしまうと、Execute コマンドが見つからないエラーになります。コンソールに入れるのは コマンド本体だけです。

ボタン配置(ASPX)

<asp:Button Text="Export to PDF"
            ID="Export2PDF"
            runat="server"
            OnClick="Export2PDF_Click" />

クリックイベント実装(VB.NET / 日本語フォント対応付き)

PDF で多い落とし穴が 日本語が豆腐(□)になる問題です。iTextSharp はデフォルトだと日本語フォントが入らないため、サーバーに存在するフォントを指定して埋め込み(Embed)する構成にします。

Imports System.IO
Imports System.Text
Imports System.Web
Imports iTextSharp.text
Imports iTextSharp.text.pdf

Protected Sub Export2PDF_Click(sender As Object, e As EventArgs)
    ' 1) 全件出力したい場合はページング解除して再バインド
    Dim originalPaging As Boolean = GV1.AllowPaging
    GV1.AllowPaging = False
    BindGrid()

    ' 2) PDF をメモリ上で生成してから Response に流す
    Using ms As New MemoryStream()
        ' PDF 文書(A4 横など、用途に応じて変更)
        Dim pdfDoc As New Document(PageSize.A4, 20, 20, 20, 20)
        Dim writer As PdfWriter = PdfWriter.GetInstance(pdfDoc, ms)

        pdfDoc.Open()

        ' 3) 日本語フォント(サーバーに存在するフォントを指定)
        ' 例:Windows サーバーなら Fonts フォルダにあることが多い
        Dim fontPath As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "meiryo.ttc")
        Dim bf As BaseFont = BaseFont.CreateFont(fontPath & ",0", BaseFont.IDENTITY_H, BaseFont.EMBEDDED)
        Dim normalFont As New iTextSharp.text.Font(bf, 9, iTextSharp.text.Font.NORMAL)
        Dim headerFont As New iTextSharp.text.Font(bf, 9, iTextSharp.text.Font.BOLD)

        ' 4) タイトル(任意)
        pdfDoc.Add(New Paragraph("GridView Export", headerFont))
        pdfDoc.Add(New Paragraph(" ", normalFont))

        ' 5) GridView の列数でテーブルを作る
        Dim colCount As Integer = GV1.HeaderRow.Cells.Count
        Dim table As New PdfPTable(colCount)
        table.WidthPercentage = 100

        ' 6) ヘッダー行
        For Each cell As TableCell In GV1.HeaderRow.Cells
            Dim text As String = CleanCellText(cell.Text)
            Dim pdfCell As New PdfPCell(New Phrase(text, headerFont))
            pdfCell.HorizontalAlignment = Element.ALIGN_CENTER
            pdfCell.Padding = 4
            table.AddCell(pdfCell)
        Next

        ' 7) データ行
        For Each row As GridViewRow In GV1.Rows
            For Each cell As TableCell In row.Cells
                Dim text As String = GetGridCellText(cell)
                Dim pdfCell As New PdfPCell(New Phrase(text, normalFont))
                pdfCell.HorizontalAlignment = Element.ALIGN_LEFT
                pdfCell.Padding = 4
                table.AddCell(pdfCell)
            Next
        Next

        pdfDoc.Add(table)
        pdfDoc.Close()
        writer.Close()

        ' 8) Response へ PDF を返す
        Dim fileName As String = "GridViewExport.pdf"
        Response.Clear()
        Response.ClearContent()
        Response.ClearHeaders()
        Response.Buffer = True
        Response.ContentType = "application/pdf"

        Dim encodedFileName As String = HttpUtility.UrlEncode(fileName, Encoding.UTF8)
        Response.AddHeader("Content-Disposition", "attachment; filename*=UTF-8''" & encodedFileName)

        Response.BinaryWrite(ms.ToArray())
        Response.Flush()
    End Using

    ' 9) 後処理(元に戻す)
    GV1.AllowPaging = originalPaging
    BindGrid()

    HttpContext.Current.ApplicationInstance.CompleteRequest()
End Sub

Private Function GetGridCellText(cell As TableCell) As String
    ' BoundField 等なら cell.Text で十分なことが多い
    ' TemplateField の場合は Controls から拾う必要がある
    If cell.Controls IsNot Nothing AndAlso cell.Controls.Count > 0 Then
        Dim sb As New StringBuilder()
        For Each c As Control In cell.Controls
            Dim t As String = ExtractText(c)
            If Not String.IsNullOrEmpty(t) Then
                If sb.Length > 0 Then sb.Append(" ")
                sb.Append(t)
            End If
        Next
        If sb.Length > 0 Then Return sb.ToString()
    End If

    Return CleanCellText(cell.Text)
End Function

Private Function CleanCellText(text As String) As String
    If text Is Nothing Then Return ""
    Dim decoded As String = HttpUtility.HtmlDecode(text)
    decoded = decoded.Replace("&nbsp;", " ").Replace(ChrW(&H00A0), " ")
    Return decoded.Trim()
End Function

Private Function ExtractText(c As Control) As String
    If TypeOf c Is ITextControl Then
        Return CType(c, ITextControl).Text
    End If
    If TypeOf c Is CheckBox Then
        Dim cb = CType(c, CheckBox)
        Return If(cb.Checked, "TRUE", "FALSE")
    End If
    If c.HasControls() Then
        Dim sb As New StringBuilder()
        For Each child As Control In c.Controls
            Dim t As String = ExtractText(child)
            If Not String.IsNullOrEmpty(t) Then
                If sb.Length > 0 Then sb.Append(" ")
                sb.Append(t)
            End If
        Next
        If sb.Length > 0 Then Return sb.ToString()
    End If
    Return Nothing
End Function

PDF 出力で実務的に効く改善ポイント

  • 列幅の調整:列数が多い一覧は A4 横(Landscape)や列幅指定(table.SetWidths)を検討します。
  • 長文セルの折り返し:PdfPCell は基本的に折り返しますが、見栄えが悪いなら文字サイズを下げる/列幅を広げるなどで調整します。
  • TemplateField の値取得:cell.Text だけでは空になることが多いので、FindControl で個別に拾うか、上のような “掘って拾う” ヘルパーを用意します。
  • フォント問題:サーバーに対象フォントがないと例外になります。運用環境に合わせてフォント名を固定するか、配置するフォントを運用設計に組み込みます。

iTextSharp を DLL 参照追加で入れたい場合(NuGet を使わない補足)

すでにダウンロード済みの DLL を使いたい場合は、プロジェクトを右クリックして [参照の追加] から DLL を選ぶ方法もあります。ただし依存関係の管理やアップデートを考えると、基本は NuGet 管理のほうがトラブルが減ります。

DocumentFormat.OpenXml.Framework の参照エラーが出る場合の切り分け

「iTextSharp を入れて PDF 出力コードを組み込んだ後に、次のような参照エラーが出た」というケースがあります。

Reference required to assembly ‘DocumentFormat.OpenXml.Framework, Version=3.2.0.0, … containing the type ‘OpenXmlCompositeElement’

このエラーは、PDF 出力(iTextSharp)そのものというより、プロジェクト内のどこかで Open XML 系の型を参照している可能性が高い症状です。対処は大きく2つです。

Open XML SDK を使うなら:必要なパッケージ参照を追加する

Open XML を使うコードが存在する(または今後使う)なら、NuGet で追加します。

Install-Package DocumentFormat.OpenXml

加えて、古い参照が残っている場合は、参照一覧から古い DLL を外して、NuGet 側に統一するほうがバージョン衝突を避けやすいです。

Open XML を使うつもりがないなら:参照・Imports を削除する

  • プロジェクト内で DocumentFormat.OpenXml 名前空間を Imports(C# なら using)している箇所を探して削除
  • OpenXmlCompositeElement など Open XML 型を使っているクラス・機能が不要ならコメントアウト/削除

PDF/Excel エクスポートと無関係な参照が原因でビルドが止まっているだけ、ということも多いので、まずは「どのファイルが OpenXML 型を使っているか」を検索して切り分けるのが近道です。

よくあるトラブルと対策(チェック表)

症状原因の可能性対策
「GridView は server form 内でレンダリングされる必要があります」系の例外VerifyRenderingInServerForm の未オーバーライドページのコードビハインドに空実装を追加
Excel 出力が表示中のページ分しか出ないページング解除せずに Render しているAllowPaging=False → 再バインド → 出力
Excel が文字化けするエンコード指定不足、meta タグ無しUTF-8 指定+ meta 出力(必要に応じて BOM)
Excel で「拡張子と形式が一致しない」警告HTML を .xls として開いている方式の仕様許容する/本格的に .xlsx(Open XML 等)へ移行
PDF の日本語が □ になる日本語フォント未指定BaseFont を Identity-H で作成し、フォントを埋め込み
PDF/Excel のダウンロードが反応しない(特に UpdatePanel)非同期ポストバックになっているPostBackTrigger を付けてフルポストバックにする
TemplateField の値が空になるcell.Text では拾えない(中身がコントロール)FindControl で拾う/Controls を掘るヘルパーを作る
「Execute コマンドが見つからない」NuGet の説明文を含めて入力しているInstall-Package iTextSharp だけを入力

運用面で意識したいポイント

  • 大量データの一括出力:全件出力は便利ですが、件数が大きいとメモリや応答時間が急増します。業務要件として必要なら、上限件数・期間指定・非同期キュー化なども検討します。
  • 検索条件の一致:画面表示と同じ条件(絞り込み・並び順)で出力するには、エクスポート時に同じデータ取得ロジックを通すのが確実です。GridView の状態だけに依存すると差異が出やすくなります。
  • ファイル名:日本語ファイル名は環境差が出ることがあります。まずは ASCII の安全な名前で運用し、必要に応じて filename* を採用するのが堅実です。

まとめ

  • GridView の Excel 出力は、まずは HTML 方式(ContentType を Excel にして RenderControl)が最短ルートです。
  • PDF 出力は iTextSharp で PdfPTable を組み立てるのが基本で、日本語フォント指定が成功の鍵になります。
  • VerifyRenderingInServerForm のオーバーライド、ページング解除、UpdatePanel のトリガー設定など、WebForms 特有の罠を押さえるとトラブルが激減します。
  • DocumentFormat.OpenXml.Framework の参照エラーは PDF/Excel 出力そのものとは別件のことが多く、Open XML SDK の導入/不要参照の削除で切り分けます。

この記事を書いた人

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

コメント

コメントする

目次