ASP.NET Core反向代理与API网关场景下如何生成正确的绝对链接
解决方案
方案1:透传X-Forwarded-Prefix + 自定义IUrlHelper装饰器(最推荐,兼容.NET5/.NET6+,无业务侵入)
这个方案不需要调整现有路由规则,完全不用改业务代码,技术端点和业务API路径天然隔离。
步骤1:配置ForwardedHeaders处理路径前缀
在ConfigureServices中添加ForwardedHeaders配置,支持网关透传的路径前缀:
using Microsoft.AspNetCore.HttpOverrides; services.Configure<ForwardedHeadersOptions>(options => { // 同时处理域名、协议、路径前缀的转发头 options.ForwardedHeaders = ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedPathBase; // 添加工厂网关IP到信任列表,防止请求头伪造 options.KnownProxies.Add(IPAddress.Parse("你的网关服务IP")); });
在Configure方法的最开头注册ForwardedHeaders中间件(优先级高于所有其他中间件),如果只有一个发布网关,也可以硬编码路径前缀:
// 优先用网关透传的方式,多网关发布时不需要改代码 app.UseForwardedHeaders(); // 单网关场景硬编码前缀的话用这个,不用网关透传X-Forwarded-Prefix // app.UsePathBase("/business-area/api-name/v1");
要求网关在转发请求时添加请求头:X-Forwarded-Prefix: /business-area/api-name/v1,ASP.NET Core会自动将该值设置为Request.PathBase,生成链接时会自动拼接该前缀。
步骤2:替换默认IUrlHelper工厂,自动去掉/api前缀
通过装饰器模式包装系统默认的IUrlHelper实现,自动移除生成链接中的/api前缀,不需要修改任何业务代码:
首先实现IUrlHelper装饰器:
public class GatewayUrlHelper : IUrlHelper { private readonly IUrlHelper _inner; private const string ApiPrefix = "/api"; public GatewayUrlHelper(IUrlHelper inner) => _inner = inner; public ActionContext ActionContext => _inner.ActionContext; public string Content(string contentPath) => _inner.Content(contentPath); public bool IsLocalUrl(string url) => _inner.IsLocalUrl(url); public string Action(UrlActionContext actionContext) { var url = _inner.Action(actionContext); return url?.StartsWith(ApiPrefix) == true ? url[ApiPrefix.Length..] : url; } public string RouteUrl(UrlRouteContext routeContext) { var url = _inner.RouteUrl(routeContext); return url?.StartsWith(ApiPrefix) == true ? url[ApiPrefix.Length..] : url; } public string Link(string routeName, object values) { var url = _inner.Link(routeName, values); return url?.Replace($"{ApiPrefix}/", string.Empty) ?? url; } }
然后实现自定义IUrlHelperFactory,替换默认实现:
using Microsoft.AspNetCore.Mvc.Routing; public class GatewayUrlHelperFactory : IUrlHelperFactory { private readonly IUrlHelperFactory _innerFactory = new EndpointRoutingUrlHelperFactory(); public IUrlHelper GetUrlHelper(ActionContext context) { var innerHelper = _innerFactory.GetUrlHelper(context); return new GatewayUrlHelper(innerHelper); } }
最后在ConfigureServices中注册替换:
services.AddSingleton<IUrlHelperFactory, GatewayUrlHelperFactory>();
配置完成后,所有通过IUrlHelper生成的链接会自动拼接网关前缀、去掉内部/api前缀,/health、/swagger等技术端点路径不受影响。
方案2:.NET6+路由组方案(升级后可选)
如果升级到.NET6+,可以用顶层路由组功能,将业务API和技术端点完全拆分,不需要自定义IUrlHelper:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddHealthChecks(); var app = builder.Build(); // 技术端点直接注册在根路径 app.MapSwagger(); app.MapSwaggerUI(c => c.RoutePrefix = "swagger"); app.MapHealthChecks("/health"); // 业务API统一注册到/api前缀的路由组 var apiGroup = app.MapGroup("/api"); apiGroup.MapControllers(); // 配置网关路径前缀 app.UsePathBase("/business-area/api-name/v1"); app.UseForwardedHeaders();
这种方式生成链接时自动适配网关路径,也不会出现路径混淆的问题。
原中间件方案失效原因
IUrlHelper生成链接时基于路由模板的配置,而非当前请求的Path属性,你在中间件中修改Request.Path不会影响路由模板的解析逻辑,放在管道前面会破坏路由匹配是因为修改后的Path没有/api前缀,找不到对应控制器路由。
内容的提问来源于stack exchange,提问作者Palec
相关产品推荐
相关产品推荐

