最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#優(yōu)雅實現(xiàn)HttpClient封裝的具體方案

 更新時間:2025年12月19日 08:47:10   作者:淺嘗輒止;  
在 C# 開發(fā)中,HttpClient 是發(fā)送 HTTP 請求的核心組件,但直接 new 實例的方式存在性能差、連接泄漏等問題,本文將分享靜態(tài)單例 + 通用方法封裝的最優(yōu)實踐,需要的朋友可以參考下

引言

在 C# 開發(fā)中,HttpClient 是發(fā)送 HTTP 請求的核心組件,但直接 new 實例的方式存在性能差、連接泄漏等問題。本文將分享「靜態(tài)單例 + 通用方法封裝」的最優(yōu)實踐,同時補充 .NET Core/.NET 5+ 推薦的 IHttpClientFactory 實現(xiàn)方案,提供可直接復(fù)制復(fù)用的工具類代碼,適配不同 .NET 版本場景。

本文核心價值:

  • 解決 HttpClient 頻繁創(chuàng)建導(dǎo)致的性能問題
  • 提供 GET/POST(JSON/表單) 通用封裝方法
  • 適配 .NET Framework 和 .NET Core 多場景
  • 包含完整異常處理、請求配置最佳實踐

一、核心前提:為什么不建議每次 new HttpClient?

很多開發(fā)者習(xí)慣在每次請求時 new HttpClient,這種寫法存在明顯缺陷:

  1. 創(chuàng)建成本高:每次 new 會初始化新的 HttpMessageHandler(含連接池、SSL 上下文等重量級組件)
  2. 連接池?zé)o法復(fù)用:導(dǎo)致 TCP 連接頻繁創(chuàng)建/銷毀,增加網(wǎng)絡(luò)開銷,甚至引發(fā)端口耗盡
  3. 資源泄漏風(fēng)險:未正確 Dispose 會導(dǎo)致 socket 連接長期占用(.NET Framework 中更明顯)
  4. DNS 緩存問題:單例 HttpClient 可能緩存 DNS,導(dǎo)致域名解析變更后無法生效(.NET Core 前版本)

核心原則:HttpClient 應(yīng)復(fù)用而非頻繁創(chuàng)建,基于單例或工廠模式管理實例生命周期。

二、基礎(chǔ)方案:靜態(tài)單例 + 通用方法封裝(.NET Framework 適用)

適合 .NET Framework 項目,通過靜態(tài)工具類封裝 HttpClient 單例,提供 GET/POST 通用方法,統(tǒng)一處理請求頭、超時、異常等邏輯。

2.1 完整工具類代碼

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

/// 
public static class HttpClientHelper
{
    // 全局單例 HttpClient(核心:復(fù)用連接池)
    private static readonly HttpClient _httpClient;

    /// 
    static HttpClientHelper()
    {
        // 1. 配置連接池、超時等核心參數(shù)
        var handler = new HttpClientHandler
        {
            // 限制每個服務(wù)器的最大并發(fā)連接數(shù)(根據(jù)目標(biāo)服務(wù)能力調(diào)整,建議 50-200)
            MaxConnectionsPerServer = 100,
            // 允許自動重定向(根據(jù)業(yè)務(wù)需求調(diào)整)
            AllowAutoRedirect = true,
            // 【測試環(huán)境專用】忽略 SSL 證書錯誤(生產(chǎn)環(huán)境務(wù)必刪除?。?
            // ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
        };

        // 2. 初始化 HttpClient 實例
        _httpClient = new HttpClient(handler)
        {
            // 全局默認(rèn)超時(30秒,避免請求無限掛起)
            Timeout = TimeSpan.FromSeconds(30)
        };

        // 3. 設(shè)置默認(rèn)請求頭(減少重復(fù)代碼)
        _httpClient.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
        _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("CSharp-HttpClient-Tool/1.0");
    }

