Skip to content

Mud.HttpUtils.Attributes ​

概述 ​

Mud.HttpUtils.Attributes 是 Mud.HttpUtils 的特性定义层,提供 HTTP API 声明式编程所需的全部特性标注。

仅依赖 Mud.HttpUtils.Abstractions,自身无其他外部依赖。

目标框架 ​

  • netstandard2.0

包含内容 ​

核心特性 ​

特性用途目标关键属性
HttpClientApiAttribute标注 HTTP API 接口InterfaceContentType, Timeout, TokenManage, HttpClient, RegistryGroupName, IsAbstract, InheritedFrom
BasePathAttribute标注接口基础路径前缀InterfacePath

HTTP 方法特性 ​

特性用途目标
GetAttributeGET 请求Method
PostAttributePOST 请求Method
PutAttributePUT 请求Method
DeleteAttributeDELETE 请求(支持带请求体)Method
PatchAttributePATCH 请求Method
HeadAttributeHEAD 请求Method
OptionsAttributeOPTIONS 请求Method

所有 HTTP 方法特性继承自 HttpMethodAttribute,支持以下公共属性:

属性类型说明
Routestring请求路径模板
ContentTypestring?请求内容类型
ResponseContentTypestring?响应内容类型
ResponseEnableDecryptbool响应是否启用解密

参数特性 ​

特性用途目标关键属性
PathAttribute路径参数Parameter / PropertyName, Format, UrlEncode
QueryAttribute查询参数Parameter / PropertyName, Encode, Format, FormatString, AliasAs, Separator
QueryMapAttribute查询参数映射(对象/字典展开)Parameter / PropertyPropertySeparator, SerializationMethod, UrlEncode, IncludeNullValues
RawQueryStringAttribute原始查询字符串ParameterPrependQuestionMark
ArrayQueryAttribute数组查询参数ParameterSeparator
HeaderAttribute请求头参数Parameter / Method / Interface / PropertyName, Value, AliasAs, Replace, FormatString
BodyAttribute请求体参数ParameterContentType, EnableEncrypt, EncryptSerializeType, EncryptPropertyName, RawString, UseStringContent
TokenAttribute令牌参数Parameter / Interface / MethodTokenType, InjectionMode, Name, Scopes, Replace, TokenManagerKey, RequiresUserId
FilePathAttribute文件路径参数(上传/下载)Parameter / PropertyBufferSize、Overwrite
FormContentAttribute表单内容参数Parameter / Class—
FormAttribute表单字段(URL 编码)ParameterFieldName
MultipartFormAttribute多部分表单字段Parameter—
UploadAttribute文件上传参数ParameterFieldName, FileName, ContentType

缓存特性 ​

特性用途目标关键属性
CacheAttribute响应缓存标注MethodDurationSeconds, CacheKeyTemplate, VaryByUser, UseSlidingExpiration(Priority 已随 CFG-27 移除,见下文)

弹性策略特性 ​

特性用途目标关键属性
RetryAttribute方法级重试策略MethodMaxRetries, DelayMilliseconds, UseExponentialBackoff
TimeoutAttribute方法级超时策略MethodTimeoutMilliseconds
CircuitBreakerAttribute方法级熔断策略MethodFailureThreshold, BreakDurationSeconds, SamplingDurationSeconds, MinimumThroughput

安全与脱敏特性 ​

特性用途目标关键属性
SensitiveDataAttribute标记敏感数据属性Property(仅属性,CFG-11 收窄)MaskMode, PrefixLength, SuffixLength

控制特性 ​

特性用途目标
IgnoreGeneratorAttribute忽略代码生成(接口级=完全不介入;方法级=仅跳过该方法)Interface / Method(E-2 收窄)
AllowAnyStatusCodeAttribute允许任意 HTTP 状态码Interface / Method
HeaderMergeAttribute头部合并模式控制Interface / Method
SerializationMethodAttribute请求体序列化方法控制Interface / Method
InterfacePathAttribute接口级固定路径参数Interface
InterfaceQueryAttribute接口级固定查询参数Interface
HttpJsonSerializableAttribute标注需纳入 JSON 源生成的实体/DTO(class / struct / record)Class / Struct

关联枚举 ​

枚举说明
HeaderMergeMode头部合并模式(Append / Replace / Ignore),配合 HeaderMergeAttribute
SerializationMethod请求体序列化方法(Json / Xml / FormUrlEncoded),配合 SerializationMethodAttribute
QuerySerializationMethodQueryMap 序列化方法(ToString / Json),配合 QueryMapAttribute
JsonNamingPolicyHintJSON 命名策略提示(Default / CamelCase / SnakeCaseLower / SnakeCaseUpper / KebabCaseLower / KebabCaseUpper),配合 HttpJsonSerializableAttribute
CachePriority❌ 已移除(CFG-27):缓存优先级枚举,运行时无消费点

