X.Helper.Http 1.0.3

dotnet add package X.Helper.Http --version 1.0.3
                    
NuGet\Install-Package X.Helper.Http -Version 1.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="X.Helper.Http" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="X.Helper.Http" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="X.Helper.Http" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add X.Helper.Http --version 1.0.3
                    
#r "nuget: X.Helper.Http, 1.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package X.Helper.Http@1.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=X.Helper.Http&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=X.Helper.Http&version=1.0.3
                    
Install as a Cake Tool

X.Helper.Http

X.Helper.Http 是基于 HttpClient 的轻量封装,面向常见业务 HTTP 场景,提供链式配置与可编程请求体两种用法。

支持能力:

  • 常规请求:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS/TRACE
  • 请求体:RAW / x-www-form-urlencoded / multipart/form-data / binary
  • 文件上传(含进度、取消)
  • 文件下载(流式落盘)
  • SSE 流式接收
  • Header / Cookie / 超时 / 编码 / 重试 / HTTP 版本配置

目标框架

  • >= net4.6.1
  • netstandard2.0
  • netstandard2.1
  • >= net6.0

说明:SSE 在 .NET Framework 目标下已弃用,建议在 net6.0+ 使用。


核心类型

Client

请求入口与链式配置核心类型。

常用能力:

  • 基础配置:SetUriSetMethodSetTimeoutSetEncodingSetHttpVersion
  • 调试:SetDebugEnabledSetDebugLogger
  • Header:SetHeaderSetDefaultRequestHeadersSetRefererSetOriginSetUserAgent
  • Cookie:SetCookieClearCookies
  • 请求体:SetContentType + AddContent / AddFile
  • 请求发送:RequestByteAsyncRequestTextAsyncRequestDownloadFile
  • 上传:RequestUploadFileAsync(...)(支持 IProgress<double> + CancellationToken
  • SSE:RequestSSEWithCallbackAsync(...).NET 6+ 额外支持 RequestSSEAsyncEnumerable(...)
    • returnRawEventBlock = false(默认):仅返回 data: 字段
    • returnRawEventBlock = true:回调/枚举返回完整事件块原文(含自定义字段)

Result

统一响应结果:

  • 状态:StatusCodeStatusDescriptionIsSuccess
  • 头与 Cookie:HeaderCollectionCookieCollection
  • 内容:Content(文本)、Bytes(字节)
  • 其他:ResponseUriRedirectUrlContentTypeDownloadFilePath
  • 异常与取消:Exception(内部异常及堆栈,成功时为 null)、IsCanceled(因外部取消令牌终止时为 true;超时被归类为失败而非取消)

HttpHandler

HttpClientHandler 的配置封装:

  • 代理、自动重定向、自动解压
  • CookieContainer
  • 证书验证回调、客户端证书
  • 凭据配置

HttpContentCreator

类型全名:X.Helper.Http.Helper.HttpContentCreator

可编程请求体构建器,适合复杂 body 场景;与 RequestByteAsync(contentCreator)RequestTextAsync(contentCreator) 配合使用。


实例生命周期与线程安全

Client 内部持有 HttpClient,请遵循以下契约:

  • 长生命周期复用:同一 Client 实例应在多次请求间复用(注册为单例或少量实例),不要每次请求都 new Client() 后立即 Dispose —— 高并发下会带来 socket 耗尽(TIME_WAIT 堆积)风险。
  • 非线程安全:同一实例不要并发发起多个请求;需要并发时请为每个并发请求使用独立 Client 实例,或自行串行化访问。
  • 典型用法(复用同一实例):
// 推荐:作为长生命周期对象复用,而非每次请求 new + Dispose
private static readonly X.Helper.Http.Client _client = new X.Helper.Http.Client(baseUrl);

var result = await _client
    .SetMethod(X.Helper.Http.Enums.HttpMethod.GET)
    .RequestTextAsync(token);

文档中的"快速开始 / 标准模板"示例为便于阅读,常写成 new Client(...) 单次使用;生产环境请改为复用实例

两种请求体模式(重要)

Client 支持两种 body 配置方式:

  1. 链式:SetContentType(...) + AddContent(...) / AddFile(...)
  2. 参数:RequestTextAsync(contentCreator)RequestByteAsync(contentCreator)

同一次请求中,这两种方式不可同时使用。若同时配置会抛出异常。

另外:

  • contentCreator 模式下当前不支持自动重试(SetRetryCount > 0 会抛异常)。
  • 链式 MULTIPART_FORM_DATA 已支持同时提交表单字段 + AddFile(...) 文件。

SetContentType(...) 行为说明

  • SetContentType(...)Set 语义是“重置并切换类型”。
  • 调用后会清空此前通过 AddContent(...) 添加的内容参数,这是预期行为,不是缺陷。
  • SetContentType(...) 仅清空内容参数,不会清空已通过 AddFile(...) 添加的文件。
  • 推荐调用顺序:先 SetContentType(...),再 AddContent(...) / AddFile(...)
  • 如果先 AddContent(...)SetContentType(...),之前内容会被清空。
// 推荐:先 SetContentType,再 AddContent
var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED)
    .AddContent("name", "alice")
    .RequestTextAsync();

