封装 C# 中的 HttpClient 功能,从此不再害怕和 Restful API 打交道

Coding-132

前言

嗨,大家好!

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

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

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

往期精彩

  1. 把 C# 里的 HttpClient 封装起来,告别复杂的配置,让 Restful API 调用更轻松更高效
  2. C#12 中 5 个节省你开发时间的的改进
  3. C# 静态类,高手不想让你知道的 15 个真相
  4. 封装一个 C# 范围判断函数,从此告别重复编写范围判断代码的烦恼
  5. 用 C# Stopwatch 计时,让代码性能飞起来!
  6. 轻装上阵,Visual Studio LocalDB:.NET 程序员的本地数据库神器
  7. 封装一个C#万能基础数据类型转换器,一招解决所有基础类型转换烦恼
  8. 闲话 .NET(7):.NET Core 能淘汰 .NET FrameWork 吗?
  9. 常用的 4 种 ORM 框架(EF Core,SqlSugar,FreeSql,Dapper)对比总结
  10. C# AutoMapper 10个常用方法总结
  11. C# 7个方法比较两个对象是否相等
  12. C# 去掉字符串最后一个字符的 4 种方法

我是老杨,一个执着于编程乐趣、至今奋斗在一线的 10年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得!欢迎点击下方卡片关注老杨的公众号,更多干货等你来!

回答: 对于ep4ce15f17c8n实验,根据引用\[1\]中的内容,可以使用Quartus II软件进行配置编程。首先,需要安装Quartus II 15.0 (64-bit)USB Blaster驱动。在Quartus II中创建一个项目,设置好引脚映射配置引脚。可以参考引用\[2\]中的方法来设置I/O口映射。在编译完成后,使用Programmer进行下载。如果下载不成功,可能是设备不对应的原因,可以尝试修改设备。此外,根据引用\[3\]中的内容,ep4ce15f17c8n是一种速度等级为8的器件,最大频率约为400Mhz。 #### 引用[.reference_title] - *1* [(FPGA学习)环境及入门-------基于黑金FPGA cyclone IV EP4CE15F17开发板](https://blog.youkuaiyun.com/m0_37479629/article/details/107965542)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [FPGA项目之分模块流水灯](https://blog.youkuaiyun.com/qq_43811597/article/details/120288276)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [基于EP4CE10F17C8N芯片详解Altera Cyclone系列器件命名规则](https://blog.youkuaiyun.com/lqq120030465/article/details/90692790)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值