使用中间件转响应为PDF时改写响应体遇异常,求解决方案
解决ASP.NET Core中间件中改写响应体的问题
你的核心问题有两个:
- 默认的
context.Response.Body是**不可查找(CanSeek=false)**的流,无法直接设置Position或读取已写入的内容 - 中间件执行顺序错误:你在调用
_next(context)之前就尝试读取响应,此时后续管道还没生成响应内容
正确的实现步骤
- 先判断当前端点是否标记了
PdfRenderableAttribute,无标记则直接放行 - 替换默认响应流为可读写的
MemoryStream,用于捕获后续管道生成的响应内容 - 调用
_next(context)让后续中间件/端点生成原始HTML响应 - 验证响应状态码为200后,读取内存流中的HTML内容
- 用IronPdf转换为PDF,重置响应头和状态码,输出PDF数据
修正后的完整代码
public class PdfRendererMiddleware { private readonly RequestDelegate _next; public PdfRendererMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 检查当前端点是否需要PDF转换 var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint; var pdfAttribute = endpoint?.Metadata.GetMetadata<PdfRenderableAttribute>(); if (pdfAttribute == null) { await _next(context); return; } // 保存原始响应流,替换为内存流用于捕获内容 var originalResponseBody = context.Response.Body; using var responseStream = new MemoryStream(); context.Response.Body = responseStream; try { // 执行后续管道,生成原始响应 await _next(context); // 仅处理200 OK的响应 if (context.Response.StatusCode == StatusCodes.Status200OK) { // 重置内存流位置,读取原始响应内容 responseStream.Position = 0; using var reader = new StreamReader(responseStream); var htmlContent = await reader.ReadToEndAsync(); // 转换为PDF using var pdfRenderer = new ChromePdfRenderer(); var pdfDocument = pdfRenderer.RenderHtmlAsPdf(htmlContent); var pdfBytes = pdfDocument.BinaryData; // 重置响应,输出PDF context.Response.Clear(); context.Response.ContentType = "application/pdf"; context.Response.ContentLength = pdfBytes.Length; await context.Response.Body.WriteAsync(pdfBytes); } } finally { // 恢复原始响应流,避免资源泄漏 context.Response.Body = originalResponseBody; } } }
关键细节说明
- 内存流替换:默认的响应流是针对网络输出的单向流,不支持查找和重复读取,必须用
MemoryStream替换才能捕获并读取响应内容 - 执行顺序:必须先调用
_next(context),让后续管道完成响应生成,才能读取到有效的HTML内容 - 响应重置:生成PDF后需要调用
Clear()清空原始响应头,设置正确的Content-Type和ContentLength,确保客户端能正确识别PDF文件 - 资源清理:用
using语句管理内存流和渲染器,确保资源被正确释放;在finally块中恢复原始响应流,避免影响后续中间件
内容的提问来源于stack exchange,提问作者Andy
相关产品推荐
相关产品推荐

