我喜歡 RDLC勝過 Report Server 報表的原因之一是報表資料來源不限定來自資料庫,可以是自己組裝的 DataTable, 甚至是自訂資料物件,具有無比的應用彈性。這篇文章用一個極簡的範例,展示如何使用 List<T> 當成 RDLC 資料來源。
我用中國重大歷史事件一覽表當素材:

先設計一個包含 Year 與 Description 屬性的 Event 類別代表每個歷史事件,寫一個 MyHistoryStore 透過 GetAllEvents() 解析文字檔吐回 List<Event>。這裡有個重點,傳回結果型別必須是 IEnumerable(T[]、List<T> 都算),RDLC 才會視為可用的資料來源。(參考:To be accessible as a data source, a class must expose a method or property that returns an IEnumerable. You can add a class or a reference to the assembly for a class to your project.)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Web.Hosting;
namespace RDLCTest
{
publicclass Event
{
publicstring Year { get; set; }
publicstring Description { get; set; }
}
publicclass MyHistoryStore
{
publicstatic List<Event> GetAllEvents()
{
using (var sr = new StreamReader(
HostingEnvironment.MapPath("~/App_Data/history.txt")))
{
string line;
List<Event> list = new List<Event>();
while ((line = sr.ReadLine()) != null)
{
var i = line.IndexOf(",");
if (i > 0)
{
list.Add(new Event()
{
Year = line.Substring(0, i),
Description = line.Substring(i + 1)
});
}
}
return list.ToList();
}
}
}
}
上述物件寫好記得先編譯一次,報表設計精靈才能從 DLL 中找出可用的資料屬性或方法。接著我們在專案中新増「Report Wizard」(開 Report 純手工改 RDLC XML 硬幹也成,但非鐵血硬漢勿試):

使用 Visual Studio 2017 新増 Report Wizard 時可能跳出下警告,只要你的 Report Designer 不是來路不明,就信任吧!

進入 Report Wizard 的 DataSet 屬性設定介面,先輸入 DataSet 名稱(本例用 HistoryDataSource,這個名字要記下來等等會用到),在 Data Source 下拉選單選取專案 NameSpace(本例為RDLCTest),接著 Available datasets 下拉選單應可找到剛才寫好的 MyHistoryStore(GetAllEvents) (記得在此步驟前專案要先編譯過,不然會找不到),選擇後右方會出現 Year、Description 欄位資料,代表 Wizard 已正確找到方法並識別出資料物件屬性。

接下來的操作與一般 RDLC 報表設計相同,在此不多贅述。

提示幾個設計報表要注意的小地方。
下圖白色部分是 Body 的範圍,要設定整張報表設定,要在白色範圍之外按右鍵,選「Report Properties」。

如果報表打算列印出來,就要留意紙張的尺寸、邊距。

欄位寬度總和記得不要超出紙張寬度減去邊距,不然每頁會印成兩張,實務上我習慣直接敲尺寸數字,比憑直覺拖拉精準。

Server 端的寫法很簡單,指定 ReportPath、DataSources.Add() 一個 ReportDataSource,建構時傳入 DataSet 名稱與 IEnumerable 就搞定了。這裡的 DataSet 名稱要輸入我們在 Report Wizard 一開始設定 DataSource 時給的名字-HistoryDataSource,若名字不符會產生「尚未提供資料來源 'HistoryDataSource' 的資料來源執行個體。」錯誤。(關於 ReportViewer 使用說明請參考前文)
publicpartialclass Report : System.Web.UI.Page
{
protectedvoid Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
rptViewer.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local;
rptViewer.PageCountMode = Microsoft.Reporting.WebForms.PageCountMode.Actual;
rptViewer.LocalReport.ReportPath = Server.MapPath("~/ObjDataSrcReport.rdlc");
rptViewer.LocalReport.DataSources.Add(
new Microsoft.Reporting.WebForms.ReportDataSource("HistoryDataSource",
MyHistoryStore.GetAllEvents()));
}
}
}
薑!薑!薑!薑~ 搞定收工。

最後補充,PageCountMode預設為 Estimate,頁數會顯示成 1 of 2?、2 of 3?,看不出實際總頁數。要設定為 Actual 才會顯示正確總頁數,Estimate 模式能避免某些情境下為取得總頁數拖累效能,本案例為記憶體中的資料物件無此疑慮,可放心設成 Actual。