TokenInjectionMode(Header / Query / Path / ApiKey / HmacSignature / BasicAuth / Cookie)与 SensitiveDataMaskMode(Hide / Mask / TypeOnly)见对应章节。

事件处理特性 ​

特性用途目标
GenerateEventHandlerAttribute生成事件处理器Class

安装 ​

xml
<PackageReference Include="Mud.HttpUtils.Attributes" Version="x.x.x" />

使用场景 ​

配合源代码生成器使用(推荐) ​

当你需要声明式定义 HTTP API 接口,配合 Mud.HttpUtils.Generator 源代码生成器自动生成实现代码时:

csharp
using 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);

    [Delete("/users/{id}")]
    Task<bool> DeleteUserAsync([Path] int id, [Body] DeleteReason reason);

    [Get("/users")]
    Task<List<UserInfo>> SearchUsersAsync([Query] string keyword);
}

仅需特性定义 ​

当你需要在共享类库中定义 API 接口合同,而不需要引入客户端实现或生成器时:

xml
<!-- 共享类库项目只引用 Attributes -->
<PackageReference Include="Mud.HttpUtils.Attributes" Version="x.x.x" />

HttpClientApiAttribute 详解 ​

三种运行模式 ​

csharp
// 模式一:默认模式(构造函数依赖 IMudAppContext)
[HttpClientApi]
public interface IDefaultApi { }

// 模式二:TokenManager 模式(构造函数依赖指定的 Token 管理器)
[HttpClientApi(TokenManage = "IFeishuAppManager")]
public interface ITokenApi { }

// 模式三:HttpClient 模式(构造函数依赖指定的 HttpClient 接口,推荐)
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
public interface IHttpClientApi { }

注意:HttpClient 与 TokenManage 互斥,同时定义时 HttpClient 优先。 CFG-27:BaseAddress 构造函数与属性已移除(此前为 [Obsolete(error: true)])。 使用将产生编译错误 CS0117;请通过 AddMudHttpClient(clientName, baseAddress) 或 AddMudHttpGeneratedClient<T>(clientName) 配置基地址。

全部属性 ​

属性类型默认值说明
ContentTypestring"application/json"默认请求内容类型
Timeoutint50超时时间(秒),生成器会在注册代码中生成 client.Timeout 设置
TokenManagestring?nullToken 管理器接口类型全名
HttpClientstring?nullHttpClient 接口类型全名
RegistryGroupNamestring?null注册组名称,影响生成的注册方法名
IsAbstractboolfalse是否生成抽象类
InheritedFromstring?null继承的基类名称

BodyAttribute 详解 ​

属性类型默认值说明
ContentTypestring?null请求体内容类型(优先级最高)
EnableEncryptboolfalse是否启用加密
EncryptSerializeTypeSerializeTypeJson加密序列化类型
EncryptPropertyNamestring"data"加密后的属性名
RawStringboolfalse是否作为原始字符串发送(不进行 JSON 序列化,也不调用 ToString())
UseStringContentboolfalse是否将参数作为字符串内容发送(调用 ToString())

RawString 用法 ​

当需要直接发送纯文本或预格式化字符串时,使用 RawString = true:

csharp
[Post("/api/content")]
Task PostContentAsync([Body(RawString = true)] string content);

UseStringContent 用法 ​

当需要将对象调用 ToString() 后作为字符串内容发送时:

csharp
[Post("/api/text")]
Task SendTextAsync([Body(UseStringContent = true)] object message);

TokenAttribute 详解 ​

属性类型默认值说明
TokenTypestring"AccessToken"Token 类型标识符(建议使用 TokenTypes 常量类)
InjectionModeTokenInjectionModeHeaderToken 注入模式
Namestring?null自定义 Header/Query 名称
Scopesstring?null令牌作用域,多个作用域用逗号分隔
Replacebooltrue是否替换已有 Header
TokenManagerKeystring?同 TokenType令牌管理器查找键,默认与 TokenType 相同,用于解耦业务概念(TokenType)和技术查找键
RequiresUserIdboolfalse是否需要用户 ID,为 true 时通过 ICurrentUserContext 获取

TokenManagerKey:当指定此值时,代码生成器将使用此键而非 TokenType 从 IMudAppContext 中查找令牌管理器。此属性用于解耦业务概念和技术查找键,例如多个不同的 TokenType 可以映射到同一个 TokenManager。如果未指定,则使用 TokenType 作为查找键。

