C# 关于HttpClient的使用
当我们在C#中调用RestApi通常有三种方式
HttpWebRequest | 是一种相对底层的处理 Http request/response 的方式 | 已过时 |
WebClient | 提供了对 HttpWebRequest 的高层封装,来简化使用者的调用 | 已过时 |
HttpClient | 是一种新的处理 Http request/response 工具包,具有更高的性能 | 推荐使用 |
为什么推荐使用HttpClient
WebRequest
.NET Framework 中第一个用来处理 Http 请求的类,非常灵活。可以用来存取 headers, cookies, protocols 和 timeouts 等等。但灵活的同时也导致使用难度加大,各个开发都有自己的写法。
HttpWebRequest http = (HttpWebRequest)WebRequest.Create("http://localhost:8081/api/default");
WebResponse response = http.GetResponse();
Stream memoryStream = response.GetResponseStream();
StreamReader streamReader = new StreamReader(memoryStream);
string data = streamReader.ReadToEnd();
WebClient
基于HttpWebRequest的封装,提供了使用便利性,但舍弃了部分性能。在只做简单api调用时优势很大。不太适用bgy各系统之间调用的千奇百怪鉴权。
using (var webClient = new WebClient()) {
var data = webClient.DownloadString("http://localhost:8081/api/default");
}
HttpClient
HttpClient 作为后来之物,它吸取了 HttpWebRequest 的灵活性及 WebClient 的便捷性,所以说 🐟 和 🐻 可兼得。
//单例注入HttpClient client
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
}
基于bgy接口调用行情的HttpClient封装
url | 请求地址 | 鉴于get请求,参数都直接拼接在url后面 |
data | json字符串 | 多数都是json交互,少量会有Form |
headers | 请求头 | 多数对接都是请求头附加token用于网关鉴权,使用字典类型声明方便快捷 |
contentType | 请求类型 | 默认json,可选Form |
tryCount | 重试次数 | 目前是即时重试,后续可拓展EventBus做延迟重试功能 |
writeLog | 是否写日志 | 建议接口调用都保留日志,避免甩锅🤷 |
Get请求
private async Task<string> GetHandle(string url, Dictionary<string, string> headers = null, int tryCount = 0, bool writeLog = true)
{
HttpRequestMessage message = new HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
if (headers?.ContainsKey("Authorization") == true)
{
headers.TryGetValue("Authorization", out string val);
headers.Remove("Authorization");
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", val);
}
if (message.Content != null)
message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(JsonDataType);
headers?.ForEach(item =>
{
message.Headers.Add(item.Key, item.Value);
});
if (writeLog)
{
var apiLog = new SystemApiLog
{
RequestUrl = message.RequestUri.AbsoluteUri
};
DateTime dtSpan = DateTime.Now;
apiLog.Method = "HttpGet";
apiLog.ResponseIp = localIp;
apiLog.ApiPath = GetApiPath();
try
{
var result = await SendAsync(message);
var html = await result.Content.ReadAsStringAsync();
apiLog.ResponseData = html;
apiLog.ElapsedTime = (DateTime.Now - dtSpan).TotalSeconds;
BusHelper.ApiLog(apiLog);
return html;
}
catch (Exception ex)
{
apiLog.ResponseData = ex.ToString();
apiLog.ElapsedTime = (DateTime.Now - dtSpan).TotalSeconds;
BusHelper.ApiLog(apiLog); //写日志换成自己的表!
if (tryCount > 0)
return await GetHandle(url, headers, --tryCount);
return null;
}
}
else
{
var result = await SendAsync(message);
var html = await result.Content.ReadAsStringAsync();
return html;
}
}
Post请求
private async Task<string> PostHandle(string url, string data, Dictionary<string, string> headers = null, ContentType contentType = ContentType.Json, int tryCount = 0, bool writeLog = true)
{
HttpRequestMessage message = new HttpRequestMessage(System.Net.Http.HttpMethod.Post, url)
{
Content = new StringContent(data, Encoding.UTF8)
};
if (headers?.ContainsKey("Authorization") == true)
{
headers.TryGetValue("Authorization", out string val);
headers.Remove("Authorization");
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", val);
}
message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType == ContentType.Form ? FromDataType : JsonDataType);
headers?.ForEach(item =>
{
message.Headers.Add(item.Key, item.Value);
});
//请求日志
if (writeLog)
{
var apiLog = new SystemApiLog
{
RequestUrl = message.RequestUri.AbsoluteUri
};
DateTime dtSpan = DateTime.Now;
apiLog.Method = "HttpPost";
apiLog.ResponseIp = localIp;
apiLog.RequestData = data;
apiLog.ApiPath = GetApiPath();
try
{
var result = await SendAsync(message);
var html = await result.Content.ReadAsStringAsync();
apiLog.ResponseData = html;
apiLog.ElapsedTime = (DateTime.Now - dtSpan).TotalSeconds;
BusHelper.ApiLog(apiLog);//写日志换成自己的表!
return html;
}
//异常重试及异常日志
catch (Exception ex)
{
apiLog.ResponseData = ex.ToString();
apiLog.ElapsedTime = (DateTime.Now - dtSpan).TotalSeconds;
BusHelper.ApiLog(apiLog);
if (tryCount > 0)
return await PostHandle(url, data, headers, contentType, --tryCount);
return null;
}
}
else
{
var result = await SendAsync(message);
var html = await result.Content.ReadAsStringAsync();
return html;
}
}
依赖的常量or方法
private const string FromDataType = "application/x-www-form-urlencoded";
private const string JsonDataType = "application/json";
private static readonly string localIp = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p => p.UnicastAddresses)
.FirstOrDefault(p => p.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(p.Address))?.Address.ToString();
/// <summary>
/// 反射请求方法获取apipath
/// </summary>
private string GetApiPath()
{
try
{
var st = new StackTrace(true);
//index:0为本身的方法;1为调用方法;2为其上上层,依次类推
MethodBase mb = st.GetFrame(4).GetMethod();
mb = NewMethod(st, mb, 4);
return $"{mb.Name}";
}
catch (Exception)
{
//catch 避免因反射层数不够导致的异常 影响接口请求
return null;
}
static MethodBase NewMethod(StackTrace st, MethodBase mb, int index)
{
if (mb.DeclaringType.Name == nameof(HttpApi) || mb.DeclaringType.Name == "AsyncMethodBuilderCore")
{
mb = st.GetFrame(index + 1).GetMethod();
return NewMethod(st, mb, index + 1);
}
return mb;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了