最稳妥方式是用 httpclient 拼接查询字符串:对每个参数值调用 uri.escapedatastring(),用 dictionary 存键值对后组装;httpclient 必须复用,推荐 static readonly 或 ihttpclientfactory;注意请求头、重定向和抓包调试。

直接用 HttpClient 拼接查询字符串最稳妥,别碰 WebClient 或手动构造 HttpWebRequest——容易漏编码、乱字符、丢超时控制。
怎么安全拼接 GET 请求的 URL 参数
手动拼 ? 和 & 很危险:中文、空格、斜杠会直接导致 400 错误。必须对每个参数值调用 Uri.EscapeDataString(),不能只靠 Uri.EscapeUriString()(后者不处理 + 和空格)。
实操建议:
- 用
Dictionary<string string></string>存参数键值对,逐个EscapeDataString后用string.Join("&", ...)组装 - 避免直接字符串插值:
$"https://api.com/v1?name={name}"—— 一旦name是"a b",请求就失效 - 如果参数来自用户输入或数据库,必须逃逸;常量参数可省,但统一处理更安全
HttpClient 实例该复用还是每次 new
每次 new HttpClient 会导致端口耗尽(SocketException: Too many open files),尤其在高并发服务中。它本就是线程安全、设计为长期复用的。
实操建议:
- 在类级别声明
static readonly HttpClient,或注入IHttpClientFactory(.NET Core 2.1+ 推荐) - 不要设置
httpClient.Timeout为TimeSpan.MaxValue—— 会掩盖下游故障,建议 30 秒内 - 若需不同 BaseAddress 或 Header,用
new HttpClient(handler) { BaseAddress = ... },别反复 new 实例
遇到 401/403 或空响应体怎么办
GET 请求没 body,但常见错误其实出在 header 或重定向上。比如第三方接口要求 Authorization: Bearer xxx,或强制 User-Agent,或返回 302 却没自动跳转。
实操建议:
- 用
httpClient.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0")补全基础头 - 检查是否需要
Accept: application/json,有些 API 不带这个头就返回 HTML 错误页 - 禁用自动重定向:
httpClient.MaxAutomaticRedirections = 0,自己用response.Headers.Location调试跳转逻辑 - 用
response.Content.ReadAsStringAsync().Result快速看原始响应体(仅调试,生产用 await)
真正麻烦的是第三方文档写得模糊:比如声称“参数用 query 传”,结果实际校验了 Referer 或要求 TLS 1.2+。这时候抓包(Fiddler / Wireshark)比读文档管用。