RequiresUserId:当设置为 true 时,生成的代码将通过 ICurrentUserContext 获取当前用户 ID,并将其传递给 ITokenProvider 以获取用户级令牌。如果未显式指定,则根据 TokenType 自动推断:TokenType 为 "UserAccessToken" 时默认为 true,否则默认为 false。

使用 Token 类型 ​

TokenTypes 提供标准化标识符(Bearer、Basic、AccessToken、RefreshToken); 平台自定义类型(如 TenantAccessToken / UserAccessToken)使用字符串字面量:

csharp
using Mud.HttpUtils;

[Token(TokenTypes.AccessToken)]
public interface IApi { }

[Token("TenantAccessToken")]
public interface IFeishuApi { }

[Get("/users/{id}")]
Task<User> GetUserAsync(
    [Path] int id,
    [Token("UserAccessToken")] string? token = null
);

Token 注入模式 ​

模式值说明
Header0注入到 HTTP Header(默认)
Query1注入到 URL Query 参数
Path2注入到 URL Path
ApiKey3API Key 认证,通过 IApiKeyProvider 获取密钥注入到请求头
HmacSignature4HMAC 签名认证,通过 IHmacSignatureProvider 计算签名注入到请求头
BasicAuth5HTTP Basic 认证,将凭据编码为 Base64 注入到 Authorization 请求头
Cookie6注入到 Cookie 请求头
csharp
// API Key 认证模式
[Token("ApiKey", InjectionMode = TokenInjectionMode.ApiKey, Name = "X-API-Key")]
public interface IApiKeyApi { }

// HMAC 签名认证模式
[Token("Hmac", InjectionMode = TokenInjectionMode.HmacSignature)]
public interface IHmacApi { }

⚠️ 安全约束:[Token] 的字符串属性只接受「键名 / 标识符」,不得放置任何机密。生成器会把这些字符串原样写入生成的源码(如 GetTokenAsync("FeishuUser", …)、GetApiKeyAsync("X-Api-Key")),因此它们会进入版本库、中间产物与反编译输出。真实密钥必须通过运行时配置注入(环境变量 / KMS / IConfiguration)。

⚠️ Path / HmacSignature 模式限制:这两种模式不被令牌恢复处理器(TokenRecoveryDelegatingHandler / TokenRecoveryEnhancedClient)支持,刷新后的新令牌无法重新注入;生成器以 HTTPCLIENT022 提示。另 Query / Path 注入会让令牌进入 URL,可能被代理/访问日志等外部系统记录,生成器以 MUD005 提示。

Token Scopes ​

csharp
// 指定令牌作用域
[Token("UserAccessToken", Scopes = "user:read,user:write")]
public interface IScopedApi { }

// 方法级别令牌
[Get("/api/user/profile")]
[Token("UserAccessToken", Scopes = "user:read")]
Task<Profile> GetProfileAsync();

TokenManagerKey 使用 ​

csharp
// 使用 TokenManagerKey 解耦业务概念和技术查找键
[Token(TokenType = "UserAccessToken", TokenManagerKey = "FeishuUser")]
public interface IFeishuUserApi { }

// 多个不同的 TokenType 映射到同一个 TokenManager
[Token(TokenType = "UserAccessToken", TokenManagerKey = "FeishuUser")]
public interface IFeishuContactApi { }

RequiresUserId 使用 ​

csharp
// 显式指定需要用户 ID
[Token(TokenType = "CustomToken", RequiresUserId = true)]
public interface ICustomUserApi { }

// 方法级别覆盖接口的 RequiresUserId
[Get("/api/public-data")]
[Token(RequiresUserId = false)]
Task<PublicData> GetPublicDataAsync();

CacheAttribute 详解 ​

属性类型默认值说明
DurationSecondsint300缓存持续时间(秒)
CacheKeyTemplatestring?null缓存键模板
VaryByUserboolfalse是否按用户区分缓存
UseSlidingExpirationboolfalse✅ 已支持:下沉为 CacheOptions.UseSlidingExpiration,生成代码生效

CFG-D04 修正:UseSlidingExpiration 受生成器支持,请勿标记为未生效。

CFG-27:Priority 属性(及 CachePriority 枚举)已移除 —— 生成器从未处理该属性,运行时无消费点; 随之移除诊断 HTTPCLIENT019([Cache] 当前已无被忽略的属性)。

csharp
[Get("/users/{id}")]
[Cache(60, VaryByUser = true)]
Task<User> GetUserAsync([Path] int id);