快速开始

1) GET + 文本响应

using var client = new X.Helper.Http.Client("https://httpbin.org/get");

var result = await client
    .SetMethod(X.Helper.Http.Enums.HttpMethod.GET)
    .RequestTextAsync();

if (result.IsSuccess)
    Console.WriteLine(result.Content);

2) POST JSON(链式 body)

using var client = new X.Helper.Http.Client("https://httpbin.org/post");

var result = await client
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
    .AddContent("{\"name\":\"SystemX\",\"age\":18}")
    .RequestTextAsync();

3) POST 表单(HttpContentCreator

var creator = new X.Helper.Http.Helper.HttpContentCreator(
    X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED);

creator.AddContent("name", "SystemX")
       .AddContent("role", "admin");

using var client = new X.Helper.Http.Client("https://httpbin.org/post");

var result = await client
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .RequestTextAsync(creator);

4) multipart(链式表单 + 文件)

using var client = new X.Helper.Http.Client("https://example.com/upload");

var result = await client
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.MULTIPART_FORM_DATA)
    .AddContent("bizType", "avatar")
    .AddFile("file", @"D:\data\avatar.png")
    .RequestTextAsync();

5) 文件上传(进度 + 取消)

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var progress = new Progress<double>(p => Console.WriteLine($"上传进度: {p:P1}"));

using var client = new X.Helper.Http.Client("https://example.com/upload");

var result = await client
    .AddFile("file", @"D:\data\test.bin")
    .RequestUploadFileAsync(progress, cts.Token);

6) 文件下载(流式)

using var client = new X.Helper.Http.Client("https://example.com/file.zip");

var result = await client
    .SetAutoCreateDirectory(true)
    .RequestDownloadFile(@"D:\download\file.zip");

Console.WriteLine(result.DownloadFilePath);

覆盖与错误处理:目标文件已存在时默认不覆盖,返回失败 ResultIsSuccess=falseException 携带 IOException)。如需覆盖,调用 SetFileDownloadOverwrite(true)。目标目录不存在且未开启 SetAutoCreateDirectory(true) 时同样返回失败 Result(异常为 DirectoryNotFoundException)。下载相关错误统一通过 Result 承载,不再抛出异常

7) SSE 流式接收(net6.0+ 推荐)

using var client = new X.Helper.Http.Client("https://example.com/sse");

await client.RequestSSEWithCallbackAsync(
    onChunk: async chunk =>
    {
        Console.WriteLine($"SSE: {chunk}");
        await Task.CompletedTask;
    },
    returnRawEventBlock: true);

returnRawEventBlock 默认值为 false,仅返回 data 事件块。 如果仅需要返回所有完整事件块(包括 data/event/id/retry、注释和自定义标识行)。,请设置 returnRawEventBlock: true

.NET 6+ 可使用异步枚举:

await foreach (var chunk in client.RequestSSEAsyncEnumerable(returnRawEventBlock: true))
{
    Console.WriteLine(chunk);
}

