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

https://mp.weixin.qq.com/s/loTn-eiLM9HQaF-6ydjAVA

前言

嗨,大家好!

在现代软件开发中,与外部服务进行通信几乎是不可避免的任务。无论是获取天气预报、同步用户数据,还是调用第三方支付接口,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<stringstring> 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<stringstring> 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<stringstring> 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<stringHttpclientCallAsync(string url, HttpContent paramlist, Dictionary<stringstring> 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 { getset; }

    // 代理协议
    public string Protocol { getset; }

    // 代理地址
    public string Address { getset; }

    // 端口
    public string Port { getset; }

    // 身份信息
    public ProxyCredentials ProxyCredentials { getset; }
}

public class ProxyCredentials
{
    public string UserName { getset; }
    public string Password { getset; }
}

使用

接下来,我们来看一个实际的具体使用例子。这是调用某个 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<stringstringsetCustomHeaders()
        {
            Dictionary<stringstring> customHeaders = new Dictionary<stringstring>() {
                { "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<stringstring> 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<stringstring> 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 的管理,更好地减少资源开销,进一步地提升性能

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

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

posted @   firespeed  阅读(338)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起