如何在Azure App Service托管的ASP.NET Web API中暴露Content-Disposition响应头?
问题背景
我开发了一个基于.NET Web API的文件下载应用,客户端用NSwag生成代码。本地运行时一切正常,但部署到Azure App Service后,客户端死活拿不到下载文件名。
客户端本来依赖Content-Disposition响应头获取文件名,这个头是API控制器返回FileContentResult时自动添加的。但Content-Disposition不属于CORS安全响应头,必须通过Access-Control-Expose-Headers头明确允许,客户端才能访问它。
本地环境里,我在Startup中配置的CORS策略(用WithExposedHeaders("Content-Disposition"))能正常生效,响应里同时有Content-Disposition和Access-Control-Expose-Headers头。但一部署到Azure,响应里只有正确的Content-Disposition头,Access-Control-Expose-Headers头就消失了,导致客户端读不到文件名。
我试过清空Azure App Service上的CORS设置(怕它覆盖应用内的策略),没用;也试过在Azure某个设置里加Content-Disposition,同样没效果,而且这个设置只有某篇Stack Overflow提到,没其他参考。另外,Azure容器应用有专门的暴露头配置,但App Service的CORS配置里根本没这个选项。
相关代码
ConfigureServices 配置
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddDefaultPolicy(builder => { builder.AllowAnyHeader(); builder.AllowAnyMethod(); builder.WithExposedHeaders("Content-Disposition"); }); }); }
Configure 配置
public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseCors(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
API控制器代码
[HttpGet] [Route("getfile")] public async Task<ActionResult> GetFile(long id) { var file = await _fileService.GetFile(id); return File(file.Content, file.MimeType, file.Name); }
解决方案
方法1:控制器内手动添加响应头
有时候Azure的中间件会拦截或覆盖CORS头,直接在控制器的响应里手动加Access-Control-Expose-Headers,绕过潜在的策略问题:
修改GetFile方法:
[HttpGet] [Route("getfile")] public async Task<ActionResult> GetFile(long id) { var file = await _fileService.GetFile(id); var result = File(file.Content, file.MimeType, file.Name); // 手动添加暴露头 Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition"); return result; }
方法2:禁用Azure的CORS预检代理
Azure App Service可能有默认的CORS代理行为,添加应用设置禁用它:
- 打开Azure门户,进入你的App Service
- 跳转到配置 > 应用设置
- 新增一个设置:
- 名称:
WEBSITE_DISABLE_CORS_PREFLIGHT - 值:
true
- 名称:
- 保存设置后重启App Service
这个设置会让Azure的前端代理不再处理CORS预检请求,确保应用内的CORS策略完全生效。
方法3:全局自定义中间件添加头
如果需要所有接口都生效,可以写个自定义中间件,给所有响应加上暴露头:
public class ExposeHeadersMiddleware { private readonly RequestDelegate _next; public ExposeHeadersMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 在响应发送前插入头 context.Response.OnStarting(() => { if (!context.Response.Headers.ContainsKey("Access-Control-Expose-Headers")) { context.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition"); } return Task.CompletedTask; }); await _next(context); } }
然后在Configure方法里注册这个中间件(要放在UseCors之前):
public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseMiddleware<ExposeHeadersMiddleware>(); app.UseCors(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
验证步骤
部署修改后的代码或设置后,用浏览器开发者工具检查:
- 发起文件下载请求
- 查看网络标签里的响应头
- 确认同时存在
Content-Disposition和Access-Control-Expose-Headers: Content-Disposition - 测试客户端是否能正常读取文件名
内容的提问来源于stack exchange,提问作者Cristian Sanchez

