ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C# WebApi实例:从前后端分离到联调发布的完整实战指南

C# WebApi实例:从前后端分离到联调发布的完整实战指南 简介这套C# WebAPI实战资源面向刚入行IT的新人以及尚未系统掌握Web API的朋友。项目以真实职场开发为蓝本演示最精髓的WebAPI特性路由与前后端调用分离技术让读者直接看到接口如何设计、前端如何异步调用后端以及UI层与DAL层间隔分明的分层方式数据网格能够自动读取配置文件并动态加载显示数据省去重复编码非常适合拿到后直接借鉴或改造。包体为rar压缩格式约14.71MB轻量精悍便于快速下载学习目前已有3195人学习下载说明内容获得不少同行认可。通过这份Demo可学到从零搭建WebAPI、特性路由配置、跨域前后端分离交互等关键技能同时还能借鉴其目录结构与数据访问层写法减少职场踩坑作者强调“终身受益”对想提升C#后端实战能力的人来说是一份高性价比的参考资源。1. C#职场最精髓Webapi实例一份能直接照着练的前后端分离项目招聘网站C#岗位里“前后端分离、WebAPI接口开发”基本是标配关键词。但很多写过两三年ASP.NET MVC的工程师现场连一个最小POST接口都讲不顺路由怎么配、返回怎么定、跨域谁来解。网上的“C# Webapi实例”资源不少多数是教程截图和残缺代码真正能把项目从零跑到联调、发布再到排错的可运行Demo含源码反而不容易找。这类资源的真正价值不在代码本身而在于把接口设计、CORS、Swagger、IIS发布这些职场硬骨头串成一条完整链路。它适合刚入行做企业级系统的初级开发也适合准备跳槽但项目经验全是Razor页面的C#工程师。别指望“终身受益”靠看一遍生效照着跑通一遍再补两个自测动作才算真的变成你的。2. 前后端分离的架构认知先搞清WebAPI在拆什么、怎么选型2.1 前后端分离在拆什么路由、数据契约与状态管理的分工先说结论前后端分离拆掉的不是“代码”而是三个硬边界——路由、数据契约和状态管理。很多人把它理解成“后端用WebAPI前端用Vue就行”结果联调时路由对不上、字段对不上、登录状态对不上三个问题来回折腾。拿Java生态做对比可能更直观。若依框架前后端分离版、Spring BootVue课程满大街都是C#阵营里成体系的Demo反而少。所以看到一份源码完整的C# WebAPI Demo值得花时间把链路捋清楚。典型的使用场景包括三类第一类给Web页面提供数据第二类给移动App提供接口第三类给工业上位机或设备管理系统做数据对接。这三类场景的共同点是前端不关心后端数据库长什么样只关心HTTP接口返回的JSON够不够稳定。路由边界要拆清楚。传统MVC时代URL对应的是cshtml页面路由由服务端生成页面跳转都是服务端回302。前后端分离之后前端自己管理路由表Vue Router的菜单树、组件的跳转关系后端Route特性只描述资源路径比如/api/products、/api/orders/{id}。后端路由一旦发布出去就是契约前端在代码里到处引用的路径不可能跟着你隔三差五改。所以资源命名从第一天就要定好用名词复数、层级用斜杠、查询参数用驼峰比如GET /api/products?categoryId3page1不要在接口路径里写动词。数据契约更隐蔽。后端把数据库表实体一层层剥开暴露给前端的应该是精简后的DTO。这个习惯很多从Razor转过来的同事容易漏结果就是EF Core实体直接返回导航属性被序列化器一路展开遇到循环引用直接500。这个坑太常见了后面避坑手册单独讲。状态管理是最大观念差异。WebAPI默认无状态连接信息不能像Session那样存在服务器上。要么前端每次请求带Token要么后端用Redis存会话标识二选一。Token的过期时间、刷新策略、哪些接口需要认证这些要在接口文档里写明否则前端换一个人接手又会把401当成“后端bug”来跟你吵。2.2 WebAPI与MVC Controller怎么选三种不该用WebAPI的场景从方法签名就能看出两者差别。MVC的Action返回ActionResult里面可以是View、File、JsonResultApiController的Action返回的是数据本身加上HTTP状态码配合[ApiController]特性会自动取请求体、自动做模型校验并要求使用属性路由。你用dotnet new webapi创建出来的项目默认就是属性路由[Route(api/[controller])]这个[controller]占位符会在运行时替换成控制器名。但有几种场景其实不该硬上WebAPI。第一种是纯SEO内容站服务端渲染对搜索引擎更友好新闻站、文档站、门户首屏都在依赖直出HTML你强行WebAPIVue等于把SEO路基拆了。第二种是重度依赖Session的老内部系统所有页面交互都靠ViewBag和Session传递用户上下文改造成WebAPI意味着要重新设计认证方案这个成本不是单靠一个接口层能扛下来的。第三种是团队只有两三个人、没有专职前端用Razor少量jQuery反而更省事硬上前后端分离只会让两个端都拖慢。反过来当项目要同时支撑Web、App、第三方系统对接或者前端团队和后端团队能独立排期、独立发布时前后端分离的WebAPI就是性价比最高的选择。判断标准很简单你是否有两个以上不同形态的客户端在消费同一份数据。有一个就值得拆没有就先不折腾。我自己接触过的不少C#上位机项目数据采集服务已经把实时状态暴露成Swagger接口再由桌面客户端和Web管理后台分别消费这种模式在生产环境大量复现也是C#技术栈从业者最值得掌握的职场形态。3. 从零搭建WebAPI Demo三条命令生成骨架再跑通一个业务接口3.1 用.NET CLI建项目默认模板、Controller目录与文件结构我建议直接用.NET CLI不要一开始就依赖Visual Studio图形向导。职场上总有一天你会遇到“只有命令行和Docker容器”的环境。打开终端执行dotnet new webapi -n ZuiJing.Api -f net8.0 cd ZuiJing.Api dotnet run第一条命令的-n指定项目名-f net8.0指定目标框架。如果你公司服务器装的是.NET 6改成-f net6.0。第一条命令创建出来的是最小API模板Minimal API没有Controllers目录接口直接用app.MapGet写在Program.cs里。这种写法的确轻量但大多数职场老项目用的还是Controller风格所以我一般会手动补出目录最终结构长这样ZuiJing.Api/ ├── Controllers/ # 存放 ProductsController.cs ├── Models/ # 存放 Product.cs实体或DTO ├── Services/ # 存放 ProductService.cs业务服务 ├── Program.cs # 入口中间件管道 ├── appsettings.json └── ZuiJing.Api.csproj补好目录后Program.cs要做两处调整注册Controller服务以及启用Controller路由。不要漏掉MapControllers()否则你会发现自己写的Controller一个都访问不到接口全部404。这也算WebAPI新手最常见的“黑匣子问题”先记下。3.2 写一个产品管理接口Model、Service、Controller完整链路先建Models/Product.cs这是最基础的实体namespace ZuiJing.Api.Models; public class Product { public int Id { get; set; } public string Sku { get; set; } string.Empty; public string Name { get; set; } string.Empty; public decimal Price { get; set; } public DateTime CreatedAt { get; set; } DateTime.UtcNow; }然后在Services/ProductService.cs写一个内存仓储。这里用ConcurrentDictionary模拟数据表方便你没接数据库时也能看到完整链路using System.Collections.Concurrent; using ZuiJing.Api.Models; namespace ZuiJing.Api.Services; public class ProductService { private readonly ConcurrentDictionaryint, Product _store new(); private int _seq 1; public TaskProduct? GetByIdAsync(int id) Task.FromResult(_store.TryGetValue(id, out var product) ? product : null); public TaskProduct CreateAsync(Product product) { product.Id _seq; _store[product.Id] product; return Task.FromResult(product); } }我故意把方法都加上了Async后缀并返回Task即使内部没有真正的异步I/O。这不是闲得慌而是职场代码一旦接上EF Core或者消息队列调用方不需要改签名。先写成异步风格后面才不会拆东墙补西墙。最后是Controllers/ProductsController.csusing Microsoft.AspNetCore.Mvc; using ZuiJing.Api.Models; using ZuiJing.Api.Services; namespace ZuiJing.Api.Controllers; [ApiController] [Route(api/[controller])] public class ProductsController : ControllerBase { private readonly ProductService _service; public ProductsController(ProductService service) { _service service; } [HttpGet({id:int})] public async TaskActionResultProduct GetById(int id) { var product await _service.GetByIdAsync(id); if (product null) { return NotFound(new { code 40401, message $product {id} not found }); } return Ok(product); } [HttpPost] public async TaskActionResultProduct Create([FromBody] ProductInput input) { var product new Product { Sku input.Sku, Name input.Name, Price input.Price }; var created await _service.CreateAsync(product); return CreatedAtAction(nameof(GetById), new { id created.Id }, created); } } public class ProductInput { public string Sku { get; set; } string.Empty; public string Name { get; set; } string.Empty; public decimal Price { get; set; } }[Route(api/[controller])]里的[controller]会替换成Products所以完整路径是/api/products。[FromBody]告诉模型绑定器从请求体里读JSON不要从URL里读。CreatedAtAction是HTTP 201的标准写法它会在响应头生成一个Location字段指回新资源的详情地址前端拿到201再去GET一次就能拿到完整数据。不要贪图省事一律返回200前后端协作最忌讳状态码语义模糊。3.3 Swagger Postman联调参数返回码、字段命名与验证要点运行dotnet run后浏览器访问http://localhost:5000/swagger/index.html能看到Swagger页面列出刚才的GET和POST接口。点开POST点Try it out在请求体里填{ sku: A1001, name: 温度传感器, price: 19.5 }这里有个容易误解的点C#属性名是Sku、Name与JSON字段用驼峰sku、name都行。ASP.NET Core的System.Text.Json默认配置了camelCase命名策略而且反序列化时大小写不敏感所以前端传Sku还是sku都能映射上。真正会翻车的是DateTime格式后面讲时区时细说。返回码的约定要当成接口规范来立200代表正常返回201代表创建成功400代表参数校验失败401代表未认证404代表资源不存在500代表服务器内部错误。Postman里新建一个RequestContent-Type选application/jsonBody用raw发一个POST请求看返回体是不是JSON。如果你拿到201且响应头Location指向/api/products/1说明链路通了。模板自带的WeatherForecast示例接口建议直接删掉留着只会干扰团队阅读PR。删掉后程序还能正常编译运行Swagger页面也更干净。4. Vue前端联调C# WebAPICORS三个必调参数与IIS发布配置4.1 跨域配置开发环境CORS的三个必调参数前端Vue开发环境跑在http://localhost:5173后端跑在http://localhost:5000端口不同浏览器同源策略会直接拦掉所有请求。最常见的报错是Access to fetch at http://localhost:5000/api/products from origin http://localhost:5173 has been blocked by CORS policy解决方式是在Program.cs里注册一个命名的CORS策略var builder WebApplication.CreateBuilder(args); builder.Services.AddCors(options { options.AddPolicy(AllowFrontend, policy { policy.WithOrigins(http://localhost:5173, http://localhost:8080) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); builder.Services.AddControllers(); var app builder.Build(); app.UseCors(AllowFrontend); app.UseAuthorization(); app.MapControllers(); app.Run();这里有三个必调参数。第一个是AllowedOrigins开发环境用WithOrigins把前端地址写死不要用AllowAnyOrigin()。第二个是AllowedHeaders前端要带Authorization头做认证就必须允许该头省事做法是.AllowAnyHeader()。第三个是AllowedMethods接口有PUT和DELETE要补上.AllowAnyMethod()。还有一点容易翻车.AllowCredentials()不能和.AllowAnyOrigin()同用同时出现时.NET会在运行时直接抛异常。Cookie和JWT要带凭据就必须写明确Origin。调试跨域问题时打开浏览器Network面板点那个失败的Request先看预检OPTIONS的响应头里有没有Access-Control-Allow-Origin。如果响应头没回显Origin优先检查中间件顺序UseCors必须放在UseAuthentication和UseAuthorization之前。中间件顺序错了策略配置得再完整也会被跳过。4.2 发布到IISWeb.config、应用池与路径重写组合开发环境跑通了接下来是发布。命令行执行dotnet publish -c Release -o ./publish把publish目录整个拷到服务器IIS创建站点指向这个目录。注意web.config必须放在站点根目录内容类似configuration system.webServer handlers add nameaspNetCore path* verb* modulesAspNetCoreModuleV2 resourceTypeUnspecified / /handlers aspNetCore processPathdotnet arguments.\ZuiJing.Api.dll stdoutLogEnabledtrue stdoutLogFile.\logs\stdout hostingModelinprocess / /system.webServer /configuration发布时这个文件会自动生成到输出目录不用手写。但你要知道它的原理processPathdotnet表示用dotnet命令启动应用arguments指向发布产物里的DLL。如果服务器没有安装ASP.NET Core Hosting BundleIIS会报500.19或500.30去微软官网下载对应版本的Hosting Bundle装上即可。在IIS里创建站点后应用池的.NET CLR版本一定要选“无托管代码”选成Classic或Integrated托管模式会导致进程回收接口全部报500。如果站点不是在根路径而是挂在虚拟目录下比如https://server/appdir前端请求的BaseURL要写成/appdir/api/...Swagger页面也会受影响。此时需要在Program.cs开头加一行var app builder.Build(); app.UsePathBase(/appdir);这行代码必须在Swagger中间件之前执行否则你会看到Swagger页面能打开但/swagger/v1/swagger.json始终404。这是搜索量极高的问题下面避坑章节展开讲。4.3 Swagger作为前后端契约让前端从swagger.json生成请求代码Swagger的核心价值不在于那个好看页面而在于/swagger/v1/swagger.json是一份机器可读的接口契约。后端启动服务后这份JSON包含了所有接口的路径、参数、请求体结构、返回类型。前端拿到这份JSON可以用openapi-generator或swagger-typescript-api直接生成TypeScript的API客户端代码请求函数不再手写字段名也不容易拼错。实际操作是这样的后端把swagger.json导出给前端同事前端跑一句npx swagger-typescript-api -p http://localhost:5000/swagger/v1/swagger.json -o ./src/api生成的文件里每个接口对应一个函数入参类型和返回类型都是强类型前端写页面的时候按函数名调用就行。这样联调时最大的争议——字段名对不上、类型不匹配——直接消失了。很多人在生产环境把Swagger关了怕暴露接口结构。我的建议是内网环境保留Swagger但加一层认证或者限制IP访问。跨部门联调、移动端同事排查问题、新成员熟悉项目都靠这个页面。关掉再开会沟通的成本比内网暴露接口的危险要高得多。5. WebAPI避坑手册Swagger 404、跨域失效、循环引用等5个高频问题5.1 发布后Swagger 404not found /swagger/v1/swagger.json现象本地dotnet run一切正常发布到IIS后访问/swagger/index.html页面空白或JS请求报404地址栏手动输入/swagger/v1/swagger.json直接not found。原因有两类。第一类是模板默认只在Development环境启用Swagger发布后如果ASPNETCORE_ENVIRONMENT被设成Productionif (app.Environment.IsDevelopment())这个分支直接跳过Swagger中间件根本没注册。第二类是站点部署在虚拟目录下路径前缀没匹配上Swagger UI加载了但API请求找不到json文件。解决先确认环境变量。在web.config的aspNetCore节点里可以显式指定aspNetCore processPathdotnet arguments.\ZuiJing.Api.dll stdoutLogEnabledtrue stdoutLogFile.\logs\stdout hostingModelinprocess environmentVariables environmentVariable nameASPNETCORE_ENVIRONMENT valueStaging / /environmentVariables /aspNetCore代码里不用IsDevelopment()做唯一判断而是写成本地用Development生产用Staging两者都注册Swagger但Staging之前加一层访问限制。如果部署在虚拟目录必须加app.UsePathBase(/appdir)并确保前端请求地址的BaseURL和页面访问路径一致。5.2 跨域失效OPTIONS预检被拦、浏览器报CORS错误现象接口在Postman里完全正常浏览器里跑Vue页面就报CORS错误Network面板里能看到一个OPTIONS请求返回403或没有响应头。原因带Authorization头或Content-Type: application/json的请求属于非简单请求浏览器会先发OPTIONS预检。后端CORS策略没配置完整或者AllowCredentials和AllowAnyOrigin同时使用导致配置冲突预检就过不去。解决按4.1的三参数配置把策略写完整。排错时看两点第一预检响应头Access-Control-Allow-Origin是否回显了前端Origin第二Access-Control-Allow-Headers是否包含Authorization。两边都没问题时再检查中间件顺序UseCors必须在认证授权之前很多人把这个顺序当玄学其实是有严格顺序要求的。5.3 JSON循环引用EF Core导航属性导致序列化栈溢出现象接口返回包含外键实体的列表时浏览器直接收到500日志里看到A possible object cycle was detected或Self referencing loop detected。原因EF Core实体类里导航属性互相引用。比如Order实体有一个Customer导航属性Customer又有一个Orders集合属性序列化器从Order出发找Customer又从Customer找Orders循环不终止直接抛异常。解决核心方案是不要让Controller直接返回实体建DTO只摘需要的字段把Customer变成CustomerId。赶进度时可以用兜底策略builder.Services.AddControllers() .AddJsonOptions(options { options.JsonSerializerOptions.ReferenceHandler ReferenceHandler.IgnoreCycles; });这个配置让序列化器遇到循环引用时自动忽略不再抛异常。但记住这是兜底不是设计。字段裸露给调用方这件事本身就有风险接口返回什么字段应当由后端控制而不是由数据库表结构决定。推荐用AutoMapper或者手写映射一个实体对应多个DTO列表页返回精简DTO详情页返回完整DTO。5.4 时间差8小时时区与DateTime序列化问题现象数据库存的时间是2025-06-01 10:00:00前端页面显示成18:00或者反过来前端传了个时间后端存进去少了8小时。原因后端用DateTime.NowKind为Local存储序列化成ISO 8601字符串时带08:00偏移前端按ISO解析后转成自己本地时区于是出现8小时偏差。反之如果后端存的是DateTime.UtcNow前端解析时又给当成北京时间加了8小时。解决前后端约定一条死规矩——接口传输统一用UTC时间字符串。后端实体属性用DateTime.UtcNow赋值序列化时统一输出带偏移的ISO格式builder.Services.AddControllers() .AddNewtonsoftJson(options { options.SerializerSettings.DateTimeZoneHandling DateTimeZoneHandling.Utc; });需要安装Microsoft.AspNetCore.Mvc.NewtonsoftJson包。前端拿到ISO字符串后用new Date(isoString)解析浏览器自动转本地时区显示就不会乱。绝不要自己拼接时间字符串那必然翻车。5.5 大文件上传超时IIS请求体限制与切片上传现象前端一次性上传50MB的设备固件或视频请求被IIS直接拦掉返回413或404.13或者一直没响应直到超时。原因IIS默认maxAllowedContentLength约30MBKestrel也有自己的默认请求体上限约28.6MB。两边任何一个不放开大文件请求都过不去。解决按业务调整两边配置。web.config里放开请求体限制system.webServer security requestFiltering requestLimits maxAllowedContentLength104857600 / /requestFiltering /security /system.webServer system.web httpRuntime maxRequestLength104857600 executionTimeout600 / /system.webmaxAllowedContentLength单位是字节104857600等于100MB。httpRuntime maxRequestLength单位是KB100MB就写102400。同时Controller上可以加[RequestSizeLimit(104857600)]特性。真正上的根治办法是前端做切片上传把文件切成每片2MB一片一片传后端接收完再合并。这样既不依赖服务器单次请求体上限上传失败还能断点续传。6. 从Demo到职场用三个自测动作确认你真正掌握了WebAPI6.1 自测一用REST客户端脚本跑通CRUD全链路看再多Demo不如自己写一个脚本把接口完整打一遍。最简单的方式是用PowerShell写个冒烟测试$base http://localhost:5000 # 1. 验证Swagger文档可访问 $swagger Invoke-RestMethod $base/swagger/v1/swagger.json Write-Host openapi: $($swagger.openapi) # 2. POST创建产品 $body { sku A1001; name 温度传感器; price 19.5 } | ConvertTo-Json $created Invoke-RestMethod $base/api/products -Method Post -Body $body -ContentType application/json Write-Host created id: $($created.id)这个脚本跑通说明Swagger注册、路由、模型绑定、JSON序列化这几层都没问题。把它存成一个smoke-test.ps1每次改完接口都跑一遍比肉眼点Swagger页面靠谱得多。6.2 自测二给接口补上JWT认证真正职场的WebAPI极少裸奔至少要会加一层JWT认证。做法不复杂注册AddAuthentication和AddJwtBearer服务在需要保护的Controller或Action上加[Authorize]特性再做一个POST/api/auth/login接口签发Token。前端拿到Token后存在localStorage请求时用Authorization: Bearer xxx带给后端。我建议你亲手做一遍因为这里面的细节——Token过期时间设多长、刷新Token怎么实现、不同角色用什么Claim区分——面试基本必问。做完这步你的WebAPI才算有职场形状。6.3 自测三写一个日志中间件观察请求全链路最后加一个自定义日志中间件打印每个请求的路径、耗时、状态码。中间件的本质是一组委托写一个之后你对ASP.NET Core请求管道的理解会上一个台阶。我的习惯是拿到任何一套不熟悉的框架先写一个最小自测脚本再翻源码细节。这套流程帮我避开不少“看着会”的坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表