所有文章 > API开发 > 把 C# 里的 HttpClient 封装起来,告别复杂的配置,让 Restful API 调用更轻松更高效

把 C# 里的 HttpClient 封装起来,告别复杂的配置,让 Restful API 调用更轻松更高效

前言

嗨,大家好!

在现代软件开发中,与外部服务进行通信几乎是不可避免的任务。无论是获取天气预报、同步用户数据,还是调用第三方支付接口,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 的管理,更好地减少资源开销,进一步地提升性能

好了,今天的分享就到这里啦,如果觉得有用,别忘了点个【赞与在看】哦,你的支持是我最大的动力!

最后,如果你有更好的想法或建议,欢迎留言讨论!

文章转自微信公众号@代码掌控者

#你可能也喜欢这些API文章!