.NET Core 3.1控制器返回CSS或404异常问题排查
我需要实现一个控制器Action来返回CSS样式表,只有当响应类型为text/css时浏览器才会使用该CSS。当前Action代码如下:
[HttpGet("{tenant}/stylesheet")] [Produces("text/css")] [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task<ActionResult<BrandingDTO>> Stylesheet(string tenant) { Branding b = dbContext.Branding.SingleOrDefault(z => z.Tenant == tenant); if (b == null) { return NotFound(); // 实际返回406 Not Acceptable } return Ok(Content($@" nav.navbar {{ background-color: {b.PrimaryBrandingColour}; }} ", "text/css")); }
我还创建了自定义输出格式化器:
/// <summary> /// 自定义CSS输出格式化器 /// </summary> internal class CssOutputFormatter : TextOutputFormatter { public CssOutputFormatter() { SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/css")); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); } protected override bool CanWriteType(Type? type) { return type == typeof(ContentResult) || type == typeof(string); } public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { string buffer = context.Object.ToString(); ContentResult content = context.Object as ContentResult; if(content != null) { buffer = content.Content; } await context.HttpContext.Response.WriteAsync(buffer, selectedEncoding); } }
现在的问题是:当租户不存在返回NotFound()时,实际响应是406 Not Acceptable,而我期望返回404状态码,同时要么返回text/css类型的空响应体,要么返回application/problem+json类型的ProblemDetails响应。想知道这种需求能否实现?
你的需求完全可以实现,核心是解决406的触发逻辑,并自定义404场景下的响应格式:
1. 406错误的原因
[Produces("text/css")]限制了该Action只能输出text/css类型响应,但默认NotFound()返回的是ProblemDetails对象,而你的自定义CssOutputFormatter仅支持ContentResult和string类型,框架找不到匹配的格式化器,因此返回406 Not Acceptable。
2. 实现404 + text/css空响应
直接构造ContentResult返回空CSS内容并指定404状态码即可:
if (b == null) { return new ContentResult { Content = string.Empty, ContentType = "text/css", StatusCode = StatusCodes.Status404NotFound }; }
这样浏览器会收到404状态码,同时响应类型为text/css,符合要求。
3. 实现404 + application/problem+json响应
如果需要返回标准的ProblemDetails,可按以下步骤调整:
- 修改
[Produces]特性,允许输出多种响应类型:[Produces("text/css", "application/problem+json")] - 手动构造ProblemDetails并返回:
if (b == null) { var problem = new ProblemDetails { Status = StatusCodes.Status404NotFound, Title = "租户不存在", Detail = $"未找到租户 {tenant} 的品牌配置" }; return NotFound(problem); }
ASP.NET Core默认包含JSON格式化器,此时会自动将ProblemDetails序列化为application/problem+json类型响应,状态码保持404。
4. 优化自定义格式化器(可选)
如果需要让格式化器兼容ProblemDetails类型,可调整CssOutputFormatter:
protected override bool CanWriteType(Type? type) { return type == typeof(ContentResult) || type == typeof(string) || type == typeof(ProblemDetails); } public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { string buffer; if (context.Object is ProblemDetails problem) { buffer = JsonSerializer.Serialize(problem); context.HttpContext.Response.ContentType = "application/problem+json"; } else if (context.Object is ContentResult content) { buffer = content.Content; } else { buffer = context.Object?.ToString() ?? string.Empty; } await context.HttpContext.Response.WriteAsync(buffer, selectedEncoding); }
内容的提问来源于stack exchange,提问作者Richard Barraclough

