C#封装HttpClient:HTTP请求处理最佳实践

news2025/6/8 1:12:35

C#封装HttpClient:HTTP请求处理最佳实践

在现代的.NET应用程序开发中,与外部服务进行HTTP通信是一项常见需求。HttpClient作为.NET框架中处理HTTP请求的核心组件,为我们提供了强大而灵活的API。然而,直接使用原生的HttpClient可能会导致代码重复、错误处理不完善等问题。为了提高代码的可维护性和可测试性,我们通常会对HttpClient进行封装。本文将介绍一个完整的HttpRequest类封装实现,并深入探讨HTTP请求处理的最佳实践。

一、完整的HttpRequest类实现

首先,让我们来看一下完整的HttpRequest类实现代码:

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响应
            var responseObject = JsonSerializer.Deserialize<Response>(responseContent, JsonConverterExtensions.SerializerSettings);
            
            if (responseObject != null)
            {
                responseObject.StatusCode = response.StatusCode;
                return responseObject;
            }
        }
        catch (JsonException)
        {
            // 如果JSON解析失败,创建一个基于HTTP状态码的响应
        }
        
        // 对于非JSON响应或解析失败的情况
        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 = $"处理请求时发生意外错误: {ex.Message}",
            StatusCode = HttpStatusCode.InternalServerError,
            Data = ex
        };
    }
}

二、设计思路与实现要点

1. 依赖注入与生命周期管理

这个封装类采用了依赖注入模式,通过构造函数接收一个HttpClient实例。这样做有几个重要好处:

  • 遵循单一职责原则,HttpRequest类专注于HTTP请求处理
  • 便于单元测试,可以轻松注入模拟的HttpClient
  • 利用.NET的IHttpClientFactory进行正确的HttpClient生命周期管理,避免资源泄漏

同时,类实现了IDisposable接口,确保在不再需要时正确释放HttpClient资源。

2. 流畅接口设计

为了提供更友好的API体验,封装类实现了流畅接口模式:

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" });

这种链式调用方式使代码更加简洁易读,同时保持了良好的可扩展性。

3. 统一的错误处理

在每个HTTP方法中,我们都实现了统一的异常处理机制:

  • 捕获HttpRequestException处理HTTP特定错误
  • 捕获其他异常处理意外错误
  • 将所有错误转换为统一的Response对象
  • 保留原始异常信息以便调试

这种统一的错误处理方式使上层调用代码更加简洁,无需重复处理各种异常情况。

4. 灵活的响应处理

ProcessResponseAsync方法负责处理HTTP响应,它尝试将响应内容解析为JSON格式的Response对象:

  • 如果解析成功,返回包含完整信息的Response对象
  • 如果解析失败,创建一个基于HTTP状态码的Response对象
  • 始终保留原始响应内容和状态码信息

这种设计使封装类能够处理各种类型的HTTP响应,同时提供一致的返回格式。

三、实际使用示例

下面是一个使用这个封装类的完整示例:

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

public class Program
{
    public static async Task Main()
    {
        try
        {
            // 创建HttpClient实例(实际应用中建议使用IHttpClientFactory)
            using var httpClient = new HttpClient();
            
            // 创建请求实例并配置
            var request = new HttpRequest(httpClient)
                .WithBaseAddress("https://api.example.com")
                .WithBearerToken("your-auth-token");
                
            // 发送GET请求
            var getResponse = await request.GetAsync("/api/users");
            Console.WriteLine($"GET请求结果: {getResponse.Success}, 状态码: {getResponse.StatusCode}");
            
            // 发送POST请求
            var postData = new { Name = "John Doe", Email = "john@example.com" };
            var postResponse = await request.PostAsync("/api/users", postData);
            Console.WriteLine($"POST请求结果: {postResponse.Success}, 状态码: {postResponse.StatusCode}");
            
            // 发送PUT请求
            var putData = new { Id = 1, Name = "Jane Doe" };
            var putResponse = await request.PutAsync("/api/users/1", putData);
            Console.WriteLine($"PUT请求结果: {putResponse.Success}, 状态码: {putResponse.StatusCode}");
            
            // 发送DELETE请求
            var deleteResponse = await request.DeleteAsync("/api/users/1");
            Console.WriteLine($"DELETE请求结果: {deleteResponse.Success}, 状态码: {deleteResponse.StatusCode}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"发生未处理的异常: {ex.Message}");
        }
    }
}

四、HttpClient使用最佳实践

在使用HttpClient和这个封装类时,还需要注意以下最佳实践:

