如何在.NET 5的Action Filter中手动实现HttpResponse的GZip压缩
实现方案
ASP.NET Core 中 MVC Filter 的响应流处理逻辑和 .NET Framework 不同,不能直接替换响应流实现压缩,需要先暂存 Action 生成的全部响应内容,再压缩写入原始响应流,完整实现如下:
using System.IO.Compression; using Microsoft.AspNetCore.Mvc.Filters; public class GzipCompressionFilter : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var httpContext = context.HttpContext; var response = httpContext.Response; // 校验客户端是否支持gzip压缩 var acceptEncoding = httpContext.Request.Headers.AcceptEncoding.ToString(); if (!acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase)) { await next(); return; } // 保存原始响应流 var originalBodyStream = response.Body; // 用内存流暂存未压缩的响应内容 using var memoryStream = new MemoryStream(); response.Body = memoryStream; // 执行Action逻辑生成响应内容 var executedContext = await next(); // 仅请求成功时执行压缩 if (executedContext.Exception == null && response.StatusCode == StatusCodes.Status200OK) { memoryStream.Seek(0, SeekOrigin.Begin); // 添加压缩标识头 response.Headers.ContentEncoding = "gzip"; // 移除原Content-Length头,压缩后内容长度已变化 response.Headers.Remove("Content-Length"); // 压缩内容写入原始响应流 using var gzipStream = new GZipStream(originalBodyStream, CompressionMode.Compress, leaveOpen: true); await memoryStream.CopyToAsync(gzipStream); } else { // 异常/非成功响应直接返回原内容 memoryStream.Seek(0, SeekOrigin.Begin); await memoryStream.CopyToAsync(originalBodyStream); } // 恢复原始响应流,避免后续框架逻辑报错 response.Body = originalBodyStream; } }
注册方式
如果需要全局生效,在Program.cs中添加配置:
builder.Services.AddControllers(options => { options.Filters.Add<GzipCompressionFilter>(); });
如果仅需要给指定Controller/Action生效,给对应类/方法添加特性即可:
[TypeFilter(typeof(GzipCompressionFilter))] public class TestController : ControllerBase { // Action逻辑 }
注意事项
- 不要使用
async void实现Filter方法,会导致异步操作未完成就释放资源,引发流异常 GZipStream的leaveOpen参数要设为true,避免压缩流释放时把框架管理的原始响应流关闭- 非200状态码或者请求异常时不要执行压缩,避免错误信息无法正常解析
内容的提问来源于stack exchange,提问作者Katie P
相关产品推荐
相关产品推荐

