ASP.NET Core 6如何通过URI扩展名设置Accept请求头值?
在ASP.NET Core 6中实现URI扩展名映射Accept头的方案
现成配置方案:路由令牌转换器
ASP.NET Core 6提供了RouteTokenTransformerConvention机制,无需手动正则处理就能实现从URI扩展名提取格式并修改请求Accept头的功能,步骤如下:
1. 实现自定义路由令牌转换器
创建转换器类,处理扩展名与媒体类型的映射,同时设置请求的Accept头:
public class FormatRouteTransformer : IInboundParameterTransformer, IOutboundParameterTransformer { private readonly Dictionary<string, string> _formatMappings = new() { ["json"] = "application/json", ["protobuf"] = "application/protobuf", ["xml"] = "application/xml" }; // 出站转换(生成URL时使用,可选实现) public string TransformOutbound(object value) { if (value is string mediaType) { return _formatMappings.FirstOrDefault(kv => kv.Value == mediaType).Key; } return null; } // 入站转换:解析扩展名并设置Accept头 public ValueTask<string> TransformAsync(HttpContext httpContext, string value) { if (_formatMappings.TryGetValue(value, out var mediaType)) { httpContext.Request.Headers.Accept = mediaType; } return ValueTask.FromResult<string>(null); // 无需修改路由参数,返回null即可 } }
2. 注册转换器并配置服务
在Program.cs中添加路由转换器的注册,同时确保已添加所需的输出格式化程序:
var builder = WebApplication.CreateBuilder(args); // 添加控制器及所需格式化程序 builder.Services.AddControllers(options => { options.OutputFormatters.Add(new XmlSerializerOutputFormatter()); // 若使用Protobuf,需添加对应格式化程序(如Google.Protobuf.AspNetCore.ProtobufOutputFormatter) }); // 注册路由令牌转换器 builder.Services.AddRouting(options => { options.ConstraintMap.Add("format", typeof(FormatRouteTransformer)); }); var app = builder.Build(); app.UseRouting(); app.UseAuthorization(); app.MapControllers(); app.Run();
3. 修改控制器路由模板
在控制器路由末尾添加{format:format}参数,匹配URI中的扩展名:
[ApiController] [Route("my/resource{format:format}")] public class ResourceController : ControllerBase { [HttpGet] public IActionResult GetResource() { var resource = new { Id = 1, Name = "Sample Resource" }; return Ok(resource); } }
此时请求http://localhost:5000/my/resource.json时,转换器会自动将Accept头设为application/json,ASP.NET Core会自动调用对应的输出格式化程序。
替代方案:中间件处理
如果不想修改现有路由结构,可以编写中间件拦截请求,解析URI扩展名并设置Accept头:
public class FormatExtensionMiddleware { private readonly RequestDelegate _next; private readonly Dictionary<string, string> _formatMappings = new() { ["json"] = "application/json", ["protobuf"] = "application/protobuf", ["xml"] = "application/xml" }; public FormatExtensionMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { var path = context.Request.Path.Value; var extension = Path.GetExtension(path); if (!string.IsNullOrEmpty(extension) && extension.StartsWith(".")) { var format = extension.TrimStart('.'); if (_formatMappings.TryGetValue(format, out var mediaType)) { context.Request.Headers.Accept = mediaType; // 可选:移除路径中的扩展名,避免路由匹配失败 context.Request.Path = path.Substring(0, path.Length - extension.Length); } } await _next(context); } } // 在Program.cs中注册中间件(需放在UseRouting之前) app.UseMiddleware<FormatExtensionMiddleware>();
这种方式无需改动现有路由规则,直接在请求到达控制器前完成扩展名解析与头设置,适合快速兼容现有客户端的场景。
内容的提问来源于stack exchange,提问作者Dave
相关产品推荐
相关产品推荐