[Get("/config")]
[Cache(300, CacheKeyTemplate = "config:{0}", UseSlidingExpiration = true)]
Task<Config> GetConfigAsync();

生成器对 [Cache] 还有两条约束诊断:HTTPCLIENT030(文件下载方法上 [Cache] 无效)与 HTTPCLIENT031/032(默认缓存键含无法稳定表达的参数 / 键模板未引用 Unsafe 参数)。

HeaderAttribute 详解 ​

HeaderAttribute 支持应用到参数、方法或接口级别:

属性类型默认值说明
Namestring?null请求头名称
Valueobject?null请求头值(方法/接口级别使用)
AliasAsstring?null别名,用于映射到不同的请求头名称
Replaceboolfalse是否替换已有的同名请求头
FormatStringstring?null请求头值格式化字符串(如 "N"、"yyyy-MM-ddTHH:mm:ssZ"),支持 string.Format 或 IFormattable
Formatstring?nullFormatString 的别名
csharp
// 参数级别
[Get("/api/users")]
Task<List<User>> GetUsersAsync([Header("X-API-Key")] string apiKey);

// 方法级别(添加固定请求头)
[Get("/api/users")]
[Header("Accept", "application/json")]
[Header("X-Request-Source", "Web")]
Task<List<User>> GetUsersAsync();

// 接口级别(所有方法自动携带)
[HttpClientApi]
[Header("X-API-Version", "v2")]
public interface IUserApi { }

头部注入守卫(GEN-18):生成器对每个字符串型 Header 额外发射运行期守卫 HttpHeaderValueValidator.IsValid(...),遇 CR/LF 立即抛 ArgumentException,对所有目标框架统一生效(netstandard2.0 的 HttpClient 不校验头值,存在头部注入风险)。

FormAttribute 详解 ​

用于 application/x-www-form-urlencoded 请求,标记参数作为表单字段:

属性类型默认值说明
FieldNamestring?null表单字段名称,未设置时使用参数名
csharp
[Post("/api/login")]
Task<LoginResult> LoginAsync(
    [Form(FieldName = "username")] string user,
    [Form(FieldName = "password")] string pass);

MultipartFormAttribute 详解 ​

用于 multipart/form-data 请求,标记参数作为多部分表单字段:

csharp
[Post("/api/upload")]
Task<UploadResult> UploadFileAsync(
    [MultipartForm] IFormFile file,
    [MultipartForm] string description);

UploadAttribute 详解 ​

专用于文件上传场景,支持自定义字段名、文件名和内容类型:

属性类型默认值说明
FieldNamestring?null表单字段名称,未设置时使用参数名
FileNamestring?null上传的文件名,未设置时使用原始文件名
ContentTypestring?null文件内容类型(MIME),未设置时自动检测
csharp
// 基本文件上传
[Post("/api/upload")]
Task<UploadResult> UploadAsync([Upload] IFormFile file);

// 自定义字段名和文件名
[Post("/api/upload")]
Task<UploadResult> UploadDocumentAsync(
    [Upload(FieldName = "document", FileName = "report.pdf")] IFormFile file);

// 指定内容类型
[Post("/api/upload")]
Task<UploadResult> UploadImageAsync(
    [Upload(ContentType = "image/png")] IFormFile image);

FilePathAttribute 详解 ​

用于标记文件路径参数,既可用于上传(将文件内容作为请求体/表单数据发送),也可用于下载(将响应内容写入本地文件)。

属性类型默认值说明
BufferSizeint81920读/写文件时的缓冲区大小(字节),默认 80KB
Overwritebooltrue下载时是否覆盖已存在的文件;设为 false 时若文件已存在将抛出 IOException
csharp
// 上传文件
[Post("/api/upload")]
Task<UploadResult> UploadFileAsync([FilePath] string filePath);

// 自定义缓冲区大小(128KB)
[Post("/api/upload-large")]
Task<UploadResult> UploadLargeFileAsync([FilePath(BufferSize = 131072)] string filePath);

// 下载文件,不覆盖已存在文件
[Get("/api/files/{id}")]
Task DownloadAsync(int id, [FilePath(Overwrite = false)] string savePath);

// 下载文件,带进度报告(通过方法签名中的 IProgress<T> 参数接收)
[Get("/api/files/{id}")]
Task DownloadWithProgressAsync(int id, [FilePath] string savePath, IProgress<long> progress);

SensitiveDataAttribute 详解 ​

属性类型默认值说明
MaskModeSensitiveDataMaskModeMask脱敏模式
PrefixLengthint2前缀保留长度(Mask 模式)
SuffixLengthint2后缀保留长度(Mask 模式)