标准链式调用模板(建议直接复用)

模板 A:普通 GET

var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.GET)
    .RequestTextAsync(token);

模板 B:JSON 提交(POST/PUT/PATCH)

var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
    .AddContent(json)
    .RequestTextAsync(token);

模板 C:表单提交(x-www-form-urlencoded

var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED)
    .AddContent("name", "alice")
    .AddContent("age", 18)
    .RequestTextAsync(token);

模板 D:multipart(表单 + 文件,走 RequestTextAsync

var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.MULTIPART_FORM_DATA)
    .AddContent("bizType", "avatar")
    .AddFile("file", @"D:\\data\\avatar.png")
    .RequestTextAsync(cancellationToken: token);

模板 E:专用上传(带进度)

var progress = new Progress<double>(p => Console.WriteLine($"upload: {p:P1}"));

var result = await new X.Helper.Http.Client(url)
    .AddFile("file", @"D:\\data\\big.bin")
    .RequestUploadFileAsync(progress, token);

模板 F:流式下载到文件

var result = await new X.Helper.Http.Client(url)
    .SetAutoCreateDirectory(true)
    .RequestDownloadFile(@"D:\\download\\target.zip", token);

模板 G:SSE(net6.0+

await new X.Helper.Http.Client(url).RequestSSEWithCallbackAsync(
    onChunk: chunk =>
    {
        Console.WriteLine(chunk);
        return Task.CompletedTask;
    },
    cancellationToken: token,
    returnRawEventBlock: true);

顺序规则:基础配置 → Header/Cookie → SetContentTypeAddContent/AddFile → 发送。

冲突规则:

  • SetContentType(...) 会清空已添加请求体参数。
  • 链式 body 与 contentCreator 参数不可同时使用。
  • contentCreator 模式下不支持自动重试(SetRetryCount > 0 会抛异常)。

常见错误示例(反例)

反例 1:先 AddContentSetContentType

// ? 错误:SetContentType 会清空之前 AddContent 的参数
var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .AddContent("name", "alice")
    .SetContentType(X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED)
    .RequestTextAsync();
// ? 正确:先 SetContentType,再 AddContent
var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.X_WWW_FORM_URLENCODED)
    .AddContent("name", "alice")
    .RequestTextAsync();

反例 2:链式 body 与 contentCreator 同时使用

// ? 错误:同一次请求不能同时使用两套请求体配置
var creator = new X.Helper.Http.Helper.HttpContentCreator(
    X.Helper.Http.Enums.HttpContentType.RAW_JSON)
    .AddContent("{\"name\":\"alice\"}");

var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
    .AddContent("{\"name\":\"bob\"}")
    .RequestTextAsync(creator); // 将抛 InvalidOperationException

反例 3:contentCreator 模式下启用自动重试

// ? 错误:contentCreator 模式不支持 RetryCount > 0
var creator = new X.Helper.Http.Helper.HttpContentCreator(
    X.Helper.Http.Enums.HttpContentType.RAW_JSON)
    .AddContent("{\"name\":\"alice\"}");

var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetRetryCount(2)
    .RequestTextAsync(creator); // 将抛 InvalidOperationException

反例 4:默认 GET 却试图发送 body

// ? 错误:未设置方法时默认 GET,GET 场景不会发送请求体
var result = await new X.Helper.Http.Client(url)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
    .AddContent("{\"name\":\"alice\"}")
    .RequestTextAsync();
// ? 正确:显式设置 POST/PUT/PATCH 等支持请求体的方法
var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.RAW_JSON)
    .AddContent("{\"name\":\"alice\"}")
    .RequestTextAsync();

反例 5:AddFile 后未使用 multipart 或专用上传接口

// ? 易错:AddFile 后若未设置 MULTIPART_FORM_DATA,文件不会按预期进入普通请求体
var result = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .AddFile("file", @"D:\\data\\a.bin")
    .RequestTextAsync();
