C# WebApi与IIS解耦:Kestrel自宿主+反向代理实战 📅 发布时间:2026/9/8 11:20:48 👁 浏览次数: 简介面向C#开发者的WebApi服务示例重点演示如何通过自托管模式构建不依赖IIS的服务解决跨平台部署与微服务扩展中的环境依赖问题。压缩包共188个文件、约5.92MB除cs源文件与csproj工程文件外还包括较多dll动态库、xml文档、nupkg依赖包、pdb调试符号、config配置文件以及项目依赖与运行环境配置等覆盖编译、运行、调试与文档所需的主要工程素材。目前已有747人学习下载适合希望摆脱Windows自带IIS束缚、尝试跨平台发布WebApi服务的.NET开发者。借助完整Demo读者可深入理解自托管WebApi服务的启动、停止与生命周期管理学习基于客户端-服务器模式的通信方案、RESTful接口设计、HTTP标准方法与JSON数据交换方式同时可参考项目中轻量级Web服务器如HttpListener或Kestrel的集成思路补全安全通信、身份验证与授权控制等扩展设计为容器化部署或微服务架构落地提供可改造蓝本。整个资源目录从工程配置到核心代码排列清晰既适合初学者对照学习也方便开发者进行二次拓展。 我们搞.NET开发的前几年一提WebApi脑子里默认就是建个ASP.NET项目然后右键发布到IIS上接着去IIS管理器里建站点、配应用池一套流程固定得很。但最近这几年越来越多的项目开始把WebApi从IIS里抽出来用自宿主Self-Host的方式跑再让IIS在前面做一层反向代理。这个思路就是我们常说的“C#构建与IIS解耦的WebApi服务Demo”。这篇东西我打算讲得细一点把为什么解耦、解耦之后架构怎么变、Kestrel在里边扮演什么角色、以及IIS反向代理怎么配全部串一遍。不管你是被领导要求把老项目迁出去还是做上位机开发时想给Vue前端提供接口但不想装IIS这篇都适合你。我会附上完整的Demo工程思路和部署步骤保证你看完能直接照着落地。1. 为什么要把WebApi从IIS里解耦出来1.1 传统IIS托管模式到底有什么痛点先聊痛点。IIS托管WebApi最常见的问题是进程回收。IIS的应用池默认空闲超时是20分钟超过这个时间没有请求工作进程直接给你回收掉。等到下一个请求进来IIS重新拉起进程这个过程中所有的静态变量、内存缓存、后台定时任务全部归零。做硬件对接、上位机通信这类项目时这个特性非常致命。比如我用C#写了一个扫码枪服务扫码枪通过串口或网口持续报送数据服务端在内存里缓存了最近收到的数据包。IIS一回收缓存清空上位机那边的一个简单的“查询当前批次数据”请求拿到的却是空结果排查半天才发现是进程被回收了。另外一个痛点就是部署环境。早些年IIS只支持Windows想部署到Linux服务器上根本没门。虽然现在.NET Framework也支持Windows Container了但那种灵活性和裸金属Linux上跑一个Kestrel进程比起来还是差了很多。还有一点是IIS作为托管宿主的时候应用程序和服务器环境是强耦合的。IIS管理器里几十个配置项什么应用程序池、管道模式、身份验证、URL重写任何一个配置错了服务就起不来。出了问题还得一边查Windows事件日志一边查IIS日志两边来回折腾。1.2 解耦之后的服务架构长什么样解耦之后的架构核心思想就是把“Web服务器”和“应用程序”彻底分开。应用程序自己充当一个独立的进程监听端口处理HTTP请求这就是自宿主模式。在ASP.NET Core时代这个自宿主进程默认就是Kestrel。Kestrel是一个跨平台的、基于libuv后来换成了自研的Socket基础设施的HTTP服务器它内嵌在应用程序里。发布出来的程序本质上就是一个控制台程序跑起来之后开始监听端口跟IIS毫无关系。这个架构下我通常这样部署WebApi服务Kestrel自宿主监听一个内网端口比如5000。IIS只扮演反向代理把公网或局域网进来的请求转发到5000端口。前端Vue等请求直接走IIS的80/443端口通过反向代理转发到后端的5000。这样一拆好处非常明显。IIS那边再出什么应用池回收、站点重启的问题后端服务完全不受影响。反过来后端服务需要更新版本只要把DLL拷贝进去然后重启进程就行IIS连动都不用动。2. 硬核拆解Kestrel和IIS在这套架构里的分工2.1 Kestrel自宿主的技术原理很多从.NET Framework时代过来的人第一次接触Kestrel会觉得有点懵。这里我打个比方。传统的IIS托管模式就好像你租了一个商场的柜台。商场IIS负责开门关门、打扫卫生、安保巡逻。你的柜台WebApi只管卖东西其他一律不用操心。但问题在于商场一旦说“我要关门改造20分钟”你这个柜台也得跟着停业。而Kestrel自宿主呢就相当于你自己租了个临街店铺自己拉电闸、自己装门锁、自己做安保。你的生存完全自理商场管不着你。代价就是你得自己处理很多之前由IIS承担的杂事。Kestrel在做这个“临街店铺”的时候有几个技术点是必须清楚的。第一个是端口监听。Kestrel通过UseUrls或者配置文件里的applicationUrl参数来设置监听地址。比如我要监听本机的5000端口就设置http://0.0.0.0:5000。0.0.0.0是必须的表示监听所有网络接口不然外部机器根本访问不到。第二个是请求处理管道。ASP.NET Core的中间件管道Middleware Pipeline是完全由Kestrel来驱动的。请求到达Kestrel之后按照你在Startup.cs或者.NET 6的Program.cs里注册的中间件顺序依次处理。这个管道模型和IIS的管道模型不一样但逻辑上更清晰——从静态文件到认证授权所有的事情都在应用程序进程内解决不需要经过IIS的那些模块。第三个是进程生命周期管理。Kestrel进程是一个独立进程所以你需要一个守护方案。Windows下可以用Windows ServiceLinux下可以用systemd或者用Docker方式跑。这个比IIS省心的是你的服务进程完全在你的掌控范围内你不需要去理解IIS那一大套工作进程和回收机制。2.2 IIS在这里的新角色反向代理解耦之后IIS变成一个反向代理但很多人其实没弄清这里的关键。我一直跟同事讲反向代理的核心工作是“转发”而不是“托管”。在IIS的世界里做反向代理需要两个模块配合缺一不可。第一个是Application Request RoutingARR。这个模块主要提供代理能力它能把请求转发到指定的后端服务器地址。ARR的安装包可以直接从微软官网下载装好之后在IIS管理器的根节点会多出一个“Server Farm”图标。第二个是URL Rewrite。这个模块负责做地址重写它的作用是把进来的请求URL按照规则重写。比如说前端请求的是http://www.example.com/api/loginURL Rewrite把路径里的/api提取出来重写成http://localhost:5000/api/login然后交给ARR去转发。这两个模块的工作顺序是这样的请求先进入IIS网站的主机头到达URL Rewrite模块URL Rewrite规则判断这个URL是不是需要代理如果需要就重写URL并把这个请求交给ARRARR根据服务器场的配置把请求转发到后端的Kestrel进程。这套反向代理模式的好处在于IIS仍然可以处理静态文件、HTTPS证书、域名绑定这些“边缘”事务动态逻辑则在Kestrel进程里处理。换句话说IIS变成了一个端口守卫和证书管家业务进程的负担被完全解放了。3. 实操落地一步步搭建解耦的WebApi服务3.1 创建ASP.NET Core WebApi项目手把手这部分我假设你用的是Visual Studio 2022或者直接用命令行。用命令行创建项目是最快的。打开终端执行dotnet new webapi -n DecoupledWebApi cd DecoupledWebApi这里-n参数指定项目名。dotnet new webapi模板生成的时候是空壳子带一个默认的天气接口我们直接改造成自己的。如果你坚持用VS2022操作路径是这样的新建项目 - 选择“ASP.NET Core Web API”模板 - 项目名称填DecoupledWebApi- 框架选.NET 8.0或者.NET 6.0看你的环境然后“身份验证类型”选“无”把“配置HTTPS”勾选去掉——因为这个Demo跑在内网用HTTP就够了。项目创建好之后打开Program.cs你会看到var builder WebApplication.CreateBuilder(args); builder.Services.AddControllers(); var app builder.Build(); app.UseAuthorization(); app.MapControllers(); app.Run();这是一个非常精简的纯Kestrel宿主管道。我们在这个基础上加两个接口——一个上传文件接口、一个下载文件接口。新建一个FileController.cs文件代码如下using Microsoft.AspNetCore.Mvc; [ApiController] [Route(api/[controller])] public class FileController : ControllerBase { private readonly IWebHostEnvironment _env; public FileController(IWebHostEnvironment env) { _env env; } [HttpPost(upload)] public async TaskIActionResult Upload(IFormFile file) { if (file null || file.Length 0) { return BadRequest(文件不能为空); } var uploadPath Path.Combine(_env.ContentRootPath, Uploads); if (!Directory.Exists(uploadPath)) { Directory.CreateDirectory(uploadPath); } var filePath Path.Combine(uploadPath, file.FileName); using (var stream new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } return Ok(new { fileName file.FileName, size file.Length }); } [HttpGet(download/{fileName})] public async TaskIActionResult Download(string fileName) { var filePath Path.Combine(_env.ContentRootPath, Uploads, fileName); if (!System.IO.File.Exists(filePath)) { return NotFound(文件不存在); } var bytes await System.IO.File.ReadAllBytesAsync(filePath); return File(bytes, application/octet-stream, fileName); } }这个接口在后面的前后端联调中很有用特别是“如何保持文件名不变”这件事我们用return File(bytes, application/octet-stream, fileName)返回fileName参数直接指定下载文件名浏览器会严格按照这个名字来做不会出现把blob当文件名的情况。3.2 修改监听端口和启动设置Kestrel默认端口是5000但我习惯在开发环境用5200、生产环境用5300这样区分度比较高。在appsettings.json里加一个节点{ Urls: http://0.0.0.0:5200, Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, AllowedHosts: * }如果你用的是.NET 6及以上版本Kestrel会自动读取Urls这个配置节点。用0.0.0.0而不是localhost或者127.0.0.1原因很简单——localhost只监听本机回环地址外部设备比如手机、另一台电脑是访问不到你的服务的。注意AllowedHosts设置为*表示允许任何Host请求进入开发环境无所谓生产环境如果域名固定建议改成你的域名避免被扫描器用IP地址直接访问。另外如果你项目中有Properties/launchSettings.json这里面的applicationUrl优先级比较高。它是给开发调试用的VS按F5启动时读的是这个文件。所以你也需要同步修改这个文件里的applicationUrl为http://0.0.0.0:5200。3.3 发布与直接运行发布命令很简单dotnet publish -c Release -o ./publish发布完成之后进入publish文件夹你会看到一堆DLL文件、一个decoupledwebapi.dll、一个web.config。这个web.config是给IIS做反向代理用的后面细说。现在直接在终端里运行dotnet DecoupledWebApi.dll你会发现它就是一个控制台程序打印出监听地址之后服务就起来了。你可以用浏览器访问http://localhost:5200/api/file/download/test.txt试试Kestrel全程自己处理没有IIS参与。4. IIS反向代理配置让IIS做一道门卫4.1 安装ARR和URL Rewrite模块IIS要配置反向代理首先必须安装ARRApplication Request Routing和URL Rewrite这两个模块。IIS默认是不带这两个的得单独下载。下载地址我直接给两条路避免你走弯路URL Rewrite在IIS管理器左边的连接树里点根节点服务器级别找到“管理”分组下的“功能视图”如果没有URL Rewrite进入微软官网搜索“IIS URL Rewrite Module”下载。ARR同样在IIS根节点如果你没有看到“服务器场”图标说明ARR未安装。微软官网搜索“IIS Application Request Routing”下载安装。安装顺序没有要求两个都装上就行。装完之后如果你看到IIS根节点下多了“服务器场”和“URL重写”这两个图标说明模块已经就位。4.2 创建网站和配置服务器场首先给这个反代创建一个独立的站点。打开IIS管理器右键“网站”-“添加网站”。站点名称填DecoupledWebApiProxy物理路径随便指一个空文件夹比如C:\WebSites\DecoupledWebApiProxy绑定端口填8080——你可以用80端口但容易跟其他站点冲突开发环境用8080更稳当。然后到IIS根节点点击“服务器场”右键“创建服务器场”。名称填DecoupledServerFarm然后在“可用服务器”里添加服务器地址填localhost或者127.0.0.1端口填5200HTTP等设置不用动按照向导完成。创建完成后ARR会弹出提示问你要不要创建一个URL重写规则它默认会生成一条“把所有请求都转发到服务器场”的规则。这里的规则我们需要自定义所以暂时不要选“创建规则”后面手动配。4.3 编写URL Rewrite规则关键中的关键现在进入到“URL重写”配置页面。找到你刚才创建的那个站点比如DecoupledWebApiProxy然后双击“URL重写”。在右侧操作面板点击“添加规则”选“空白规则”。填入以下配置。规则名称填ProxyToKestrel。匹配URL部分“请求的 URL”选择“与模式匹配”“使用”选“正则表达式”“模式”填(.*)条件部分这个规则默认是所有请求都匹配不需要额外添加条件。注意如果你想限制只有/api路径的请求做代理可以设置模式为^api/(.*)$。服务器变量不需要设置。操作部分“操作类型”选“重写”“重写为”填http://DecoupledServerFarm/{R:1}“停止规则重写”勾选“是”配好之后点“应用”规则立即生效。这里的原理是当请求地址是http://localhost:8080/api/file/upload时正则表达式(.*)捕获整个路径api/file/upload然后重写到http://DecoupledServerFarm/api/file/upload。DecoupledServerFarm是之前建的服务器场名称ARR会自动在这个服务器场的所有服务器之间做负载均衡这里只有一台就转发到localhost:5200。注意重写为字段里的DecoupledServerFarm不要加端口号这是引用服务器场的名字。如果你不想用服务器场也可以直接写http://localhost:5200/{R:1}效果一样走的是URL Rewrite模块直接转发但用服务器场的好处是可以配置健康检查、会话保持等高级功能。配置完之后在浏览器访问http://localhost:8080/api/file/upload你看到的不再是IIS的欢迎页而是Kestrel返回的404因为GET请求不支持上传说明IIS已经把请求转发到Kestrel了。4.4 通过web.config直接配置反代备选方案有时候你不想配服务器场觉得有点重。那可以直接在站点的web.config里写规则。其实IIS的“URL重写”界面配置最终也是落到web.config文件里的所以你完全可以手动编辑。在站点物理路径下新建一个web.config文件内容如下?xml version1.0 encodingUTF-8? configuration system.webServer rewrite rules rule nameProxyToKestrel stopProcessingtrue match url(.*) / action typeRewrite urlhttp://localhost:5200/{R:1} / /rule /rules /rewrite /system.webServer /configuration这个配置的意思是所有匹配(.*)的URL全部重写到http://localhost:5200/路径下。{R:1}是正则表达式第一个捕获组的引用也就是原始请求的完整路径。提示如果你要代理的站点和Kestrel监听的端口在同一个机器上用localhost就行。如果Kestrel跑在另一台服务器上把localhost:5200改成那台服务器的IP和端口。这里要注意跨服务器的防火墙设置确保反向代理服务器IIS所在机器能访问后端Kestrel所在机器的5200端口。5. 和上位机联动的实战经验扫码枪、文件下载与Vue对接5.1 扫码枪和C#的配合这个Demo虽然是一个WebApi服务但它的实际场景往往不是单纯的互联网应用更多是给上位机提供服务接口。比如你有个C#写的上位机程序需要把扫码枪扫描到的数据通过HTTP请求提交到WebApi服务怎么做我们的做法是在上位机侧写一个扫码枪事件处理器。扫码枪通过串口或者USB模拟键盘输入当有扫码事件发生时C#程序捕获到完整的扫码字符串然后构造一个POST请求发到WebApi的接口。这里的关键点在于扫码枪的输入是有分隔符的。如果你用串口接收你需要在代码里判断帧头和帧尾把完整的一包数据拼出来。如果你用USB模拟键盘模式那么直接用一个全局键盘钩子捕获即可但要注意扫描速度很多扫码枪一次扫描会触发多个键盘事件你需要把所有字符收集起来直到遇到回车键。上位机收集到扫码数据后调用我们Demo里的接口类似POST http://localhost:8080/api/scan/receive传一个JSON字符串WebApi接收到之后把数据写入数据库或者内存队列。因为WebApi已经在Kestrel进程里独立运行不依赖IIS上位机的请求即使再频繁也不会触发IIS进程回收导致数据丢失。5.2 Vue前端与后端接口联调做Vue前端联调的时候解耦架构的好处更明显。前端开发人员只需要知道WebApi的地址是http://localhost:8080然后所有请求都走这个域名就行。代理规则由IIS统一管理后端服务的内部地址http://localhost:5200完全对前端透明。前端上传文件的代码需要注意FormData的传参名要和后端[FromForm]的参数名字一致。举个例子const formData new FormData(); formData.append(file, file); axios.post(/api/file/upload, formData, { headers: { Content-Type: multipart/form-data } });这里append的第一个参数file必须和后端接口的参数名IFormFile file对应上否则后端收到的是null。前端下载文件时也有一个经典问题——文件名保持。很多人在请求下载接口时直接用axios然后拿到一个blob用URL.createObjectURL创建地址下载但发现下载下来的文件是一个随机字符串。解决方法是从后端返回的响应头里拿Content-Disposition字段解析出filename然后手动指定const disposition response.headers[content-disposition]; let fileName download.txt; if (disposition) { const match disposition.match(/filename?([^])?/); if (match) fileName match[1]; } const url window.URL.createObjectURL(new Blob([response.data])); const link document.createElement(a); link.href url; link.setAttribute(download, fileName); document.body.appendChild(link); link.click();这个方法百试百灵关键是后端的return File(bytes, application/octet-stream, fileName)已经设置了正确的响应头前端解析Content-Disposition就能保证文件名不变。5.3 隐藏IIS响应头的小技巧解耦部署之后前端通过IIS代理访问响应头里会带有Server: Microsoft-IIS/10.0这样的服务器版本信息。做硬件对接时很多客户对安全扫描很敏感这个版本信息暴露在外网怎么看都不太舒服最好去掉。去掉IIS响应头的方法是在IIS的web.config里加上system.webServer httpProtocol customHeaders remove nameX-Powered-By / /customHeaders /httpProtocol security requestFiltering removeServerHeadertrue / /security /system.webServer其中customHeaders里的remove nameX-Powered-By /可以去掉ASP.NET默认添加的X-Powered-By响应头。而requestFiltering removeServerHeadertrue可以让IIS不发送Server响应头。注意removeServerHeader属性需要IIS 7.5及以上版本才支持。如果你还在用IIS 7.0这个属性不生效得用URL Rewrite的serverVariables去手动删除。6. 常见问题与排查技巧实录6.1 访问站点提示503 Service Unavailable这是最常见的坑。IIS代理模式下ARR转发目标不可用时会报503。先确认Kestrel进程是否在监听5200端口。排查方法在IIS所在的机器上打开浏览器访问http://localhost:5200/api/file/download/test.txt如果能正常返回说明后端是通的如果不能说明Kestrel没起来或者防火墙拦截了。如果localhost能访问但通过IIS代理之后报503那大概率是ARR到服务器场的转发问题。在IIS根节点点开“服务器场”选中DecoupledServerFarm双击“代理”检查“HTTP端口”是不是填写的5200还可在右侧面板点“重启”应用健康检查。6.2 响应头里出现两条Server信息那多半是你还保留了旧版本的静态文件服务器。注意一个细节解耦之后IIS和Kestrel都在处理HTTP请求如果Kestrel也开启了Server头而IIS没来得及移除响应头就会叠加。解决方法是同时关掉Kestrel和IIS的Server头。Kestrel关闭Server头的方法是在Program.cs里配置var builder WebApplication.CreateBuilder(args); builder.WebHost.ConfigureKestrel(options { options.AddServerHeader false; });然后IIS侧按5.3的方法移除自己的Server头这样两层都干净了。6.3 上传大文件失败默认情况下Kestrel的请求体大小限制是30MB左右IIS的也有限制。做上位机上传日志、相机拍照图片的场景很容易就超出这个限制。Kestrel侧修改限制在Program.cs里builder.Services.ConfigureFormOptions(options { options.ValueLengthLimit int.MaxValue; options.MultipartBodyLengthLimit int.MaxValue; });IIS侧的限制在web.config里设置system.webServer security requestFiltering requestLimits maxAllowedContentLength524288000 / /requestFiltering /security /system.webServer这里面maxAllowedContentLength的单位是字节524288000就是500MB。两边的限制都要改否则哪一个先拦截都会导致上传失败。特别说一句MultipartBodyLengthLimit默认是128MB这个值经常被忽略。很多人在IIS那边改了限制发现大文件还是传不上去其实就是Kestrel这边卡住了。6.4 前端请求跨域CORS问题解耦模式下IIS代理的域名和Kestrel自宿主的域名不一样如果前端直接用axios请求Kestrel的5200端口必然触发跨域。虽然在我们的方案里前端都走IIS的8080端口但在开发阶段很多前端工程师习惯直连后端。为了解决联调阶段的跨域问题后端需要启用CORS。在Program.cs里加上var builder WebApplication.CreateBuilder(args); builder.Services.AddCors(options { options.AddPolicy(AllowAll, policy { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); // ... app.UseCors(AllowAll);注意生产环境不建议用AllowAnyOrigin这等于允许任意网站调用你的接口。如果接口只在内部用建议配置为WithOrigins(http://localhost:8080, http://192.168.1.100)等具体的来源地址。6.5 发布之后配置文件没生效不少人会把appsettings.json遗忘在发布目录之外。dotnet publish命令默认会把appsettings.json复制到输出目录但如果你改了文件内容却没重新发布运行的时候读的还是旧的配置。判断方法在运行目录下打开DecoupledWebApi.dll旁边的appsettings.json看里面的Urls配置是不是想要的端口如果不对手动改一下再重启服务就行——这是自宿主模式一个特有的便利不用重新编译改完配置文件直接重启进程就生效了。7. 实际操作中的几点体会写到这里关于这个方案我想再做几点补充。在硬件测试、上位机开发这个圈子里解耦WebApi的思路越来越流行。我们之前一套设备机箱里的工控机运行着C#写的上位机软件同时又跑着一个WebApi给MES系统上报数据。以前这个WebApi部署在IIS里用户改完配置点了一下“回收应用程序池”整个MES通信中断了十几秒产线报警。后来改成Kestrel自宿主Windows服务注册重启时间和MES断线问题就再也没出现过了。第二个体会是关于部署工具的选型。如果你面向的生产环境是Windows Server我特别推荐用Windows Service方式注册Kestrel。在发布目录里执行一行命令就能注册sc create DecoupledWebApi binPath C:\path\to\publish\DecoupledWebApi.exe start auto这样开机自启、异常重启都由Windows服务控制控制台程序挂掉还能自动恢复运维省心太多了。第三个体会是日志。自宿主模式的日志默认打到哪里很多人搞不清。Kestrel默认是写到控制台的如果你注册成了Windows服务跑控制台窗口根本不存在日志就丢了。我们实际项目里都是接Serilog输出到文件或者数据库这样出了问题才能有据可查。这个点放在最后说是因为我见过太多人解耦之后说“服务挂了我都不知道为什么”其实就是日志没配好。以上就是我从传统IIS托管模式迁移到Kestrel自宿主反向代理模式的全过程。这套架构不是银弹但确实解决了很多实际的部署和维护问题。如果你也在做C#相关的服务端开发手里有硬件设备要对接强烈建议花一天时间照着Demo把流程跑一遍你会对“解耦”这两个字的体会完全不一样。本文还有配套的精品资源点击获取