Mud.HttpUtils 是一个基于 Roslyn 源代码生成器的声明式 HTTP 客户端框架,通过特性标注的方式自动生成类型安全的 HTTP API 客户端代码。无需手写 HttpClient 调用代码,只需定义接口并添加特性标注,编译器会自动生成完整的实现代码。
- 🚀 零运行时开销:编译时生成代码,无反射,性能优异
- 🎯 类型安全:强类型 API 调用,编译时检查错误
- 📝 声明式编程:通过特性标注定义 HTTP API,简洁直观
- 🔧 功能丰富:支持多种 HTTP 方法、参数类型、内容格式、Token 认证、加密传输等
- 🛡️ 弹性策略:内置重试、超时、熔断策略,基于 Polly 实现
- 🔐 加密支持:可插拔的加密提供程序,内置 AES 加密实现
- 🔄 令牌管理:并发安全的令牌刷新,支持持久化存储契约
- 🌐 多客户端:支持多命名客户端场景,通过
IHttpClientResolver动态解析 - 🎨 灵活配置:支持接口级、方法级、参数级的配置优先级
- 🏗️ 接口级动态属性:在接口上定义
[Query]/[Path]属性,实现全局参数 - 🗺️ QueryMap 参数映射:将对象/字典展开为查询参数,支持序列化控制
- 🔗 Base Path 支持:在接口级别定义统一路径前缀
- 📦 Response<T> 包装类型:同时返回响应内容和元数据
- 📦 多框架支持:支持 .NET Standard 2.0、.NET 6.0、.NET 8.0、.NET 10.0
MudHttpUtils/
├── Mud.HttpUtils/ # 元包:一站式引用 + DI 注册
│ └── ServiceCollectionExtensions # AddMudHttpUtils() 一站式注册
├── Mud.HttpUtils.Abstractions/ # 接口定义层(零依赖)
│ ├── IBaseHttpClient # 基础 HTTP 操作接口
│ ├── IEnhancedHttpClient # 增强客户端组合接口
│ ├── IEncryptionProvider # 加密提供程序接口
│ ├── ITokenManager # 令牌管理接口
│ ├── ITokenStore / IUserTokenStore # 令牌持久化存储契约
│ ├── IHttpClientResolver # 命名客户端解析接口
│ ├── TokenManagerBase # 令牌管理器抽象基类
│ ├── TokenTypes # 令牌类型常量
│ └── IMudAppContext # 应用上下文接口
├── Mud.HttpUtils.Attributes/ # 特性定义层
│ ├── HttpClientApiAttribute # API 接口标注
│ ├── Get/Post/Put/Delete/... # HTTP 方法特性
│ └── Path/Query/Body/Token/... # 参数特性
├── Mud.HttpUtils.Client/ # 客户端实现层
│ ├── EnhancedHttpClient # 抽象基类
│ ├── HttpClientFactoryEnhancedClient # IHttpClientFactory 实现
│ ├── DefaultAesEncryptionProvider # AES 加密默认实现
│ ├── HttpClientResolver # 命名客户端解析器
│ └── ServiceCollectionExtensions # AddMudHttpClient() 注册
├── Mud.HttpUtils.Resilience/ # 弹性策略扩展包
│ ├── ResilientHttpClient # 装饰器(重试/超时/熔断)
│ ├── PollyResiliencePolicyProvider # Polly 策略提供器
│ ├── HttpRequestMessageCloner # 请求克隆工具
│ └── ServiceCollectionExtensions # AddMudHttpResilienceDecorator() 注册
├── Mud.HttpUtils.Generator/ # 源代码生成器
│ ├── HttpInvokeClassSourceGenerator # 实现类生成器
│ └── HttpInvokeRegistrationGenerator # 注册代码生成器(含 Timeout 配置)
├── Demos/ # 示例项目
└── Tests/ # 测试项目
# 安装元包(包含 Abstractions + Attributes + Client + Resilience)
dotnet add package Mud.HttpUtils
# 安装源代码生成器
dotnet add package Mud.HttpUtils.Generatorusing Mud.HttpUtils.Attributes;
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
public interface IUserApi
{
[Get("/users/{id}")]
Task<UserInfo> GetUserAsync([Path] int id);
[Post("/users")]
Task<UserInfo> CreateUserAsync([Body] CreateUserRequest request);
[Get("/users")]
Task<List<UserInfo>> GetUsersAsync(
[Query] string? name = null,
[Query] int page = 1,
[Query] int pageSize = 20
);
[Put("/users/{id}")]
Task<UserInfo> UpdateUserAsync([Path] int id, [Body] UpdateUserRequest request);
[Delete("/users/{id}")]
Task<bool> DeleteUserAsync([Path] int id);
[Post("/upload")]
Task<UploadResult> UploadAsync([Upload] IFormFile file);
[Post("/login")]
Task<LoginResult> LoginAsync([Form("username")] string user, [Form("password")] string pass);
}// 一站式注册:Client + 弹性策略
services.AddMudHttpUtils("userApi", "https://api.example.com", options =>
{
options.Retry.MaxRetryAttempts = 3;
options.Timeout.TimeoutSeconds = 30;
});
// 注册生成器生成的 API 接口
services.AddWebApiHttpClient();public class UserService
{
private readonly IUserApi _userApi;
public UserService(IUserApi userApi)
{
_userApi = userApi;
}
public async Task<UserInfo> GetUserByIdAsync(int id)
{
return await _userApi.GetUserAsync(id);
}
}[Get]- GET 请求[Post]- POST 请求[Put]- PUT 请求[Delete]- DELETE 请求(支持带请求体)[Patch]- PATCH 请求[Head]- HEAD 请求[Options]- OPTIONS 请求
| 特性 | 说明 | 示例 |
|---|---|---|
[Path] |
URL 路径参数 | [Get("/users/{id}")] + [Path] int id |
[Query] |
URL 查询参数 | [Query] string? name |
[QueryMap] |
查询参数映射(对象/字典展开为查询参数) | [QueryMap] SearchCriteria criteria |
[ArrayQuery] |
数组查询参数 | [ArrayQuery] int[] ids |
[RawQueryString] |
原始查询字符串 | [RawQueryString] string queryString |
[Header] |
HTTP 请求头(支持参数/方法/接口级别) | [Header("X-API-Key")] string apiKey |
[Body] |
请求体 | [Body] UserRequest request |
[Body(RawString = true)] |
原始字符串请求体 | [Body(RawString = true)] string content |
[Body(UseStringContent = true)] |
字符串内容请求体 | [Body(UseStringContent = true)] string content |
[FormContent] |
表单数据 | [FormContent] IFormContent formData |
[Form] |
表单字段(application/x-www-form-urlencoded) |
[Form("username")] string user |
[MultipartForm] |
多部分表单字段(multipart/form-data) |
[MultipartForm] IFormFile file |
[Upload] |
文件上传参数(支持自定义字段名/文件名/内容类型) | [Upload(FieldName = "doc")] IFormFile file |
[FilePath] |
文件下载路径 | [FilePath] string savePath |
[Token] |
Token 认证(支持参数/接口/方法级别) | [Token(TokenTypes.UserAccessToken)] string token |
支持三级配置,优先级从高到低:
Body 参数级 > 方法级 > 接口级 > 默认值 (application/json)
[Header] 特性支持应用到参数、方法或接口级别:
// 参数级别
[Get("/users")]
Task<List<User>> GetUsersAsync([Header("X-API-Key")] string apiKey);
// 方法级别(添加固定请求头)
[Get("/users")]
[Header("Accept", "application/json")]
[Header("X-Request-Source", "Web")]
Task<List<User>> GetUsersAsync();
// 接口级别(所有方法自动携带)
[HttpClientApi]
[Header("X-API-Version", "v2")]
public interface IUserApi { }HeaderAttribute 支持 AliasAs(别名映射)和 Replace(替换模式)属性。
基于 Polly 的弹性策略,通过装饰器模式包装 HTTP 客户端:
| 策略 | 默认状态 | 说明 |
|---|---|---|
| 重试 | 启用 | 默认 3 次重试,支持指数退避 |
| 超时 | 启用 | 默认 30 秒,悲观超时策略 |
| 熔断 | 关闭 | 连续失败阈值触发,支持半开状态 |
策略组合顺序:重试(外层) → 熔断 → 超时(内层)
services.AddMudHttpUtils("myApi", "https://api.example.com", options =>
{
options.Retry.MaxRetryAttempts = 3;
options.Retry.UseExponentialBackoff = true;
options.Timeout.TimeoutSeconds = 30;
options.CircuitBreaker.Enabled = true;
options.CircuitBreaker.FailureThreshold = 5;
options.CircuitBreaker.BreakDurationSeconds = 30;
});也支持从 appsettings.json 绑定:
{
"MudHttpResilience": {
"Retry": { "Enabled": true, "MaxRetryAttempts": 3, "UseExponentialBackoff": true },
"Timeout": { "Enabled": true, "TimeoutSeconds": 30 },
"CircuitBreaker": { "Enabled": true, "FailureThreshold": 5, "BreakDurationSeconds": 30 }
}
}| 模式 | 配置 | 构造函数依赖 | 适用场景 |
|---|---|---|---|
| HttpClient(推荐) | HttpClient = "IEnhancedHttpClient" |
IOptions<JsonSerializerOptions>, IEnhancedHttpClient |
通用场景,配合 AddMudHttpUtils |
| TokenManager | TokenManage = "IFeishuAppManager" |
IOptions<JsonSerializerOptions>, Token 管理器 |
飞书/钉钉等需要 Token 管理 |
| 默认 | 无 | IOptions<JsonSerializerOptions>, IMudAppContext |
遗留场景 |
HttpClient与TokenManage互斥,同时定义时HttpClient优先。
// 接口级 Token(建议使用 TokenTypes 常量)
[Token(TokenTypes.TenantAccessToken)]
public interface IApi { }
// 参数级 Token
[Get("/users/{id}")]
Task<User> GetUserAsync([Path] int id, [Token(TokenTypes.UserAccessToken)] string? token = null);
// Token 注入模式:Header(默认)、Query、Path、ApiKey、HmacSignature
[Token(TokenTypes.AppAccessToken, InjectionMode = TokenInjectionMode.Header, Name = "Authorization")]// 使用默认 AES 加密注册
services.AddMudHttpClient("myApi", encryption =>
{
encryption.Key = Convert.FromBase64String("your-base64-key");
encryption.IV = Convert.FromBase64String("your-base64-iv");
}, client =>
{
client.BaseAddress = new Uri("https://api.example.com");
});
// 或注册自定义加密提供程序
services.AddSingleton<IEncryptionProvider, MyCustomEncryptionProvider>();
// 请求体加密
[Post("/api/secure")]
Task<Response> PostSecureAsync(
[Body(EnableEncrypt = true, EncryptSerializeType = SerializeType.Json, EncryptPropertyName = "data")] Request request
);
// 响应解密
[Post("/api/secure-data", ResponseEnableDecrypt = true)]
Task<SecureData> GetSecureDataAsync([Body] Request request);// 核心接口
ITokenManager // 通用令牌管理
IUserTokenManager // 用户令牌管理
ITokenStore // 令牌持久化存储契约
IUserTokenStore // 用户级令牌持久化存储契约
TokenManagerBase // 令牌管理器抽象基类(并发安全刷新)
UserTokenManagerBase // 用户令牌管理器抽象基类(并发安全刷新)
TokenTypes // 令牌类型常量(TenantAccessToken、UserAccessToken 等)
// 实现自定义令牌管理器
public class MyTokenManager : TokenManagerBase
{
protected override Task<TokenInfo?> GetCachedTokenAsync(string tokenType, CancellationToken ct) { }
protected override Task<TokenInfo> RefreshTokenCoreAsync(string tokenType, CancellationToken ct) { }
}// 注册多个客户端
services.AddMudHttpClient("userApi", "https://user-api.example.com");
services.AddMudHttpClient("orderApi", "https://order-api.example.com");
// 通过 IHttpClientResolver 动态获取
public class MultiApiService
{
private readonly IHttpClientResolver _resolver;
public MultiApiService(IHttpClientResolver resolver) => _resolver = resolver;
public async Task CallUserApiAsync()
{
var client = _resolver.GetClient("userApi");
await client.GetAsync<User>("/users/1");
}
}// IAsyncEnumerable 流式处理
await foreach (var message in _httpClient.SendAsAsyncEnumerable<ChatMessage>(request, cancellationToken: ct))
{
yield return message;
}
// 原始 HttpResponseMessage
var response = await _httpClient.SendRawAsync(request);
// 响应流
var stream = await _httpClient.SendStreamAsync(request);// 文件上传(支持 JsonPropertyName 属性名映射)
[Post("/upload")]
Task<UploadResult> UploadAsync([FormContent] IFormContent formData);
// 文件下载
[Get("/files/{fileId}")]
Task DownloadFileAsync([Path] string fileId, [FilePath(BufferSize = 81920)] string savePath);
// 二进制数据下载
[Get("/files/{fileId}/content")]
Task<byte[]> DownloadFileContentAsync([Path] string fileId);支持在接口上定义 [Query] 或 [Path] 属性,作为所有方法的默认查询参数或路径参数。生成的实现类将包含对应的可读写属性:
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
[BasePath("{tenantId}/api/v1")]
public interface ITenantApi
{
[Path("tenantId")]
string TenantId { get; set; }
[Query("apiKey")]
string ApiKey { get; set; }
[Get("users")]
Task<List<User>> GetUsersAsync();
}
// 使用
var api = serviceProvider.GetRequiredService<ITenantApi>();
api.TenantId = "tenant-123";
api.ApiKey = "my-api-key";
await api.GetUsersAsync();
// 实际请求: /tenant-123/api/v1/users?apiKey=my-api-key[QueryMap] 支持将对象属性展开为查询参数,支持字典类型和 POCO 对象:
public class SearchCriteria
{
public string? Keyword { get; set; }
public int Page { get; set; }
}
[Get("/api/search")]
Task<SearchResult> SearchAsync(
[QueryMap(PropertySeparator = "_", SerializationMethod = QuerySerializationMethod.ToString)]
SearchCriteria criteria);
// 字典类型
[Get("/api/search")]
Task<SearchResult> SearchAsync([QueryMap] IDictionary<string, object> filters);QueryMapAttribute 属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
PropertySeparator |
string |
"_" |
嵌套属性名称分隔符 |
SerializationMethod |
QuerySerializationMethod |
ToString |
序列化方法(ToString / Json) |
UrlEncode |
bool |
true |
是否对查询参数值进行 URL 编码 |
IncludeNullValues |
bool |
false |
是否包含值为 null 的属性 |
支持在接口级别定义统一的路径前缀:
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
[BasePath("api/v1")]
public interface IUserApi
{
[Get("users/{id}")] // 实际路径: /api/v1/users/{id}
Task<User> GetUserAsync([Path] int id);
[Get("/admin/users")] // 以 / 开头,忽略 BasePath,实际路径: /admin/users
Task<List<User>> GetAllUsersAsync();
}Response<T> 类型同时返回响应内容和元数据(状态码、响应头):
[Get("/users/{id}")]
Task<Response<User>> GetUserAsync([Path] int id);
// 使用
var response = await api.GetUserAsync(1);
var user = response.Data; // 响应内容
var status = response.StatusCode; // HTTP 状态码
var headers = response.Headers; // 响应头注意:不建议将
Response<T>与[Cache]特性组合使用,缓存会存储整个Response<T>对象(包括 StatusCode 和 Headers),可能导致后续请求返回过期的状态码和响应头。生成器会对此组合发出 HTTPCLIENT011 编译警告。
// 继承
[HttpClientApi("https://api.example.com", IsAbstract = true)]
public interface IBaseApi { }
[HttpClientApi("https://api.example.com", InheritedFrom = "BaseApiClass")]
public interface IUserApi : IBaseApi { }
// 事件处理器
[GenerateEventHandler(EventType = "UserCreatedEvent", HandlerClassName = "UserCreatedEventHandler")]
public class UserCreatedEvent { }| 包名 | 说明 | 文档 |
|---|---|---|
| Mud.HttpUtils | 元包,一站式引用 + DI 注册 | README |
| Mud.HttpUtils.Abstractions | 接口定义,零依赖 | README |
| Mud.HttpUtils.Attributes | 特性标注 | README |
| Mud.HttpUtils.Client | 客户端实现 | README |
| Mud.HttpUtils.Resilience | 弹性策略 | README |
| Mud.HttpUtils.Generator | 源代码生成器 | README |
# 运行所有测试
dotnet test
# 运行特定测试项目
dotnet test Tests/Mud.HttpUtils.Tests
dotnet test Tests/Mud.HttpUtils.Client.Tests
dotnet test Tests/Mud.HttpUtils.Resilience.Tests
dotnet test Tests/Mud.HttpUtils.Generator.Tests欢迎提交 Issue 和 Pull Request 来改进这个项目!
本项目遵循 MIT 许可证。详细信息请参见 LICENSE-MIT 文件。
Mud.HttpUtils is a declarative HTTP client framework based on Roslyn source code generator. It automatically generates type-safe HTTP API client code through attribute annotations. No need to write HttpClient calling code manually, just define interfaces with attribute annotations, and the compiler will generate complete implementation code automatically.
- 🚀 Zero Runtime Overhead: Compile-time code generation, no reflection, excellent performance
- 🎯 Type Safety: Strongly typed API calls, compile-time error checking
- 📝 Declarative Programming: Define HTTP APIs through attribute annotations, simple and intuitive
- 🔧 Feature Rich: Supports multiple HTTP methods, parameter types, content formats, token authentication, encryption, etc.
- 🛡️ Resilience Policies: Built-in retry, timeout, and circuit breaker policies based on Polly
- 🔐 Encryption Support: Pluggable encryption provider with built-in AES implementation
- 🔄 Token Management: Concurrent-safe token refresh with persistence storage contracts
- 🌐 Multi-Client: Named client resolution via
IHttpClientResolverfor multi-API scenarios - 🎨 Flexible Configuration: Supports interface-level, method-level, parameter-level configuration priority
- 🏗️ Interface-Level Dynamic Properties: Define
[Query]/[Path]properties on interfaces for global parameters - 🗺️ QueryMap: Flatten objects/dictionaries into query parameters with serialization control
- 🔗 Base Path: Define common URL prefix at interface level
- 📦 Response<T>: Return response data with metadata (status code, headers)
- 📦 Multi-Framework Support: Supports .NET Standard 2.0, .NET 6.0, .NET 8.0, .NET 10.0
# Install metapackage (includes Abstractions + Attributes + Client + Resilience)
dotnet add package Mud.HttpUtils
# Install source code generator
dotnet add package Mud.HttpUtils.Generatorusing Mud.HttpUtils.Attributes;
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
public interface IUserApi
{
[Get("/users/{id}")]
Task<UserInfo> GetUserAsync([Path] int id);
[Post("/users")]
Task<UserInfo> CreateUserAsync([Body] CreateUserRequest request);
[Get("/users")]
Task<List<UserInfo>> GetUsersAsync([Query] string? name = null, [Query] int page = 1);
}// One-stop registration: Client + Resilience
services.AddMudHttpUtils("userApi", "https://api.example.com", options =>
{
options.Retry.MaxRetryAttempts = 3;
options.Timeout.TimeoutSeconds = 30;
});
// Register generator-produced API implementations
services.AddWebApiHttpClient();public class UserService
{
private readonly IUserApi _userApi;
public UserService(IUserApi userApi)
{
_userApi = userApi;
}
public async Task<UserInfo> GetUserByIdAsync(int id)
{
return await _userApi.GetUserAsync(id);
}
}| Package | Description | Docs |
|---|---|---|
| Mud.HttpUtils | Metapackage + DI registration | README |
| Mud.HttpUtils.Abstractions | Interface definitions, zero dependencies | README |
| Mud.HttpUtils.Attributes | Attribute annotations | README |
| Mud.HttpUtils.Client | Client implementation | README |
| Mud.HttpUtils.Resilience | Resilience policies (Polly) | README |
| Mud.HttpUtils.Generator | Source code generator | README |
Contributions are welcome! Please feel free to submit Issues and Pull Requests.
This project is licensed under the MIT License. See LICENSE-MIT file for details.
Made with ❤️ by Mud Studio