作用目标(CFG-11):[SensitiveData] 仅对对象属性生效 —— DefaultSensitiveDataMasker 通过反射遍历 Type.GetProperties() 读取该特性; AotSafeSensitiveDataMasker 则由编译期注册驱动,完全忽略该特性。 AttributeUsage 已收窄为仅 Property:标注在方法参数上会产生编译错误 CS0592 (此前允许标注但不会产生任何掩码效果,属静默失效)。 如需对请求体/参数脱敏,请在请求 DTO 的属性上标注,或实现自定义 ISensitiveDataMasker。

脱敏模式说明:

模式说明示例
Hide完全隐藏"***"
Mask部分遮盖"张***01"
TypeOnly仅显示类型"[String]"
csharp
public class UserRequest
{
    public string Name { get; set; }

    [SensitiveData(MaskMode = SensitiveDataMaskMode.Mask, PrefixLength = 3, SuffixLength = 4)]
    public string IdCard { get; set; }

    [SensitiveData(MaskMode = SensitiveDataMaskMode.Hide)]
    public string Password { get; set; }
}

生效前提(CFG-33,三态必须区分):

  1. 未注册掩码器 ⇒ 特性完全无效;
  2. services.AddSensitiveDataMasker() ⇒ 注册的是 AotSafeSensitiveDataMasker,它按设计忽略 [SensitiveData] ⇒ 特性仍然无效;
  3. 仅 services.AddSensitiveDataMasker<DefaultSensitiveDataMasker>() 才会反射读取 [SensitiveData] —— 该实现非 AOT 安全。

上述任一「无效」情形都不会产生编译期或运行期提示,请务必按需求显式选择实现。

BasePathAttribute 详解 ​

支持在接口级别定义统一的路径前缀,避免每个方法重复书写相同的路径段:

属性类型说明
Pathstring基础路径前缀,可包含占位符(如 {tenantId})

URL 构建规则:

情况实际路径
正常[Base Address] + [Base Path] + [Method Path]
Method Path 以 / 开头[Base Address] + [Method Path](忽略 Base Path)
Method Path 是绝对 URL[Method Path](忽略 Base Address 和 Base Path)
csharp
[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();
}

// 带占位符的 Base Path
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
[BasePath("{tenantId}/api/v1")]
public interface ITenantApi
{
    [Path("tenantId")]
    string TenantId { get; set; }

    [Get("users")]
    Task<List<User>> GetUsersAsync();
}

QueryAttribute 详解 ​

属性类型默认值说明
Namestring?null查询参数名称
FormatStringstring?null格式化字符串(如日期格式 "yyyy-MM-dd")
Formatstring?nullFormatString 的别名
AliasAsstring?null别名,用于映射到不同的查询参数名
Separatorstring?null数组元素分隔符。设置后数组序列化为单个参数(如 ?ids=1;2;3);为 null 则多个同名参数
csharp
// 基本用法
[Get("/api/users")]
Task<List<User>> GetUsersAsync([Query] string? keyword, [Query] int page = 1);

// 自定义参数名和格式
[Get("/api/users")]
Task<List<User>> GetUsersAsync([Query("page_size")] int pageSize);

// 数组分隔符
[Get("/api/users")]
Task<List<User>> GetUsersAsync([Query(Separator = ",")] int[] ids);
// 生成: /api/users?ids=1,2,3

// 方法级别添加固定查询参数
[Get("/api/users")]
[Query("status", "active")]
Task<List<User>> GetActiveUsersAsync();

QueryMapAttribute 详解 ​

将对象属性或字典键值对展开为 URL 查询参数,适用于动态查询条件场景:

属性类型默认值说明
PropertySeparatorstring"_"嵌套属性名称分隔符
SerializationMethodQuerySerializationMethodToString序列化方法(ToString / Json)
UrlEncodebooltrue是否对查询参数值进行 URL 编码
IncludeNullValuesboolfalse是否包含值为 null 的属性
csharp
// POCO 对象展开
public class SearchCriteria
{
    public string? Keyword { get; set; }
    public int Page { get; set; }
    public int PageSize { get; set; }
}

[Get("/api/search")]
Task<SearchResult> SearchAsync([QueryMap] SearchCriteria criteria);

// 调用: api.SearchAsync(new SearchCriteria { Keyword = "test", Page = 1, PageSize = 10 });
// 生成: /api/search?Keyword=test&Page=1&PageSize=10

