资讯动态

.NET Core 实战:Web API、后台任务与PDF处理五大案例

发布时间:2026/9/17 23:17:55 来源:尧图企业网站定制
简介《.Net Core开发五大案例详解》是一份面向初中级.NET/ASP.NET Core开发者的实战教程覆盖学生信息管理系统、商城系统、校园图书管理系统、WPF桌面端与WebAPI五大完整案例并结合.NET 6.0、C#、Vue3、Element UI、SQL Server等主流技术栈。整册约30万字、400页提供1个PDF文件11.68MB图文与代码结合紧密适合希望从零搭建完整项目的读者系统学习。资源围绕真实业务场景展开从环境配置、登录鉴权到数据库交互、Entity Framework Core操作再到前端UI集成和API接口开发均有分步讲解与源码思路。尤其针对MVC、WPFPrism、WebAPI等架构模式给出可复用的实现方案帮助读者理解项目骨架并快速迁移到实际工作中。目前已有294人学习下载是一份兼具广度和深度的.NET Core进阶参考资料。1. 为什么拿“五大案例”学 .NET Core作为工程师回头看.NET Core 最反常的地方在于“语法容易落地难”。网上教程能让你写完 DTO、Controller 和 EF Core 查询但真到项目里连接串放在哪、PDF 解析放在哪个项目、后台任务为什么卡死、网页打印出来的 PDF 中文为什么全是方块这些才是把编码经验变成工程能力的分水岭。我习惯把这类问题压缩成五个案例一套推理能复用到 80% 的后续项目。第一个案例做 Web API EF Core解决“接口和数据怎么串起来”第二个案例写 Worker Service解决“耗时的 PDF 解析放哪”第三个案例讲 PDF 生成和网页打印第四个案例做 PDF 文件监控转换顺手把发布部署的细节讲清楚第五个案例是把前面内容整理成可检索的 PDF 手册。合起来看是一条“请求进来、文件出去、文档留下来”的完整链路。这个过程不适合只看不敲的人。下面的每一章都会先给结论再给最小可运行的代码最后把错误和排错节点单独拎出来说。命令和参数尽量保持可以直接复制执行的状态。2. 案例一用 ASP.NET Core Web API EF Core 搭一个订单查询服务2.1 为什么第一个案例要选“控制器 EF Core”而不是 Minimal API新项目里 Minimal API 很常见代码短、启动快但把它当第一个案例并不合适。控制器的价值在于把路由、模型绑定、返回格式这三件事的边界划得很清楚EF Core 的导航属性、状态跟踪又恰好需要“控制器方法 → DbContext → SQL”这样一层一层地看。订单查询只有一张表、一个条件没有分页和复杂报表正好能把注意力集中在链路本身HTTP 请求进来、路由命中、模型绑定、EF Core 生成 SQL、数据库返回。如果上来就用 Minimal API所有逻辑挤在MapGet一行里代码是短了但查询条件和返回逻辑耦合在一起遇到异常时连在哪一层挂掉的都看不出来。先写控制器再回去看 Minimal API两者差别会非常直观。2.2 从 dotnet new 到第一个可运行的最小代码先建项目并装上 EF Core 的 SQL Server 包dotnet new webapi -n OrderApi cd OrderApi dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.ToolsProgram.cs要做的事只有四件注册控制器、注册 DbContext、把连接字符串交给 EF Core、映射路由。var builder WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddDbContextOrderDbContext(options options.UseSqlServer( builder.Configuration.GetConnectionString(OrderDb), sql sql.EnableRetryOnFailure(6, TimeSpan.FromSeconds(5), null))); var app builder.Build(); app.MapControllers(); app.Run();OrderController里只放一个查询动作避免作用太散[ApiController] [Route(api/[controller])] public class OrderController : ControllerBase { private readonly OrderDbContext _db; public OrderController(OrderDbContext db) _db db; [HttpGet({orderNo})] public async TaskActionResultOrder Get(string orderNo) { var order await _db.Orders .AsNoTracking() .FirstOrDefaultAsync(o o.OrderNo orderNo); return order ?? (ActionResultOrder)NotFound(); } }代码里的EnableRetryOnFailure是 EF Core 对 SQL Server 提供的瞬态故障重试机制三个参数分别是最大重试次数、单次重试间隔、要额外识别的错误码。生产环境建议打开它能把网络抖动导致的偶发连接失败挡在外面。AsNoTracking()表示这次查询不需要跟踪实体状态读多写少时能省下不少内存分配控制器的 show_status 也会更简单。2.3 连接字符串五个配置项与对应坑appsettings.json里我常用这样一个最小连接串Serverlocalhost,1433;DatabaseOrderDb;User Idsa;Password***;TrustServerCertificateTrue;EncryptTrue配置项典型值说明与常见坑Serverlocalhost,1433容器里要写成服务名或 IP写 localhost 会连到容器自身DatabaseOrderDb与 DbContext 指定的一致大小写敏感度随数据库排序规则而定TrustServerCertificateTrue本地没有正式证书时必须为 True否则报证书链错误EncryptTrue云端数据库要求加密本地测试连不上时先看这个值Password强密码不要提交进 Git用环境变量或 User Secrets 覆盖本地开发遇到 “certificate” 或 “SSL” 关键字八成是TrustServerCertificate没设。遇到HostNotFound或 “network-related”先检查Server是不是被写成了数据库实例名以及 SQL Server 服务有没有真的启动。2.4 跑起来之后怎么验证接口和 EF Core SQLdotnet run curl -i http://localhost:5000/api/order/20240301-001收到200和 JSON 说明链路通了404说明库里没有这个单号500则去控制台看完整异常栈重点分辨是连接问题、SQL 语法问题还是模型绑定问题。想看到 EF Core 生成的 SQL把日志级别调到Information{ Logging: { LogLevel: { Default: Information, Microsoft.EntityFrameworkCore: Information } } }控制台会打印每条查询的 SQL 和执行耗时。这一步很值得做它能直接告诉你条件是拼在数据库端还是内存端也能较早发现 N1 查询的苗头。3. 案例二用 Worker Service 做后台 PDF 解析批处理3.1 解析 PDF 为什么不能塞进 Web API 控制器把 PDF 解析逻辑写进控制器遇到的第一个问题是超时。一份十几页的 PDF 光是抽取文本就要一两秒遇到扫描件还要走图像识别请求会在网关层直接断掉。更难处理的是重试解析失败要重试重试逻辑如果和接口返回逻辑混在一起控制器就会被异常处理和状态判断塞满。常见做法是把这类耗时任务单独拆成一个 Worker Service 项目输入用消息队列或共享目录解析完再把结果写回数据库。3.2 最小 Worker Service 的骨架新建一个后台任务项目并引入 PDF 解析库dotnet new worker -n PdfParseWorker cd PdfParseWorker dotnet add package UglyToad.PdfPigPdfPig 适合做文本层抽取如果要把 PDF 页面渲染成图像再识别需要另外用 PDFium 或 OCR 组件。先让 Worker 跑通“取文件、解析、落库”三步public class PdfParseWorker : BackgroundService { private readonly ILoggerPdfParseWorker _logger; private readonly IFileQueue _fileQueue; public PdfParseWorker(ILoggerPdfParseWorker logger, IFileQueue fileQueue) { _logger logger; _fileQueue fileQueue; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { string file await _fileQueue.DequeueAsync(stoppingToken); using var document PdfDocument.Open(file); string text string.Join(\n, document.GetPages().Select(page page.Text)); await SaveTextAsync(file, text, stoppingToken); } } }BackgroundService是 .NET Core 提供的后台任务基类ExecuteAsync在应用启动时执行stoppingToken在服务停止时触发。PdfDocument.Open是惰性加载不会一次性把整份 PDF 读进内存所以大文件也能较快打开。page.Text提取的是文本层内容扫描件拿到的会是空字符串这一点要格外留意。3.3 解析 PDF 时最容易被忽视的三个参数参数作用不设置时的现象文件密码打开加密 PDF使用PdfDocument.Open(file, password)抛 “Incorrect password”页面范围只取需要的页用GetPages().Skip(3).Take(2)全量解析耗时成倍增加字体与编码回退未嵌入字体的 PDF 文本层会出现乱码文本内容不可用只能退回图像 OCR文本抽取结果为空时常见做法是转图片 OCR。.NET Core 下可以先把 PDF 页面渲染成图片再用 Tesseract 一类引擎识别中文需要额外语言包识别效果受 DPI 影响明显一般要设在 200 到 300 之间。注意 OCR 是 CPU 密集型操作不建议在 API 请求线程里同步执行。3.4 失败重试与并发控制后台任务没有请求上下文兜底异常必须自己处理。我一般会在循环里包一层 try-catch把失败文件和异常栈写进日志表再配合重试计数private async Task ProcessWithRetryAsync(string file, int maxRetry 3) { for (int i 0; i maxRetry; i) { try { await ParseAndSaveAsync(file); return; } catch (Exception ex) when (i maxRetry - 1) { _logger.LogWarning(ex, 解析失败第 {Retry} 次重试, i 1); } } }并发方面PdfDocument.Open不是线程安全的多个 Worker 并行解析时要限制并发数。一个可靠的做法是用SemaphoreSlim把并行度控制在 CPU 核心数到 4 之间。解析 PDF 是 CPU 密集任务并发设成几十个不会更快反而可能先把内存打满。4. 案例三在 .NET Core 里生成 PDF 并接入网页打印4.1 三条路径先看需求再选PDF 生成有几种完全不同的做法选错方向会白费很多功夫。直接绘制画布适合发票、合同这类排版固定的场景常见库是 QuestPDF把 HTML 模板交给无头浏览器渲染适合已经有 Web 页面、想让 PDF 和页面保持同一套样式的场景常见做法是 PuppeteerSharp还有种是“网页 PDF 打印”后端只输出 HTML用户在浏览器里用打印功能另存为 PDF适合交互完成后由用户主动触发的场景。路径典型场景实现方式直接绘制发票、固定报表QuestPDF 等库HTML 渲染需复用 Web 模板PuppeteerSharp 无头 Chromium网页打印用户交互后再导出的页面浏览器自带打印或系统 PDF 打印驱动选择标准其实很简单模板是 HTML 就用第二条路没有模板且版式固定就用第一条需要用户确认后再导出就用第三条。4.2 用 PuppeteerSharp 把 HTML 渲染成 PDFPuppeteerSharp 调用无头 Chromium样式、字体、脚本都沿用浏览器渲染引擎和“网页另存为 PDF”的效果几乎一致。最小代码var options new LaunchOptions { Headless true, Args new[] { --no-sandbox } // 容器或受限用户下必加 }; using var browser await Puppeteer.LaunchAsync(options); using var page await browser.NewPageAsync(); await page.SetContentAsync(html, new NavigationOptions { WaitUntil new[] { WaitUntilNavigation.Load } }); await page.PdfAsync(output.pdf, new PdfOptions { Format PdfFormat.A4, PrintBackground true, DisplayHeaderFooter true, FooterTemplate div stylefont-size:9px; width:100%; text-align:center;span classpageNumber/span / span classtotalPages/span/div });--no-sandbox在 root 用户或 Docker 环境里几乎必选否则 Chromium 启动即退出。WaitUntilNavigation.Load表示等页面 load 事件它不等图片和异步脚本加载完成模板里有动态数据时应改用NetworkIdle0或轮询等待。PrintBackground决定 CSS 背景色和背景图是否打印默认是 false很多人发现生成的 PDF 没有背景就是这个开关没开。4.3 设置 PDF 中文显示Linux 字体配置与验证同一套 HTML 在 Windows 上生成 PDF 正常部署到 Linux 容器里中文变成方块这多半是系统缺少 CJK 字体不是代码问题。先看系统里有没有中文字体fc-list :langzh没有任何输出就安装字体apt-get install -y fonts-noto-cjk安装完要重启进程再生成。PuppeteerSharp 生成的 PDF 会把文字嵌入或转为路径只要宿主环境有对应字体PDF 里的中文就能正常显示。如果不想依赖宿主机可以在LaunchOptions里设置Env把FONTCONFIG_PATH指向镜像内的字体目录方案更可控。4.4 批量导出时避免内存泄漏生成 PDF 最常见的坑是无头浏览器进程泄漏。每次导出都调用LaunchAsync会反复创建 Chromium 进程内存很快被撑爆。正确做法是把browser做成单例一次启动后复用// 浏览器只 Launch 一次页面用完立即关闭 using var page await browser.NewPageAsync(); await page.SetContentAsync(html); await page.PdfAsync(fileName, pdfOptions); await page.CloseAsync();NewPageAsync每次产生一个新的页对象如果页面里有图片、脚本等资源不执行CloseAsync的话内存不会自动回收。批量导出建议把并发限制在 2 到 4 之间。Chromium 不是数据库一轮一轮地跑比同时开几十个页面更稳定。5. 案例四PDF 文件监控转换与发布部署从命令到参数5.1 做文件监控为什么轮询比 FileSystemWatcher 可靠共享文件夹或 Docker 挂载卷上的文件变动FileSystemWatcher经常丢事件文件还在写入时触发解析拿到的是半个文件。常见做法是轮询。轮询的代价是延迟换来的是“确认写完整、再处理”三个可控步骤。用Directory.EnumerateFiles加文件修改时间就能实现不引入额外组件也不至于过度设计。5.2 轮询目录的最小实现public class PdfWatchWorker : BackgroundService { private readonly string _inputDir /data/in; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var pending Directory.EnumerateFiles(_inputDir, *.pdf) .Where(f DateTime.UtcNow - File.GetLastWriteTimeUtc(f) TimeSpan.FromSeconds(10)); foreach (var file in pending) { await ConvertAsync(file, stoppingToken); } await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } } }过滤条件“最近 10 秒没有变化的文件”是为了确认 PDF 已经写完避免读到不完整文件。如果担心处理过程中进程被杀可以在转换前把源文件改名为.processing后缀完成后再改名进程重启后.processing文件列表就是待恢复任务清单。5.3 把 PDF 转 Word 和转文本的两种落地方式抽取文本用 PdfPig 就够了做法和控制台逻辑一致。要“PDF 转 Word”我一般调用 LibreOffice 命令行注意它本身不对出来的是 PDF 还是 docx但确实支持 PDF 转 docxsoffice --headless --convert-to docx input.pdf --outdir /output \ -env:UserInstallationfile:///tmp/lo_profile-env:UserInstallation是很容易被忽略的必设项。LibreOffice 第一次运行会在当前用户的 home 下创建配置目录容器或服务账户没有写权限时进程会直接退出把配置路径指到独立目录也能避免多个转换进程抢同一份配置。转换速度不快一份 30 页的 PDF 大概要几秒适合放在后台任务里做。5.4 部署时到底要不要装 net core hosting bundle发布部署时不少人会搜“net core hosting bundle 下载”但它只在 Windows IIS 场景下需要。Hosting Bundle 的作用是让 IIS 接收请求并转给 Kestrel如果服务器是 Linux 跑 systemd装 ASP.NET Core Runtime 就行。部署方式需要安装的东西适用场景Windows IIS.NET Core Hosting Bundle内网企业应用Linux systemdASP.NET Core Runtime云服务器、容器自包含发布什么都不用装离线环境、定制镜像发布命令常见如下dotnet publish -c Release -r linux-x64 --self-contained false -o ./out--self-contained false表示依赖目标机器运行时更新时包小但机器上要先装对应版本改成--self-contained true则带运行时一起发布缺点是输出目录会达到几十 MB。IIS 部署常见的 HTTP 500.31、500.36 等错误多半是 Hosting Bundle 和运行时版本不匹配把安装包和运行时版本对齐即可。6. 案例五把整套 .NET Core 手册转成可检索 PDF6.1 Markdown 写案例比 Word 更省心四个案例都跑通后代码、命令、参数表已经积累了一大堆。我会把它整理成 Markdown 手册代码块的展示天然适合终端命令和 C# 代码改动可以通过文本 diff 看到。在 VSCode 里写这种笔记一边写一边预览最后统一导出比 Word 复制粘贴省力很多。6.2 用 md-to-pdf 生成带代码高亮的 PDF常见做法是先安装 md-to-pdf它通过本地 Chromium 渲染式 PDF代码高亮、链接可点击生成的目录也能在阅读器里跳转npm i -g md-to-pdf md-to-pdf .net-core-cases.md默认会在当前目录生成同名 PDF 文件。模板包含中文标题时给配置加上white-space: pre-wrap避免标题跨行时出现破折号在行首的情况需要每页页眉显示项目名可以在配置里加headerTemplate。如果团队里已经装了 pandoc想要更精细的排版控制也可以走 LaTeXpandoc .net-core-cases.md -o .net-core-cases.pdf \ --pdf-enginexelatex \ -V CJKmainfontNoto Sans CJK SCCJKmainfont指定 xelatex 处理中文使用的字体Debian/Ubuntu 上要先安装fonts-noto-cjk不指定时中文很可能直接不显示或变成空白方块。6.3 验证 PDF 的代码块与目录生成完我会检查三件事书签目录能不能用代码块有没有被跨页切开——太长的代码块在 Markdown 里手动插入分页标记避免读者截图时缺行以及 PDF 里的命令复制到终端后是否真的能跑。相比把案例留在个人笔记里这份带完整命令和参数表的 PDF对团队里下一个接手的人更有价值遇到EnableRetryOnFailure报错、LibreOffice 没配置独立用户目录时在 PDF 里搜一下就能定位到答案不用重新翻一遍代码仓库。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价