WebApiClientCore 高级功能深度解析
前言
WebApiClientCore 是一个强大的 .NET HTTP 客户端库,它极大地简化了与 Web API 的交互过程。在前面的基础教程中,我们已经了解了它的基本用法。本文将深入探讨 WebApiClientCore 的高级功能,帮助开发者应对更复杂的业务场景。
URI 拼接规则详解
在 WebApiClientCore 中,URI 拼接遵循 .NET 核心库的 Uri 类构造规则。理解这些规则对于正确构建请求 URL 至关重要。
基础规则
-
以斜杠结尾的 baseUri:
http://a.com/
+b/c/d
=http://a.com/b/c/d
http://a.com/path1/
+b/c/d
=http://a.com/path1/b/c/d
-
不以斜杠结尾的 baseUri:
http://a.com
+b/c/d
=http://a.com/b/c/d
http://a.com/path1
+b/c/d
=http://a.com/b/c/d
最佳实践
建议始终使用以斜杠结尾的 baseUri,这样可以确保路径拼接行为符合预期。从技术角度看,http://a.com
和 http://a.com/
是等效的,它们的路径都是 /
。
异常处理机制
WebApiClientCore 采用统一的异常处理模式,所有异常最终都会包装为 HttpRequestException,而实际异常作为其内部异常存在。
异常处理模式
try
{
var result = await api.GetAsync();
}
catch (HttpRequestException ex) when (ex.InnerException is ApiInvalidConfigException)
{
// 处理配置异常
}
catch (HttpRequestException ex) when (ex.InnerException is ApiResponseStatusException)
{
// 处理响应状态码异常
}
catch (HttpRequestException ex)
{
// 处理其他请求异常
}
异常类型说明
- ApiInvalidConfigException:请求配置异常
- ApiResponseStatusException:响应状态码异常
- SocketException:底层连接异常
请求重试机制
WebApiClientCore 提供了灵活的重试机制,可以基于异常类型或响应结果进行条件性重试。
基本用法
var result = await userApi.GetAsync("id001")
.Retry(maxCount: 3)
.WhenCatch<HttpRequestException>()
.WhenResult(r => r.Age <= 0);
高级场景
对于更复杂的重试策略(如指数退避),建议结合 Polly 库实现全局重试策略。
表单数据处理
WebApiClientCore 支持多种集合格式处理方式,符合 OpenAPI 规范。
支持的集合格式
| 格式 | 示例 | 特性用法 | |------|------|---------| | Csv | id=001,002
| [PathQuery(CollectionFormat = CollectionFormat.Csv)]
| | Ssv | id=001 002
| [PathQuery(CollectionFormat = CollectionFormat.Ssv)]
| | Tsv | id=001\002
| [PathQuery(CollectionFormat = CollectionFormat.Tsv)]
| | Pipes | id=001\|002
| [PathQuery(CollectionFormat = CollectionFormat.Pipes)]
| | Multi | id=001&id=002
| [PathQuery(CollectionFormat = CollectionFormat.Multi)]
|
处理非标准接口
在实际开发中,我们经常会遇到不符合 RESTful 规范的接口,WebApiClientCore 提供了多种方式应对这种情况。
1. 非标准参数名
public interface IUserApi
{
[HttpGet("api/users")]
ITask<string> GetAsync([AliasAs("field-Name")] string fieldName);
}
2. 表单中的 JSON 字段
public interface IUserApi
{
Task PostAsync([FormField] string field1, [JsonFormField] Field2 field2);
}
3. 嵌套表单字段
services.AddHttpApi<IUserApi>().ConfigureHttpApi(o =>
{
o.KeyValueSerializeOptions.KeyNamingStyle = KeyNamingStyle.FullName;
});
4. 非标准 Content-Type
[JsonReturn(EnsureMatchAcceptContentType = false)]
public interface IUserApi
{
}
动态主机配置
1. 使用 Uri 参数
public interface IUserApi
{
[HttpGet]
ITask<User> GetAsync([Uri] string urlString, [PathQuery] string id);
}
2. 自定义主机解析
public class ServiceNameHostAttribute : HttpHostBaseAttribute
{
public override Task OnRequestAsync(ApiRequestContext context)
{
var hostProvider = context.HttpContext.ServiceProvider.GetRequiredService<HostProvider>();
string host = hostProvider.ResolveHost(this.ServiceName);
context.HttpContext.RequestMessage.RequestUri = new Uri(host);
return Task.CompletedTask;
}
}
请求签名与安全
1. 动态签名
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;
}
}
2. 表单字段排序
public class SortedFormContentAttribute : FormContentAttribute
{
protected override IEnumerable<KeyValue> SerializeToKeyValues(ApiParameterContext context)
{
return base.SerializeToKeyValues(context).OrderBy(item => item.Key);
}
}
.NET 8 AOT 支持
通过 System.Text.Json 的源生成功能,WebApiClientCore 可以支持 AOT 发布。
配置步骤
- 创建 JSON 上下文:
[JsonSerializable(typeof(User[]))]
public partial class AppJsonSerializerContext : JsonSerializerContext
{
}
- 配置 WebApiClientCore:
services.AddWebApiClient()
.ConfigureHttpApi(options =>
{
options.PrependJsonSerializerContext(AppJsonSerializerContext.Default);
});
高级 HTTP 配置
1. HttpClient 配置
services.AddHttpApi<IUserApi>().ConfigureHttpClient(httpClient =>
{
httpClient.Timeout = TimeSpan.FromMinutes(1);
httpClient.DefaultRequestVersion = HttpVersion.Version20;
});
2. 代理配置
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
UseProxy = true,
Proxy = new WebProxy("http://proxy.com")
});
3. 客户端证书
.ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(yourCert);
return handler;
});
4. Cookie 持久化
var cookieContainer = new CookieContainer();
.ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
handler.CookieContainer = cookieContainer;
return handler;
});
自定义内容处理
1. Protobuf 请求内容
public class ProtobufContentAttribute : HttpContentAttribute
{
protected override Task SetHttpContentAsync(ApiParameterContext context)
{
var stream = new MemoryStream();
Serializer.NonGeneric.Serialize(stream, context.ParameterValue);
context.HttpContext.RequestMessage.Content = new StreamContent(stream);
return Task.CompletedTask;
}
}
2. Protobuf 响应解析
public class ProtobufReturnAttribute : ApiReturnAttribute
{
public override async Task SetResultAsync(ApiResponseContext context)
{
var stream = await context.HttpContext.ResponseMessage.Content.ReadAsStreamAsync();
context.Result = Serializer.NonGeneric.Deserialize(context.ApiAction.Return.DataType.Type, stream);
}
}
高级身份验证
自动刷新 Cookie
public class AutoRefreshCookieHandler : CookieAuthorizationHandler
{
protected override Task<HttpResponseMessage> RefreshCookieAsync()
{
return this.api.LoginAsync(new Account { /* 凭据 */ });
}
}
自定义日志与缓存
1. 自定义日志输出
public class CustomLoggingAttribute : LoggingFilterAttribute
{
protected override Task WriteLogAsync(ApiResponseContext context, LogMessage logMessage)
{
// 自定义日志输出
return Task.CompletedTask;
}
}
2. Redis 缓存提供者
public class RedisResponseCacheProvider : IResponseCacheProvider
{
public Task<ResponseCacheResult> GetAsync(string key)
{
// 从 Redis 获取
}
public Task SetAsync(string key, ResponseCacheEntry entry, TimeSpan expiration)
{
// 存储到 Redis
}
}
自定义参数类型
对于特殊的数据传输需求,可以实现 IApiParameter 接口来自定义参数处理逻辑。
public class FaceModel : IApiParameter
{
public Bitmap Image1 { get; set; }
public Bitmap Image2 { get; set; }
public Task OnRequestAsync(ApiParameterContext context)
{
// 自定义序列化逻辑
}
}
结语
WebApiClientCore 提供了丰富的高级功能,可以满足各种复杂的业务场景需求。通过本文的介绍,相信你已经掌握了这些高级特性的使用方法。在实际开发中,可以根据具体需求选择合适的功能组合,构建出强大而灵活的 HTTP 客户端解决方案。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考