// 字典类型
[Get("/api/search")]
Task<SearchResult> SearchAsync([QueryMap] IDictionary<string, object> filters);

// 自定义序列化
[Get("/api/search")]
Task<SearchResult> SearchAsync(
    [QueryMap(PropertySeparator = ".", SerializationMethod = QuerySerializationMethod.Json)]
    SearchCriteria criteria);

QuerySerializationMethod 枚举 ​

值说明
ToString调用 ToString() 方法转换为字符串(默认)
Json使用 JSON 序列化器序列化为 JSON 字符串

RawQueryStringAttribute 详解 ​

直接传递原始查询字符串,不做任何编码或处理:

属性类型默认值说明
PrependQuestionMarkbooltrue是否在字符串前添加 ? 前缀
csharp
[Get("/api/search")]
Task<SearchResult> SearchAsync([RawQueryString] string queryString);

// 调用: api.SearchAsync("keyword=test&page=1");
// 生成: /api/search?keyword=test&page=1

接口级动态属性 ​

PathAttribute、QueryAttribute 和 HeaderAttribute 支持应用到接口属性(AttributeTargets.Property),用于定义全局参数(接口级 Header 属性支持 Replace、FormatString、AliasAs 参数):

csharp
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
[BasePath("{tenantId}/api/v1")]
public interface ITenantApi
{
    [Path("tenantId")]
    string TenantId { get; set; }

    [Query("apiKey")]
    string ApiKey { get; set; }

    [Query("locale")]
    string? Locale { get; set; }

    [Get("users")]
    Task<List<User>> GetUsersAsync();

    [Get("users/{id}")]
    Task<User> GetUserAsync([Path] int id);
}

// 使用
var api = serviceProvider.GetRequiredService<ITenantApi>();
api.TenantId = "tenant-123";
api.ApiKey = "my-api-key";
api.Locale = "zh-CN";

await api.GetUsersAsync();
// 实际请求: /tenant-123/api/v1/users?apiKey=my-api-key&locale=zh-CN

优先级:方法参数优先级高于接口属性。如果方法参数与接口属性同名,方法参数值会覆盖接口属性值。

接口级 Header 属性:[Header] 可标记在接口属性上,作为所有方法的动态请求头。支持 Replace(替换同名请求头)与 FormatString(格式化值,如 GUID 的 "N");当 HeaderMergeMode 为 Ignore 时该属性 Header 被跳过,Replace 时先移除同名头再添加;若 Header 名为 Authorization 且存在 TokenManager,则该属性 Header 由 Token 注入机制处理而被跳过。属性级 Header 在方法参数 Header 之后、接口级静态 Header 之前生成(动态优先于静态)。

csharp
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
[BasePath("{tenantId}/api/v1")]
public interface ITenantApi
{
    [Path("tenantId")] string TenantId { get; set; }
    [Query("apiKey")] string ApiKey { get; set; }

    // 接口级 Header 动态属性
    [Header("X-App-Version")] string AppVersion { get; set; }
    [Header("X-Trace-Id", FormatString = "N")] Guid TraceId { get; set; }

    [Get("users")]
    Task<List<User>> GetUsersAsync();
}

RetryAttribute 详解 ​

属性类型默认值说明
MaxRetriesint3最大重试次数
DelayMillisecondsint1000基础延迟时间(毫秒)
UseExponentialBackoffbooltrue是否使用指数退避
AllowNonIdempotentboolfalse是否允许非幂等方法重试
csharp
[Get("/api/data")]
[Retry(MaxRetries = 5, DelayMilliseconds = 2000, UseExponentialBackoff = true)]
Task<Data> GetDataAsync();

方法级 [Retry] 只覆盖 MaxRetries、DelayMilliseconds、UseExponentialBackoff 三项;RetryStatusCodes、OnRetry、UseJitter 恒取自全局 RetryOptions。

TimeoutAttribute 详解 ​

属性类型说明
TimeoutMillisecondsint超时时间(毫秒)
csharp
[Get("/api/slow")]
[Timeout(60000)]  // 60 秒超时
Task<Data> GetSlowDataAsync();

CircuitBreakerAttribute 详解 ​

属性类型默认值说明
FailureThresholdint5失败阈值(简单模式为连续失败次数,高级模式为失败率百分比)
BreakDurationSecondsint30熔断持续时间(秒)
SamplingDurationSecondsint0采样窗口时间(秒),大于 0 时启用高级熔断策略
MinimumThroughputint10采样窗口内最小请求数(仅高级模式生效)
csharp
[Get("/api/unstable")]
[CircuitBreaker(FailureThreshold = 5, BreakDurationSeconds = 30)]
Task<Data> GetUnstableDataAsync();

