c# 如何调用webapi接口

接入与开发

给 .NET 开发者的 HttpClient 调用 WebAPI 落地写法:请求构造、超时并发、重试降级、日志与联调排查顺序。

写 C# 调 WebAPI,第一次能跑通不难,难的是接进项目以后:超时该设几秒、并发一上来就报限流、流式返回中途断掉、出问题只能靠猜。这篇不重复讲协议和客户端配置,只讲工程侧怎么写:一次请求怎么发、错误怎么分类、重试和降级放哪一层、日志该记什么,以及调不通时按什么顺序排查。

写 C# 调 WebAPI,第一次跑通其实不难,难的是接进项目以后:超时设几秒合适、并发一上来就报限流、流式返回中途断掉、出问题只能靠猜。这些都不是协议层的问题,而是工程层的问题。

这篇不重复讲 HttpClient 怎么 new、DI 怎么配,那些官方文档和站内已有篇目都写过。这里只讲一件事:一次调用怎么写成能长期活在项目里的方法——请求怎么构造、超时并发放哪、重试降级归谁管、日志记什么,以及调不通时按什么顺序排查。

把一次调用写成可复制的方法

最常见也最伤人的写法,是在业务方法里直接 new HttpClient()。短时间请求量大一点,连接池就耗尽,报出来的错还很难定位。正确做法是通过 IHttpClientFactory 拿实例,由容器管理生命周期。但也要留意:长生命周期的静态实例虽然省事,仍会碰到 DNS 变更不生效、默认请求头被别人改脏的问题,多线程共用时尤其明显。

  • URL 拼接、鉴权位置、Body 格式三件事先在纸上写清楚:鉴权放 Header 还是查询串、Body 是 JSON 还是表单,别一边写一边改
  • 序列化统一走一种:System.Text.Json 或 Newtonsoft 选一个,团队里混用迟早出现日期格式和空值处理不一致的问题
  • POST 用 HttpRequestMessage 显式设方法、头和内容,不要用 GetAsync 拼字符串绕过语义
  • 读响应先判 IsSuccessStatusCode,成功再按对象反序列化,失败保留原文,否则报错信息会被序列化异常盖掉

把这套流程封装成一个泛型方法:传 URL、HTTP 方法、请求模型,内部负责发请求、拿字符串、转成目标类型。业务代码只关心这几个参数,后面加超时、重试、日志才有统一入口。没有这层封装,每加一个能力都要在几十个调用点改一遍。

环节容易出错的做法建议改成
客户端获取方法内 new HttpClient()IHttpClientFactory 注入或命名客户端
请求构造GetAsync 拼查询串做写操作HttpRequestMessage 显式设方法与内容
响应处理直接反序列化不看状态码先判 IsSuccessStatusCode 再分支
错误处理catch 住全部异常统一吞掉按状态码与异常类型分类上抛

超时、并发和取消,别等出事再补

超时要分两层理解:HttpClient.Timeout 是这次请求的整体上限,CancellationToken 是调用方主动喊停。两者同时存在时,谁先触发谁生效。只设其中一个都不完整——只设 Timeout,上层业务想提前放弃时请求还在跑;只传 Token,忘了设上限,对方不响应就能一直挂着。

具体设几秒,取决于你自己链路的延迟基线,不要抄别人的数字。先到 /tools/speed-test 量一遍真实往返时间,再留出合理余量。同一套参数换到不同地区、不同网络出口,结果可能差很多。

  1. 先量基线用 /tools/speed-test 对目标端点采样,记录正常延迟区间,而不是拍一个整数当超时
  2. 设两层保护HttpClient.Timeout 设整体上限,方法签名统一带 CancellationToken,由上层决定何时取消
  3. 限在途数量用 SemaphoreSlim 或 Parallel.ForEachAsync 限制并发数,不要靠 for 循环里堆 Task.Run
  4. 验证效果压一轮观察是否出现限流类状态码,出现就下调并发,上限口径可到 /ai-nav/category/ai-api 比对

这里有个例外:如果调用的是自己内网、延迟稳定可控的服务,并发可以放得比调外部接口宽一些。外网链路受运营商和对端限流影响,参数就得保守。这个判断不能反过来用。

重试、降级和幂等:哪些能重,哪些不能

很多人一遇到失败就加循环重试,结果是参数写错了也重试三次,白白浪费时间还掩盖了真正的问题。正确顺序是先分类,再决定要不要重试。

失败类型能否重试处理方式
连接失败、网关超时可以带退避与抖动,限次数
限流类状态码可以延长间隔,必要时降并发
鉴权失败、参数错误不可以直接上抛,重试无意义
资源不存在不可以检查目标是否正确
扣款、下单等写操作默认不重带幂等键才考虑自动重试

退避间隔要递增并加随机抖动,避免多个实例同一时刻一起重试把对端再打挂。次数要有上限,超过了就交给上层处理或走降级。降级的顺序建议是:换备用端点或备用模型、缩短要求的回复长度、返回缓存或兜底文案。每次降级都要打点记录,否则线上悄悄降级运行,过很久才发现。

  • 重试只放在幂等请求上,写操作要么带幂等键,要么不自动重试
  • 退避间隔递增加抖动,设最大次数,避免重试风暴
  • 降级路径要打点,能被监控看见,不能只在代码里默默走掉
  • 错误分类沿用本区已有篇目的口径,别自己另起一套命名

日志记什么,以及调不通时的排查顺序

日志不是记得越多越好。每条请求至少要能回答三件事:发了什么、花了多久、结果算哪一类。具体字段可以固定成:时间、目标、方法、耗时、状态码、重试次数、错误分类、请求标识。请求正文只在脱敏后按需记录,Key 和用户数据绝不能整段进日志。

  1. 第一步查连通先确认域名解析和网络出口正常,curl 或简单脚本能通再说别的,这一步不通后面都是白费
  2. 第二步看状态码按状态码归类,鉴权类、限流类、网关类各有各的处理方向,别混在一起猜
  3. 第三步对字段检查请求体字段名、类型、必填项是否和对端文档一致,大小写和嵌套层级最容易出错
  4. 第四步才怀疑服务端前三步都排除后,再联系对端或换备用端点验证,避免一上来就甩锅

还有个容易踩的坑:流式接口断流和普通请求失败不是同一条链路,排查手段也不同,别拿普通请求的经验套上去。这个顺序做成项目里的一页清单,新同事第一次排错就照着走,比口头传授靠谱。例外情况是:如果错误信息本身就明确指向参数问题,可以直接跳到第三步,不必死板走完四步。

下一步想知道自己链路的真实延迟和限流表现,用真实端点跑一遍再定参数,比抄别人的配置靠谱得多。

常见问题

HttpClient 该用单例还是每次都 new?

都不要。用 IHttpClientFactory 按名取实例,它帮你管连接池和 DNS 刷新;直接 new 多了会耗尽端口,做成一个静态全局实例则容易因为改默认头和超时而互相影响。核心是把创建收敛到一处,由容器统一提供。

接口偶尔超时,是不是把超时时间调大就行?

调大只是把问题往后推。先区分是网络慢、服务端慢还是并发太高排队。用日志里的耗时分布看是普遍慢还是偶发尖刺,普遍慢就该改小请求或换端点,偶发尖刺优先加退避重试和限流,而不是无限加时间。

重试几次比较合适?

从两三次起步,配合递增间隔和上限总时长,别写成无脑循环。判断依据有三个:这个接口是否幂等、上层能不能等、失败重试要付多少调用成本。任何一条不满足就减少次数甚至不重试,改为直接返回错误让业务侧决定。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。