当前位置: 首页 > news >正文

ABP VNext 报表:EPPlus DinkToPdf 多格式导出

ABP VNext 报表:EPPlus & DinkToPdf 多格式导出 🚀


📚 目录

  • ABP VNext 报表:EPPlus & DinkToPdf 多格式导出 🚀
    • 一、引言 ✨
    • 二、环境与依赖 ⚙️
    • 三、项目骨架 📁
      • 项目结构高层流程
    • 四、Excel 报表(EPPlus) 📊
      • 4.1 许可设置与性能考量
      • 4.2 构建 Excel 文件
    • 五、PDF 报表(DinkToPdf + Razor) 📄
      • 5.1 本机库部署与 DI 注册
      • 5.2 Razor 模板与虚拟文件系统
      • 5.3 渲染与返回
    • 六、生产级优化 🔧
    • 七、端到端流程 🔄


一、引言 ✨

TL;DR

  • 🚀 在 ABP VNext 中集成 EPPlus,使用 ExcelPackage.License.SetNonCommercialOrganization 设置 Polyform 非商业许可,避免调试时抛出 LicenseNotSetException
  • 📄 使用 SynchronizedConverter 单例注入 DinkToPdf,确保多线程环境下转换任务队列化执行,提高稳定性。
  • 🔄 结合 Volo.Abp.TextTemplating.Razor 渲染 Razor 模板,实现端到端 PDF 导出流程,模板作为嵌入资源管理。
  • 💡 分层设计:ApplicationService 仅负责数据与字节流生成,Controller 专责返回文件;引入分布式缓存、后台作业与日志,打造可复用、高可用方案。

二、环境与依赖 ⚙️

  • 平台:.NET 9 + ABP Framework 9.x
  • EPPlus 8.x:需设置商业或非商业许可
  ExcelPackage.License.SetNonCommercialOrganization("Your Organization");// 商业用途请使用 SetCommercial("<Your License Key>");
  • DinkToPdf.Core + DinkToPdf.Native.*:跨平台需部署对应 libwkhtmltox.* 本机库,避免运行时因找不到 DLL 报错。
  • Volo.Abp.TextTemplating.Razor:渲染 Razor 为字符串,需安装并在模块上声明依赖
  • Volo.Abp.AspNetCore.Mvc:MVC/Razor 支持

三、项目骨架 📁

├─ Modules/ReportModule/
│    ├─ Application/
│    │    ├─ ReportManager.cs
│    │    └─ ReportAppService.cs
│    ├─ Domain/
│    │    └─ ReportDto.cs
│    └─ Web/Controllers/
│         └─ ReportController.cs
├─ ReportsTemplates/
│    ├─ InvoiceTemplate.cshtml   (嵌入式资源 via VFS)
│    └─ Styles/report.css        (嵌入式资源 via VFS)
└─ Program.cs├ builder.Services.AddRazorPages();├ builder.Services.AddSingleton<IConverter>(new SynchronizedConverter(new PdfTools()));├ ExcelPackage.License.SetNonCommercialOrganization("Your Organization");└ Configure<AbpVirtualFileSystemOptions>(opt =>opt.FileSets.AddEmbedded<ReportModule>("YourNamespace.ReportsTemplates"));

说明:通过 ABP 的虚拟文件系统(VFS)将模板与样式打包为嵌入资源,避免发布时路径错漏。

项目结构高层流程

Program.cs 初始化
ReportManager 构建流
ReportAppService 返回 DTO
ReportController 返回文件
客户端浏览器 下载

四、Excel 报表(EPPlus) 📊

4.1 许可设置与性能考量

// Program.cs 顶部
ExcelPackage.License.SetNonCommercialOrganization("Your Organization");
  • EPPlus 8.x 在未设置许可时会抛出 LicenseNotSetException,非商业项目使用上述 Polyform Noncommercial 模式。

  • 海量数据场景:EPPlus 在内存中构建整个工作簿,导出百万行以上时易 OOM。推荐:

    • 拆批导出、分文件下载
    • 商业版 Streaming 模式或第三方流式库

💡Tips:可封装 IExcelPackageFactory,统一许可与模板配置,便于单元测试与维护。

4.2 构建 Excel 文件

public class ReportManager
{private readonly IRepository<Invoice, Guid> _invoiceRepo;public ReportManager(IRepository<Invoice, Guid> invoiceRepo) => _invoiceRepo = invoiceRepo;public async Task<Stream> BuildInvoiceExcelAsync(Guid invoiceId){var dto = await _invoiceRepo.GetAsync(invoiceId);using var package = new ExcelPackage();var sheet = package.Workbook.Worksheets.Add("Invoice");// 标题sheet.Cells[1,1,1,5].Merge = true;sheet.Cells[1,1].Value = "发票";sheet.Cells[1,1].Style.Font.Size = 16;sheet.Cells[1,1].Style.Font.Bold = true;// 表头var headers = new[]{ "商品","数量","单价","总价","备注" };for (int i = 0; i < headers.Length; i++){var cell = sheet.Cells[2, i+1];cell.Value = headers[i];cell.Style.Font.Bold = true;cell.Style.Border.BorderAround(ExcelBorderStyle.Thin);}// 数据行int row = 3;foreach (var item in dto.Items){sheet.Cells[row,1].Value = item.ProductName;sheet.Cells[row,2].Value = item.Quantity;sheet.Cells[row,3].Value = item.UnitPrice;sheet.Cells[row,4].Formula = $"B{row}*C{row}";sheet.Cells[row,5].Value = item.Remark;row++;}// 列宽自适应if (sheet.Dimension != null)sheet.Cells[sheet.Dimension.Address].AutoFitColumns();var stream = new MemoryStream();await package.SaveAsAsync(stream);stream.Position = 0;return stream;}
}

五、PDF 报表(DinkToPdf + Razor) 📄

5.1 本机库部署与 DI 注册

csproj 确保 wkhtmltox 文件复制到输出目录:

<ItemGroup><None Include="wkhtmltox\**\*.*"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></None>
</ItemGroup>

Program.cs 注入线程安全转换器:

builder.Services.AddSingleton<IConverter>(new SynchronizedConverter(new PdfTools()));

5.2 Razor 模板与虚拟文件系统

