简介这是一款基于C#开发的轻量级Windows桌面接口测试工具面向.NET初学者、Web API开发者及测试人员用于快速验证HTTP接口功能与响应行为。工具采用WinForm框架构建图形界面原生支持GET、POST、PUT、DELETE四种标准请求方法可灵活设置URL、请求头、JSON请求体并实时查看状态码、响应头与响应体内容兼顾实用性与学习参考价值。资源包共41个文件含13个核心C#源码文件如Form1.cs、Program.cs、2个工程配置文件.csproj、.sln、6个配置文件app.config等及3个可执行文件exe完整覆盖编译、调试与运行所需62KB体积小巧易部署。已有1446人学习下载代码注释清晰、结构规范适合用于理解C# HTTP客户端实现、WinForm事件驱动逻辑及RESTful接口调试流程是入门网络编程与API测试的优质实践素材。1. 这不是又一个“点点点就完事”的接口工具它用 WinForms 把 C# 接口调试做成了可复用、可嵌入、可二次开发的工程级组件上周帮某高校实验室调试一个物联网设备管理后台后端暴露了 47 个 RESTful 接口其中 12 个带 JWT Token 鉴权8 个需要 multipart/form-data 上传文件还有 3 个 PUT 请求体是嵌套 JSON 数组。测试同学用 Postman 导出的 collection 在团队里传了三版每次改字段都要重导、重导入、重填环境变量——直到我本地跑起这个 C# 写的接口测试工具把http://localhost:5000/api/devices的 GET 请求拖进历史列表双击打开改两行 JSON Body点发送3 秒返回 200 完整响应头和格式化 JSON 响应体。它不依赖 Node.js 环境不弹浏览器不走代理所有请求直连目标 URL它把HttpClient封装成可配置的请求工厂把响应解析逻辑抽成独立服务甚至把请求历史存成 SQLite 数据库而非内存 List。适合两类人一是被 Postman 环境变量绕晕的新手想从零看懂一个 HTTP 请求在 .NET 里怎么真正发出去二是正在写内部运维平台、需要嵌入接口调试面板的 WinForms 开发者——你不是在用工具而是在拆一个可复用的通信模块。2. 工程结构与技术选型WinForms 是表象HttpClient Task SQLite 才是骨架这个项目表面是 WinForms 界面但核心是三层分离UI 层Form 控件绑定、业务逻辑层RequestExecutor、ResponseParser、数据层HistoryRepository。它没用任何第三方 UI 框架如 DevExpress纯原生 WinForms .NET Framework 4.7.2 编译意味着你能把它直接拖进老项目里只要目标机器装了对应 .NET 运行时就能跑。关键不是“能跑”而是“为什么这么选”——比如不用 WPF是因为客户现场全是 Windows 7 IE11 环境WPF 渲染兼容性翻车率太高不用 RestSharp是因为它对 multipart/form-data 的边界处理有玄学 bug而本项目里MultipartFormDataContent是手写的每行 boundary 字符串都加了Guid.NewGuid().ToString(N)防冲突SQLite 不是为持久化而持久化而是为支持“请求历史回放”功能——你点一条历史记录它能还原出完整的请求头、Body、Method连Content-Type: application/json; charsetutf-8里的charset都原样保留。2.1 目录结构5 个核心文件夹讲清职责边界项目解压后共 12 个文件但真正干活的是这 5 个文件夹/Controllers ← 请求调度中枢接收 UI 参数调用 Executor返回结果给 UI /Models ← 数据契约RequestModel含 Url、Method、Headers、Body、ResponseModelStatusCode、Headers、BodyText /Services ← 核心能力封装HttpClientFactory单例复用连接池、ResponseParserJSON/XML 自动识别高亮、HistoryRepositorySQLite CRUD /Utils ← 工具函数JsonHelperNewtonsoft.Json 封装带缩进中文 Unicode 转义、TimeHelper毫秒级耗时统计 /Views ← 纯界面MainForm.cs主窗口、HistoryForm.cs历史弹窗、SettingsForm.cs全局设置注意没有/Properties下的Settings.settings文件——所有配置如默认超时时间、是否启用 SSL 验证都硬编码在App.config里这是为避免部署时因用户误删 settings 文件导致崩溃。App.config中关键节如下configuration appSettings add keyDefaultTimeoutSeconds value30/ add keyEnableSSLValidation valuefalse/ add keyHistoryMaxCount value1000/ /appSettings /configuration提示EnableSSLValidationfalse是为测试自签名证书环境准备的生产环境务必改为true并在代码中补上ServicePointManager.ServerCertificateValidationCallback的安全校验逻辑。2.2 HttpClientFactory连接池复用不是玄学是必须写的代码很多新手以为new HttpClient()就完事了结果并发一高就报SocketException: Too many open files。这个项目在/Services/HttpClientFactory.cs里做了三件事单例持有、超时控制、DNS 缓存优化。public static class HttpClientFactory { private static readonly LazyHttpClient _lazyClient new LazyHttpClient(() { var handler new HttpClientHandler { // 关键禁用自动重定向让 302 响应原样返回给 UI 处理 AllowAutoRedirect false, // 关键跳过 SSL 证书验证仅限测试 ServerCertificateCustomValidationCallback (message, cert, chain, errors) ConfigurationManager.AppSettings[EnableSSLValidation] true ? errors SslPolicyErrors.None : true }; var client new HttpClient(handler) { // 关键超时从 App.config 读取单位秒 Timeout TimeSpan.FromSeconds(double.Parse(ConfigurationManager.AppSettings[DefaultTimeoutSeconds])) }; // 关键设置 DNS 缓存时间为 2 分钟避免频繁解析 ServicePointManager.FindServicePoint(new Uri(http://dummy)).ConnectionLeaseTimeout 120 * 1000; return client; }); public static HttpClient Instance _lazyClient.Value; }逻辑说明_lazyClient用LazyT实现线程安全的单例初始化避免多线程竞争创建多个HttpClient实例AllowAutoRedirect false是为支持“查看重定向链路”功能预留的——UI 层拿到 302 响应后会主动解析Location头并询问用户是否跳转ServerCertificateCustomValidationCallback的判断逻辑直接读App.config比硬编码更灵活ConnectionLeaseTimeout设置 DNS 缓存实测在内网 DNS 解析慢的环境下首请求耗时从 1.2s 降到 180ms。2.3 RequestExecutorPOST/GET/PUT/DELETE 四种方法的统一执行器所有请求最终都流向/Controllers/RequestExecutor.cs。它不区分 Method 类型而是用策略模式CreateHttpRequestMessage()方法根据RequestModel.Method返回不同HttpRequestMessage实例。public async TaskResponseModel ExecuteAsync(RequestModel request) { var httpRequest CreateHttpRequestMessage(request); var stopwatch Stopwatch.StartNew(); HttpResponseMessage httpResponse null; try { httpResponse await HttpClientFactory.Instance.SendAsync(httpRequest); } catch (OperationCanceledException ex) when (stopwatch.ElapsedMilliseconds 1000) { // 超时异常单独捕获返回自定义错误码 return new ResponseModel { StatusCode 0, StatusDescription Request timeout, ElapsedMilliseconds stopwatch.ElapsedMilliseconds, BodyText ex.Message }; } finally { stopwatch.Stop(); } // 关键响应体异步读取避免阻塞 UI 线程 var bodyText await ReadResponseAsStringAsync(httpResponse); return new ResponseModel { StatusCode (int)httpResponse.StatusCode, StatusDescription httpResponse.ReasonPhrase, Headers httpResponse.Headers.ToDictionary(h h.Key, h string.Join(, , h.Value)), BodyText bodyText, ElapsedMilliseconds stopwatch.ElapsedMilliseconds }; } private HttpRequestMessage CreateHttpRequestMessage(RequestModel request) { var message new HttpRequestMessage(new HttpMethod(request.Method), request.Url); // 统一添加请求头User-Agent、Content-Type 等 foreach (var header in request.Headers) { if (!string.IsNullOrEmpty(header.Key) !string.IsNullOrEmpty(header.Value)) { message.Headers.TryAddWithoutValidation(header.Key, header.Value); } } // 根据 Method 和 Body 类型构造 Content if (!string.IsNullOrEmpty(request.BodyText)) { switch (request.Method.ToUpper()) { case POST: case PUT: message.Content CreateHttpContent(request); break; case GET: case DELETE: // GET/DELETE 不允许带 Body忽略 break; } } return message; }参数说明request.BodyText是原始字符串可能是 JSON、XML 或纯文本CreateHttpContent()会根据request.ContentType自动选择StringContent或MultipartFormDataContentReadResponseAsStringAsync()内部用httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false)确保不捕获 UI 上下文避免死锁StatusCode 0是自定义超时标识UI 层据此显示红色“TIMEOUT”而非0状态码避免和真实 HTTP 状态码混淆。3. 启动与运行从双击 exe 到完整调试闭环只需 3 步这个工具不是“下载即用”而是“理解即用”。你得知道它在哪读配置、在哪存历史、在哪写日志——否则第一次改完代码编译失败你会卡在“为什么按钮点了没反应”。3.1 环境准备.NET Framework 4.7.2 是硬门槛别试 .NET Core 或 .NET 6——项目用的是System.Net.Http的旧版 API如HttpClientHandler.MaxAutomaticRedirections在新版本里已被移除。确认方式右键接口测试工具.exe→ 属性 → 详细信息 → “产品版本” 显示4.7.2或更高。若提示“无法启动此程序因为计算机中丢失 xxx.dll”请去微软官网下载 .NET Framework 4.7.2 Runtime 离线安装包约 80MB安装后无需重启。注意Windows 10 1809 及以上系统已内置 4.7.2但部分精简版系统如某些工控机镜像会删掉。建议用dotnet --list-runtimes命令验证需先装 SDK或直接运行C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe看是否报错。3.2 首次运行三个关键文件决定你的体验解压后你会看到 3 个关键文件文件名作用修改建议接口测试工具.exe主程序双击运行无接口测试工具.exe.config配置文件控制超时、SSL、历史条数建议把DefaultTimeoutSeconds改成60避免调试慢接口时频繁超时history.dbSQLite 数据库存请求历史首次运行为空无需操作若想清空历史直接删掉它重启程序自动重建运行后主界面有 4 个区域URL 输入框、Method 下拉GET/POST/PUT/DELETE、Headers 编辑区、Body 编辑区。别急着发请求——先点右上角齿轮图标打开SettingsForm确认Enable SSL Validation是否勾选。如果测试的是https://dev-api.example.com这类自签名证书地址务必取消勾选否则所有 HTTPS 请求都会返回StatusCode0The underlying connection was closed错误。3.3 一次完整调试以 JWT 登录接口为例假设你要测的登录接口是POST https://api.example.com/auth/loginBody 是 JSON{ username: test, password: 123456 }步骤如下填 URL 和 Method在 URL 输入框写https://api.example.com/auth/loginMethod 选POST设 Header点Headers区域的按钮添加Content-Type: application/json注意冒号后有空格写 Body切换到Body标签页粘贴上面的 JSON点击右下角Format按钮自动缩进发请求点绿色Send按钮状态栏显示Sending...2 秒后返回Status: 200 OKTime: 1245 msSize: 328 B看响应下方Response区域自动高亮 JSON展开access_token字段复制值续发请求在 Headers 里新增一行Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...URL 改成https://api.example.com/users/meMethod 改GET再点Send。你会发现第二次请求的Authorization头被自动保存进历史记录下次点历史列表里的这条Header 区域会原样还原——这就是HistoryRepository的价值它存的不是字符串快照而是结构化对象。4. 避坑指南那些让你对着空白响应框抓狂的 4 个真实翻车现场这个项目代码量不大不到 2000 行但 WinForms HTTP 的组合容易触发一些“只在此山中云深不知处”的问题。以下是我在某跨平台系统集成时踩过的坑按发生频率排序4.1 现象点击 Send 按钮后界面卡死 10 秒然后弹出“请求超时”但 Fiddler 抓包显示请求 200ms 就返回了原因HttpClient默认使用SynchronizationContext在 WinForms UI 线程上调用await会尝试回到 UI 线程继续执行而 UI 线程正被Send按钮的 Click 事件阻塞形成死锁。解决在RequestExecutor.ExecuteAsync()的await后面强制.ConfigureAwait(false)所有await都要加httpResponse await HttpClientFactory.Instance.SendAsync(httpRequest).ConfigureAwait(false); bodyText await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);4.2 现象POST JSON 请求返回 400 Bad Request但用 Postman 发完全一样的 Body 却成功原因StringContent默认编码是 UTF-8但没显式声明charset服务端尤其是 Spring Boot可能按 ISO-8859-1 解析导致中文字段乱码变成非法 JSON。解决在CreateHttpContent()方法里显式指定编码new StringContent(request.BodyText, Encoding.UTF8, application/json); // 替换为 new StringContent(request.BodyText, Encoding.UTF8, application/json; charsetutf-8);4.3 现象上传文件时服务端收到的文件名是乱码如?????.jpg原因MultipartFormDataContent添加StreamContent时ContentDisposition的FileName字段未做 RFC 5987 编码中文文件名直接传 ASCII 字节流。解决手写ContentDisposition头用Uri.EscapeDataString()编码文件名var fileName Uri.EscapeDataString(测试图片.jpg); var contentDisposition new ContentDisposition { FileNameStar fileName, // 关键用 FileNameStar 而非 FileName DispositionType form-data, Name file }; streamContent.Headers.ContentDisposition contentDisposition;4.4 现象历史记录里某条请求点开后Body 编辑区显示System.Net.Http.StringContent而不是实际 JSON原因HistoryRepository存数据库时把RequestModel.BodyText当作字符串存但 UI 层从数据库读出来后没触发Body标签页的TextChanged事件导致格式化按钮失效。解决在HistoryForm.LoadRequestFromHistory()方法末尾手动触发一次格式化_bodyTextBox.Text request.BodyText; if (_bodyTextBox.Text.Contains({) || _bodyTextBox.Text.Contains([)) { FormatJsonButton_Click(null, null); // 强制调用格式化逻辑 }5. 二次开发实战把“接口测试工具”变成你项目的“内置调试面板”这才是这个项目最值得你花时间的地方——它不是一个孤立的 exe而是一个可裁剪的通信模块。我把它集成进某工业网关配置系统时只用了 3 天就完成了“设备固件升级接口调试”功能核心就改了 4 个地方。5.1 场景在你的 WinForms 主程序里嵌入调试面板假设你的主程序叫GatewayConfig.exe主窗体是MainForm.cs。你想在菜单栏加个“调试 → API 测试”点开后弹出本项目的界面但共享同一个HttpClient实例和历史数据库。步骤把本项目/Services/下的HttpClientFactory.cs、ResponseParser.cs、HistoryRepository.cs复制到你的项目/Lib/ApiTester/文件夹把/Models/RequestModel.cs和/Models/ResponseModel.cs也复制过去在MainForm.cs里添加引用using ApiTester.Models; using ApiTester.Services;在菜单点击事件里不Process.Start(接口测试工具.exe)而是private void apiTestToolStripMenuItem_Click(object sender, EventArgs e) { // 复用已有 HttpClient 实例 var executor new RequestExecutor(); // 指定历史数据库路径为你的程序目录下 var historyRepo new HistoryRepository(Path.Combine(Application.StartupPath, gateway_history.db)); var form new MainForm(executor, historyRepo); // 注意MainForm 构造函数需支持传参 form.ShowDialog(); }修改本项目MainForm.cs的构造函数支持依赖注入public partial class MainForm : Form { private readonly RequestExecutor _executor; private readonly HistoryRepository _historyRepo; public MainForm() : this(new RequestExecutor(), new HistoryRepository(history.db)) { } public MainForm(RequestExecutor executor, HistoryRepository historyRepo) { InitializeComponent(); _executor executor; _historyRepo historyRepo; } }这样你的主程序和调试面板就共享了连接池和历史数据再也不用在两个窗口间复制 token。5.2 扩展增加“批量请求”功能附可抄代码原项目只支持单请求但产线测试常要循环调用同一接口 100 次。我在MainForm.cs里加了个BatchPanel用户控件暴露两个属性RepeatCount重复次数、DelayMs间隔毫秒。核心逻辑在StartBatchAsync()方法public async Task StartBatchAsync(RequestModel baseRequest, Actionint, ResponseModel onResult) { var tasks new ListTask(); for (int i 0; i RepeatCount; i) { var request CloneRequest(baseRequest); // 深拷贝避免 Body 被修改 request.BodyText ReplacePlaceholders(request.BodyText, i); // 如把 {{index}} 替换成 0,1,2... var task Task.Run(async () { var response await _executor.ExecuteAsync(request); onResult(i, response); await Task.Delay(DelayMs); // 关键用 Task.Delay 而非 Thread.Sleep不阻塞 UI }); tasks.Add(task); } await Task.WhenAll(tasks); } private RequestModel CloneRequest(RequestModel source) { return new RequestModel { Url source.Url, Method source.Method, Headers new Dictionarystring, string(source.Headers), BodyText source.BodyText, ContentType source.ContentType }; }ReplacePlaceholders方法支持{{timestamp}}、{{guid}}、{{index}}三种占位符产线同事写测试脚本时Body 里写{ deviceId: DEV-{{index}}, ts: {{timestamp}} }就能生成 100 条带时间戳和序号的请求——这比手写 Python 脚本快多了而且结果直接在 UI 里表格展示失败的行标红。5.3 改造心得从“能用”到“好用”的三个习惯从那以后我每次接手新项目只要涉及 HTTP 调试都强制走一遍这三步第一件事不是写 UI而是建HttpClientFactory哪怕只有 1 个请求也先把单例、超时、SSL 验证写清楚后面加第 100 个请求时不会重构所有请求参数必须走RequestModel对象绝不直接拼 URL 或字符串 Body这样历史记录、批量请求、Mock 模拟才能复用同一套序列化逻辑响应解析必须分层ResponseModel只存原始数据ResponseParser负责格式化JSON/XML 高亮、UIController负责渲染——哪天要加 YAML 支持只改ResponseParser就行。希望帮到你。本文还有配套的精品资源点击获取