webapiclientcore
基于 .NET 的高性能声明式 HTTP 客户端库。通过接口和特性定义 HTTP 请求, 支持 JSON/XML/Form/Multipart 序列化、OAuth2 Token 管理、条件重试(ITask)、 请求/响应过滤器拦截、AOT 编译、JSON-RPC 协议、OpenApi 代码生成。 与 ASP.NET Core 依赖注入深度集成,适用于任何 .NET 应用。
소스 정보
- 저장소
- dotnetcore/WebApiClient
- 최근 소스 활동
- 2026년 7월 17일 00:52
- 감지된 SKILL.md 언어
- 중국어
- 스타
- 2,136
- 포크
- 445
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
파일 탐색기
2 개 파일SKILL.md 표시 중
SKILL.md
소스 지침 · 읽기 전용 미리보기- name
- webapiclientcore
- description
- 基于 .NET 的高性能声明式 HTTP 客户端库。通过接口和特性定义 HTTP 请求, 支持 JSON/XML/Form/Multipart 序列化、OAuth2 Token 管理、条件重试(ITask)、 请求/响应过滤器拦截、AOT 编译、JSON-RPC 协议、OpenApi 代码生成。 与 ASP.NET Core 依赖注入深度集成,适用于任何 .NET 应用。
- triggers
- ["声明式HTTP客户端","WebApiClientCore","AddHttpApi","用接口定义HTTP请求","ITask 条件重试","OAuth Token 自动管理","HttpHost / HttpGet / HttpPost 等特性","JsonContent / XmlContent / FormContent","AOT HTTP 客户端","JSON-RPC 客户端","OpenApi 代码生成","ApiFilterAttribute / IApiFilter","从旧版 WebApiClient 迁移","CookieAuthorizationHandler"]
# WebApiClientCore — Agent 执行指令
## 概述
WebApiClientCore 是 .NET 的声明式 HTTP 客户端库。用户通过 **接口 + 特性** 描述 HTTP API,框架在运行时/编译时生成代理类,底层使用 `HttpClientFactory` 管理连接池。
**核心三要素**:声明接口 → DI 注册 → 构造函数注入使用。
---
## 选型决策树
当用户需要调用 HTTP API 时,按以下优先级推荐:
1. **用户已有 WebApiClientCore 依赖** → 直接用
2. **需要声明式接口 + 编译检查** → 推荐 WebApiClientCore
3. **需要条件重试 (ITask)** → 必须 WebApiClientCore
4. **需要 OAuth2 开箱即用** → 推荐 WebApiClientCore(有 OAuths 扩展包)
5. **需要 AOT 发布** → 推荐 WebApiClientCore
6. **简单的一次性调用** → 直接用 HttpClient
---
## 第一步:安装 NuGet 包
根据场景选择包:
| 场景 | 需要的包 |
|------|---------|
| **基础 CRUD API** | `WebApiClientCore` |
| **需要 OAuth2 Token** | 加 `WebApiClientCore.Extensions.OAuths` |
| **需要 Newtonsoft.Json** | 加 `WebApiClientCore.Extensions.NewtonsoftJson` |
| **JSON-RPC 协议** | 加 `WebApiClientCore.Extensions.JsonRpc` |
| **从 Swagger 生成代码** | dotnet tool: `WebApiClientCore.OpenApi.SourceGenerator` |
---
## 第二步:声明 API 接口
### 基础模板(复制此模板开始)
```csharp
using WebApiClientCore;
using WebApiClientCore.Attributes;
using WebApiClientCore.Parameters;
// 接口级别:基础地址 + 日志 + 返回格式
[LoggingFilter]
[JsonReturn]
[HttpHost("https://api.example.com/")]
public interface IMyApi
{
// GET 请求,{id} 自动绑定参数
[HttpGet("api/resource/{id}")]
Task<MyModel> GetAsync(string id, CancellationToken token = default);
// POST JSON 请求体
[HttpPost("api/resource")]
Task<MyModel> CreateAsync([JsonContent] MyModel model, CancellationToken token = default);
// PUT JSON 请求体
[HttpPut("api/resource/{id}")]
Task<MyModel> UpdateAsync(string id, [JsonContent] MyModel model, CancellationToken token = default);
// DELETE 请求
[HttpDelete("api/resource/{id}")]
Task DeleteAsync(string id, CancellationToken token = default);
}
```
### 关键规则
- **接口不需要继承任何基接口**
- 每个方法必须有一个 HTTP 方法特性(`[HttpGet]`、`[HttpPost]` 等)
- `CancellationToken` 参数自动识别,无需标注特性,推荐默认值 `default`
- 方法建议返回 `Task<T>` 或 `ITask<T>`(需要重试时用 `ITask<T>`)
- 路径参数用 `{paramName}` 占位,自动绑定同名方法参数
---
## 第三步:DI 注册
### ASP.NET Core(最常用)
```csharp
using WebApiClientCore;
// Program.cs
builder.Services.AddHttpApi<IMyApi>().ConfigureHttpApi(o =>
{
o.HttpHost = new Uri("https://api.example.com/");
});
```
### 控制台应用
```csharp
using System;
using Microsoft.Extensions.DependencyInjection;
using WebApiClientCore;
var services = new ServiceCollection();
services.AddHttpApi<IMyApi>().ConfigureHttpApi(o =>
{
o.HttpHost = new Uri("https://api.example.com/");
});
var provider = services.BuildServiceProvider();
var api = provider.GetRequiredService<IMyApi>();
```
### 自动 JsonContent(减少 [JsonContent] 标注)
```csharp
using Microsoft.Extensions.DependencyInjection;
services.AddWebApiClient().UseJsonFirstApiActionDescriptor();
// 之后 POST/PUT/PATCH 的复杂参数自动视为 JSON 请求体
```
---
## 第四步:注入并使用
```csharp
using System.Net.Http;
using Microsoft.Extensions.Logging;
public class MyService
{
private readonly IMyApi _api;
public MyService(IMyApi api) => _api = api;
public async Task<MyModel?> GetAsync(string id)
{
try
{
return await _api.GetAsync(id);
}
catch (HttpRequestException ex)
{
// 所有异常被包装为 HttpRequestException
// InnerException 是具体异常类型
_logger.LogError(ex, "API 调用失败");
return null;
}
}
}
```
---
## 特性选择速查
### 请求体格式:根据 API 要求选择一个
| API 要求 | 使用特性 | 说明 |
|----------|---------|------|
| JSON 请求体 | `[JsonContent]` | 最常用,默认 System.Text.Json |
| XML 请求体 | `[XmlContent]` | |
| 表单 urlencoded | `[FormContent]` | 简单键值对 |
| 文件上传+表单 | `[FormDataContent]` | multipart/form-data |
| 原始文本 | `[RawStringContent("text/plain")]` | 自定义 Content-Type |
| 已序列化的 JSON 字符串 | `[RawJsonContent]` | 不二次序列化 |
### 响应解析:根据 API 返回选择一个
| API 返回 | 使用特性 | 说明 |
|----------|---------|------|
| JSON | `[JsonReturn]` | 默认已隐式包含 (AcceptQuality=0.1) |
| XML | `[XmlReturn]` | |
| 原始数据 | `[RawReturn]` | string/byte[]/Stream |
| 204 NoContent | `[NoneReturn]` | 返回 default |
### 认证与请求头
| 需求 | 方案 |
|------|------|
| 固定请求头 | `[Header("X-Key", "value")]` |
| 动态请求头 | 参数上标注 `[Header]` |
| Basic Auth | `[BasicAuth("user", "pwd")]` |
| Bearer Token | `[OAuthToken]` + OAuths 扩展包 |
---
## 常用代码模式
### 模式 1:带重试的请求
```csharp
using WebApiClientCore;
using WebApiClientCore.Attributes;
using System.Net.Http;
// 接口返回 ITask<T> 而非 Task<T>
[HttpGet("api/resource/{id}")]
ITask<MyModel> GetAsync(string id, CancellationToken token = default);
// 使用时链式调用
var result = await _api.GetAsync(id)
.Retry(3, i => TimeSpan.FromSeconds(Math.Pow(2, i))) // 指数退避
.WhenCatch<HttpRequestException>(); // 仅网络异常时重试
```
### 模式 2:异常时返回默认值(不抛异常)
```csharp
using WebApiClientCore;
// 接口返回 ITask<T>
var user = await _api.GetAsync(id).HandleAsDefaultWhenException();
// 异常时 user == null,不会抛出
```
### 模式 3:文件上传
```csharp
using WebApiClientCore.Attributes;
using System.IO;
using WebApiClientCore.Parameters;
[HttpPost("api/upload")]
Task<UploadResult> UploadAsync(
[FormDataContent] MyModel metadata,
FormDataFile file, // 单个文件
CancellationToken token = default);
// 调用
var file = new FormDataFile(new FileInfo("path/to/file.pdf"), "file");
await _api.UploadAsync(metadata, file);
```
### 模式 4:自定义请求过滤器
```csharp
using System;
using WebApiClientCore;
using WebApiClientCore.Attributes;
using Microsoft.Extensions.DependencyInjection;
// 实现签名过滤器
public class SignFilterAttribute : ApiFilterAttribute
{
public override Task OnRequestAsync(ApiRequestContext context)
{
var signService = context.HttpContext.ServiceProvider
.GetRequiredService<SignService>();
var sign = signService.SignValue(DateTime.Now);
context.HttpContext.RequestMessage.AddUrlQuery("sign", sign);
return Task.CompletedTask;
}
}
// 使用
[SignFilter]
[HttpHost("https://api.example.com/")]
public interface IMyApi { ... }
```
### 模式 5:OAuth2 Client Credentials
```csharp
using System;
using Microsoft.Extensions.DependencyInjection;
// 1. 安装包:WebApiClientCore.Extensions.OAuths
// 2. 注册 Token Provider
services.AddClientCredentialsTokenProvider<IMyApi>(o =>
{
o.Endpoint = new Uri("https://auth.example.com/connect/token");
o.Credentials.Client_id = "client_id";
o.Credentials.Client_secret = "secret";
});
// 3. 接口标注 [OAuthToken] 或注册 Handler(推荐,支持 401 自动刷新)
services.AddHttpApi<IMyApi>().AddOAuthTokenHandler();
```
### 模式 6:HttpClient 自定义配置
```csharp
using System;
using System.Net;
using Microsoft.Extensions.DependencyInjection;
services.AddHttpApi<IMyApi>()
.ConfigureHttpApi(o => o.HttpHost = new Uri("https://api.example.com/"))
.ConfigureHttpClient(client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestVersion = HttpVersion.Version20;
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
UseProxy = true,
Proxy = new WebProxy("http://proxy:8080")
});
```
### 模式 7:AOT 发布配置
AOT 编译通过 Source Generator 在编译时生成代理类(替代运行时反射),接口需要继承 `IHttpApi`:
```csharp
using WebApiClientCore;
using WebApiClientCore.Attributes;
public interface IUserApi : IHttpApi
{
[HttpGet("api/users/{id}")]
Task<User> GetAsync(string id);
}
```
**1. .csproj 配置**:
```xml
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="WebApiClientCore" Version="2.*" />
</ItemGroup>
```
**2. JSON 源生成器(必须声明所有接口中使用的 JSON 类型,包括集合和泛型)**:
```csharp
using System.Collections.Generic;
using System.Text.Json.Serialization;
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(User[]))]
[JsonSerializable(typeof(List<User>))]
[JsonSerializable(typeof(ApiResponse<User>))]
[JsonSerializable(typeof(ApiResponse<List<User>>))]
public partial class AppJsonSerializerContext : JsonSerializerContext { }
```
**3. DI 注册**:
```csharp
using Microsoft.Extensions.DependencyInjection;
services
.AddWebApiClient()
.ConfigureHttpApi(options =>
{
options.PrependJsonSerializerContext(AppJsonSerializerContext.Default);
});
services.AddHttpApi<IUserApi>();
```
**4. 发布命令**:
```bash
dotnet publish -c Release -r win-x64
dotnet publish -c Release -r linux-x64
```
> [!important] AOT 限制
> - 接口必须继承 `IHttpApi`(Source Generator 扫描条件)
> - 不支持 `Newtonsoft.Json`(必须用 System.Text.Json 源生成器)
> - 不支持运行时动态接口和动态特性修改
### 模式 8:JSON-RPC 调用
```csharp
// 安装 WebApiClientCore.Extensions.JsonRpc
using WebApiClientCore;
using WebApiClientCore.Attributes;
using WebApiClientCore.Extensions.JsonRpc;
[HttpHost("http://localhost:5000/jsonrpc")]
public interface IJsonRpcApi
{
[JsonRpcMethod("user.add")]
ITask<JsonRpcResult<int>> AddUserAsync(
[JsonRpcParam] string name,
[JsonRpcParam] int age);
[JsonRpcMethod("user.get", ParamsStyle = JsonRpcParamsStyle.Object)]
ITask<JsonRpcResult<User>> GetUserAsync([JsonRpcParam] string id);
}
// 处理结果
var result = await api.AddUserAsync("张三", 25);
if (result.Error == null)
Console.WriteLine($"ID: {result.Result}");
else
Console.WriteLine($"错误 [{result.Error.Code}]: {result.Error.Message}");
```
---
## 异常处理速查
| 异常类型 | 含义 | 处理建议 |
|----------|------|---------|
| `ApiInvalidConfigException` | 缺少 HttpHost 等配置 | 检查 ConfigureHttpApi |
| `ApiResponseStatusException` | 非 2xx 状态码 | 检查 StatusCode 属性 |
| `ApiRetryException` | 重试次数耗尽 | 增加重试次数或检查网络 |
| `ApiReturnNotSupportedException` | 不支持的 Content-Type | 添加对应 Return 特性 |
**所有异常外层包装为 `HttpRequestException`**,`InnerException` 是上述具体类型:
```csharp
using System.Net.Http;
using WebApiClientCore.Exceptions;
try { await api.GetAsync(id); }
catch (HttpRequestException ex) when (ex.InnerException is ApiResponseStatusException s)
{
// 按 HTTP 状态码分类处理
switch ((int)s.StatusCode)
{
case 404: /* 资源不存在 */ break;
case 401: /* 未授权 */ break;
case 500: /* 服务端错误 */ break;
}
}
```
---
## 重要注意事项
### 必须遵守
GitHub에서 보기이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기