背景
最近項目上遇到一個需求,要后台通過定時任務把水晶報表生成pdf文件,然后郵件發送給相關人。
技術實現思路
選用ASP.NET Core框架(基於2.2版本),通過IHostedService接口結合Quartz實現定時任務。但由於當前水晶報表SDK只支持Framework框架,所以ASP.NET Core選擇基於.net framework 4.7。關於在ASP.NET Core下整合Quartz定時任務功能,在博客園里已經與很多的技術貼,不再贅述,特別要說明的一定,由於默認IIS托管,會導致應用程序池回收問題,會導致定時任務退出,所以如果IIS托管,需要設置應用程序池的啟動模式的值為AlwaysRunning,閑置超時的值設置為0。
下面言歸正傳,重點說下如何使用水晶報表。
-
下載Crystal Reports For VS的開發包,地址:https://www.crystalreports.com/crvs/confirm/
-
獲取rpt水晶報表模板文件
-
編碼實現導出功能
編碼
首先需要引用兩個程序集:CrystalDecisions.CrystalReports.Engine``CrystalDecisions.Shared
封裝的主要代碼邏輯
using CrystalDecisions.CrystalReports.Engine;
using CrystalDecisions.Shared;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace CrontabService.Services.report
{
public class CrystalReportGenerator
{
private ReportDocument _rd;
private readonly ILogger<PurchaseNotifyMsgCreatorService> _logger;
public CrystalReportGenerator(string templatePath, ConnectionInfo connectionInfo, ILogger<PurchaseNotifyMsgCreatorService> logger)
{
_logger = logger;
TableLogOnInfo t = new TableLogOnInfo();
t.ConnectionInfo = connectionInfo;
_rd = new ReportDocument();
_rd.Load(templatePath);
foreach (Table table in _rd.Database.Tables)
{
table.ApplyLogOnInfo(t);
}
}
public string GenerateReport(Dictionary<string,object> paras,string reportFileName)
{
try
{
foreach (var kv in paras)
{
_rd.SetParameterValue(kv.Key, kv.Value);
}
var reportPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"report\" + reportFileName);
//導出為pdf格式
_rd.ExportToDisk(ExportFormatType.PortableDocFormat, reportPath);
return reportPath;
}
catch (Exception e)
{
_logger?.LogError(e, "GenerateReport error");
}
}
}
}
其中ConnectionInfo類里面主要是數據庫的信息,這個里面需要把報表中應用到的表重新應用下數據庫連接信息,否則導出的時候,會提示數據庫連接失敗的錯誤
由於水晶報表有兩種獲取數據源的兩種方式:push、pull,push就是主動編碼設置數據源,調用方法SetDataSource既可以了,pull就是報表根據模板中維護的數據源信息,自己到數據庫中拉去信息。
因為我這個是后台任務去生成報表,所以就沒有 reportview的控件,這個里面我就遇到了問題了,我開始一直通過SetDataSource的方式去給數據源,結果一執行ExportToDisk方法就throw exception,提示數據庫連接失敗,后來網上找到解決方法,讓用pull方式,定義好數據庫連接,並把報表模板的參數值設置好,執行ExportToDisk就成功了,這個我猜測可能直接通過ReportDocument去導出時,默認還是用的pull方式,由於不熟悉水晶報表的使用,還忘大神指點!
遇到的坑
上面順利在開發環境測試成功,等發布后,部署到IIS后,出現一堆問題!
- 出現無法加載log4net的異常錯誤,如下,一臉懵逼,怎么和log4net有關了,網上一頓研究,也有sap官方社區的回答,基本都是說編譯時的目標平台選擇有問題,應該選擇成X86,然后一頓狂試,無果,一直這個異常。
Could not load file or assembly ‘log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=692fbea5521e1304’ or one of its dependencies. The system cannot find the file specified
-
實在沒轍,找了個32位的log4net放到目錄中,咦,換了個異常,提示沒有CrystalReports 的runtime,此時才意識到,開發環境,按照sdk時已經默認安裝運行時,生產環境沒有,還是到這個https://www.crystalreports.com/crvs/confirm/這個網站下載SAP Crystal Reports runtime engine for .NET framework,進行安裝
-
信心滿滿的重啟,哎,奇跡還是沒有出現,又出現了新的異常,如下,空指針異常,my god,又是什么鬼,發現是執行
_rd.ExportToDisk時異常,但_rd並不為null啊。System.NullReferenceException: 未將對象引用設置到對象的實例。 在 CrystalDecisions.CrystalReports.Engine.FormatEngine.ExportToStream(ExportRequestContext reqContext) 在 CrystalDecisions.CrystalReports.Engine.FormatEngine.Export(ExportRequestContext reqContext) 在 CrystalDecisions.CrystalReports.Engine.ReportDocument.ExportToDisk(ExportFormatType formatType, String fileName) -
在一臉懵逼之際,自己神出鬼沒的取看了下運行時和sdk的版本
SDK版本:
CRforVS13SP25_0-10010309運行時版本:
CR13SP26MSI64_0-10010309
下意識的看到一個是 SP25 ,一個是 SP26 ,難度是因為版本不一致導致的?
試試吧,到官網下載了新的SDK版本 CRforVS13SP26_0-10010309安裝部署。
- 這次奇跡出現了,順利跑起來,生成了期待已久的報表文件!
總結
確認過眼神,要選對的人!~~~~~~~~
邏輯本身很簡單,部署過程一波三折,運行時,版本匹配一定要牢記!
最后,坑都是自己挖的,也是自己跳的,但自己再跳出來后,眼界可能有點不一樣了!