    #region 通用 GET 請求(返回字符串結(jié)果)
    /// 
    /// <param name="url">請求地址(必填)</param>
    /// <param name="headers">自定義請求頭(可選,如 Authorization)</param>
    /// <returns>響應(yīng)內(nèi)容字符串</returns>
    /// <exception cref="ArgumentNullException">url 為空時拋出</exception>
    /// <exception cref="HttpRequestException">HTTP 請求失敗時拋出(非 2xx 狀態(tài)碼)</exception>
    public static async Task<string> GetAsync(string url, HeaderDictionary headers = null)
    {
        // 入?yún)⑿r?
        if (string.IsNullOrEmpty(url))
            throw new ArgumentNullException(nameof(url), "請求地址不能為空");

        // 構(gòu)建請求消息
        using var request = new HttpRequestMessage(HttpMethod.Get, url);

        // 添加自定義請求頭
        if (headers != null)
        {
            foreach (var header in headers)
            {
                // 嘗試添加到請求頭,失敗則嘗試添加到內(nèi)容頭
                if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value) &&
                    !request.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value) ?? false)
                {
                    throw new InvalidOperationException($"無法添加請求頭:{header.Key}");
                }
            }
        }

        // 發(fā)送請求(復(fù)用單例 HttpClient)
        using var response = await _httpClient.SendAsync(request);
        // 確保響應(yīng)成功(非 2xx 狀態(tài)碼會拋 HttpRequestException)
        response.EnsureSuccessStatusCode();

        // 讀取響應(yīng)內(nèi)容
        return await response.Content.ReadAsStringAsync();
    }
    #endregion

    #region 通用 POST 請求(JSON 入?yún)ⅲ?
    /// 
    /// <param name="url">請求地址(必填)</param>
    /// <param name="jsonBody">JSON 字符串(必填)</param>
    /// <param name="headers">自定義請求頭(可選)</param>
    /// <returns>響應(yīng)內(nèi)容字符串</returns>
    /// <exception cref="ArgumentNullException">url 或 jsonBody 為空時拋出</exception>
    public static async Task<string> PostJsonAsync(string url, string jsonBody, HeaderDictionary headers = null)
    {
        // 入?yún)⑿r?
        if (string.IsNullOrEmpty(url))
            throw new ArgumentNullException(nameof(url), "請求地址不能為空");
        if (string.IsNullOrEmpty(jsonBody))
            throw new ArgumentNullException(nameof(jsonBody), "JSON 入?yún)⒉荒転榭?);

        // 構(gòu)建 JSON 內(nèi)容(指定 UTF-8 編碼和 application/json 格式)
        using var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");

        // 構(gòu)建請求消息
        using var request = new HttpRequestMessage(HttpMethod.Post, url)
        {
            Content = content
        };

        // 添加自定義請求頭
        if (headers != null)
        {
            foreach (var header in headers)
            {
                if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value) &&
                    !request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value))
                {
                    throw new InvalidOperationException($"無法添加請求頭:{header.Key}");
                }
            }
        }

        // 發(fā)送請求并處理響應(yīng)
        using var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
    #endregion

    #region 擴展:POST 表單請求(鍵值對入?yún)ⅲ?
    ///     /// <param name="url">請求地址(必填)</param>
    /// <param name="formData">表單數(shù)據(jù)(鍵值對)</param>
    /// <returns>響應(yīng)內(nèi)容字符串</returns>
    public static async Task<string> PostFormAsync(string url, FormUrlEncodedContent formData)
    {
        if (string.IsNullOrEmpty(url))
            throw new ArgumentNullException(nameof(url), "請求地址不能為空");
        if (formData == null)
            throw new ArgumentNullException(nameof(formData), "表單數(shù)據(jù)不能為空");

        // 直接調(diào)用 PostAsync(簡化表單請求邏輯)
        using var response = await _httpClient.PostAsync(url, formData);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
    #endregion
}

/// 
public class HeaderDictionary : Dictionary<string, string>
{
    public HeaderDictionary() : base(StringComparer.OrdinalIgnoreCase) { }
}