// ? 方式 A:普通请求中显式设置 multipart
var resultA = await new X.Helper.Http.Client(url)
    .SetMethod(X.Helper.Http.Enums.HttpMethod.POST)
    .SetContentType(X.Helper.Http.Enums.HttpContentType.MULTIPART_FORM_DATA)
    .AddFile("file", @"D:\\data\\a.bin")
    .RequestTextAsync();

// ? 方式 B:直接使用专用上传接口
var resultB = await new X.Helper.Http.Client(url)
    .AddFile("file", @"D:\\data\\a.bin")
    .RequestUploadFileAsync();

常用配置与行为说明

请求体顺序

推荐顺序:

  1. SetContentType(...)
  2. AddContent(...) / AddFile(...)

SetContentType(...) 会清空当前已添加的请求体参数。

  • SetDefaultRequestHeaders:作用于当前 Client 实例生命周期内的所有请求
  • SetHeader:作用于单次请求消息

调试输出

  • 默认关闭,通过 SetDebugEnabled(true) 开启。
  • 可通过 SetDebugLogger(Action<string>) 自定义输出位置(如 ILogger、文件、控制台)。
  • 开启后会输出关键节点信息:
    • 请求参数(含 Header/Cookie 完整值、请求体参数、文件参数)
    • 请求过程(发送、重试、超时、异常、完成)
    • 响应信息(状态码、响应头、Cookie、响应正文/字节长度)
    • SSE 每个返回块的完整内容
using var client = new X.Helper.Http.Client(url)
    .SetDebugEnabled(true)
    .SetDebugLogger(msg => Console.WriteLine(msg));

重试

  • 通过 SetRetryCount(int) 配置
  • RequestTextAsyncRequestByteAsync 参与重试
  • 上传/下载/SSE 不参与重试
  • 非成功状态码仅对以下状态自动重试:4084295xx
  • 仅幂等方法重试(默认安全):默认开启「仅幂等重试」,POST/PATCH 等非幂等写请求不会自动重试,避免重复下单/扣款等副作用;GET/HEAD/OPTIONS/TRACE/PUT/DELETE 才参与重试。如需恢复"所有方法均重试"的旧行为,调用 SetRetryIdempotentOnly(false)(存在重复写风险,请谨慎)。
  • 不可 seek 的流不重试:链式 BINARY 模式若传入不可随机读取的 Stream,为避免重试时发送残缺内容,该请求不会自动重试。
  • 退避策略:固定 N×500ms(封顶 2s),并叠加 ±25% 随机 jitter 防止惊群;命中 429 时优先采用响应头 Retry-After 指定的等待时间(上限 60s)。

支持两种模式:

  1. 手动 SetCookie(...)
  2. 通过 HttpHandler.SetCookieContainer(CookieContainer) 注入自定义 CookieContainer
  • SetCookieContainer(...) 传入的 CookieContainer 会被库复用(共享 Cookie 容器,不会被内部替换为新的空容器),可实现跨请求维持会话。
  • 建议不要混用两种模式;混用时以实际请求写入为准。

HTTP 版本

  • 默认 HTTP/1.1
  • 可通过 SetHttpVersion(...) 指定版本

结果与异常建议

  • 业务判断优先使用 result.IsSuccess
  • 失败优先查看 result.StatusDescription
  • 文本响应读取 result.Content
  • 字节响应读取 result.Bytes
  • 上传/下载/SSE 建议始终传入 CancellationToken
  • 需要排查失败时,读取 result.Exception(内部异常及堆栈,成功或纯取消时为 null)
  • 区分「外部取消」与「真实失败」:取消时 result.IsCanceled == trueException == null;超时 / 网络异常时 IsCanceled == falseException 非空
  • 大响应保护RequestByteAsync / RequestTextAsync 默认最多将 64MB 响应读入内存,超过阈值会拒绝读取并返回失败 Result(含 Exception)。超大响应请改用 RequestDownloadFile 流式落盘,或调用 SetMaxResponseBufferSize(long) 调高阈值。

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 is compatible.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.3 70 8/11/2026
1.0.2.4 109 6/17/2026
1.0.2.3 118 6/16/2026
1.0.2.2 111 6/11/2026
1.0.2.1 108 6/7/2026