非IIS环境下实现多个.NET6 AspNetCore WebApi同端口部署
问题背景
我们有一批基于.NET 4.7.2的WebApi,通过Microsoft.Owin.Hosting包托管(非IIS环境),所有程序及Web控制器均监听同一端口但不同虚拟路径,例如http://ourserver:4711/ServiceA/和http://ourserver:4711/ServiceB/。.NET4.7.2的OWIN包会在首个服务启动时创建内存主机,其余服务仅注册虚拟路径即可正常路由,采用单端口是出于防火墙规则及负载均衡等需求。
现计划迁移至.NET6,虽依赖Microsoft.AspNetCore.Owin包,但无法正常运行。测试使用示例WheatherForecastController,当前有两个核心目标:
- 目标1:实现服务监听
http://localhost:4711/ServiceA/,而非根路径http://localhost:4711 - 目标2:启动第二个独立exe,使其监听
http://localhost:4711/ServiceB/
当前尝试的基础配置代码如下(多种调整后仍未达预期):
public static void Main() { var host = new WebHostBuilder() .UseUrls("http://localhost:4711/") .UseKestrel() .UseStartup<Startup>() .Build(); host.Run(); } public sealed class Startup { public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddMvc(sa => { sa.EnableEndpointRouting = false; }); } public void Configure(IApplicationBuilder app) { app.UsePathBase("/ServiceA") .Use((context, next) => { context.Request.PathBase = "/ServiceA"; return next(); }) .UseRouting() .UseMvc() .UseOwin(); } }
核心说明
.NET 6的Kestrel不支持多独立进程共享同一端口并各自绑定不同虚拟路径,这和.NET Framework下OWIN内存主机的机制完全不同。要实现单端口多虚拟路径的需求,需调整架构,以下是两种可行方案:
方案一:YARP反向代理(生产环境推荐)
通过YARP(微软官方反向代理库)搭建主网关进程,对外暴露统一端口,将不同路径的请求转发到本地不同端口的子服务,既满足单端口需求,又保证服务隔离性。
- 主网关项目配置
var builder = WebApplication.CreateBuilder(args); // 添加YARP服务 builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); var app = builder.Build(); app.MapReverseProxy(); app.Run("http://localhost:4711");
配置文件appsettings.json:
{ "ReverseProxy": { "Routes": { "serviceA": { "ClusterId": "serviceA", "Match": { "Path": "/ServiceA/{**catch-all}" }, "Transforms": [ { "PathRemovePrefix": "/ServiceA" } ] }, "serviceB": { "ClusterId": "serviceB", "Match": { "Path": "/ServiceB/{**catch-all}" }, "Transforms": [ { "PathRemovePrefix": "/ServiceB" } ] } }, "Clusters": { "serviceA": { "Destinations": { "serviceA": { "Address": "http://localhost:5001/" } } }, "serviceB": { "Destinations": { "serviceB": { "Address": "http://localhost:5002/" } } } } } }
- ServiceA项目配置
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); var app = builder.Build(); app.UseRouting(); app.MapControllers(); app.Run("http://localhost:5001");
控制器添加基础路由:
[Route("api/[controller]")] [ApiController] public class WeatherForecastController : ControllerBase { // 业务代码 }
访问路径:http://localhost:4711/ServiceA/api/WeatherForecast
- ServiceB项目配置
与ServiceA逻辑一致,仅需将监听端口改为http://localhost:5002,通过网关的/ServiceB路径访问。
方案二:端口共享+路径过滤(测试环境可用)
通过Kestrel的端口共享特性,让多进程监听同一端口,再通过中间件过滤处理各自路径的请求。但该方案存在请求分配随机的问题,稳定性不足,仅适合测试场景。
- ServiceA项目配置
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); // 启用端口共享 builder.WebHost.ConfigureKestrel(options => { options.ListenAnyIP(4711, listenOptions => { listenOptions.UseConnectionLogging(); listenOptions.UsePortSharing(); }); }); var app = builder.Build(); // 仅处理/ServiceA开头的请求 app.UseWhen(context => context.Request.Path.StartsWithSegments("/ServiceA"), appBuilder => { appBuilder.UsePathBase("/ServiceA"); appBuilder.UseRouting(); appBuilder.MapControllers(); }); app.Run();
- ServiceB项目配置
仅需将路径过滤逻辑改为/ServiceB:
// ... app.UseWhen(context => context.Request.Path.StartsWithSegments("/ServiceB"), appBuilder => { appBuilder.UsePathBase("/ServiceB"); appBuilder.UseRouting(); appBuilder.MapControllers(); }); // ...
注意事项
- .NET 6中OWIN集成仅能通过
UseOwin中间件实现,但多进程共享端口的机制与.NET Framework完全不同,无法直接复用旧模式; - 反向代理方案是生产环境的最优选择,兼顾单端口需求与服务稳定性;
- 端口共享方案存在请求分配随机的风险,不建议用于生产环境。
内容的提问来源于stack exchange,提问作者Dee J. Doena

