Azure App Service大文件上传报413及CORS错误,本地正常
尝试向API发送包含70MiB二进制文件的multipart/form-data表单,上传完成时出现两个错误:
Access to XMLHttpRequest at 'https://api.contoso.com/v1/controller/upload' from origin 'https://www.contoso.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
POST https://api.contoso.com/v1/controller/upload net::ERR_FAILED 413 (Request Entity Too Large)
仅小文件(如5MiB)可上传成功,其他端点无CORS问题,本地运行时无此报错,控制器方法从未执行,"Uploading asset"日志未出现。
现有配置
ASP.NET Core 基础配置
builder.Services.Configure<KestrelServerOptions>(options => { options.Limits.MaxRequestBodySize = long.MaxValue; options.Limits.MaxRequestBufferSize = long.MaxValue; }); builder.Services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = 200 * 1024 * 1024; //200 MB }); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); //DB connection //Blob storage connection //Auth/JWT //Services/DI var app = builder.Build(); app.Logger.LogInformation("Starting...");
生产环境配置
app.Logger.LogInformation("Production mode"); app.UseCors(policyBuilder => { policyBuilder.SetIsOriginAllowed((origin) => { app.Logger.LogInformation("Origin of call: " + origin); return origin.Equals("https://contoso.com") || origin.Equals("https://www.contoso.com"); }) .AllowAnyHeader().AllowAnyMethod().WithExposedHeaders("App-Desktop"); }); //Allow access by the desktop app to these endpoints. app.Use(async (context, next) => { if (context.Request.Headers.ContainsKey("App-Desktop")) { var path = context.Request.Path.Value?.ToLower() ?? ""; app.Logger.LogInformation("Path of call from desktop app: " + path); if (path.StartsWith("/v1/blogs")) context.Response.Headers.Append("Access-Control-Allow-Origin", "*"); } await next(); });
控制器方法
[HttpPost("upload")] [Consumes("multipart/form-data")] //[RequestSizeLimit(209_715_200)] //200Mib [DisableRequestSizeLimit] public async Task<IActionResult> UploadBinary(IFormFile file, [FromForm] BinaryRequest binary) { _logger.LogInformation("Uploading asset: " + file.FileName); //... }
补充优化后的配置(仍无效)
builder.Services.Configure<KestrelServerOptions>(options => { options.Limits.MaxRequestBodySize = long.MaxValue; options.Limits.MaxRequestBufferSize = long.MaxValue; options.Limits.MaxRequestHeadersTotalSize = int.MaxValue; options.Limits.MaxResponseBufferSize = long.MaxValue; }); builder.Services.Configure<FormOptions>(options => { options.ValueLengthLimit = int.MaxValue; options.MultipartBodyLengthLimit = long.MaxValue; options.MultipartHeadersLengthLimit = int.MaxValue; options.BufferBodyLengthLimit = long.MaxValue; options.MemoryBufferThreshold = int.MaxValue; });
Azure API Insights 日志
Request starting HTTP/1.1 OPTIONS https://api.contoso.com/v1/controller/upload - - -
Origin of call: https://www.contoso.com
CORS policy execution successful.
Request finished HTTP/1.1 OPTIONS https://api.contoso.com/v1/controller/upload - 204 - - 2.1230ms
核心原因
- 413错误根源:Azure App Service 层面的请求大小限制
你已经配置了Kestrel和FormOptions的大小限制,但生产环境部署在Azure App Service时,默认POST请求大小限制为30MB,这个限制会在请求到达ASP.NET Core应用之前就被拦截,导致控制器方法根本不会执行。 - CORS错误的连锁反应
Azure返回413错误时,没有在响应中添加Access-Control-Allow-Origin头,浏览器因此将这个错误额外解读为CORS问题,同时抛出两个错误。
解决方案
1. 调整Azure App Service的上传限制
- 登录Azure门户,找到目标App Service实例
- 进入配置 -> 常规设置
- 在上传限制处设置大于70MB的数值(比如200MB)
- 保存设置并重启App Service
2. 确保错误响应中包含CORS头
调整中间件顺序,让CORS中间件优先执行,并添加全局错误处理逻辑,保证异常场景下也能返回正确的CORS头:
// 优先注册CORS中间件 app.UseCors(policyBuilder => { policyBuilder.SetIsOriginAllowed((origin) => { app.Logger.LogInformation("Origin of call: " + origin); return origin.Equals("https://contoso.com") || origin.Equals("https://www.contoso.com"); }) .AllowAnyHeader().AllowAnyMethod().WithExposedHeaders("App-Desktop"); }); // 全局错误处理中间件,确保错误响应携带CORS头 app.Use(async (context, next) => { try { await next(); } catch (Exception ex) { // 补全CORS头 if (!context.Response.Headers.ContainsKey("Access-Control-Allow-Origin")) { var origin = context.Request.Headers["Origin"].FirstOrDefault(); if (!string.IsNullOrEmpty(origin) && (origin.Equals("https://contoso.com") || origin.Equals("https://www.contoso.com"))) { context.Response.Headers.Append("Access-Control-Allow-Origin", origin); } } // 自定义错误响应 context.Response.StatusCode = StatusCodes.Status500InternalServerError; await context.Response.WriteAsync("服务器内部错误"); } }); // 再注册桌面应用相关中间件 app.Use(async (context, next) => { if (context.Request.Headers.ContainsKey("App-Desktop")) { var path = context.Request.Path.Value?.ToLower() ?? ""; app.Logger.LogInformation("Path of call from desktop app: " + path); if (path.StartsWith("/v1/blogs")) context.Response.Headers.Append("Access-Control-Allow-Origin", "*"); } await next(); });
3. 验证配置
重启App Service后,再次尝试上传70MiB文件,检查Azure API Insights日志,确认POST请求能到达控制器(出现"Uploading asset"日志)。
内容的提问来源于stack exchange,提问作者Nicke Manarin

