我喜歡 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
{
    public class Event
    {
        public string Year { get; set; }
        public string Description { get; set; }
    }
 
    public class MyHistoryStore
    {
        public static 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 使用說明請參考前文

    public partial class Report : System.Web.UI.Page
    {
        protected void 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。


Comments

# by Yang

試過蠻多次的 Available DataSet選單能出現Event,但是GetAllEvents()一直沒出現。 重新建置蠻多次也沒出現,真是神奇... 使用工具:vs 2015 ent、reportviewer 2010

# by Jeffrey

to Yang, 聽不出什麼頭緒,要不要分享範例專案讓大家幫看?

# by Yand

阿呀,將近一個月了,其實我以為過半年了。閒來沒事來回來看我的問題。 我剛試看還是沒有GetAllEvents(),作為替代使用Event,其他步驟都差不多,一樣有資料XD。 原本的構想是先從資料庫撈資料,再餵資料進去,結果發現報表精靈產生Adapter的方法都是獨立的。後來改用這樣餵去會比較方便ㄎㄎ。 報表功能摸索中。

# by HI

rptViewer 是什麼東西呢? 一直未定義

# by Jeffrey

to HI, 可參考這篇 https://blog.darkthread.net/blog/rdlc-in-vs2017/ 在 WebForm 中加上 ScriptManager 跟 rsweb:ReportViewer 控制項的說明。

Post a comment