    1. 使用IHttpClientFactory:在ASP.NET Core应用中,始终使用IHttpClientFactory创建HttpClient实例,避免直接实例化HttpClient
    1. 设置合理的超时时间:默认情况下,HttpClient的超时时间是100秒,根据实际需求调整这个值,防止长时间阻塞。
    1. 处理取消请求:考虑实现请求取消机制,通过CancellationToken参数传递取消令牌。
    1. 处理重试逻辑:对于临时性网络错误,考虑实现重试机制。可以使用Polly等库来简化重试策略的实现。
    1. 监控HTTP请求性能:记录HTTP请求的执行时间、成功率等指标,便于性能分析和问题排查。

通过这个完整的HttpRequest类封装,我们可以更加高效、安全地处理HTTP通信,同时保持代码的整洁和可维护性。希望这篇文章对你理解C#中的HTTP请求处理有所帮助。

这个实现提供了完整的HTTP请求功能,包括GET、POST、PUT、DELETE方法,以及灵活的请求配置和统一的响应处理。博客中详细解释了设计思路、实现要点和最佳实践。如果你需要进一步调整代码或博客内容,请随时告诉我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2403561.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

构建 MCP 服务器:第 4 部分 — 创建工具

这是我们构建 MCP 服务器的四部分教程的最后一部分。在第一部分中&#xff0c;我们使用基本资源创建了第一个 MCP 服务器。第二部分添加了资源模板并改进了代码组织。在第三部分中&#xff0c;我们添加了提示符并进一步完善了服务器结构。现在&#xff0c;我们将通过添加工具来…

如何以 9 种方式将照片从手机传输到笔记本电脑

使用 USB 电缆可以将照片从智能手机复制到计算机。但是&#xff0c;如果没有 USB 数据线&#xff0c;如何将照片从手机无线传输到笔记本电脑呢&#xff1f;为了解决这个问题&#xff0c;我们搜索并测试了不同的应用程序&#xff0c;然后总结了本指南中分享的 9 个有效选项。您可…

生成JavaDoc文档

生成 JavaDoc 文档 1、快速生成 文档 注解 2、常见的文档注解 3、脚本生成 doc 文档 4、IDEA工具栏生成 doc 文档 第一章 快速入门 第01节 使用插件 在插件工具当中&#xff0c;找到插件 javaDoc 使用方式&#xff0c;在代码区域&#xff0c;直接点击右键。选择 第02节 常用注…

Web后端基础(Maven基础)

https://blog.csdn.net/q20202828/article/details/148459525?spm1001.2014.3001.5501 这是我总结了一下aliyun私服maven依赖配置Maven 3.9.1下载安装的操作 Maven的作用 统一项目结构 Maven 还提供了标准、统一的项目结构 。 1). 未使用Maven 由于java的开发工具呢&#x…

set map数据结构

#include <set> #include <iostream> using namespace std;int main() {// 设置控制台输出编码为UTF-8system("chcp 65001");set<int> s1; // 创建一个整数集合// 插入元素s1.insert(5);s1.insert(3);s1.insert(7);s1.insert(1);s1.insert(9);//默…

面试题小结(真实面试)

面试题 1.call与apply的区别2.vue3的响应式原理3.js的垃圾回收机制4.说说原型链5.什么是防抖和节流6.说一下作用域链7.在一个页面加载数据时&#xff08;还没加载完成&#xff09;&#xff0c;切换到另一个页面&#xff0c;怎么暂停之前页面的数据加载。 浏览器自动中止机制 这…

计算机网络领域所有CCF-A/B/C类期刊汇总!

本期小编统计了【计算机网络】领域CCF推荐所有期刊的最新影响因子&#xff0c;分区、年发文量以及投稿经验&#xff0c;供大家参考&#xff01; CCF-A类 1 IEEE Journal on Selected Areas in Communications 【影响因子】13.8 【期刊分区】JCR1区&#xff0c;中科院1区TOP …

有意向往gis开发靠,如何规划学习?

听说GIS开发工资不错、还不像互联网那么卷&#xff1f;心动了&#xff1f;但一看那些“WebGL”、“空间分析”、“OGC规范”的词儿就头大&#xff1f;别急&#xff01; 今天咱就聊聊零基础/转行选手&#xff0c;咋规划学习GIS开发这条路。不整高大上&#xff0c;就讲实在的&am…

五、查询处理和查询优化

五、查询处理和查询优化 主要内容 查询概述查询处理过程关系操作的基本实现算法查询优化技术代数优化基于存取路径的优化基于代价估算的优化 1. 查询概述 查询是数据库管理系统中使用最频繁、最基本的操作&#xff0c;对系统性能有很大影响。 对于同一个SQL查询&#xff0c…

缓解骨质疏松 —— 补钙和补维 D

骨质老化/疏松原理&#xff08;机制&#xff09;骨密度下降与骨小梁结构退化局部受压导致的微损伤或压力集中 诊断要点治疗策略吃什么食物能补钙呢&#xff1f;钙片吃什么食物能补维生素 D 呢&#xff1f; 骨质老化/疏松 骨质老化&#xff08;常指骨密度下降或骨质疏松&#x…

《PMBOK® 指南》第八版草案重大变革:6 大原则重构项目管理体系

项目管理领域的权威指南迎来关键升级&#xff01;PMI 最新发布的《PMBOK 指南》第八版草案引发行业广泛关注&#xff0c;此次修订首次将项目管理原则浓缩为 6 大黄金法则&#xff0c;重构 7 大绩效域&#xff0c;并首度公开过程组与绩效域的映射关系。本文将全面解析新版核心变…

Ctrl+R 运行xxx.exe,发现有如下问题.

CtrlR 运行xxx.exe,发现有如下问题. (1)找不到Qt5Core.all,Qt5Cored.dll,Qt5Gui.dll,Qt5Guid.dll,Qt5Widgets.all,Qt5Widgetsd.dll? (2)之后找不到libwinpthread-1.dll 从这个目录拷贝相应的库到运行xx.exe目录下 方法二:将库路径添加到系统PATH环境变量里: 在Path中添加路…

极智项目 | 基于PyQT+Whisper实现的语音识别软件设计

这是一个基于OpenAI的Whisper模型的语音识别应用程序&#xff0c;使用PyQt5构建了简洁直观的用户界面。该应用支持多语言识别&#xff0c;特别优化了中文识别体验。 项目下载&#xff1a;链接 功能特点 简洁现代的深色主题界面支持多语言识别&#xff08;中文、英语、日语等…

vue+cesium示例:地形开挖(附源码下载)

基于cesium和vue绘制多边形实现地形开挖效果&#xff0c;适合学习Cesium与前端框架结合开发3D可视化项目。 demo源码运行环境以及配置 运行环境&#xff1a;依赖Node安装环境&#xff0c;demo本地Node版本:推荐v18。 运行工具&#xff1a;vscode或者其他工具。 配置方式&#x…

升级:用vue canvas画一个能源监测设备和设备的关系监测图!

用vue canvas画一个能源电表和设备的监测图-CSDN博客 上一篇文章&#xff0c;我是用后端的数据来画出监测图。这次我觉的&#xff0c;用前端来控制数据&#xff0c;更爽。 本期实现功能&#xff1a; 1&#xff0c;得到监测设备和设备的数据&#xff0c;然后进行存库 2&…

深入理解 transforms.Normalize():PyTorch 图像预处理中的关键一步

深入理解 transforms.Normalize()&#xff1a;PyTorch 图像预处理中的关键一步 在使用 PyTorch 进行图像分类、目标检测等深度学习任务时&#xff0c;我们常常会在数据预处理部分看到如下代码&#xff1a; python复制编辑transform transforms.Compose([transforms.ToTensor…

爆炸仿真的学习日志

今天学习了一下【Workbench LS-DYNA中炸药在空气中爆炸的案例-哔哩哔哩】 https://b23.tv/kmXlN29 一开始 如果你的 ANSYS Workbench 工具箱&#xff08;Toolbox&#xff09;里 只有 SPEOS&#xff0c;即使尝试了 右键刷新、重置视图、显示全部 等方法仍然没有其他分析系统&a…

[华为eNSP] OSPF综合实验

目录 配置流程 画出拓扑图、标注重要接口IP 配置客户端IP 配置服务端IP 配置服务器服务 配置路由器基本信息&#xff1a;名称和接口IP 配置路由器ospf协议 测试结果 通过配置OSPF路由协议&#xff0c;实现跨多路由器的网络互通&#xff0c;并验证终端设备的访问能力。 …

完美搭建appium自动化环境

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 桌面版appium提供可视化操作appium主要功能的使用方式&#xff0c;对于初学者非常适用。 如何在windows平台安装appium桌面版呢&#xff0c;大体分两个步骤&…

c++中的输入输出流(标准IO,文件IO,字符串IO)

目录 &#xff08;1&#xff09;I/O概述 I/O分类 不同I/O的继承关系 不同I/O对应的头文件 &#xff08;2&#xff09;iostream 标准I/O流 iostream头文件中的IO流对象 iostream头文件中重载了<<和>> 缓冲区示意图 标准输入流 cin用法 cin&#xff1a;按空…