  1. 安装并依赖包:

    abp add-package Volo.Abp.TextTemplating.Razor
    
  2. 在模块类添加依赖:

    [DependsOn(typeof(AbpTextTemplatingRazorModule))]
    public class ReportModule : AbpModule { }
    
  3. InvoiceTemplate.cshtml(嵌入式资源)示例:

@model InvoiceDto
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<InvoiceDto>
<!DOCTYPE html>
<html>
<head><link href="Styles/report.css" rel="stylesheet" />
</head>
<body><header><h1>@L["Invoice"] - @Model.InvoiceNo</h1></header><table></table><footer>@L["PrintDate"]: @DateTime.Now:yyyy-MM-dd | @L["Page"]: [page]/[toPage]</footer>
</body>
</html>

5.3 渲染与返回

public class ReportManager
{// 注入 IConverter 与 IRazorRendererpublic async Task<byte[]> BuildInvoicePdfAsync(Guid invoiceId){var dto = await _invoiceRepo.GetAsync(invoiceId);var html = await _razorRenderer.RenderAsync("ReportsTemplates/InvoiceTemplate", dto);var doc = new HtmlToPdfDocument{GlobalSettings = {Orientation = Orientation.Portrait,PaperSize   = PaperKind.A4,Margins     = new MarginSettings { Top = 20, Bottom = 20 }},Objects = {new ObjectSettings {HtmlContent   = html,WebSettings   = { DefaultEncoding = "utf-8" },HeaderSettings= { Center = "发票", FontSize=9 },FooterSettings= { Right  = "[page]/[toPage]", FontSize=9 }}}};return _converter.Convert(doc);}
}

💡Tips:确保静态资源(CSS/图片)可由 wkhtmltopdf 加载,可使用嵌入资源或绝对 URL。


六、生产级优化 🔧

  • 分布式缓存:使用 ABP 扩展的 IDistributedCache(Redis 实现)缓存热点报表,减少重复生成。
  • 后台作业预生成:结合 ABP Background Jobs 模块异步预渲染大型报表,用户获取下载链接后再下载。
  • 异常与日志:在 ReportManager 内部捕获关键异常,利用 ILogger<ReportManager> 记录堆栈与上下文。
  • 单元测试:将 Excel/PDF 生成逻辑拆分为独立服务,编写针对空数据、超大数据与异常场景的单元测试,确保兼容性与稳定性。
  • 安全与输入校验:Razor 默认会对所有变量输出进行 HTML 编码,避免使用 Html.Raw 渲染未验证的用户输入;结合数据注解与输出编码防 XSS。
  • 容器化与监控:在 ASP.NET Core 中集成 OpenTelemetry + Prometheus,暴露 /metrics 端点,并利用 Grafana 可视化监控,确保报表服务的可观测性与健壮性。

七、端到端流程 🔄

Excel
PDF
数据库实体
ReportManager
格式
EPPlus → MemoryStream
Razor → HTML
DinkToPdf → byte[]
ReportAppService
ReportController → FileResult
  1. 启动应用 → 登录 → 访问 /reports
  2. 点击“导出 Excel”或“生成 PDF”
  3. 浏览器下载并打开,验证分页、样式、页眉/页脚 🎉

http://www.dtcms.com/a/292614.html

相关文章:

  • redis秒杀之lua脚本
  • 20250722解决在Ubuntu 24.04.2下编译RD-RK3588开发板的Android13出现找不到python2的问题
  • GraphRAG的部署和生成检索过程体验
  • C++11--锁分析
  • npm全局安装后,依然不是内部或外部命令,也不是可运行的程序或批处理文件
  • 大数据量查询计算引发数据库CPU告警问题复盘
  • 使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第二十二讲)
  • Linux_Ext系列文件系统基本认识(一)
  • Product Hunt 每日热榜 | 2025-07-22
  • “鱼书”深度学习入门 笔记(1)前四章内容
  • day19 链表
  • 【科研绘图系列】R语言绘制柱状堆积图
  • 基于 Vue,SPringBoot开发的新能源充电桩的系统
  • 豪鹏科技锚定 “AI + 固态” 赛道:从电池制造商到核心能源方案引领者的战略跃迁
  • mybatis拦截器实现唯一索引的动态配置
  • 网络基础DAY16-MSTP-VRRP
  • git reset --soft和 git reset --mixed的主要区别
  • 智能制造——解读制造业企业数字化转型实施指南2025【附全文阅读】
  • libgmp库(GNU高精度算术库)介绍
  • 算法训练营day28 贪心算法②122.买卖股票的最佳时机II、55. 跳跃游戏、 45.跳跃游戏II 、1005.K次取反后最大化的数组和
  • Web服务器(Tomcat、项目部署)
  • 0722 数据结构顺序表
  • 循环神经网络--NLP基础
  • <另一种思维:语言模型如何展现人类的时间认知>总结
  • 大型语言模型(Large Language Models,LLM)
  • Science Robotics 机器人成功自主完成猪胆囊切除手术
  • vue3 动态判断 el-table列 用 v-if 是否显示
  • 微算法科技(NASDAQ: MLGO)探索优化量子纠错算法,提升量子算法准确性
  • 4.组合式API知识点(2)
  • 计算机视觉领域的AI算法总结——目标检测