2.2 使用示例(復(fù)制即運行)

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // 示例1:調(diào)用 GET 方法(帶 Authorization 頭)
        await TestGetAsync();

        // 示例2:調(diào)用 POST JSON 方法
        await TestPostJsonAsync();

        // 示例3:調(diào)用 POST 表單方法
        await TestPostFormAsync();
    }

    /// 
    private static async Task TestGetAsync()
    {
        try
        {
            // 自定義請求頭(如 Token 認(rèn)證)
            var headers = new HeaderDictionary
            {
                { "Authorization", "Bearer your_token_here" }
            };

            // 調(diào)用工具類方法
            string result = await HttpClientHelper.GetAsync(
                "https://api.example.com/data", headers);

            Console.WriteLine("GET 響應(yīng)結(jié)果:" + result);
        }
        catch (Exception ex)
        {
            Console.WriteLine("GET 請求失?。? + ex.Message);
        }
    }

    /// 
    private static async Task TestPostJsonAsync()
    {
        try
        {
            // 構(gòu)造 JSON 入?yún)?
            string jsonBody = "{\"name\":\"測試用戶\",\"age\":25}";

            // 調(diào)用工具類方法
            string result = await HttpClientHelper.PostJsonAsync(
                "https://api.example.com/submit", jsonBody);

            Console.WriteLine("POST JSON 響應(yīng)結(jié)果:" + result);
        }
        catch (Exception ex)
        {
            Console.WriteLine("POST JSON 請求失?。? + ex.Message);
        }
    }

    /// 
    private static async Task TestPostFormAsync()
    {
        try
        {
            // 構(gòu)造表單數(shù)據(jù)(用戶名密碼登錄)
            var formData = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("username", "admin"),
                new KeyValuePair<string, string>("password", "123456")
            });

            // 調(diào)用工具類方法
            string result = await HttpClientHelper.PostFormAsync(
                "https://api.example.com/login", formData);

            Console.WriteLine("POST 表單響應(yīng)結(jié)果:" + result);
        }
        catch (Exception ex)
        {
            Console.WriteLine("POST 表單請求失?。? + ex.Message);
        }
    }
}

三、進階方案:IHttpClientFactory 實現(xiàn)(.NET Core/.NET 5+ 推薦)

對于 .NET Core/.NET 5+ 項目,官方推薦使用IHttpClientFactory 管理 HttpClient 生命周期,解決了傳統(tǒng)單例的 DNS 緩存問題,支持靈活配置和生命周期管理。

3.1 步驟1:注冊服務(wù)(Program.cs)

// .NET 6+ 極簡模式(Program.cs)
var builder = WebApplication.CreateBuilder(args);

// 方式1:注冊命名客戶端(推薦,按業(yè)務(wù)分組配置)
builder.Services.AddHttpClient("DefaultClient", client =>
{
    // 基礎(chǔ)地址(后續(xù)請求可省略域名)
    client.BaseAddress = new Uri("https://api.example.com/");
    // 全局超時配置
    client.Timeout = TimeSpan.FromSeconds(30);
    // 默認(rèn)請求頭
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));
})
// 配置連接池和 Handler 生命周期
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
    MaxConnectionsPerServer = 100, // 連接池最大并發(fā)數(shù)
    AllowAutoRedirect = true
})
// 每 5 分鐘刷新 Handler(解決 DNS 緩存問題)
.SetHandlerLifetime(TimeSpan.FromMinutes(5));

// 注冊自定義服務(wù)(后續(xù)注入使用)
builder.Services.AddScoped<HttpClientService>();

var app = builder.Build();

// 其他中間件配置...

app.Run();

3.2 步驟2:封裝服務(wù)類

using System;
using System.Net.Http;
using System.Threading.Tasks;

/// 
public class HttpClientService
{
    private readonly IHttpClientFactory _httpClientFactory;

    // 構(gòu)造函數(shù)注入 IHttpClientFactory
    public HttpClientService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    /// 
    public async Task<string> GetAsync(string url, HeaderDictionary headers = null)
    {
        if (string.IsNullOrEmpty(url))
            throw new ArgumentNullException(nameof(url), "請求地址不能為空");

        // 從工廠獲取命名客戶端("DefaultClient" 對應(yīng)注冊時的名稱)
        var client = _httpClientFactory.CreateClient("DefaultClient");

        using var request = new HttpRequestMessage(HttpMethod.Get, url);

        // 添加自定義請求頭
        if (headers != null)
        {
            foreach (var header in headers)
            {
                request.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }
        }

        // 發(fā)送請求并處理響應(yīng)
        using var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }

    /// 
    public async Task<string> PostJsonAsync(string url, string jsonBody, HeaderDictionary headers = null)
    {
        if (string.IsNullOrEmpty(url))
            throw new ArgumentNullException(nameof(url), "請求地址不能為空");
        if (string.IsNullOrEmpty(jsonBody))
            throw new ArgumentNullException(nameof(jsonBody), "JSON 入?yún)⒉荒転榭?);

        var client = _httpClientFactory.CreateClient("DefaultClient");
        using var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
        using var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };

        if (headers != null)
        {
            foreach (var header in headers)
            {
                request.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }
        }

        using var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

// 復(fù)用之前定義的 HeaderDictionary 輔助類
public class HeaderDictionary : Dictionary<string, string>
{
    public HeaderDictionary() : base(StringComparer.OrdinalIgnoreCase) { }
}

3.3 使用示例(Web 項目控制器)

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

/// 
[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
    private readonly HttpClientService _httpClientService;

    // 構(gòu)造函數(shù)注入
    public TestController(HttpClientService httpClientService)
    {
        _httpClientService = httpClientService;
    }

    [HttpGet("data")]
    public async Task<IActionResult> GetData()
    {
        try
        {
            // 調(diào)用服務(wù)方法(此處 url 可省略基礎(chǔ)地址,因為注冊時配置了 BaseAddress)
            var result = await _httpClientService.GetAsync("data");
            return Ok(result);
        }
        catch (Exception ex)
        {
            return BadRequest($"請求失?。簕ex.Message}");
        }
    }

    [HttpPost("submit")]
    public async Task<IActionResult> SubmitData([FromBody] UserInfo userInfo)
    {
        try
        {
            // 序列化對象為 JSON(需引用 Newtonsoft.Json 或使用 System.Text.Json)
            string jsonBody = System.Text.Json.JsonSerializer.Serialize(userInfo);
            var result = await _httpClientService.PostJsonAsync("submit", jsonBody);
            return Ok(result);
        }
        catch (Exception ex)
        {
            return BadRequest($"請求失敗:{ex.Message}");
        }
    }
}

// 測試實體類
public class UserInfo
{
    public string Name { get; set; }
    public int Age { get; set; }
}

四、關(guān)鍵注意事項(避坑指南)

  1. 禁止每次請求 new HttpClient:無論哪種方案,核心都是復(fù)用實例,避免重復(fù)創(chuàng)建 HttpMessageHandler
  2. 不要手動 Dispose 復(fù)用的 HttpClient:Dispose 會銷毀連接池,導(dǎo)致后續(xù)請求失敗(using 語句僅用于 HttpRequestMessage/HttpResponseMessage)
  3. 必須處理非 2xx 狀態(tài)碼EnsureSuccessStatusCode() 會在非 2xx 時拋 HttpRequestException,需通過 try-catch 捕獲并處理業(yè)務(wù)邏輯
  4. 合理配置超時:默認(rèn)超時 100 秒,建議顯式設(shè)置為 30 秒內(nèi)(根據(jù)業(yè)務(wù)場景調(diào)整),避免請求長期掛起
  5. 限制連接池并發(fā)數(shù)MaxConnectionsPerServer 建議設(shè)置為 50-200,避免壓垮目標(biāo)服務(wù)器
  6. DNS 緩存問題處理:.NET Core 前版本使用靜態(tài)單例時,若目標(biāo)域名解析變更,需手動刷新 HttpMessageHandler;.NET Core/.NET 5+ 直接使用 IHttpClientFactory 即可
  7. 生產(chǎn)環(huán)境禁用 SSL 證書忽略:測試環(huán)境可臨時開啟 ServerCertificateCustomValidationCallback,生產(chǎn)環(huán)境務(wù)必刪除,避免安全風(fēng)險

五、方案選型建議

項目類型推薦方案優(yōu)勢
.NET Framework(4.x)靜態(tài)單例 + 通用方法封裝簡單易用,無需依賴注入,適配舊項目
.NET Core/.NET 5+(Web/控制臺)IHttpClientFactory + 注入式服務(wù)自動管理生命周期,解決 DNS 緩存問題,支持靈活配置
簡單控制臺/工具類項目靜態(tài)單例 HttpClient代碼簡潔,無額外依賴

六、總結(jié)

本文提供的兩種 HttpClient 封裝方案,核心都是「復(fù)用實例、統(tǒng)一配置、簡化調(diào)用」:

  • 基礎(chǔ)方案適配 .NET Framework 舊項目,復(fù)制代碼即可直接使用;
  • 進階方案適配 .NET Core 新項目,符合官方最佳實踐,擴展性更強。

通過封裝,不僅能提升請求性能(連接池復(fù)用),還能統(tǒng)一處理異常、請求頭、超時等邏輯,大幅降低重復(fù)編碼成本。如果需要擴展其他請求類型(如 PUT/DELETE),可參考文中方法直接補充即可。

以上就是C#優(yōu)雅實現(xiàn)HttpClient封裝的具體方案的詳細(xì)內(nèi)容,更多關(guān)于C# HttpClient封裝的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C#實現(xiàn)對文件進行加密解密的方法

    C#實現(xiàn)對文件進行加密解密的方法

    這篇文章主要介紹了C#實現(xiàn)對文件進行加密解密的方法,涉及C#加密與解密的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-04-04
  • 基于C#中XmlWriter寫入Xml的深入分析

    基于C#中XmlWriter寫入Xml的深入分析

    本篇文章是對C#中XmlWriter寫入Xml進行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • C#多線程系列之線程完成數(shù)

    C#多線程系列之線程完成數(shù)

    本文詳細(xì)講解了C#多線程中的線程完成數(shù),文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-02-02
  • C# 寫入XML文檔三種方法詳細(xì)介紹

    C# 寫入XML文檔三種方法詳細(xì)介紹

    如何使用LINQ to XML對XML進行操作,我們分別用這三個類將同樣的xml內(nèi)容寫入文檔,需要了解的朋友可以參考下
    2012-12-12
  • C# 內(nèi)部類與Lambda表達式用法詳解

    C# 內(nèi)部類與Lambda表達式用法詳解

    Lambda表達式是一個匿名函數(shù),Lambda表達式基于數(shù)學(xué)中的λ演算得名,直接對應(yīng)于其中的lambda抽象,是一個匿名函數(shù),即沒有函數(shù)名的函數(shù);內(nèi)部類是將一個類定義在另一個給類里面或者方法里面,這樣的類就被稱為內(nèi)部類
    2021-10-10
  • C#讀寫config配置文件的方法

    C#讀寫config配置文件的方法

    下面小編就為大家?guī)硪黄狢#讀寫config配置文件的方法。小編覺的挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • WinForm中實現(xiàn)數(shù)據(jù)的異步加載與進度可視化

