XYS.FRCore
4.3.0
dotnet add package XYS.FRCore --version 4.3.0
NuGet\Install-Package XYS.FRCore -Version 4.3.0
<PackageReference Include="XYS.FRCore" Version="4.3.0" />
<PackageVersion Include="XYS.FRCore" Version="4.3.0" />
<PackageReference Include="XYS.FRCore" />
paket add XYS.FRCore --version 4.3.0
#r "nuget: XYS.FRCore, 4.3.0"
#:package XYS.FRCore@4.3.0
#addin nuget:?package=XYS.FRCore&version=4.3.0
#tool nuget:?package=XYS.FRCore&version=4.3.0
XYS.FRCore
FastReport.Core 渲染宿主。只负责:拿 XYS.FR(XYS.FR.V3 项目)准备好的 DataSet 与模板路径,调用 FastReport.Core 引擎导出 PDF/JPG/HTML/XLSX 流。不含数据契约与配置解析(实体注册、FRTables.frd 生成、模板号映射都在 XYS.FR 里),也不含打印(见下)。
- 目标框架:
net462/netstandard2.0/netcoreapp3.1/net8.0 - 报表引擎:
FastReport.Core,版本按目标框架分流(见下) - 依赖:
XYS.FR.V3(传递带入XYS.Utils.Sys、XYS.Utils.Log4Net) - 程序集名:
XYS.FRCore;命名空间XYS.FR
⚠️ 当前引用的是 Demo 版 FastReport 包,导出结果带演示水印、有页数限制。正式部署需替换为已授权的
FastReport.Core包(仅需改PackageReference,代码无需改动)。
与 XYS.FRNet 的分工
| XYS.FRCore(本项目) | XYS.FRNet | |
|---|---|---|
| 引擎 | FastReport.Core(跨平台版) |
FastReport.Net(完整版) |
| 目标框架 | net462 / netstandard2.0 / netcoreapp3.1 / net8.0 | net462 / net8.0-windows |
| 导出流 | ✅ | ✅ |
| 直接打印 | ❌ 不实现 IFRPrintService |
✅ 实现 IFRPrintService |
两者的服务基类 API 形状刻意保持一致(ExportStream + ConfigureExport + ExportType 分发),便于在两个引擎间迁移。
不提供打印
BaseFRCoreService 不实现 IFRPrintService,本项目里根本没有 Print 方法。
原因:FastReport.Core 是跨平台版,不含任何打印 API —— 已核实 2023.3.13 版中 Report.Print()、Report.PrintPrepared()、PrintSettings 类型全部不存在(这些依赖 System.Drawing.Printing / WinForms)。
打印须由宿主完成:先用 ExportStream 导出 PDF,再交给宿主的打印组件。本解决方案中由 XYS.FRCore.PrintWinService 经 PrintIt.Core(Pdfium)打印。若需要 FastReport 直接打印,请改用 XYS.FRNet。
4.0.0 时
IFRService/BaseFRService的抽象成员,本项目只能"实现了但恒抛NotSupportedException",属 Liskov 违例。XYS.FR4.1.0 把IFRService拆成IFRExportService+IFRPrintService后,本项目直接不声明打印接口,该违例已消除。
核心类型
| 类型 | 职责 |
|---|---|
BaseFRCoreService |
抽象渲染服务基类,实现 BaseFRService 的 ExportStream |
BaseFRCoreService 是本项目唯一的公开类型。
快速使用
using System;
using System.Collections.Generic;
using System.IO;
using XYS.FR;
public sealed class LabExportService : BaseFRCoreService
{
public LabExportService()
{
LoggerName = "LabExportService";
ActualType = typeof(LabExportService);
Init(); // 注册实体 + 生成 frd + 加载配置,放在构造函数末尾
}
protected override List<Type> GetFRTypes() => new List<Type>
{
typeof(FRInfo),
typeof(FRItem),
};
protected override void InitFRConfig()
{
var cfg = new XmlConfigManager(); // 默认 AppBase/Config/fr.config.xml
cfg.Init(); // 失败抛异常,视为致命错误
ConfigManager = cfg;
}
}
var svc = new LabExportService();
var report = new FRReport { ModelNo = "LAB001", RID = "R123" };
report.Items.Add(new FRInfo { ID = "1", RID = "R123", PatName = "张三" });
using (var fs = File.Create("out.pdf"))
{
svc.ExportStream(report, fs, ExportType.Pdf);
}
导出格式映射
ExportType |
导出器 | 默认设置(ConfigureExport) |
|---|---|---|
Pdf |
PDFExport |
HideWindowUI=true、EmbeddingFonts=true、AllowModify=false |
Jpg |
ImageExport(ImageFormat=Jpeg) |
SeparateFiles=false |
Html |
HTMLExport |
SinglePage=true、EmbedPictures=true、Navigator=false |
Xls |
Excel2007Export |
无 |
默认设置只保证单流输出这一底线语义:图片/HTML 导出若不关掉分文件,FastReport 只会把首页写进 Stream,其余页和图片会散落成流外的独立文件。
重写 ConfigureExport 可定制签名、分辨率、压缩等,建议先调 base。
关键契约
| 成员 | 语义 |
|---|---|
ExportStream(FRReport, Stream, ExportType) |
渲染到调用方提供的流;不负责创建或释放该流 |
ExportStreamAsync(FRReport, Stream, ExportType, CancellationToken) |
同上,可取消;排队等待限流的过程一定可取消,渲染阶段视 TFM 而定(见下) |
Print |
不存在:本类不实现 IFRPrintService,见上文 |
ConfigureExport(ExportBase, ExportType) |
virtual 扩展点 |
MaxConcurrentRenders(static) |
并发渲染上限,默认 Min(核数, 4);见「并发限流」 |
| 异常 | 参数为 null 抛 ArgumentNullException;未知 ExportType 抛 ArgumentOutOfRangeException;取消抛 OperationCanceledException;渲染异常记日志后原样上抛(保留堆栈) |
ExportStream 无状态:每次调用独立取空数据集副本、独立 new Report(),同一实例可并发调用(并发度受 MaxConcurrentRenders 限制)。
并发限流
BaseFRCoreService 内置一个进程级 SemaphoreSlim,默认放行 Min(Environment.ProcessorCount, 4) 个并发渲染。
限流的理由是防塌,不是控吞吐。 2026-09-04 实测(FastReport 2025.2.8-demo,32 核,三轮一致):
| 线程数 | 1 | 2 | 4 | 8 | 16 |
|---|---|---|---|---|---|
| 相对单线程吞吐 | 1.00x | 1.6–1.9x | 2.0–2.4x | 1.8–2.2x | 0.39–0.48x |
三个结论:引擎只能并行到 2 倍上下,4 线程就到顶,堆核没用;超订会塌,16 线程比单线程还慢一倍以上,且是"越忙越慢"的正反馈,一次并发高峰就能把渲染打进这个区间再也出不来;单份约 13 ms。
服务端跑在 ASP.NET 里,请求并发度由外部流量决定,没有限流就等于把渲染暴露在任意并发下。
本次改动后在同一台 32 核机器上复测(限流生效):
| 场景 | 吞吐 |
|---|---|
| 4 线程 | 171.9 份/秒 |
| 16 线程(上限 4) | 193.4 份/秒 |
| 16 线程(上限调到 16,等于不限流) | 110.6 份/秒 = 前者的 0.57x |
即:限流后 16 线程不再掉进塌陷区,而把闸门打开就立刻重现了文档里的塌陷。
调整上限:
BaseFRCoreService.MaxConcurrentRenders = 8; //宿主启动时设定一次
- 信号量必须是 static——竞争在 FastReport 引擎内部,是进程级的。每个服务实例各持一个等于没限,宿主注册多个渲染服务时尤其明显。
- 调整上限会重建信号量,只对调整之后进入的渲染生效;已在途的那几份仍按取得时的信号量释放,因此切换瞬间在途数可能短暂超过新上限。
- 客户端侧(
XYS.FRNet)不需要限流——单用户交互,不存在并发风暴。
异步与取消
await service.ExportStreamAsync(report, stream, ExportType.Pdf, ct);
| 阶段 | 能否被 ct 中断 |
|---|---|
| 排队等待限流 | 能(全部 TFM) |
渲染(Prepare) |
net462 / net8.0 能(Report.PrepareAsync(ct));netcoreapp3.1 / netstandard2.0 不能 |
导出(Export) |
不能(FastReport 无异步导出 API) |
netcoreapp3.1 / netstandard2.0 用的 FastReport.Core 2023.3.13 没有 PrepareAsync(2025.2.8 才有),只能退回"丢弃结果式取消"。这个差异由 csproj 里的 FR_PREPARE_ASYNC 编译开关控制,它与 FastReport 版本分流写在同一处条件,改版本时不会漏改。
FastReport.Core 版本分流
FastReport.Core 2025.2.8-demo 只提供 net462 / net6.0 / net6.0-windows7.0,没有 netcoreapp3.1 与 netstandard2.0 的 lib,因此不能统一到单一版本:
| 目标框架 | FastReport.Core |
|---|---|
net462、net8.0 |
2025.2.8-demo |
netcoreapp3.1、netstandard2.0 |
2023.3.13-demo |
csproj 中另显式钉住了 FastReport.Compat 2025.2.8 与 FastReport.DataVisualization 2025.2.8。原因:FastReport.Core 2025.2.8-demo 的 nuspec 声明依赖这两个包的 2025.2.8-demo 版本,但它们不存在 -demo 后缀的版本,NuGet 只能回退推断到稳定版并对每个 TFM 报 NU1603。显式钉版后告警消失,而实际解析结果不变。
改动本项目的 TargetFrameworks 时,务必同步检查所选 FastReport.Core 版本是否提供对应的 lib。
4.3.0 变更(相对 4.2.0)
- 模板改走
BaseFRService.OpenModel取内容流(不再拼磁盘路径),模板可来自对象存储;日志里的模板标识随之改为"编号(文件名)" - 新增
ExportStreamAsync,重写基类默认实现:net462/net8.0用Report.PrepareAsync(ct)做真取消 - 新增进程级并发限流
MaxConcurrentRenders,默认Min(核数, 4) - 静态构造补齐脚本沙箱:
EnableScriptSecurity+ForbidLocalData(WebMode本就已开) - csproj 新增
FR_PREPARE_ASYNC编译开关,与 FastReport 版本分流同处一地
关于安全开关
模板将来由实施和客户编辑,也就是不可信输入;FastReport 模板能内嵌脚本,脚本可执行就等于客户能把代码送进服务端进程。
实测(FastReport.Core 2025.2.8-demo,模板内嵌 File.WriteAllText):
| 配置 | 结果 |
|---|---|
| 全关 | 脚本照跑,文件真的写出来了 |
只开 EnableScriptSecurity |
脚本照跑(官方摘要 "For WebMode only" 属实) |
开 WebMode |
编译期即被拒:CS0117: Please, don't use the method 'WriteAllText' |
所以真正起作用的是 WebMode,本项目原本就开着。EnableScriptSecurity 在其上追加禁用名单,默认为 DllImport / LoadLibrary / GetProcAddress(即 P/Invoke 出逃这一路),开启时由 FastReport 自动填充,无须再调 ScriptSecurityProps.SetDefaultStopList()。
ForbidLocalData 的官方摘要是"不允许在 Xml 和 Csv 中指定本地数据路径"——它只管 Xml/Csv 的本地数据路径,挡不住模板内嵌的数据库连接。这只是一层,不是全部;真正的保障是渲染入口只吃 FRReport.Items 传进来的数据。
4.2.0 变更(相对 4.1.0)
- 新增目标框架
net8.0 net462与net8.0的 FastReport.Core 升到 2025.2.8-demo;netcoreapp3.1与netstandard2.0保持 2023.3.13-demo(该版本无这两个 TFM 的 lib)- 四个按 TFM 拆分的条件
ItemGroup合并为两个 - 显式钉住
FastReport.Compat/FastReport.DataVisualization稳定版,消除 16 条NU1603
4.1.0 变更(相对 4.0.0)
破坏性
- 跟随
XYS.FR4.1.0 的接口拆分,BaseFRCoreService.Print(...)整块删除。该方法在 4.0.0 中恒抛NotSupportedException,从无可用调用方。 - 本类现在只实现
IFRExportService(经BaseFRService),不声明IFRPrintService。
4.0.0 变更(相对 3.0.1)
破坏性
BaseStreamService更名为BaseFRCoreService,与XYS.FRNet的BaseFRNetService对称- 泛型导出方法
ExportStream<T>删除,改为按ExportType枚举分发的ExportStream(FRReport, Stream, ExportType) protected abstract void SetExport(ExportBase)→protected virtual void ConfigureExport(ExportBase, ExportType),由abstract改virtual- 依赖由
FastReport.Core 2021.4.16-demo升到2023.3.13-demo;目标框架netstandard2.1换为netcoreapp3.1(与该包实际提供的 TFM 一致) - 由 NuGet
XYS.FR改为ProjectReference ..\XYS.FR.V3,与解决方案统一
修复
- 项目此前无法编译:
ExportType.pdf大小写未跟随XYS.FR4.0.0 的 PascalCase 改名(CS0117);ExportStream缺override隐藏基类抽象成员(CS0533);方法体残留已删除的泛型参数new T() throw ex;丢失堆栈:改为throw;Config.WebMode每次导出都重设:它是进程级全局配置,移入静态构造函数只设一次catch块内可能二次抛 NRE:日志用ActualType.FullName,子类未设ActualType时会 NRE 并掩盖真实异常,现回退到运行时类型名- 图片/HTML 导出丢页:单
Stream输出下未关SeparateFiles/ 未开EmbedPictures,只有首页进流
清理
MethodBase.GetCurrentMethod().Name反射取名 →nameof- README 从残桩重写(原文写"FastReport Core 2021 版本集成",实际引用 2023.3.13)
注意事项
- 多目标下语言版本不一致:
net462/netstandard2.0默认 C# 7.3,netcoreapp3.1默认 C# 8。本项目代码按 C# 7.3 编写,刻意避开 switch 表达式等 C# 8+ 语法。 - 完整版 FastReport .NET 的
ReportSettings.ShowProgress/ShowPerformance是 WinForms 专属属性,Core 版的ReportSettings不含它们(Core 无进度 UI,本就无需关闭)。 Init()放在子类构造函数末尾。- 同进程内允许多个子类共存,共享
XYS.FR的DataStruct实体注册表。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.1 is compatible. |
| .NET Framework | net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
-
.NETCoreApp 3.1
- FastReport.Core (>= 2023.3.13-demo)
- XYS.FR (>= 4.2.0)
-
.NETFramework 4.6.2
- FastReport.Compat (>= 2025.2.8)
- FastReport.Core (>= 2025.2.8-demo)
- FastReport.DataVisualization (>= 2025.2.8)
- XYS.FR (>= 4.2.0)
-
net8.0
- FastReport.Compat (>= 2025.2.8)
- FastReport.Core (>= 2025.2.8-demo)
- FastReport.DataVisualization (>= 2025.2.8)
- XYS.FR (>= 4.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.