HeaderMergeAttribute 详解 ​

控制接口级与方法级同名 HTTP 头部的合并策略:

模式说明
Append追加模式:接口级和方法级的同名头部都会被添加(默认)
Replace替换模式:方法级头部替换接口级同名头部
Ignore忽略模式:方法级头部被忽略,只使用接口级头部
csharp
[HttpClientApi]
[Header("Accept", "application/json")]
[HeaderMerge(HeaderMergeMode.Replace)]
public interface IUserApi
{
    [Get("/api/users")]
    [Header("Accept", "text/plain")]
    Task<string> GetUsersAsTextAsync();
}

SerializationMethodAttribute 详解 ​

指定接口或方法级别的请求体序列化方式,方法级优先于接口级:

值说明
Json使用 JSON 序列化(默认)
Xml使用 XML 序列化
FormUrlEncoded使用表单 URL 编码序列化
csharp
[HttpClientApi]
[SerializationMethod(SerializationMethod.Xml)]
public interface IXmlApi
{
    [Post("/api/data")]
    Task SendDataAsync([Body] DataModel data);  // XML 序列化

    [Post("/api/json-data")]
    [SerializationMethod(SerializationMethod.Json)]
    Task SendJsonDataAsync([Body] DataModel data);  // 方法级覆盖为 JSON
}

InterfacePathAttribute 详解 ​

在接口级别添加固定路径参数,自动替换 URL 模板中的占位符:

属性类型说明
Namestring路径参数名(占位符名称)
Valuestring?路径参数值
csharp
[HttpClientApi]
[InterfacePath("tenantId", "default-tenant")]
public interface IUserApi
{
    [Get("/api/tenants/{tenantId}/users/{userId}")]
    Task<User> GetUserAsync(int userId);
    // 实际请求: /api/tenants/default-tenant/users/123
}

InterfaceQueryAttribute 详解 ​

在接口级别添加固定查询参数,自动附加到所有方法的 URL:

属性类型说明
Namestring查询参数名
Valuestring?查询参数值
csharp
[HttpClientApi]
[InterfaceQuery("api_version", "2.0")]
[InterfaceQuery("client_id", "my-app")]
public interface IUserApi
{
    [Get("/api/users/{id}")]
    Task<User> GetUserAsync(int id);
    // 实际请求: /api/users/1?api_version=2.0&client_id=my-app
}

GenerateEventHandlerAttribute 详解 ​

用于标记类,指示源代码生成器为该类生成事件处理器实现(常用于 Webhook / 事件订阅场景,如飞书事件订阅)。提供两个构造函数:GenerateEventHandler() 与 GenerateEventHandler(string? eventType)。

属性类型说明
EventTypestring?事件类型标识符,对应构造函数参数 eventType
HandlerClassNamestring?生成的处理器类名称(默认按约定生成)
HandlerNamespacestring?生成的处理器类所在命名空间
InheritedFromstring?生成的处理器类继承的基类名称
ConstructorParametersstring?构造函数参数字符串,用于生成构造函数签名(如 "ILogger logger, IEmailService email")
ConstructorBaseCallstring?构造函数基类调用字符串,用于生成 base(...) 调用(如 "logger")
HeaderTypestring?反序列化事件请求头所用的请求头类型
csharp
[GenerateEventHandler(
    EventType = "UserCreatedEvent",
    HandlerClassName = "UserCreatedEventHandler",
    HandlerNamespace = "MyApp.Handlers",
    InheritedFrom = "BaseEventHandler",
    ConstructorParameters = "ILogger logger, IEmailService emailService",
    ConstructorBaseCall = "logger"
)]
public class UserCreatedEvent
{
    public string UserId { get; set; }
    public string UserName { get; set; }
}

HttpJsonSerializableAttribute 详解(Native AOT 支持) ​

标注在需纳入 JSON 源生成的实体/DTO 上(支持 class / struct / record),用于 Native AOT 场景。Scaffolder 会按此特性聚合生成 JsonSerializerContext 源文件,使 STJ 源生成在 AOT 下获得类型元数据。

属性类型默认值说明
SerializerClassNamestring?null(自动派生)生成的 Context 类名(不含 JsonContext 后缀);同名实体合并进同一 Context
NamingPolicyJsonNamingPolicyHintDefault(自动推导)JSON 命名策略:Default(按 [JsonPropertyName] 模式自动推导)/ CamelCase / SnakeCaseLower / SnakeCaseUpper / KebabCaseLower / KebabCaseUpper
csharp
[HttpJsonSerializable(SerializerClassName = "FeishuAI", NamingPolicy = JsonNamingPolicyHint.SnakeCaseLower)]
public class ContractFileUploadRequest { ... }