    WinForm中實現(xiàn)數(shù)據(jù)的異步加載與進度可視化

    在開發(fā)WinForm應(yīng)用程序時,經(jīng)常會遇到需要加載大量數(shù)據(jù)的場景,比如讀取文件、查詢數(shù)據(jù)庫或調(diào)用遠(yuǎn)程接口,如果這些操作直接在主線程中執(zhí)行,UI界面就會出現(xiàn)假死現(xiàn)象,所以本文將介紹如何使用.NET提供的BackgroundWorker組件,實現(xiàn)數(shù)據(jù)的異步加載與進度可視化
    2025-09-09
  • C#關(guān)于Task.Yeild()函數(shù)的討論

    C#關(guān)于Task.Yeild()函數(shù)的討論

    這篇文章主要介紹了C#中關(guān)于Task.Yeild()函數(shù)的相關(guān)資料,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • C# Random類的正確應(yīng)用方法

    C# Random類的正確應(yīng)用方法

    這篇文章主要介紹了C# Random類的正確應(yīng)用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 為IObservable實現(xiàn)自己的運算符(詳解)

    為IObservable實現(xiàn)自己的運算符(詳解)

    下面小編就為大家?guī)硪黄獮镮Observable實現(xiàn)自己的運算符(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05

最新評論

姜堰市| 深州市| 望谟县| 汪清县| 班戈县| 扶沟县| 廉江市| 买车| 阿拉善盟| 耒阳市| 安多县| 九龙坡区| 科技| 巴青县| 舞阳县| 全椒县| 宿松县| 大庆市| 博野县| 错那县| 铜川市| 喀什市| 前郭尔| 宁蒗| 天全县| 青田县| 合阳县| 紫金县| 东乌珠穆沁旗| 高陵县| 武鸣县| 孟村| 富平县| 海盐县| 江达县| 沧州市| 南溪县| 兴和县| 信丰县| 遵义市| 苗栗市|