ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C# 手写接口测试工具:HttpClient 实现四种请求与 WinForms 界面

C# 手写接口测试工具:HttpClient 实现四种请求与 WinForms 界面 简介这是一款基于C#与WinForm开发的HTTP接口测试工具面向需要调试API的开发者与C#学习者可解决日常接口验证中快速发起请求、查看响应的问题。工具支持POST、GET、PUT、DELETE四种常见请求方法能够填写请求体、设置请求头并展示状态码与响应内容适合开发调试、接口联调及教学参考等场景。资源包共41个文件以cs源码、config配置、exe可执行程序、csproj工程文件及resx资源文件为主另含pdb调试符号与sln解决方案整体约62KB结构完整便于直接运行或二次开发。目前已有1443人学习下载。代码注释清晰、可读性较好读者既能将其作为即用型接口调试工具也能通过阅读源码理解WinForm界面与HTTP通信的实现方式对学习C#网络编程与桌面应用开发具有参考价值。1. 一个 C# 接口测试工具为什么值得自己动手写线上联调最怕的不是接口报错而是你手里没有一个能立刻发请求、看响应、改参数的工具。浏览器地址栏只能发 GETPostman 装完还要登录账号团队里想统一一份测试用例又得付费。于是很多人动了自己用 C# 写一个接口测试工具的念头——支持 POST、GET、PUT、DELETE 四种请求能填 URL、请求头、请求体点一下就能看到状态码和返回内容。这个方向对 C# 开发者来说性价比很高HttpClient是官方库WinForms或WPF拖几个控件就能出界面一两天能跑通最小版本。它适合两类人一类是日常要调 REST 接口的后端和上位机开发者另一类是想通过一个真实小项目把 C# 网络编程、异步、委托这几块串起来的学习者。下面我按自己实际做过的路径把选型、请求封装、界面联动和踩过的坑讲清楚。2. 请求方法怎么封装HttpClient 的四种 Method 落地2.1 为什么选 HttpClient 而不是 HttpWebRequest.NET 里发 HTTP 请求有两条路老的HttpWebRequest和新的HttpClient。HttpWebRequest在 .NET Framework 时代是主力但它的 API 偏底层每次都要手动处理流、编码、连接释放写四个方法会重复大量代码。HttpClient从 .NET 4.5 开始提供天然支持异步、连接池复用、请求头统一管理而且HttpRequestMessage允许你在运行时动态指定HttpMethod这意味着 POST、GET、PUT、DELETE 可以共用一套发送逻辑只在构造请求时换一个 Method 对象。我一般会用一个HttpClient单例而不是每次请求都new。原因是HttpClient内部维护连接池频繁创建会导致 socket 耗尽这个坑在批量压测时特别明显。单例的写法// 全局唯一的 HttpClient避免频繁创建导致 socket 耗尽 private static readonly HttpClient _client new HttpClient { // 默认超时 30 秒可按接口实际情况调整 Timeout TimeSpan.FromSeconds(30) };参数说明Timeout控制单次请求的最长等待时间接口慢就调大但不要设成无限否则界面会一直卡在等待状态。如果你要测的是内网接口30 秒足够如果是跨地域调用可以放到 60 秒。2.2 用 HttpRequestMessage 统一四种请求核心思路是把「方法、URL、头、体」四样东西组装成一个HttpRequestMessage再交给HttpClient.SendAsync。GET 和 DELETE 通常不带请求体POST 和 PUT 需要带 Body。下面是一个可以直接抄的发送方法public static async Taskstring SendAsync( string method, // GET / POST / PUT / DELETE string url, // 完整请求地址 string body, // 请求体GET/DELETE 传 null Dictionarystring, string headers) // 自定义请求头 { // 根据字符串创建对应的 HttpMethod var request new HttpRequestMessage(new HttpMethod(method), url); // POST/PUT 才需要写 BodyGET/DELETE 跳过 if (!string.IsNullOrEmpty(body) (method POST || method PUT)) { // 默认按 JSON 发送字符集用 UTF-8 request.Content new StringContent( body, Encoding.UTF8, application/json); } // 逐个添加自定义请求头比如 Authorization、Cookie if (headers ! null) { foreach (var kv in headers) { // Content 相关的头要加到 Content 上其余加到 request 上 if (!request.Headers.TryAddWithoutValidation(kv.Key, kv.Value)) { request.Content?.Headers.TryAddWithoutValidation(kv.Key, kv.Value); } } } // 发送并读取响应文本 var response await _client.SendAsync(request); string result await response.Content.ReadAsStringAsync(); // 把状态码拼在结果前面方便界面直接展示 return ${(int)response.StatusCode} {response.StatusCode}\r\n{result}; }逻辑说明new HttpMethod(method)把界面传来的字符串转成方法对象这样四种请求走同一条代码路径。TryAddWithoutValidation用于添加非标准头避免因为头格式校验抛异常。状态码拼在返回文本最前面是为了让界面不用再单独解析。参数说明body只在 POST/PUT 时使用GET 传null即可headers用字典传入键值对就是请求头的名字和值。注意Content-Type这类头如果直接加到request.Headers会失败代码里已经做了回退到Content.Headers的处理。2.3 异步调用与界面线程的配合WinForms 里如果直接在按钮事件里.Result或.Wait()界面会假死。正确做法是事件处理器标async然后await发送方法private async void btnSend_Click(object sender, EventArgs e) { // 禁用按钮防止重复点击 btnSend.Enabled false; try { // 从界面控件读取参数 string method cboMethod.Text; string url txtUrl.Text; string body txtBody.Text; var headers ParseHeaders(txtHeaders.Text); // 异步发送await 期间界面保持响应 string result await HttpHelper.SendAsync(method, url, body, headers); txtResponse.Text result; } catch (Exception ex) { // 网络异常统一在这里兜底 txtResponse.Text 请求失败 ex.Message; } finally { btnSend.Enabled true; } }await会把耗时操作放到线程池完成后自动回到 UI 线程更新控件这是 WinForms 里最省心的写法。ParseHeaders是你自己写的一个小方法把多行文本按「键: 值」拆成字典注意冒号后面可能有空格要Trim。3. 界面与参数联动让工具真正好用3.1 方法切换时控件该显示什么四种请求对界面的要求不一样。GET 和 DELETE 不需要请求体POST 和 PUT 需要。如果不管选什么都显示请求体输入框用户会困惑。我的做法是在方法下拉框的SelectedIndexChanged事件里控制请求体区域的可见性private void cboMethod_SelectedIndexChanged(object sender, EventArgs e) { // 只有 POST 和 PUT 需要请求体 bool needBody cboMethod.Text POST || cboMethod.Text PUT; lblBody.Visible needBody; txtBody.Visible needBody; }这样切换方法时界面立刻反馈减少误填。请求头区域则始终显示因为 GET 也经常需要带Authorization或Cookie。3.2 请求头的解析与常见格式请求头输入框一般让用户一行写一个头格式是Key: Value。解析时要注意几个细节空行跳过、冒号只切第一个、值两边的空格去掉。下面是一个稳妥的解析方法private Dictionarystring, string ParseHeaders(string raw) { var dict new Dictionarystring, string(); if (string.IsNullOrWhiteSpace(raw)) return dict; // 按行拆分兼容 \r\n 和 \n var lines raw.Split(new[] { \r\n, \n }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { // 只切第一个冒号避免值里含冒号被切错 int idx line.IndexOf(:); if (idx 0) continue; string key line.Substring(0, idx).Trim(); string val line.Substring(idx 1).Trim(); if (key.Length 0) dict[key] val; } return dict; }参数说明IndexOf(:)只找第一个冒号这样Authorization: Bearer abc:def这种值里带冒号的头也能正确解析。RemoveEmptyEntries跳过空行避免用户多敲回车导致异常。3.3 响应展示与状态码着色响应区域如果只是一坨纯文本排查效率低。我习惯把状态码单独提出来2xx 显示绿色、4xx 显示橙色、5xx 显示红色。实现上可以在拿到结果后解析第一行// result 第一行是 200 OK 这种格式 var firstLine result.Split(\n)[0].Trim(); if (firstLine.StartsWith(2)) txtResponse.ForeColor Color.Green; else if (firstLine.StartsWith(4)) txtResponse.ForeColor Color.Orange; else if (firstLine.StartsWith(5)) txtResponse.ForeColor Color.Red; else txtResponse.ForeColor Color.Black;这个改动很小但联调时一眼就能看出请求是成功还是失败比逐行读返回内容快得多。4. 避坑与排查四个真实翻过车的地方4.1 中文请求体乱码现象POST 发送含中文的 JSON服务端收到的是乱码。原因StringContent默认编码不一定是 UTF-8或者服务端按 GBK 解析。解决构造StringContent时显式传Encoding.UTF8并在请求头里确认Content-Type带charsetutf-8。如果服务端只认 GBK就把Encoding.UTF8换成Encoding.GetEncoding(GBK)但这种情况现在很少见。4.2 请求头加不进去抛异常现象调用request.Headers.Add(Content-Type, application/json)直接抛InvalidOperationException。原因Content-Type、Content-Length属于内容头必须加到request.Content.Headers上不能加到request.Headers。解决用TryAddWithoutValidation并做回退先试request.Headers失败再试request.Content.Headers这也是 2.2 节代码里那样写的原因。4.3 界面卡死无响应现象点发送后窗口拖动不了像死机。原因在 UI 线程上同步等待异步方法比如写了SendAsync(...).Result。解决事件处理器改成async void用await调用绝不在 UI 线程上阻塞等待。如果某个老代码必须同步至少用Task.Run包一层但优先改异步。4.4 HTTPS 证书校验失败现象请求内网自签名 HTTPS 接口时报「基础连接已关闭」或证书错误。原因HttpClient默认校验证书链自签名证书过不了。解决如果确认是内网可信环境可以在HttpClientHandler里放宽校验但生产环境不要这么做var handler new HttpClientHandler { // 仅用于内网自签名证书调试生产环境务必去掉 ServerCertificateCustomValidationCallback (msg, cert, chain, errors) true }; var client new HttpClient(handler);注意这段代码只适合本地调试正式工具里应该保留校验让用户导入受信任证书。5. 进阶技巧把单次请求变成可复用的测试用例工具能发单次请求只是起点真正省时间的是把常用请求存下来批量跑。我的习惯是在界面上加一个「保存用例」按钮把方法、URL、头、体序列化成 JSON 存到本地文件下次直接加载。序列化用System.Text.Json就够public class ApiCase { public string Method { get; set; } public string Url { get; set; } public string Body { get; set; } public Dictionarystring, string Headers { get; set; } } // 保存 string json JsonSerializer.Serialize(apiCase, new JsonSerializerOptions { WriteIndented true }); File.WriteAllText(path, json); // 加载 var loaded JsonSerializer.DeserializeApiCase( File.ReadAllText(path));存成 JSON 的好处是可读、可版本管理团队里可以直接放进 Git 共享。批量跑的时候遍历用例列表逐个await SendAsync把结果汇总到一个表格里。这里有个细节批量跑不要并发太高内网接口一般 5 到 10 个并发就够太高会把测试环境打挂反而看不出问题。再进一步可以把断言也加进去。比如用例里存一个「期望状态码」字段跑完对比实际状态码不一致就标红。这样工具就从「手动发请求」变成了「轻量回归测试」每次改完接口跑一遍比人肉点一遍可靠得多。我自己做这类工具最大的教训是一开始总想功能全结果界面越堆越乱最后自己都不想用。后来砍到只留方法、URL、头、体、响应五个区域反而天天在用。先把最小闭环跑通再按实际痛点加功能这个顺序别反。希望帮到你。本文还有配套的精品资源点击获取
返回列表