C#封裝HttpClient實(shí)現(xiàn)HTTP請求處理
在現(xiàn)代的.NET應(yīng)用程序開發(fā)中,與外部服務(wù)進(jìn)行HTTP通信是一項(xiàng)常見需求。HttpClient作為.NET框架中處理HTTP請求的核心組件,為我們提供了強(qiáng)大而靈活的API。然而,直接使用原生的HttpClient可能會導(dǎo)致代碼重復(fù)、錯誤處理不完善等問題。為了提高代碼的可維護(hù)性和可測試性,我們通常會對HttpClient進(jìn)行封裝。本文將介紹一個完整的HttpRequest類封裝實(shí)現(xiàn),并深入探討HTTP請求處理的最佳實(shí)踐。
一、完整的HttpRequest類實(shí)現(xiàn)
首先,讓我們來看一下完整的HttpRequest類實(shí)現(xiàn)代碼:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
???????public class Response
{
public bool Success { get; set; }
public string Message { get; set; }
public object Data { get; set; }
public HttpStatusCode StatusCode { get; set; }
}
public static class JsonConverterExtensions
{
public static readonly JsonSerializerOptions SerializerSettings = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
IgnoreNullValues = true,
WriteIndented = false
};
}
public class HttpRequest : IDisposable
{
private readonly HttpClient client;
private bool disposed = false;
public HttpRequest(HttpClient client)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
client?.Dispose();
}
disposed = true;
}
}
public async Task<Response> GetAsync(string resource)
{
try
{
var response = await client.GetAsync(resource);
return await ProcessResponseAsync(response);
}
catch (HttpRequestException ex)
{
return HandleException(ex);
}
catch (Exception ex)
{
return HandleUnexpectedException(ex);
}
}
public async Task<Response> PostAsync(string resource, object body)
{
try
{
var content = CreateJsonContent(body);
var response = await client.PostAsync(resource, content);
return await ProcessResponseAsync(response);
}
catch (HttpRequestException ex)
{
return HandleException(ex);
}
catch (Exception ex)
{
return HandleUnexpectedException(ex);
}
}
public async Task<Response> PutAsync(string resource, object body)
{
try
{
var content = CreateJsonContent(body);
var response = await client.PutAsync(resource, content);
return await ProcessResponseAsync(response);
}
catch (HttpRequestException ex)
{
return HandleException(ex);
}
catch (Exception ex)
{
return HandleUnexpectedException(ex);
}
}
public async Task<Response> DeleteAsync(string resource)
{
try
{
var response = await client.DeleteAsync(resource);
return await ProcessResponseAsync(response);
}
catch (HttpRequestException ex)
{
return HandleException(ex);
}
catch (Exception ex)
{
return HandleUnexpectedException(ex);
}
}
public HttpRequest WithBaseAddress(string baseAddress)
{
if (!string.IsNullOrEmpty(baseAddress))
{
client.BaseAddress = new Uri(baseAddress);
}
return this;
}
public HttpRequest WithTimeout(TimeSpan timeout)
{
client.Timeout = timeout;
return this;
}
public HttpRequest WithHeader(string name, string value)
{
if (!client.DefaultRequestHeaders.Contains(name))
{
client.DefaultRequestHeaders.Add(name, value);
}
return this;
}
public HttpRequest WithHeaders(IDictionary<string, string> headers)
{
if (headers != null)
{
foreach (var header in headers)
{
WithHeader(header.Key, header.Value);
}
}
return this;
}
public HttpRequest WithAuthorization(string scheme, string parameter)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, parameter);
return this;
}
public HttpRequest WithBearerToken(string token)
{
return WithAuthorization("Bearer", token);
}
private StringContent CreateJsonContent(object body)
{
if (body == null)
{
return new StringContent("{}", Encoding.UTF8, "application/json");
}
var json = JsonSerializer.Serialize(body, JsonConverterExtensions.SerializerSettings);
return new StringContent(json, Encoding.UTF8, "application/json");
}
private async Task<Response> ProcessResponseAsync(HttpResponseMessage response)
{
var responseContent = await response.Content.ReadAsStringAsync();
try
{
// 嘗試解析JSON響應(yīng)
var responseObject = JsonSerializer.Deserialize<Response>(responseContent, JsonConverterExtensions.SerializerSettings);
if (responseObject != null)
{
responseObject.StatusCode = response.StatusCode;
return responseObject;
}
}
catch (JsonException)
{
// 如果JSON解析失敗,創(chuàng)建一個基于HTTP狀態(tài)碼的響應(yīng)
}
// 對于非JSON響應(yīng)或解析失敗的情況
return new Response
{
Success = response.IsSuccessStatusCode,
Message = response.ReasonPhrase,
StatusCode = response.StatusCode,
Data = responseContent
};
}
private Response HandleException(HttpRequestException ex)
{
return new Response
{
Success = false,
Message = $"HTTP請求錯誤: {ex.Message}",
StatusCode = ex.StatusCode ?? HttpStatusCode.InternalServerError,
Data = ex
};
}
private Response HandleUnexpectedException(Exception ex)
{
return new Response
{
Success = false,
Message = $"處理請求時發(fā)生意外錯誤: {ex.Message}",
StatusCode = HttpStatusCode.InternalServerError,
Data = ex
};
}
}
二、設(shè)計(jì)思路與實(shí)現(xiàn)要點(diǎn)
1. 依賴注入與生命周期管理
這個封裝類采用了依賴注入模式,通過構(gòu)造函數(shù)接收一個HttpClient實(shí)例。這樣做有幾個重要好處:
遵循單一職責(zé)原則,HttpRequest類專注于HTTP請求處理
便于單元測試,可以輕松注入模擬的HttpClient
利用.NET的IHttpClientFactory進(jìn)行正確的HttpClient生命周期管理,避免資源泄漏
同時,類實(shí)現(xiàn)了IDisposable接口,確保在不再需要時正確釋放HttpClient資源。
2. 流暢接口設(shè)計(jì)
為了提供更友好的API體驗(yàn),封裝類實(shí)現(xiàn)了流暢接口模式:
var response = await new HttpRequest(httpClient)
.WithBaseAddress("https://api.example.com")
.WithBearerToken("your-token-here")
.WithHeader("X-Custom-Header", "value")
.PostAsync("/resource", new { Key = "value" });
這種鏈?zhǔn)秸{(diào)用方式使代碼更加簡潔易讀,同時保持了良好的可擴(kuò)展性。
3. 統(tǒng)一的錯誤處理
在每個HTTP方法中,我們都實(shí)現(xiàn)了統(tǒng)一的異常處理機(jī)制:
- 捕獲HttpRequestException處理HTTP特定錯誤
- 捕獲其他異常處理意外錯誤
- 將所有錯誤轉(zhuǎn)換為統(tǒng)一的Response對象
- 保留原始異常信息以便調(diào)試
這種統(tǒng)一的錯誤處理方式使上層調(diào)用代碼更加簡潔,無需重復(fù)處理各種異常情況。
4. 靈活的響應(yīng)處理
ProcessResponseAsync方法負(fù)責(zé)處理HTTP響應(yīng),它嘗試將響應(yīng)內(nèi)容解析為JSON格式的Response對象:
- 如果解析成功,返回包含完整信息的Response對象
- 如果解析失敗,創(chuàng)建一個基于HTTP狀態(tài)碼的Response對象
- 始終保留原始響應(yīng)內(nèi)容和狀態(tài)碼信息
這種設(shè)計(jì)使封裝類能夠處理各種類型的HTTP響應(yīng),同時提供一致的返回格式。
三、實(shí)際使用示例
下面是一個使用這個封裝類的完整示例:
using System;
using System.Net.Http;
using System.Threading.Tasks;
???????public class Program
{
public static async Task Main()
{
try
{
// 創(chuàng)建HttpClient實(shí)例(實(shí)際應(yīng)用中建議使用IHttpClientFactory)
using var httpClient = new HttpClient();
// 創(chuàng)建請求實(shí)例并配置
var request = new HttpRequest(httpClient)
.WithBaseAddress("https://api.example.com")
.WithBearerToken("your-auth-token");
// 發(fā)送GET請求
var getResponse = await request.GetAsync("/api/users");
Console.WriteLine($"GET請求結(jié)果: {getResponse.Success}, 狀態(tài)碼: {getResponse.StatusCode}");
// 發(fā)送POST請求
var postData = new { Name = "John Doe", Email = "john@example.com" };
var postResponse = await request.PostAsync("/api/users", postData);
Console.WriteLine($"POST請求結(jié)果: {postResponse.Success}, 狀態(tài)碼: {postResponse.StatusCode}");
// 發(fā)送PUT請求
var putData = new { Id = 1, Name = "Jane Doe" };
var putResponse = await request.PutAsync("/api/users/1", putData);
Console.WriteLine($"PUT請求結(jié)果: {putResponse.Success}, 狀態(tài)碼: {putResponse.StatusCode}");
// 發(fā)送DELETE請求
var deleteResponse = await request.DeleteAsync("/api/users/1");
Console.WriteLine($"DELETE請求結(jié)果: {deleteResponse.Success}, 狀態(tài)碼: {deleteResponse.StatusCode}");
}
catch (Exception ex)
{
Console.WriteLine($"發(fā)生未處理的異常: {ex.Message}");
}
}
}
四、HttpClient使用最佳實(shí)踐
在使用HttpClient和這個封裝類時,還需要注意以下最佳實(shí)踐:
- 使用IHttpClientFactory:在ASP.NET Core應(yīng)用中,始終使用IHttpClientFactory創(chuàng)建HttpClient實(shí)例,避免直接實(shí)例化HttpClient。
- 設(shè)置合理的超時時間:默認(rèn)情況下,HttpClient的超時時間是100秒,根據(jù)實(shí)際需求調(diào)整這個值,防止長時間阻塞。
- 處理取消請求:考慮實(shí)現(xiàn)請求取消機(jī)制,通過CancellationToken參數(shù)傳遞取消令牌。
- 處理重試邏輯:對于臨時性網(wǎng)絡(luò)錯誤,考慮實(shí)現(xiàn)重試機(jī)制。可以使用Polly等庫來簡化重試策略的實(shí)現(xiàn)。
- 監(jiān)控HTTP請求性能:記錄HTTP請求的執(zhí)行時間、成功率等指標(biāo),便于性能分析和問題排查。
通過這個完整的HttpRequest類封裝,我們可以更加高效、安全地處理HTTP通信,同時保持代碼的整潔和可維護(hù)性。希望這篇文章對你理解C#中的HTTP請求處理有所幫助。
這個實(shí)現(xiàn)提供了完整的HTTP請求功能,包括GET、POST、PUT、DELETE方法,以及靈活的請求配置和統(tǒng)一的響應(yīng)處理。
到此這篇關(guān)于C#封裝HttpClient實(shí)現(xiàn)HTTP請求處理的文章就介紹到這了,更多相關(guān)C#封裝HttpClient內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)將字符串轉(zhuǎn)化為日期格式的方法詳解
這篇文章主要為大家詳細(xì)介紹了C#如何使用DateTime結(jié)構(gòu)的ParseExact方法和Parse方法分別將字符串轉(zhuǎn)化為日期格式,有需要的小伙伴可以了解一下2024-01-01
C#使用GZipStream實(shí)現(xiàn)文件的壓縮與解壓
這篇文章主要為大家詳細(xì)介紹了C#使用GZipStream實(shí)現(xiàn)文件的壓縮與解壓,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
Unity游戲開發(fā)之射擊小游戲的實(shí)現(xiàn)
本篇文章為大家?guī)硪粋€橫版2D射擊小游戲,游戲制作超級簡單,玩法一學(xué)就會。文中的示例代碼講解詳細(xì),快跟隨小編一起動手試一試2022-03-03
C#使用System.Environment獲取電腦的相關(guān)屬性
這篇文章主要為大家詳細(xì)介紹了C#使用System.Environment獲取電腦的相關(guān)屬性,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
VS2015為console.readkey添加代碼片段的方法
這篇文章主要介紹了VS2015為console.readkey添加代碼片段的方法,需要的朋友可以參考下2016-12-12