工作流:标注实体 → 运行 Mud.HttpUtils.JsonContextScaffolder 脚手架(dotnet mud-jsonctx --project <你的.csproj>)自动生成 XxxJsonContext.g.cs → 在 .NET 8+ 启动注册 services.AddMudHttpClientJsonContext(FeishuAIJsonContext.Default)。HttpContentSerializerFactory.BuildOptions 会自动合并消费方 resolver 与库内置 MudHttpJsonContext.Default。

编译期保障:若类型标注了 [HttpJsonSerializable] 却未被任何 JsonSerializerContext 覆盖,源生成器会发出 AOT006 编译诊断(可由 Mud.HttpUtils.CodeFixes 的 AotJsonContextCodeFixProvider 一键修复);[HttpClientApi] 接口的闭合泛型返回/Body 类型未被覆盖则发出 AOT004/AOT005。CI 严格模式(-p:AotStrictMode=true)下这些诊断升级为 Error。

详见 JsonContext 脚手架 与 源代码生成器 的 AOT 诊断章节。

多态序列化与 [JsonDerivedType](P0-2) ​

当 [HttpClientApi] 接口方法的响应类型声明为基类,且基类标注了 [JsonDerivedType] 参与多态序列化时,所有声明的派生类型也必须被 JsonSerializerContext 覆盖。否则 AOT 下反序列化派生实例会抛 NotSupportedException。

csharp
// 正确:Dog 已被 Context 覆盖
[JsonDerivedType(typeof(Dog))]
public class Animal { public string Name { get; set; } }
public class Dog : Animal { public string Breed { get; set; } }

// AOT004 会报:Dog 未被 Context 覆盖

未标注 [JsonDerivedType] 的非 sealed 类不会被 STJ 按多态处理,无需额外检查。

AOT 安全脱敏(P0-3) ​

AotSafeSensitiveDataMasker 要求显式 Register<T>() 注册脱敏规则。当类型未注册、但其某个基类已注册时,不会套用基类规则(基类规则看不到派生类新增的敏感字段,套用会导致明文输出),两种取值都只输出类型占位串:

enableBaseTypeFallback回退输出适用场景
false(默认)[TypeName]不需要区分"基类已注册"这一线索
true[TypeName, BaseType=BaseTypeName]便于定位"注册了基类、忘了派生类"的遗漏

两者都会发出一次性告警(注入了 ILogger 走日志,否则回退 Console.Error),且都不输出任何字段值。请为每个需要脱敏的派生类型显式调用 Register<T>()。

内置 MudHttpJsonContext 的 Dictionary<string, object> 处置(P1-6) ​

库内置兜底 Context MudHttpJsonContext 不再注册 Dictionary<string, object>:源生成的 typeof(object) 元数据在 Native AOT 下对非基元运行时值会抛 NotSupportedException,属"看起来能编译、上线才崩"的潜伏雷。库内无该注册的调用方;Dictionary<string, string> 注册保留。

消费方若确需以 object 为值的字典参与 JSON 序列化,请在自己的 JsonSerializerContext 上挂 ObjectToInferredTypesConverter:

csharp
[JsonSourceGenerationOptions(Converters = [typeof(ObjectToInferredTypesConverter)])]
[JsonSerializable(typeof(Dictionary<string, object>))]
internal partial class AppJsonContext : JsonSerializerContext;

非泛型 IEncryptableHttpClient.EncryptContent(object, ...) 内部会构造 Dictionary<string, object>,该重载已标注 [Obsolete] + [RequiresUnreferencedCode]/[RequiresDynamicCode],AOT 场景请改用泛型重载 EncryptContent<T>。

设计原则 ​

  • 轻量级:仅依赖 Abstractions,无其他传递依赖
  • netstandard2.0 兼容性:确保在尽可能多的项目中可用
  • 特性属性类型均为基础类型:string、int、bool、enum,无复杂依赖
  • 与生成器解耦:特性可在不引用生成器的项目中使用,便于接口定义共享
  • 安全优先:内置 SensitiveDataAttribute 支持敏感数据脱敏,CacheAttribute 支持缓存控制
  • 弹性策略:内置 RetryAttribute、TimeoutAttribute、CircuitBreakerAttribute 支持方法级弹性策略配置
  • 灵活控制:HeaderMergeAttribute、SerializationMethodAttribute、InterfacePathAttribute、InterfaceQueryAttribute 提供精细化的请求控制