You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core Web API:ProducesAttribute未提前过滤不支持的请求

问题

我有一个ASP.NET Core Web API,支持生成XML和JSON响应。其中一个控制器允许返回两种格式,但其他端点只需要返回JSON。现在当发送携带Accept: application/xml的请求时,会收到HTTP 406状态码,但对应的动作方法代码仍然会执行,造成不必要的资源消耗。

环境配置

我使用Visual Studio中的ASP.NET Core Web API模板(.NET 8)完成配置。Program.cs中的代码如下:

var builder = WebApplication.CreateBuilder(args);

// 向容器添加服务
builder.Services.AddControllers(options =>
    {
        // 启用对不支持的媒体类型返回406错误
        options.ReturnHttpNotAcceptable = true;
    })
    // 为API启用XML支持
    .AddXmlSerializerFormatters();

var app = builder.Build();

// 配置HTTP请求管道
app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

随后在自动生成的WeatherForecastController中添加了Produces特性,代码如下:

namespace TestApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    [Produces(MediaTypeNames.Application.Json)]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

当我发送如下请求时:

GET {{TestApi_HostAddress}}/weatherforecast/
Accept: application/xml

虽然会收到错误,但在Get()方法中设置断点后发现,ASP.NET Core在识别到无可用格式化器之前,已经执行了该方法的代码。

请问是否有办法让ASP.NET在内容协商阶段就确定返回406,而非先执行API中的代码?


解决方案

要在内容协商阶段提前拦截不支持的Accept头部并返回406,避免执行动作方法,可以通过自定义资源过滤器实现:

步骤1:创建资源过滤器

这个过滤器会在动作方法执行前,检查请求的Accept头部是否与控制器/动作声明的Produces特性兼容:

public class ValidateAcceptHeaderFilter : IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        // 获取当前动作或控制器上的Produces特性
        var producesAttributes = context.ActionDescriptor.EndpointMetadata.OfType<ProducesAttribute>();
        if (!producesAttributes.Any()) return;

        var supportedMediaTypes = producesAttributes.SelectMany(p => p.ContentTypes).ToList();
        var acceptHeaders = context.HttpContext.Request.Headers.Accept;

        // 未指定Accept头部时默认允许(可根据需求调整)
        if (!acceptHeaders.Any()) return;

        // 检查是否存在匹配的媒体类型
        var hasMatch = acceptHeaders.Any(accept => 
            supportedMediaTypes.Any(supported => 
                new MediaTypeHeaderValue(supported).IsSubsetOf(new MediaTypeHeaderValue(accept))));

        if (!hasMatch)
        {
            context.Result = new StatusCodeResult(StatusCodes.Status406NotAcceptable);
        }
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
        // 无需处理后续逻辑
    }
}

步骤2:注册过滤器

可以选择全局注册(对所有控制器生效),或者仅在特定控制器/动作上添加:

全局注册(Program.cs)

builder.Services.AddControllers(options =>
    {
        options.ReturnHttpNotAcceptable = true;
        // 添加自定义过滤器
        options.Filters.Add<ValidateAcceptHeaderFilter>();
    })
    .AddXmlSerializerFormatters();

仅在特定控制器上添加

[ApiController]
[Route("[controller]")]
[Produces(MediaTypeNames.Application.Json)]
// 为当前控制器添加过滤器
[ServiceFilter(typeof(ValidateAcceptHeaderFilter))]
public class WeatherForecastController : ControllerBase
{
    // 现有代码不变
}

这样配置后,当请求的Accept头部不在控制器支持的媒体类型范围内时,ASP.NET Core会在动作方法执行前直接返回406状态码,避免不必要的资源消耗。


内容的提问来源于stack exchange,提问作者mrbask

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:04:50