
前言
嗨,大家好!
在現代軟件開發中,與外部服務進行通信幾乎是不可避免的任務。無論是獲取天氣預報、同步用戶數據,還是調用第三方支付接口,HTTP 請求都是最常見的手段之一。而在 .NET 生態系統中,HttpClient
無疑是處理這類任務的最佳工具。
HttpClient
支持多種 HTTP 請求方式(如 GET、POST、PUT、DELETE),使得與 Web API 的交互變得輕而易舉。
在性能方面,它支持異步操作,這意味著你可以同時發送多個請求而不阻塞主線程,性能杠杠的!
可以說,HttpClient
是我們與 Web 服務打交道的最佳伙伴!
然而,盡管 HttpClient
功能強大,但其復雜的配置和使用方式有時會讓人望而卻步,尤其是對于初學者來說。為了簡化這一過程,我決定封裝一個通用的方法,讓團隊成員能夠更加便捷地使用 HttpClient
調用 Restful API
接下來,讓我們一起看看詳細的代碼吧!
封裝方法
創建一個 HttpUtil 類,封裝所有與 HttpClient
相關的操作方法,留意注釋
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Connect.Enums;
using System.IO;
using System.Net;
using System.IO.Compression;
using Connect.Model;
using Newtonsoft.Json;
namespace Connect.Utils
{
public class HttpUtil
{
/// <summary>
/// 配置并返回一個 HttpClient 實例
/// </summary>
/// <param name="customHeaders">自定義請求頭</param>
/// <param name="proxySetting">代理設置</param>
/// <param name="url">請求的 URL</param>
/// <returns>配置好的 HttpClient 實例</returns>
public HttpClient HttpClientSettings(Dictionary<string, string> customHeaders, ProxySetting proxySetting, string url)
{
// 創建 HttpClientHandler 并禁用自動解壓縮
var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.None };
if (proxySetting != null && proxySetting.UseProxy)
{
// 在日志里記錄是否使用代理,方便排查問題
Logger.CONNECT.InfoFormat("UseProxy is [{0}]", proxySetting.UseProxy);
// 構建代理 URL
string proxyUrl = string.Format("{0}://{1}:{2}", proxySetting.Protocol, proxySetting.Address, proxySetting.Port);
Logger.CONNECT.InfoFormat("Proxy url is [{0}]", proxyUrl);
// 創建 Web 代理對象
IWebProxy webProxy = new WebProxy(proxyUrl);
if (proxySetting.ProxyCredentials != null &&
!string.IsNullOrEmpty(proxySetting.ProxyCredentials.UserName) &&
!string.IsNullOrEmpty(proxySetting.ProxyCredentials.Password))
{
Logger.CONNECT.InfoFormat("ProxyCredentials.UserName is [{0}]", proxySetting.ProxyCredentials.UserName);
// 設置代碼身份信息
webProxy.Credentials = new NetworkCredential(proxySetting.ProxyCredentials.UserName, proxySetting.ProxyCredentials.Password);
}
handler.Proxy = webProxy; // 設置 HttpClientHandler 的代理
handler.UseProxy = true; // 啟用代理
}
// 創建 HttpClient 實例
HttpClient httpclient = new HttpClient(handler);
// 清除默認的 Accept 頭
httpclient.DefaultRequestHeaders.Accept.Clear();
// 添加 Accept 頭,指定接受 JSON 格式
httpclient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
// 添加自定義請求頭
if (customHeaders != null)
{
var headerInfo = JsonConvert.SerializeObject(customHeaders);
Logger.CONNECT.InfoFormat("Custom header info: [{0}]", headerInfo);
foreach (KeyValuePair<string, string> headerItem in customHeaders)
{
if (!string.IsNullOrEmpty(headerItem.Value))
{
httpclient.DefaultRequestHeaders.Add(headerItem.Key, headerItem.Value.Trim());
}
}
}
// 獲取請求的 HTTP 模式(HTTP 或 HTTPS)
HttpMode httpMode = getHttpModeByUrl(url);
if (httpMode == HttpMode.HTTPS)
{
trustCertificate(); //如果是 HTTPS,忽略證書警告
}
// 返回配置好的 HttpClient 實例
return httpclient;
}
/// <summary>
/// 默認信任證書,忽略證書警告。
/// </summary>
private void trustCertificate()
{
//默認忽略證書
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
//兼容所有ssl協議
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls | SecurityProtocolType.Ssl3;
}
/// <summary>
/// 根據 URL 判斷 HTTP 模式(HTTP 或 HTTPS)。
/// </summary>
/// <param name="url">請求的 URL</param>
/// <returns>HTTP 模式</returns>
private HttpMode getHttpModeByUrl(string url)
{
HttpMode httpMode = HttpMode.HTTP;
if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
httpMode = HttpMode.HTTPS;
}
return httpMode;
}
/// <summary>
/// 同步方式發送 HTTP 請求并返回響應字符串。
/// </summary>
/// <param name="url">請求的 URL</param>
/// <param name="paramlist">請求參數</param>
/// <param name="customHeaders">自定義請求頭</param>
/// <param name="proxySetting">代理設置</param>
/// <param name="httpMethodType">HTTP 方法類型</param>
/// <returns>響應字符串</returns>
public string HttpclientCall(string url, HttpContent paramlist, Dictionary<string, string> customHeaders, ProxySetting proxySetting, HttpMethodType httpMethodType)
{
HttpClient httpclient = null;
Stream ResponseStream = null;
StreamReader sr = null;
HttpResponseMessage response = null;
try
{
// 配置 HttpClient 實例
httpclient = HttpClientSettings(customHeaders, proxySetting, url);
CompressedContent content = null;
if (paramlist != null)
{
// 壓縮請求內容
content = new CompressedContent(paramlist, CompressionType.GZip);
}
string retString = string.Empty;
Logger.CONNECT.InfoFormat("Call Url=[{0}], Method=[{1}]", url, httpMethodType.ToString());
switch (httpMethodType)
{
case HttpMethodType.GET:
response = httpclient.GetAsync(new Uri(url)).Result;
break;
case HttpMethodType.POST:
response = httpclient.PostAsync(new Uri(url), content).Result;
break;
case HttpMethodType.PUT:
response = httpclient.PutAsync(new Uri(url), content).Result;
break;
case HttpMethodType.DELETE:
response = httpclient.DeleteAsync(new Uri(url)).Result;
break;
default:
throw new NotSupportedException("This type is not supported!");
}
// 確保響應狀態碼為成功
response.EnsureSuccessStatusCode();
// 獲取響應流
ResponseStream = response.Content.ReadAsStreamAsync().Result;
// 創建 StreamReader 對象
sr = new StreamReader(ResponseStream, Encoding.GetEncoding("utf-8"));
// 讀取響應內容
retString = sr.ReadToEnd();
// 返回響應字符串
return retString;
}
catch
{
throw; // 重新拋出異常
}
finally
{
// 依次釋放資源
if (response != null) response.Dispose();
if (sr != null) sr.Close();
if (ResponseStream != null) ResponseStream.Close();
if (httpclient != null) httpclient.Dispose();
}
}
/// <summary>
/// 異步發送 HTTP 請求并返回響應字符串。
/// </summary>
public async Task<string> HttpclientCallAsync(string url, HttpContent paramlist, Dictionary<string, string> customHeaders, ProxySetting proxySetting, HttpMethodType httpMethodType)
{
HttpClient httpclient = null;
Stream ResponseStream = null;
StreamReader sr = null;
HttpResponseMessage response = null;
try
{
httpclient = HttpClientSettings(customHeaders, proxySetting, url);
CompressedContent content = null;
if (paramlist != null)
{
content = new CompressedContent(paramlist, CompressionType.GZip);
}
string retString = string.Empty;
Logger.CONNECT.InfoFormat("Call Url=[{0}], Method=[{1}]", url, httpMethodType.ToString());
switch (httpMethodType)
{
case HttpMethodType.GET:
response = await httpclient.GetAsync(new Uri(url));
break;
case HttpMethodType.POST:
response = await httpclient.PostAsync(new Uri(url), content);
break;
case HttpMethodType.PUT:
response = await httpclient.PutAsync(new Uri(url), content);
break;
case HttpMethodType.DELETE:
response = await httpclient.DeleteAsync(new Uri(url));
break;
default:
throw new NotSupportedException("This type is not supported!");
}
response.EnsureSuccessStatusCode();
ResponseStream = await response.Content.ReadAsStreamAsync();
sr = new StreamReader(ResponseStream, Encoding.GetEncoding("utf-8"));
retString = sr.ReadToEnd();
return retString;
}
catch
{
throw;
}
finally
{
if (response != null) response.Dispose();
if (sr != null) sr.Close();
if (ResponseStream != null) ResponseStream.Close();
if (httpclient != null) httpclient.Dispose();
}
}
}
}
其中,CompressedContent
代碼如下:
public class CompressedContent : HttpContent
{
private readonly HttpContent _originalContent; // 存儲原始的 HttpContent 對象
private readonly CompressionType _compressionMethod; // 存儲壓縮方法
/// <summary>
/// 構造函數,初始化 CompressedContent 對象
/// </summary>
/// <param name="content">原始的 HttpContent 對象</param>
/// <param name="compressionMethod">壓縮方法(GZip 或 Deflate)</param>
public CompressedContent(HttpContent content, CompressionType compressionMethod)
{
if (content == null)
{
throw new ArgumentNullException("content");
}
// 初始化原始內容
_originalContent = content;
// 初始化壓縮方法
_compressionMethod = compressionMethod;
// 將原始內容的所有頭部信息復制到新的 CompressedContent 對象中
foreach (KeyValuePair<string, IEnumerable<string>> header in _originalContent.Headers)
{
Headers.TryAddWithoutValidation(header.Key, header.Value);
}
// 添加 Content-Encoding 頭部,說明所使用的壓縮方法
Headers.ContentEncoding.Add(_compressionMethod.ToString().ToLowerInvariant());
}
/// <summary>
/// 重寫基類的TryComputeLength方法,直接設置長度為-1,返回false
/// </summary>
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
/// <summary>
/// 重寫基類的SerializeToStreamAsync方法,異步序列化內容到指定的流中
/// </summary>
/// <param name="stream">目標流</param>
/// <param name="context">傳輸上下文</param>
/// <returns>任務對象</returns>
protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
if (_compressionMethod == CompressionType.GZip)
{
using (var gzipStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true))
{
await _originalContent.CopyToAsync(gzipStream);
}
}
else if (_compressionMethod == CompressionType.Deflate)
{
using (var deflateStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true))
{
await _originalContent.CopyToAsync(deflateStream);
}
}
}
}
ProxySetting
代理配置模型如下:
public class ProxySetting
{
// 是否使用代理
public bool UseProxy { get; set; }
// 代理協議
public string Protocol { get; set; }
// 代理地址
public string Address { get; set; }
// 端口
public string Port { get; set; }
// 身份信息
public ProxyCredentials ProxyCredentials { get; set; }
}
public class ProxyCredentials
{
public string UserName { get; set; }
public string Password { get; set; }
}
使用
接下來,我們來看一個實際的具體使用例子。這是調用某個 RESTful API 上傳一些 CSV 數據:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Connect.Utils;
using Connect.Model;
using System.Net.Http;
using Newtonsoft.Json;
using System.IO;
using System.Net;
using LogLib;
namespace Connect.Restful
{
public class RestfulApi
{
private Dictionary<string, string> setCustomHeaders()
{
Dictionary<string, string> customHeaders = new Dictionary<string, string>() {
{ "job-id", "1" },
{ "table-name", "testdb" },
{ "license", "li8ZeOp6XPw=" }
};
return customHeaders;
}
public APIResult UploadData(string url, string csvData, ProxySetting proxySetting)
{
int retry = 0; // 重試次數
// 循環,如果失敗,重試 5 次,成功則跳出循環,規避網絡不穩定帶來的問題
while (true)
{
try
{
StringContent body = new StringContent(csvData, Encoding.UTF8, "text/plain");
HttpUtil httpUtil = new HttpUtil();
Dictionary<string, string> customHeaders = setCustomHeaders();
string responsetext = httpUtil.HttpclientCall(url, body, customHeaders, proxySetting, HttpMethodType.POST);
APIResult apiresult = JsonConvert.DeserializeObject<APIResult>(responsetext);
return apiresult;
}
catch (Exception ex)
{
retry++;
System.Threading.Thread.Sleep(3000); // 每次重試間隔 5 秒
if (retry > 5)
{
throw;
}
else
{
Logger.CONNECT.Error("Error occured when executing UploadData method: ", ex);
}
}
}
}
public async Task<APIResult> UploadDataAsync(string url, string csvData, ProxySetting proxySetting)
{
StringContent body = new StringContent(csvData, Encoding.UTF8, "text/plain");
HttpUtil httpUtil = new HttpUtil();
Dictionary<string, string> customHeaders = setCustomHeaders();
string responsetext = await httpUtil.HttpclientCallAsync(url, body, customHeaders, proxySetting, HttpMethodType.POST);
APIResult apiresult = JsonConvert.DeserializeObject<APIResult>(proxySetting);
return apiresult;
}
}
}
總結
如果你的項目需要調用 Restful API,使用 HttpClient
絕對是一個非常明智的選擇。
你可以把這個封裝方法靈活應用在你的項目中,相信可以極大地簡化 API 的調用流程,提高項目的可維護性和復用性
需要注意的是,基于我的項目的實際情況,在這里我是采用每次調用時創建和銷毀 HttpClient
實例的方式,這種方式簡單直接,但在高頻率調用的情況下,可能會導致性能瓶頸。
因此,如果你的項目需要頻繁調用 Restful API,建議考慮使用單例模式來優化 HttpClient
的管理,更好地減少資源開銷,進一步地提升性能。
該文章在 2025/2/12 10:46:32 編輯過