Crystal Reports 是傳統 ASP.NET WebForms 專案常用的報表引擎。本文說明在 WebForm 中正確初始化 `ReportDocument`、設定資料來源、處理分頁/匯出、以及關鍵的資源釋放模式,避免記憶體洩漏與「報表文件過多」錯誤。
public partial class ReportViewerPage : System.Web.UI.Page
{
private ReportDocument _rptDocument;
protected void Page_Init(object sender, EventArgs e)
{
// 必須在 Page_Init 重新載入報表,確保 ViewState/Postback 正常
ConfigureCrystalReport();
}
protected void Page_Unload(object sender, EventArgs e)
{
// 關鍵:每次請求結束必須釋放 ReportDocument
if (_rptDocument != null)
{
_rptDocument.Close();
_rptDocument.Dispose();
_rptDocument = null;
}
}
private void ConfigureCrystalReport()
{
// 1. 解密查詢參數
string encryptedId = Request.QueryString["toid"];
string decrypted = BusinessObject.Util.Decrypt(encryptedId);
int tourOrderId = Util.ConvertTo<int>(decrypted, 0);
// 2. 檢查 ViewState 快取(避免重複載入 .rpt 檔與查詢資料)
if (ViewState["reportdoc"] == null)
{
// 3. 載入報表範本
string reportPath = Server.MapPath("~/Report/TourNote.rpt");
_rptDocument = new ReportDocument();
_rptDocument.Load(reportPath);
// 4. 取得資料集(含多張 Table)
DataSet ds = BusinessObject.TourOrders.GetTourNoteDsRpt(tourOrderId);
// 快取 DataSet 供 Postback 重用
ViewState["reportdata"] = ds;
// 5. 設定資料來源
_rptDocument.SetDataSource(ds);
// 6. 列印設定
_rptDocument.PrintOptions.PaperSize = PaperSize.DefaultPaperSize;
// 7. 快取 ReportDocument
ViewState["reportdoc"] = _rptDocument;
}
else
{
// Postback 時從 ViewState 還原
_rptDocument = (ReportDocument)ViewState["reportdoc"];
}
// 8. 綁定至 CrystalReportViewer
this.CrystalReportViewer1.ReportSource = _rptDocument;
// 選用:隱藏群組樹、工具列按鈕
this.CrystalReportViewer1.HasToggleGroupTreeButton = false;
this.CrystalReportViewer1.DisplayGroupTree = false;
}
}
| 優點 | 缺點 |
|---|---|
| 避免重複載入 .rpt 檔案 | ViewState 體積大(含報表定義與資料),增加頁面傳輸量 |
| Postback 保持報表狀態(分頁、排序) | 併發用戶多時伺服器記憶體壓力大 |
建議: 小型報表、低併發可用 ViewState;大型報表或高併發應改用 `Session` 或暫存檔快取,並設定適當逾期清理。
// 正確:使用強型別 DataSet 或 DataTable
_rptDocument.SetDataSource(typedDataSet);
// 或逐張表設定(報表包含多張不相關表時)
_rptDocument.Database.Tables["Table1"].SetDataSource(dt1);
_rptDocument.Database.Tables["Table2"].SetDataSource(dt2);
// 參數欄位設定
_rptDocument.SetParameterValue("ReportTitle", "月銷售報表");
_rptDocument.SetParameterValue("DateRange", $"{startDate:d} ~ {endDate:d}");
protected void btnExportPdf_Click(object sender, EventArgs e)
{
ConfigureCrystalReport(); // 確保報表已載入
Stream stream = _rptDocument.ExportToStream(ExportFormatType.PortableDocFormat);
Response.Clear();
Response.Buffer = true;
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", "attachment;filename=Report.pdf");
Response.BinaryWrite((stream as MemoryStream).ToArray());
Response.Flush();
Response.End();
}
protected void btnPrint_Click(object sender, EventArgs e)
{
ConfigureCrystalReport();
_rptDocument.PrintOptions.PrinterName = "\\\\PrintServer\\HP_LaserJet"; // 網路印表機
_rptDocument.PrintToPrinter(1, false, 0, 0); // 份數、雙面、起始頁、結束頁
}
private void ToggleSectionVisibility(string sectionName, bool visible)
{
var section = _rptDocument.ReportDefinition.Sections[sectionName];
if (section != null) section.SectionFormat.EnableSuppress = !visible;
}
<configSections>
<section name="crystalReport" type="CrystalDecisions.Shared.CrystalReportsSection, CrystalDecisions.Shared" />
</configSections>
<crystalReport>
<resourceUri /> <!-- 避免資源 URI 問題 -->
</crystalReport>
Crystal Reports 在 ASP.NET WebForms 中的正確使用關鍵在於:`Page_Init` 初始化、`Page_Unload` 確實釋放、資料來源正確綁定、部署環境 Runtime 版本一致。雖然功能強大,但資源佔用高、授權複雜,新專案建議評估現代化替代方案。