文章目录
前言
HTTP请求是客户端向服务器发送的请求信息。它包含请求行、请求头部和请求主体三部分。请求行包含请求的方法(GET、POST等),请求的URL和HTTP协议的版本。请求头部包含一些附加的信息,例如请求的主机名、浏览器类型、Cookie等。请求主体则是可选的,用于传输请求的数据。服务器收到HTTP请求后,根据请求信息进行相应的处理并返回响应结果。
一、HTTP请求组件WebApiClientCore
1.WebApiClientCore简介
WebApiClientCore是一个基于ASP.NET Core的轻量级WebAPI客户端开发框架。它提供了简单易用的API,使得开发人员可以轻松地创建和调用WebAPI,并且支持使用自定义的拦截器和过滤器来实现自定义的请求和响应处理。WebApiClientCore还支持与Swagger进行集成,以便于根据API文档生成客户端代码。该框架具有良好的可扩展性和可定制性,使得它在开发WebAPI客户端时很受欢迎。
WebApiClientCore是WebApiClient.JIT/AOT[1]的.NET Core版本,集高性能高可扩展性于一体的声明式http客户端库,特别适用于微服务的restful资源请求,也适用于各种畸形http接口请求。
源码网址:https://github.com/dotnetcore/WebApiClient
包名描述WebApiClientCore基础包WebApiClientCore.Extensions.OAuthsOAuth扩展包WebApiClientCore.Extensions.NewtonsoftJsonJson.Net扩展包WebApiClientCore.Extensions.JsonRpcJsonRpc调用扩展包WebApiClientCore.OpenApi.SourceGenerator将本地或远程OpenApi文档解析生成WebApiClientCore接口代码的dotnet tool
2.WebApiClientCore的基本使用
WebApiClientCore 是一个基于 .NET Core 的 HTTP 客户端库,可方便地进行 HTTP 请求。它支持基本的 HTTP 请求,如 GET、POST、PUT、DELETE 等,并提供了丰富的配置选项,如请求超时、请求头、代理等。
以下是 WebApiClientCore 的基本使用:
- 安装 WebApiClientCore 库
通过 NuGet 包管理器或 .NET CLI 安装 WebApiClientCore 库。
- 创建接口
创建一个接口,用于定义 HTTP 请求。每个方法对应一种 HTTP 请求方式,并包含请求的地址、参数、请求头等信息。
[HttpHost("https://localhost:7030")]// 设置主机地址publicinterfaceIUserApi:IHttpApi{[HttpGet("/api/users")]Task<string>GetUserAsync();[HttpGet("/api/users/{id}")]Task<string>GetUserAsync(int id);[HttpPost("/api/users")]Task<User>AddUserAsync([JsonContent]User user);[HttpPut("/api/users/{id}")]Task<User>UpdateUserAsync(int id,[JsonContent]User user);[HttpDelete("/api/users/{id}")]Task<bool>DeleteUserAsync(int id);}publicclassUser{publicint Id {get;set;}publicstring Name {get;set;}}
- 创建 WebApiClient 实例
Console控制台项目中使用
usingConsoleTest;usingMicrosoft.Extensions.DependencyInjection;usingWebApiClientCore;//无依赖注入的环境需要自行创建IServiceCollection services =newServiceCollection();
services.AddHttpApi<IUserApi>();var serviceprovider = services.BuildServiceProvider();var client = serviceprovider.GetService<IUserApi>();
WebApi项目中使用
publicvoidConfigureServices(IServiceCollection services){// 注册并配置
services.AddHttpApi(typeof(IUserApi), o =>{
o.UseLogging = Environment.IsDevelopment();
o.HttpHost =newUri("http://localhost:7030/");});//注册,然后配置
services.AddHttpApi<IUserApi>().ConfigureHttpApi(o =>{
o.UseLogging = Environment.IsDevelopment();
o.HttpHost =newUri("http://localhost:7030/");});//添加全局配置
services.AddWebApiClient().ConfigureHttpApi(o =>{
o.UseLogging = Environment.IsDevelopment();
o.HttpHost =newUri("http://localhost:7030/");});}publicclassMyService{privatereadonlyIUserApi userApi;publicMyService(IUserApi userApi){this.userApi = userApi;}publicasyncTaskGetAsync(){//使用接口var user=await userApi.GetAsync(100);}}
- 发送请求
通过调用接口中的方法,发送 HTTP 请求。
Console.WriteLine(await client.GetUserAsync());
Console.WriteLine(await client.GetUserAsync(1));
Console.WriteLine(await client.AddUserAsync(newUser{ Id =2, Name ="愚公搬代码"}));
Console.WriteLine(await client.UpdateUserAsync(1,newUser{ Id =1, Name ="愚公搬代码"}));
Console.WriteLine(await client.DeleteUserAsync(2));
二、WebApiClientCore概念解析
1.编译时语法分析
WebApiClientCore.Analyzers 提供接口声明的语法分析与提示,帮助开发者声明接口时避免使用不当的语法。
- 1.x 版本,接口继承 IHttpApi 才获得语法分析提示
- 2.0 以后的版本,不继承 IHttpApi 也获得语法分析提示
例如[Header]特性,可以声明在Interface、Method和Parameter三个地方,但是必须使用正确的构造器,否则运行时会抛出异常。有了语法分析功能,在声明接口时就不会使用不当的语法。
/// <summary>/// 记得要实现IHttpApi/// </summary>publicinterfaceIUserApi:IHttpApi{...}
2.配置
调用services.AddHttpApi()即可完成接口注册, 每个接口的选项对应为HttpApiOptions,选项名称通过 HttpApi.GetName()方法获取得到。
配置文件的 json
{"IUserApi":{"HttpHost":"http://www.webappiclient.com/","UseParameterPropertyValidate":false,"UseReturnValuePropertyValidate":false,"JsonSerializeOptions":{"IgnoreNullValues":true,"WriteIndented":false}}}
2.1 IHttpClientBuilder 配置
services
.AddHttpApi<IUserApi>().ConfigureHttpApi(Configuration.GetSection(nameof(IUserApi))).ConfigureHttpApi(o =>{// 符合国情的不标准时间格式,有些接口就是这么要求必须不标准
o.JsonSerializeOptions.Converters.Add(newJsonDateTimeConverter("yyyy-MM-dd HH:mm:ss"));});
2.2 IServiceCollection 配置
services
.ConfigureHttpApi<IUserApi>(Configuration.GetSection(nameof(IUserApi))).ConfigureHttpApi<IUserApi>(o =>{// 符合国情的不标准时间格式,有些接口就是这么要求必须不标准
o.JsonSerializeOptions.Converters.Add(newJsonDateTimeConverter("yyyy-MM-dd HH:mm:ss"));});
3.数据验证
3.1 参数值验证
publicinterfaceIUserApi:IHttpApi{[HttpGet("api/users/{email}")]Task<User>GetAsync([EmailAddress,Required]string email);[HttpGet("api/users/{account}")]ITask<HttpResponseMessage>GetAsync([Required]string account);[HttpGet("api/users/{account}")]ITask<string>GetAsStringAsync([Required]string account,CancellationToken token =default);[HttpGet("api/users/{account}")][JsonReturn]ITask<string>GetExpectJsonAsync([Required]string account,CancellationToken token =default);[HttpGet("api/users/{account}")][XmlReturn]ITask<string>GetExpectXmlAsync([Required]string account,CancellationToken token =default);[HttpGet("api/users/{account}")]ITask<byte[]>GetAsByteArrayAsync([Required]string account,CancellationToken token =default);[HttpGet("api/users/{account}")]ITask<Stream>GetAsStreamAsync([Required]string account,CancellationToken token =default);[HttpGet("api/users/{account}")]ITask<User>GetAsModelAsync([Required]string account,CancellationToken token =default);[HttpPost("api/users/body")]Task<User>PostByJsonAsync([Required,JsonContent]User user,CancellationToken token =default);[HttpPost("api/users/body")]Task<User>PostByXmlAsync([Required,XmlContent]User user,CancellationToken token =default);[HttpPost("api/users/form")]Task<User>PostByFormAsync([Required,FormContent]User user,CancellationToken token =default);[HttpPost("api/users/formdata")]Task<User>PostByFormDataAsync([Required,FormDataContent]User user,FormDataFile file,CancellationToken token =default);[HttpDelete("api/users/{account}")]TaskDeleteAsync([Required]string account);}
publicinterfaceIUserApi_ParameterStyle:IHttpApi{[HttpGet("api/users/{account}")]Task<HttpResponseMessage>GetAsync([Required,Parameter(Kind.Path)]string account);[HttpGet("api/users/{account}")]Task<string>GetAsStringAsync([Required,Parameter(Kind.Path)]string account,CancellationToken token =default);[HttpGet("api/users/{account}")]Task<byte[]>GetAsByteArrayAsync([Required,Parameter(Kind.Path)]string account,CancellationToken token =default);[HttpGet("api/users/{account}")]Task<Stream>GetAsStreamAsync([Required,Parameter(Kind.Path)]string account,CancellationToken token =default);[HttpGet("api/users/{account}")]Task<User>GetAsModelAsync([Required,Parameter(Kind.Path)]string account,CancellationToken token =default);[HttpPost("api/users/body")]Task<User>PostByJsonAsync([Required,Parameter(Kind.JsonBody)]User user,CancellationToken token =default);[HttpPost("api/users/body")]Task<User>PostByXmlAsync([Required,Parameter(Kind.XmlBody)]User user,CancellationToken token =default);[HttpPost("api/users/form")]Task<User>PostByFormAsync([Required,Parameter(Kind.Form)]User user,CancellationToken token =default);[HttpPost("api/users/formdata")]Task<User>PostByFormDataAsync([Required,Parameter(Kind.FormData)]User user,FormDataFile file,CancellationToken token =default);[HttpDelete("api/users/{account}")]TaskDeleteAsync([Required]string account);}
3.2 模型属性验证
/// <summary>/// 表示用户模型/// </summary>publicclassUser{[Required][StringLength(10, MinimumLength =1)]publicstring Account {get;set;}[Required][StringLength(10, MinimumLength =1)]publicstring Password {get;set;}publicstring NickName {get;set;}[JsonDateTime("yyyy年MM月dd日")]publicDateTime? BirthDay {get;set;}publicGender Gender {get;set;}[JsonIgnore]publicstring Email {get;set;}}/// <summary>/// 性别/// </summary>publicenumGender{
Female =0,
Male =1}
4.内置特性
内置特性是指在一些编程框架或库中,预定义的特性(Attribute),用于标记类、方法、属性等元素,以提供一些额外的功能或行为。这些内置特性可以在编写代码时直接使用,而无需自己编写或引入额外的代码。
在WebApiClientCore中,内置特性是指一些预定义的特性(Attribute),用于对HTTP请求和响应进行描述和控制。这些特性可以方便地设置请求头、请求参数、请求内容、响应内容等。使用这些内置特性,可以让我们更加高效地开发和调试HTTP API客户端,减少重复开发的工作量。
4.1 内置特性位置
[IApiFilterAttribute]/*作用于接口内所有方法的FilterAttribute*/[IApiReturnAttribute]/*作用于接口内所有方法的ReturnAttribute*/publicinterfaceDemoApiInterface{[IApiActionAttribute]/*作用于本方法的ActionAttribute*/[IApiFilterAttribute]/*作用于本方法的FilterAttribute*/[IApiReturnAttribute]/*作用于本方法的ReturnAttribute*/ITask<HttpResponseMessage>DemoApiMethod([IApiParameterAttribute]ParameterClass parameterClass);}
4.2 ReturnAttribute
ReturnAttribute是WebApiClientCore中的一个内置特性,用于标记WebApi接口的返回类型。它可以设置返回类型的数据格式,如Json、Xml、Form等,并可以设置返回类型的字符编码、内容类型等属性。
当我们在WebApiClientCore中调用WebApi接口时,可以使用ReturnAttribute来描述接口的返回类型,这样,在调用接口时,WebApiClientCore就会根据ReturnAttribute中设置的信息来对返回数据进行处理,并将其转化为相应的对象或数据类型。
特性名称功能描述备注RawReturnAttribute处理原始类型返回值缺省也生效JsonReturnAttribute处理 Json 模型返回值缺省也生效XmlReturnAttribute处理 Xml 模型返回值缺省也生效NoneReturnAttribute处理空返回值缺省也生效
4.3 ActionAttribute
ActionAttribute是其中一种特殊的自定义属性,表示一个接口方法对应的API路由地址。
在WebApiClientCore框架中使用ActionAttribute,可以在接口方法上标识对应的API路由地址和HTTP请求方式。
特性名称功能描述备注HttpHostAttribute请求服务 http 绝对完整主机域名优先级比 Options 配置低、它也支持直接在 interface 级别使用HttpGetAttribute声明 Get 请求方法与路径支持 null、绝对或相对路径HttpPostAttribute声明 Post 请求方法与路径支持 null、绝对或相对路径HttpPutAttribute声明 Put 请求方法与路径支持 null、绝对或相对路径HttpDeleteAttribute声明 Delete 请求方法与路径支持 null、绝对或相对路径HeaderAttribute声明请求头常量值TimeoutAttribute声明超时时间常量值FormFieldAttribute声明 Form 表单字段与值常量键和值FormDataTextAttribute声明 FormData 表单字段与值常量键和值
4.3 ParameterAttribute
ParameterAttribute是WebApiClientCore中的一个特性,用于标识HTTP请求参数。当我们使用WebApiClientCore访问接口时,可以通过在参数上应用ParameterAttribute来指定该参数的名称、类型等信息,以便能正确地将参数传递到HTTP请求中。
特性名称功能描述备注PathQueryAttribute参数值的键值对作为 url 路径参数或 query 参数的特性缺省特性的参数默认为该特性FormContentAttribute参数值的键值对作为 x-www-form-urlencoded 表单FormDataContentAttribute参数值的键值对作为 multipart/form-data 表单JsonContentAttribute参数值序列化为请求的 json 内容XmlContentAttribute参数值序列化为请求的 xml 内容UriAttribute参数值作为请求 uri只能修饰第一个参数ParameterAttribute聚合性的请求参数声明不支持细颗粒配置HeaderAttribute参数值作为请求头TimeoutAttribute参数值作为超时时间值不能大于 HttpClient 的 Timeout 属性FormFieldAttribute参数值作为 Form 表单字段与值只支持简单类型参数FormDataTextAttribute参数值作为 FormData 表单字段与值只支持简单类型参数
4.4 FilterAttribute
FilterAttribute表示可以在WebApiClientCore的请求管道的不同阶段添加各种过滤器,以实现各种功能。
特性名称功能描述备注ApiFilterAttributeFilter 特性抽象类LoggingFilterAttribute请求和响应内容的输出为日志的过滤器
4.5 自解释参数类型
WebApiClientCore框架中,可以使用一些特定的参数类型来进行请求参数的定义,这些参数类型可以自动解释对应的参数名称和值,从而简化了开发者的代码编写。
特性名称功能描述备注FormDataFileform-data 的一个文件项无需特性修饰,等效于 FileInfo 类型JsonPatchDocument表示将 JsonPatch 请求文档无需特性修饰
5.请求声明
5.1 参数解析
对于
id = new string []{"001","002"}
这样的值,在 PathQueryAttribute 与 FormContentAttribute 处理后分别是:
CollectionFormatData[PathQuery(CollectionFormat = CollectionFormat.Csv)]id=001,002[PathQuery(CollectionFormat = CollectionFormat.Ssv)]id=001 002[PathQuery(CollectionFormat = CollectionFormat.Tsv)]id=001\002[PathQuery(CollectionFormat = CollectionFormat.Pipes)]id=001[PathQuery(CollectionFormat = CollectionFormat.Multi)]id=001&id=002
5.2 CancellationToken 参数
WebApiClientCore中的CancellationToken是用于取消HTTP请求的令牌,可以用于在需要的时候取消正在进行的请求。CancellationToken是一个结构体,它包含一个bool类型的IsCancellationRequested属性,如果该属性为true,则表示请求已经被取消。CancellationToken是在发送HTTP请求时传递给WebApiClientCore方法的一个参数,当请求被取消时,WebApiClientCore会抛出OperationCanceledException异常。可以通过调用CancellationToken的Cancel方法来取消正在进行的请求。
[HttpGet("api/users/{id}")]ITask<User>GetAsync([Required]string id,CancellationToken token =default);
5.3 ContentType CharSet
在WebApiClientCore中,ContentType和CharSet是两个不同的属性。
ContentType指示HTTP请求或响应中的内容类型。例如,ContentType可以是"application/json"或"application/xml"等。
CharSet则指示字符集。例如,CharSet可以是"utf-8"或"gb2312"等。
这两个属性一起描述了HTTP请求或响应中的内容类型和字符集。在WebApiClientCore中,可以通过在方法的特性上设置ContentType和CharSet属性来指示请求或响应的相应内容类型和字符集。例如:
[HttpPost,JsonReturn][HttpContentType(MediaType.Json)]publicITask<MyResponse>Post([HttpContentTypeCharSet("utf-8")]MyRequest request){// Code here}
在这个例子中,HttpPost特性指示HTTP请求使用POST方法,JsonReturn特性指示返回结果使用Json格式。HttpContentType特性指示内容类型为"application/json"。HttpContentTypeCharSet特性指示字符集为"utf-8"。当这个方法被调用时,WebApiClientCore将使用这些特性的设置来构造HTTP请求,并设置相应的内容类型和字符集。
AttributeContentType[JsonContent]Content-Type: application/json; charset=utf-8[JsonContent(CharSet =“utf-8”)]Content-Type: application/json; charset=utf-8[JsonContent(CharSet =“unicode”)]Content-Type: application/json; charset=utf-16
5.4 Accpet ContentType
在WebApiClientCore中,Accept ContentType用于指定客户端期望接收哪种类型的响应内容。可以通过使用
Accept
属性或者在方法的参数中指定来设置请求的Accept ContentType。
例如:
[HttpGet("/api/users")][Header("Accept","application/json")]Task<List<User>>GetUsersAsync();
上面的代码中,指定了请求的Accept ContentType为
application/json
,表示客户端期望接收JSON格式的响应内容。
另外,WebApiClientCore还提供了一些默认的Accept ContentType,例如:
Accept.Json
:表示接收JSON格式的响应内容;Accept.Xml
:表示接收XML格式的响应内容;Accept.Form
:表示接收表单格式的响应内容;Accept.Text
:表示接收纯文本格式的响应内容。
可以直接使用这些默认的Accept ContentType来快速设置请求的Accept ContentType。例如:
[HttpGet("/api/users")][Header(Accept.Json)]Task<List<User>>GetUsersAsync();
5.5 PATCH 请求
5.5.1 PATCH 请求介绍
PATCH请求是HTTP请求方法之一,它被用来向服务器发送部分更新资源的请求。与PUT请求不同的是,PATCH方法通常用于对资源进行部分修改,也就是只修改资源的某一部分,而不是对整个资源进行替换。例如,你想要更新一个用户的邮箱,你可以使用PATCH方法仅更新邮箱字段,而不必对整个用户对象进行替换。
PATCH请求的语法与PUT请求类似,但是它只更新资源的部分内容,而不是整个资源。PATCH请求方法通常使用JSON格式的负载来封装请求的更新内容。如果成功,服务器通常会返回HTTP状态码200或204,指示操作已成功完成。
publicinterfaceIUserApi{[HttpPatch("api/users/{id}")]Task<UserInfo>PatchAsync(string id,JsonPatchDocument<User> doc);}var doc =newJsonPatchDocument<User>();
doc.Replace(item => item.Account,"laojiu");
doc.Replace(item => item.Email,"[email protected]");
请求内容
PATCH /api/users/id001 HTTP/1.1
Host: localhost:6000
User-Agent: WebApiClientCore/1.0.0.0
Accept: application/json; q=0.01, application/xml; q=0.01
Content-Type: application/json-patch+json
[{"op":"replace","path":"/account","value":"laojiu"},{"op":"replace","path":"/email","value":"[email protected]"}]
5.5.2 PATCH 请求案例
下面是一个使用WebApiClientCore中JsonPatchDocument的完整客户端请求和服务端响应流程案例:
- 定义实体类和JsonPatchDocument类
假设我们有一个名为Person的实体类,包含属性Name和Age。我们可以定义一个UpdatePerson类,包含Name和Age属性的部分更新信息。
publicclassPerson{publicstring Name {get;set;}publicint Age {get;set;}}publicclassUpdatePerson{publicstring Name {get;set;}publicint? Age {get;set;}}
- 创建API接口类
创建一个继承自HttpApi的API接口类,并定义一个Patch方法,用于将UpdatePerson类的信息应用到Person实体类中。
publicinterfaceIPersonApi:IHttpApi{[Patch("/api/persons/{id}")]Task<Person>PatchPersonAsync(int id,[JsonPatch(typeof(UpdatePerson))]JsonPatchDocument<UpdatePerson> patchDoc);}publicclassPersonApi:HttpApi,IPersonApi{publicPersonApi(IHttpClientFactory httpClientFactory):base(httpClientFactory){}publicasyncTask<Person>PatchPersonAsync(int id,JsonPatchDocument<UpdatePerson> patchDoc){returnawaitPatchAsync<Person>($"/api/persons/{id}", patchDoc);}}
- 创建API服务
为了测试这个API接口,我们需要创建一个API服务。在这个例子中,我们使用ASP.NET Core创建一个Web API服务,供客户端测试使用。
[Route("api/[controller]")][ApiController]publicclassPersonsController:ControllerBase{privatereadonlyList<Person> _persons =newList<Person>{newPerson{ Name ="Tom", Age =25},newPerson{ Name ="Jerry", Age =30}};[HttpGet("{id}")]publicIActionResultGet(int id){var person = _persons.FirstOrDefault(p => p.Id == id);if(person ==null){returnNotFound();}returnOk(person);}[HttpPatch("{id}")]publicIActionResultPatch(int id,[FromBody]JsonPatchDocument<UpdatePerson> patchDoc){var person = _persons.FirstOrDefault(p => p.Id == id);if(person ==null){returnNotFound();}
patchDoc.ApplyTo(person);returnOk(person);}}
在上面的代码中,我们创建了一个名为PersonsController的ASP.NET Core Web API控制器。控制器包含一个Get方法用于获取指定ID的Person实体类,以及一个Patch方法用于更新指定ID的Person实体类。注意,在Patch方法中,我们使用JsonPatchDocument.ApplyTo方法将更新文档应用到Person实体类中。
- 发送请求并获取响应
现在,我们可以使用PersonApi类中的PatchPersonAsync方法发送请求,更新指定ID的Person实体类,并获取更新后的数据。
using(var client =newHttpClient()){
client.BaseAddress =newUri("http://localhost:5000");var api = RestService.For<IPersonApi>(client);var patchDoc =newJsonPatchDocument<UpdatePerson>();
patchDoc.Replace(p => p.Name,"John");var updatedPerson =await api.PatchPersonAsync(1, patchDoc);}
在上面的代码中,我们创建了一个JsonPatchDocument实例,用于将Name属性替换为"John"。然后我们使用IPersonApi接口中的PatchPersonAsync方法发送请求,并传递更新文档和指定的ID。最后,我们从响应中获取更新后的Person实体类。
这就是一个使用WebApiClientCore中JsonPatchDocument的完整客户端请求和服务端响应流程案例。注意,在服务端控制器中,我们使用FromBody属性将更新文档从请求体中获取,并使用JsonPatchDocument.ApplyTo方法将更新应用到Person实体类中。
5.6 非模型请求
有时候我们未必需要强模型,假设我们已经有原始的 form 文本内容,或原始的 json 文本内容,甚至是 System.Net.Http.HttpContent 对象,只需要把这些原始内请求到远程远程器。
5.6.1 原始文本
[HttpPost]TaskPostAsync([RawStringContent("txt/plain")]string text);
[HttpPost]TaskPostAsync(StringContent text);
5.6.2 原始 json
[HttpPost]TaskPostAsync([RawJsonContent]string json);
5.6.3 原始 xml
[HttpPost]TaskPostAsync([RawXmlContent]string xml);
5.6.4 原始表单内容
[HttpPost]TaskPostAsync([RawFormContent]string form);
5.7 自定义自解释的参数类型
1、服务端要求的 json 模型
{"image1":"图片1的base64","image2":"图片2的base64"}
2、客户端期望的业务模型
classFaceModel{publicBitmap Image1 {get;set;}publicBitmap Image2 {get;set;}}
3、构造转换
classFaceModel:IApiParameter{publicBitmap Image1 {get;set;}publicBitmap Image2 {get;set;}publicTaskOnRequestAsync(ApiParameterContext context){var image1 =GetImageBase64(this.Image1);var image2 =GetImageBase64(this.Image2);var model =new{ image1, image2 };var options = context.HttpContext.HttpApiOptions.JsonSerializeOptions;
context.HttpContext.RequestMessage.Content =newJsonContent(model,options);}privatestaticstringGetImageBase64(Bitmap image){usingvar stream =newMemoryStream();
image.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);return Convert.ToBase64String(stream.ToArray());}}
4、使用
publicinterfaceIFaceApi{[HttpPost("/somePath")]Task<HttpResponseMessage>PostAsync(FaceModel faces);}
6.响应处理
6.1 缺省配置值
缺省配置是
[JsonReturn(0.01),XmlReturn(0.01)]
,对应的请求 accept 值是
Accept: application/json; q=0.01, application/xml; q=0.01
6.2 Json 优先
在 Interface 或 Method 上显式地声明
[JsonReturn]
,请求 accept 变为
Accept: application/json, application/xml; q=0.01
6.3 禁用 json
在 Interface 或 Method 上声明
[JsonReturn(Enable = false)]
,请求变为
Accept: application/xml; q=0.01
当接口返回值声明为如下类型时,我们称之为原始类型,会被 RawReturnAttribute 处理。
6.4 原始类型返回值
在WebApiClientCore中,原始类型的返回值包括int、long、float、double、bool等。标记方法的返回值类型,从而告诉WebApiClientCore这个方法应该返回一个原始类型的值。示例如下:
返回类型说明Task不关注响应消息Task原始响应消息类型Task原始响应流Task<byte[]>原始响应二进制数据Task原始响应消息文本
如果不使用标记方法的返回值类型,WebApiClientCore会默认将返回值解析为一个Json字符串,并使用Newtonsoft.Json进行反序列化。如果方法返回的是原始类型的值,则解析会失败,导致程序报错。
6.5 响应内容缓存
6.5.1 声明缓存
1、基本使用
在使用WebApiClientCore进行Web API访问时,可通过以下方式声明缓存,在接口上使用CacheAttribute指定缓存策略,例如:
[HttpGet("api/user/{id}")][Cache(TimeExpire =60)]Task<User>GetUserAsync(int id);
其中,CacheAttribute的TimeExpire属性指定了缓存过期时间,单位为秒。
WebApiClientCore的ApiCacheAttribute提供了默认的缓存路径,但是对于一些特殊的业务场景,可能需要自定义缓存路径。为了实现自定义缓存路径,我们可以通过实现IApiCacheKeyGenerator接口来实现。
2、自定义缓存路径
WebApiClientCore中可以使用ApiCacheAttribute来实现自定义缓存路径。默认情况下,ApiCacheAttribute会根据请求的URL、method、headers和body等信息作为缓存键,生成缓存的文件名。但是,如果你想自定义缓存路径,可以通过重写ApiCacheAttribute类的GetCacheKey方法来实现。
以下是一个示例,展示如何通过ApiCacheAttribute自定义缓存路径:
[AttributeUsage(AttributeTargets.Method, AllowMultiple =false, Inherited =true)]publicclassCacheByAbsolutePathAttribute:ApiCacheAttribute{publicCacheByPathAttribute(double expiration):base(expiration){}publicoverrideTask<string>GetCacheKeyAsync(ApiRequestContext context){// 自定义缓存路径规则// string cacheKey = $"{context.RequestMessage.Method}-{context.RequestMessage.RequestUri.Host}-{context.RequestMessage.RequestUri.AbsolutePath}";return Task.FromResult(context.HttpContext.RequestMessage.RequestUri.AbsolutePath);}}
在上述代码中,CustomCacheAttribute继承了ApiCacheAttribute,并实现了GetCacheKey方法。这个方法中,我们可以自定义一个缓存路径规则,并将它返回作为缓存文件名。
接下来,在WebApiClient中,我们可以使用自定义的CustomCacheAttribute来为请求添加缓存策略,例如:
[HttpGet("weather")][CacheByAbsolutePath(30)]Task<WeatherForecast[]>GetWeatherForecastAsync();
在上述代码中,我们将自定义的CustomCacheAttribute作为请求的缓存策略,并指定了缓存时间。此时,就会以自定义的缓存路径规则来生成缓存文件名,从而实现自定义缓存路径的需求。
6.5.2 自定义缓存
6.5.2.1 自定义实现
WebApiClientCore的缓存功能可以通过自定义IResponseCacheProvider来实现。下面是一个示例代码:
- 创建一个自定义的缓存提供程序实现IResponseCacheProvider接口:
publicclassMyResponseCacheProvider:IResponseCacheProvider{privatereadonlyDictionary<string,byte[]> cache =newDictionary<string,byte[]>();publicTask<byte[]>GetAsync(string key){if(cache.ContainsKey(key))return Task.FromResult(cache[key]);return Task.FromResult<byte[]>(null);}publicTaskSetAsync(string key,byte[]value,TimeSpan expirationTime){
cache[key]=value;return Task.CompletedTask;}}
- 在WebApiClientCore配置中注册自定义的缓存提供程序:
services
.AddHttpApi<MyHttpApi>().ConfigureHttpApiOptions<MyHttpApiOptions>(options =>{
options.ResponseCacheProvider =newMyResponseCacheProvider();});
- 在HttpClient请求中添加缓存头信息:
[HttpGet("weatherforecast")][Cache(60)]// 设置缓存时间为60秒Task<string[]>GetWeatherForecastAsync();
6.5.2.2 redis实现实现
WebApiClientCore的缓存功能可以通过自定义IResponseCacheProvider来实现,Redis是一个高性能的缓存数据库,可以作为WebApiClientCore的缓存提供程序。下面是示例代码:
- 引入StackExchange.Redis包:
<ItemGroup><PackageReference Include="WebApiClient" Version="4.5.9"/><PackageReference Include="StackExchange.Redis" Version="2.2.4"/></ItemGroup>
- 创建一个自定义的缓存提供程序实现IResponseCacheProvider接口,并使用StackExchange.Redis作为缓存后端:
usingStackExchange.Redis;publicclassRedisResponseCacheProvider:IResponseCacheProvider{privatereadonlyConnectionMultiplexer redis;publicRedisResponseCacheProvider(){
redis = ConnectionMultiplexer.Connect("localhost");}publicasyncTask<byte[]>GetAsync(string key){var database = redis.GetDatabase();returnawait database.StringGetAsync(key);}publicasyncTaskSetAsync(string key,byte[]value,TimeSpan expirationTime){var database = redis.GetDatabase();await database.StringSetAsync(key,value, expirationTime);}}
- 在WebApiClientCore配置中注册自定义的缓存提供程序:
services
.AddHttpApi<MyHttpApi>().ConfigureHttpApiOptions<MyHttpApiOptions>(options =>{
options.ResponseCacheProvider =newRedisResponseCacheProvider();});
- 在HttpClient请求中添加缓存头信息:
[HttpGet("weatherforecast")][Cache(60)]// 设置缓存时间为60秒Task<string[]>GetWeatherForecastAsync();
7.日志
7.1 默认日志
WebApiClientCore还提供了一个带特性的日志过滤器
LoggingFilterAttribute
,可以通过在接口方法上标记特性来记录请求和响应的日志信息。下面是使用和查看默认日志过滤器特性的示例代码:
- 引入Serilog包和Serilog.Sinks.File包:
<ItemGroup><PackageReference Include="WebApiClient" Version="4.5.9"/><PackageReference Include="Serilog" Version="2.10.0"/><PackageReference Include="Serilog.Sinks.File" Version="4.1.0"/></ItemGroup>
- 在Program.cs中配置Serilog:
usingSerilog;usingSerilog.Events;publicclassProgram{publicstaticvoidMain(string[] args){
Log.Logger =newLoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning).Enrich.FromLogContext().WriteTo.File("log.txt",rollingInterval: RollingInterval.Day).CreateLogger();try{CreateHostBuilder(args).Build().Run();}catch(Exception ex){
Log.Fatal(ex,"Host terminated unexpectedly");}finally{
Log.CloseAndFlush();}}publicstaticIHostBuilderCreateHostBuilder(string[] args)=>
Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{
webBuilder.UseStartup<Startup>();}).UseSerilog();}
- 在接口方法上标记特性来记录请求和响应的日志信息:
[LoggingFilter][HttpHost("https://api.example.com")]publicinterfaceIMyHttpApi:IHttpApi{[HttpGet("/example")]Task<string>GetExampleAsync();}
- 启动应用程序并执行请求后,在log.txt文件中查看日志信息。
以上就是WebApiClientCore默认日志过滤器特性的使用和查看的示例代码。需要注意的是,
LoggingFilterAttribute
默认会记录请求和响应的详细信息,如果需要自定义日志格式和布局,可以参考示例代码中自定义日志过滤器的实现方式。
注意:在整个 Interface 或某个 Method 上声明[LoggingFilter],即可把请求和响应的内容输出到 LoggingFactory 中。如果要排除某个 Method 不打印日志,在该 Method 上声明[LoggingFilter(Enable = false)],即可将本 Method 排除。
[LoggingFilter]publicinterfaceIUserApi{[HttpGet("api/users/{account}")]ITask<HttpResponseMessage>GetAsync([Required]string account);// 禁用日志[LoggingFilter(Enable =false)][HttpPost("api/users/body")]Task<User>PostByJsonAsync([Required,JsonContent]User user,CancellationToken token =default);}
7.2 自定义日志
在WebApiClientCore中,您可以通过LoggerFactory创建一个ILogger实例,并在LoggingFilterAttribute特性中重写WriteLogAsync方法实现自定义日志记录。例如:
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple =false, Inherited =false)]publicclassMyLoggingFilterAttribute:LoggingFilterAttribute{privatereadonlyILogger logger;publicMyLoggingFilterAttribute(){this.logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger("WebApiClientCore");}protectedoverrideasyncTaskWriteLogAsync(ApiActionContext context,LogMessage logMessage){// 记录请求和响应信息this.logger.LogInformation($"{context.RequestMessage.Method}{context.RequestMessage.RequestUri}");var response = context.ResponseMessage;if(response !=null){// 记录状态码和响应正文var statusCode =(int)response.StatusCode;var content =await response.Content.ReadAsStringAsync();this.logger.LogInformation($"Status Code: {statusCode}");this.logger.LogInformation($"Response Content: {content}");}return Task.CompletedTask;}}
在上面的示例中,我们使用ILoggerFactory创建了一个ILogger实例,并在重写的WriteLogAsync方法中记录了请求和响应信息,包括HTTP方法、URI、状态码和响应正文。可以根据实际需要自定义日志输出内容。
然后,我们将重写过的MyLoggingFilterAttribute特性添加到接口方法中:
[MyLoggingFilter][HttpPost("api/login")]ITask<ResponseModel>LoginAsync([JsonContent]LoginRequest request);
8.文件下载
8.1 客户端
publicinterfaceIUserApi{[HttpGet("/files/{fileName}"]Task<HttpResponseMessage>DownloadAsync(string fileName);}
using System.Net.Http
var response =await userApi.DownloadAsync('123.zip');usingvar fileStream = File.OpenWrite("123.zip");await response.SaveAsAsync(fileStream);
8.2 服务端
[HttpGet("/download")]publicIActionResultGetFile(string fileName){var filePath = Path.Combine(_configuration["FileFolderPath"], fileName);if(System.IO.File.Exists(filePath)){var fileStream = System.IO.File.OpenRead(filePath);returnFile(fileStream,"application/octet-stream", filename);}returnNotFound();}
9.接口声明
/// <summary>/// Everything about your Pets/// </summary>[LoggingFilter][HttpHost("https://petstore.swagger.io/v2/")]publicinterfaceIPetApi:IHttpApi{/// <summary>/// Add a new pet to the store/// </summary>/// <param name="body">Pet object that needs to be added to the store</param>/// <param name="cancellationToken">cancellationToken</param>/// <returns></returns>[HttpPost("pet")]TaskAddPetAsync([Required][JsonContent]Pet body,CancellationToken cancellationToken =default);/// <summary>/// Update an existing pet/// </summary>/// <param name="body">Pet object that needs to be added to the store</param>/// <param name="cancellationToken">cancellationToken</param>/// <returns></returns>[HttpPut("pet")]TaskUpdatePetAsync([Required][JsonContent]Pet body,CancellationToken cancellationToken =default);/// <summary>/// Finds Pets by status/// </summary>/// <param name="status">Status values that need to be considered for filter</param>/// <param name="cancellationToken">cancellationToken</param>/// <returns>successful operation</returns>[HttpGet("pet/findByStatus")]ITask<List<Pet>>FindPetsByStatusAsync([Required]IEnumerable<Anonymous> status,CancellationToken cancellationToken =default);/// <summary>/// Finds Pets by tags/// </summary>/// <param name="tags">Tags to filter by</param>/// <param name="cancellationToken">cancellationToken</param>/// <returns>successful operation</returns>[Obsolete][HttpGet("pet/findByTags")]ITask<List<Pet>>FindPetsByTagsAsync([Required]IEnumerable<string> tags,CancellationToken cancellationToken =default);/// <summary>/// Find pet by ID/// </summary>/// <param name="petId">ID of pet to return</param>/// <param name="cancellationToken">cancellationToken</param>/// <returns>successful operation</returns>[HttpGet("pet/{petId}")]ITask<Pet>GetPetByIdAsync([Required]long petId,CancellationToken cancellationToken =default);/// <summary>/// Updates a pet in the store with form data/// </summary>/// <param name="petId">ID of pet that needs to be updated</param>/// <param name="name">Updated name of the pet</param>/// <param name="status">Updated status of the pet</param>/// <param name="cancellationToken">cancellationToken</param>/// <returns></returns>[HttpPost("pet/{petId}")]TaskUpdatePetWithFormAsync([Required]long petId,[FormField]string name,[FormField]string status,CancellationToken cancellationToken =default);/// <summary>/// Deletes a pet/// </summary>/// <param name="api_key"></param>/// <param name="petId">Pet id to delete</param>/// <param name="cancellationToken">cancellationToken</param>/// <returns></returns>[HttpDelete("pet/{petId}")]TaskDeletePetAsync([Header("api_key")]string api_key,[Required]long petId,CancellationToken cancellationToken =default);/// <summary>/// uploads an image/// </summary>/// <param name="petId">ID of pet to update</param>/// <param name="additionalMetadata">Additional data to pass to server</param>/// <param name="file">file to upload</param>/// <param name="cancellationToken">cancellationToken</param>/// <returns>successful operation</returns>[HttpPost("pet/{petId}/uploadImage")]ITask<ApiResponse>UploadFileAsync([Required]long petId,[FormDataText]string additionalMetadata,FormDataFile file,CancellationToken cancellationToken =default);}
10.请求条件性重试
使用
ITask<>
异步声明,就有 Retry 的扩展,Retry 的条件可以为捕获到某种 Exception 或响应模型符合某种条件。
publicinterfaceIUserApi{[HttpGet("api/users/{id}")]ITask<User>GetAsync(string id);}var result =await userApi.GetAsync(id:"id001").Retry(maxCount:3).WhenCatch<HttpRequestException>().WhenResult(r => r.Age <=0);
11.异常和异常处理
HttpApiException和ApiResultException等,这些异常类型通常以响应错误代码和响应正文的形式抛出。对于这些异常,可以使用try-catch块来捕获并处理它们。
另外,WebApiClientCore还提供了一些扩展方法来处理异常。例如,使用AddHttpApi()方法注册API时,可以通过使用AddHttpApi(o => o.FormatResponse = true)来确保API的响应正文始终可用,即使出现响应错误。
在处理异常时,WebApiClientCore还提供了一些额外的功能,如自定义异常处理程序和异常筛选器。这些功能可以帮助您进一步定制异常处理,并确保应用程序在出现异常时能够保持稳定和可靠。
try{var model =await api.GetAsync();}catch(HttpRequestException ex)when(ex.InnerException isApiInvalidConfigException configException){// 请求配置异常}catch(HttpRequestException ex)when(ex.InnerException isApiResponseStatusException statusException){// 响应状态码异常}catch(HttpRequestException ex)when(ex.InnerException isApiException apiException){// 抽象的api异常}catch(HttpRequestException ex)when(ex.InnerException isSocketException socketException){// socket连接层异常}catch(HttpRequestException ex){// 请求异常}catch(Exception ex){// 异常}
12.适配畸形接口
12.1 不友好的参数名别名
AliasAs是一个属性特性,它用于给参数或返回值指定一个别名。它的作用是当使用HttpClient的POST或PUT方法发送一个对象时,可以用这个别名来指定对象的属性名称。
publicinterfaceIDeformedApi{[HttpGet("api/users")]ITask<string>GetAsync([AliasAs("field-Name")]string fieldName);}
12.2 Form 的某个字段为 json 文本
JsonFormField是WebApiClientCore中的一个类,用于在发送请求时添加一个Json格式的表单字段。
可以使用JsonFormField来将一个Json格式的对象转换为表单字段添加到请求中。
字段值field1someValuefield2{“name”:“sb”,“age”:18}
classField2{publicstring Name {get;set;}publicint Age {get;set;}}publicinterfaceIDeformedApi{TaskPostAsync([FormField]string field1,[JsonFormField]Field2 field2)}
12.3 Form 提交嵌套的模型
KeyValueSerializeOptions是WebApiClientCore中的一个类,用于控制键值对序列化选项。
它有以下属性:
- KeyNamingType:键的命名方式。 - Default:默认使用.NET属性名作为键名。- CamelCase:使用camelCase命名方式。- SnakeCase:使用snake_case命名方式。
- ValueEncodingType:值的编码方式。 - Default:默认不进行编码。- UrlEncode:进行URL编码。
通过设置这些属性,可以控制键值对序列化的方式,以适应不同的数据格式和API要求。例如,在与某些API进行交互时,可能需要使用snake_case命名方式来匹配API的数据格式;在处理含有特殊字符的值时,可能需要使用URL编码方式来避免错误。
字段值filed1someValuefield2.namesbfield2.age18
{"field1":"someValue","filed2":{"name":"sb","age":18}}
services.AddHttpApi<IDeformedApi>(o =>{
o.KeyValueSerializeOptions.KeyNamingStyle = KeyNamingStyle.FullName;});
12.4 响应未指明 ContentType
EnsureMatchAcceptContentType是WebApiClientCore框架中的一个方法,用于确保请求头中的Accept内容与响应的Content-Type匹配。
在WebApiClientCore框架中,发送HTTP请求时,可以指定请求头中的Accept内容,用于告知服务端期望接收的响应格式。而服务端在响应时,会通过响应头中的Content-Type告知响应的内容格式。
在EnsureMatchAcceptContentType方法中,会首先判断请求头中是否指定了Accept内容,如果没有指定,就不做任何处理。如果指定了Accept内容,则会从响应头中获取Content-Type,并与Accept内容进行匹配。如果匹配成功,则直接返回,否则抛出异常。
该方法的目的是为了防止服务端返回的响应格式与客户端期望的格式不匹配,从而导致数据无法解析或解析错误的情况发生。
[JsonReturn(EnsureMatchAcceptContentType =false)]publicinterfaceIDeformedApi{}
12.5 类签名参数或 apikey 参数
WebApiClientCore是一个非常强大的开源.NET库,它可以轻松地生成使用RESTful API的异步.NET客户端代码。
ApiFilterAttribute是WebApiClientCore库中的一个特性,它提供了在请求发出前、后或请求发生异常时执行自定义代码的能力。其中,OnRequestAsync方法签名是ApiFilterAttribute的重要方法之一。
OnRequestAsync方法签名允许开发人员在请求执行前、后或请求发生异常时执行自定义代码。该方法返回值为一个Task对象,可以在该方法中进行异步操作。开发人员可以使用这些方法来实现各种自定义逻辑,例如日志记录、身份验证、缓存控制等等。
在WebApiClientCore中,开发人员可以通过继承ApiFilterAttribute类,并重写OnRequestAsync方法签名实现自定义逻辑。例如:
publicclassMyApiFilterAttribute:ApiFilterAttribute{publicoverrideTaskOnRequestAsync(ApiRequestContext context){// 在请求执行前的逻辑var signService = context.HttpContext.ServiceProvider.GetService<SignService>();var sign = signService.SignValue(DateTime.Now);
context.HttpContext.RequestMessage.AddUrlQuery("sign", sign);return Task.CompletedTask;}publicoverrideTaskOnResponseAsync(ApiResponseContext context){// 在请求执行后的逻辑return Task.CompletedTask;}publicoverrideTaskOnExceptionAsync(ApiExceptionContext context){// 在请求发生异常时的逻辑return Task.CompletedTask;}}
可以看到,在MyApiFilterAttribute中,我们重写了OnRequestAsync、OnResponseAsync和OnExceptionAsync方法签名。在这些方法中,我们可以执行各种自定义逻辑,例如记录请求日志、验证请求头、设置缓存等。
开发人员可以在WebApiClientCore的客户端接口中应用这些特性。例如:
[HttpHost("https://api.example.com")][MyApiFilterAttribute]publicinterfaceIMyApiClient:IHttpApiClient{[HttpGet("users/{id}")]Task<User>GetUserAsync(int id);}
在上面的代码中,我们将MyApiFilterAttribute应用到了IMyApiClient接口上,这就意味着在执行IMyApiClient中的任何方法时,都会执行我们定义的自定义逻辑。同时,我们还设置了HttpHost特性,这定义了API的主机地址。通过这种方式,我们可以轻松地为我们的客户端API添加自定义逻辑和主机地址信息。
12.6 表单字段排序
在WebApiClientCore中,我们可以使用FormContentAttribute来指定表单内容。它可以将对象转换为表单数据,以便在HTTP POST请求中使用。
而表单字段排序则是指在发送表单数据时,按照字母顺序对表单字段进行排序。这样做有利于提高表单数据的可读性和可维护性,同时还可以保证数据的顺序一致性,避免出现因字段顺序不同而导致的问题。
WebApiClientCore中的FormContentAttribute已经实现了表单字段排序功能。当使用FormContentAttribute时,它会自动对对象属性按照字母顺序进行排序,然后将其转换为表单数据。
classSortedFormContentAttribute:FormContentAttribute{protectedoverrideIEnumerable<KeyValue>SerializeToKeyValues(ApiParameterContext context){
这里可以排序、加上其它衍生字段等
returnbase.SerializeToKeyValues(context).OrderBy(item => item.Key);}}publicinterfaceIDeformedApi{[HttpGet("/path")]Task<HttpResponseMessage>PostAsync([SortedFormContent]Model model);}
12.7 自定义请求内容与响应内容解析
1、自定义请求内容处理特性
publicclassProtobufContentAttribute:HttpContentAttribute{publicstring ContentType {get;set;}="application/x-protobuf";protectedoverrideTaskSetHttpContentAsync(ApiParameterContext context){var stream =newMemoryStream();if(context.ParameterValue !=null){
Serializer.NonGeneric.Serialize(stream, context.ParameterValue);
stream.Position =0L;}var content =newStreamContent(stream);
content.Headers.ContentType =newMediaTypeHeaderValue(this.ContentType);
context.HttpContext.RequestMessage.Content = content;return Task.CompletedTask;}}
2、自定义响应内容解析特性
publicclassProtobufReturnAttribute:ApiReturnAttribute{publicProtobufReturnAttribute(string acceptContentType ="application/x-protobuf"):base(newMediaTypeWithQualityHeaderValue(acceptContentType)){}publicoverrideasyncTaskSetResultAsync(ApiResponseContext context){var stream =await context.HttpContext.ResponseMessage.Content.ReadAsStreamAsync();
context.Result = Serializer.NonGeneric.Deserialize(context.ApiAction.Return.DataType.Type, stream);}}
3、应用相关自定义特性
[ProtobufReturn]publicinterfaceIProtobufApi{[HttpPut("/users/{id}")]Task<User>UpdateAsync([Required,PathQuery]string id,[ProtobufContent]User user);}
12.8 HttpMessageHandler 配置
12.8.1 Http 代理配置
在WebApiClientCore中,
ConfigurePrimaryHttpMessageHandler
方法用于配置主要的
HttpMessageHandler
,它是在HTTP请求发送之前和接收响应之后的关键组件。通常,你可以使用这个方法来配置HTTP代理或其他自定义的消息处理器。以下是配置和使用
ConfigurePrimaryHttpMessageHandler
的示例:
首先,确保你已经安装了WebApiClientCore库并配置了HTTP API接口,如上一个回答所示。
在Startup.cs文件(如果你使用ASP.NET Core)或Program.cs文件(如果你使用控制台应用程序)中,你可以进行如下配置:
usingWebApiClientCore;usingWebApiClientCore.Extensions.HttpClientFactory;usingSystem.Net.Http;// ...// 配置WebApiClientCore
services.AddHttpApi<IUserProfileApi>(c =>{
c.HttpHost =newUri("https://your-api-url.com");// 配置主要的HttpMessageHandler
c.ConfigurePrimaryHttpMessageHandler(()=>{var handler =newHttpClientHandler();
handler.UseProxy =true,// 在这里进行自定义配置,例如设置代理
handler.Proxy =newWebProxy("http://your-proxy-url.com");// 配置凭据(用户名和密码)
handler.Credentials =newNetworkCredential("your-username","your-password");return handler;});});
在上述示例中,我们通过
ConfigurePrimaryHttpMessageHandler
方法配置了主要的
HttpMessageHandler
。在这个例子中,我们创建了一个
HttpClientHandler
实例,并设置了代理。
你可以根据你的需要在
ConfigurePrimaryHttpMessageHandler
中进行自定义配置。这个方法允许你设置各种HTTP处理器的选项,以满足你的应用程序的特定需求,比如设置超时、处理证书、设置代理等。
一旦配置好主要的
HttpMessageHandler
,你可以使用WebApiClientCore的HTTP API接口来发送HTTP请求,这些请求将使用你配置的处理器来处理。例如:
var api = serviceProvider.GetRequiredService<IUserProfileApi>();// 发送HTTP请求并处理响应var response =await api.GetUserInfoAsync();if(response.IsSuccessStatusCode){// 处理成功响应}else{// 处理失败响应}
以上示例演示了如何配置和使用WebApiClientCore中的
ConfigurePrimaryHttpMessageHandler
方法,以自定义HTTP消息处理器以满足你的应
12.8.2 客户端证书配置
要在WebApiClientCore的
ConfigurePrimaryHttpMessageHandler
中配置客户端证书,你可以使用
HttpClientHandler
的
ClientCertificates
属性。以下是一个示例,展示了如何配置客户端证书:
usingSystem;usingSystem.Net.Http;usingSystem.Net.Http.Headers;usingSystem.Security.Cryptography.X509Certificates;usingSystem.Threading.Tasks;usingWebApiClientCore;usingWebApiClientCore.Attributes;usingWebApiClientCore.Extensions.HttpClientFactory;usingWebApiClientCore.HttpContents;classProgram{staticasyncTaskMain(){var services =newServiceCollection();ConfigureServices(services);var serviceProvider = services.BuildServiceProvider();var api = serviceProvider.GetRequiredService<IUserProfileApi>();try{var result =await api.GetUserInfoAsync();if(result.IsSuccessStatusCode){var user =await result.GetHttpContentAsAsync<UserProfile>();
Console.WriteLine($"User ID: {user.Id}, Name: {user.Name}, Email: {user.Email}");}else{
Console.WriteLine($"HTTP Request Failed. Status Code: {result.StatusCode}");}}catch(Exception ex){
Console.WriteLine($"An error occurred: {ex.Message}");}}staticvoidConfigureServices(IServiceCollection services){
services.AddHttpApi<IUserProfileApi>(c =>{
c.HttpHost =newUri("https://your-api-url.com");// 配置主要的HttpMessageHandler,设置客户端证书
c.ConfigurePrimaryHttpMessageHandler(()=>{var handler =newHttpClientHandler();// 配置客户端证书var certificate =newX509Certificate2("path-to-client-certificate.pfx","certificate-password");
handler.ClientCertificates.Add(certificate);return handler;});});}}publicclassUserProfile{publicstring Id {get;set;}publicstring Name {get;set;}publicstring Email {get;set;}}[HttpHost("https://your-api-url.com")]publicinterfaceIUserProfileApi:IHttpApi{[HttpGet("/api/userprofile")]ITask<HttpResponseMessage>GetUserInfoAsync();}
在上述示例中,我们首先创建了一个
HttpClientHandler
实例并配置了客户端证书。配置客户端证书时,我们使用
X509Certificate2
类加载证书文件(.pfx 格式)并提供证书密码。然后,我们将证书添加到
ClientCertificates
属性中。
12.8.3 维持 CookieContainer 不变
要在WebApiClientCore的
ConfigurePrimaryHttpMessageHandler
中配置
CookieContainer
,你可以使用
HttpClientHandler
的
CookieContainer
属性。以下是一个示例,展示了如何配置
CookieContainer
以处理和管理HTTP请求中的Cookie:
usingSystem;usingSystem.Net;usingSystem.Net.Http;usingSystem.Net.Http.Headers;usingSystem.Threading.Tasks;usingWebApiClientCore;usingWebApiClientCore.Attributes;usingWebApiClientCore.Extensions.HttpClientFactory;usingWebApiClientCore.HttpContents;classProgram{staticasyncTaskMain(){var services =newServiceCollection();ConfigureServices(services);var serviceProvider = services.BuildServiceProvider();var api = serviceProvider.GetRequiredService<IUserProfileApi>();try{// 第一次发送请求,将在响应中接收和处理Cookievar result1 =await api.GetUserInfoAsync();if(result1.IsSuccessStatusCode){var user1 =await result1.GetHttpContentAsAsync<UserProfile>();
Console.WriteLine($"User ID: {user1.Id}, Name: {user1.Name}, Email: {user1.Email}");}else{
Console.WriteLine($"HTTP Request Failed. Status Code: {result1.StatusCode}");}// 第二次发送请求,将包含之前响应中接收到的Cookievar result2 =await api.GetAnotherInfoAsync();if(result2.IsSuccessStatusCode){var user2 =await result2.GetHttpContentAsAsync<UserProfile>();
Console.WriteLine($"User ID: {user2.Id}, Name: {user2.Name}, Email: {user2.Email}");}else{
Console.WriteLine($"HTTP Request Failed. Status Code: {result2.StatusCode}");}}catch(Exception ex){
Console.WriteLine($"An error occurred: {ex.Message}");}}staticvoidConfigureServices(IServiceCollection services){
services.AddHttpApi<IUserProfileApi>(c =>{
c.HttpHost =newUri("https://your-api-url.com");// 配置主要的HttpMessageHandler,设置CookieContainer
c.ConfigurePrimaryHttpMessageHandler(()=>{var handler =newHttpClientHandler();// 创建一个新的CookieContainer
handler.CookieContainer =newCookieContainer();return handler;});});}}publicclassUserProfile{publicstring Id {get;set;}publicstring Name {get;set;}publicstring Email {get;set;}}[HttpHost("https://your-api-url.com")]publicinterfaceIUserProfileApi:IHttpApi{[HttpGet("/api/userprofile")]ITask<HttpResponseMessage>GetUserInfoAsync();[HttpGet("/api/another")]ITask<HttpResponseMessage>GetAnotherInfoAsync();}
在上述示例中,我们首先创建了一个
HttpClientHandler
实例并配置了
CookieContainer
。每次发送HTTP请求时,
CookieContainer
将处理和管理Cookie。在示例中,我们首先发送一个请求来接收和处理Cookie,然后发送第二个请求,它将包含之前响应中接收到的Cookie。
12.8.4 Cookie 过期自动刷新
对于使用 Cookie 机制的接口,只有在接口请求之后,才知道 Cookie 是否已失效。通过自定义 CookieAuthorizationHandler,可以做在请求某个接口过程中,遇到 Cookie 失效时自动刷新 Cookie 再重试请求接口。
首先,我们需要把登录接口与某它业务接口拆分在不同的接口定义,例如 IUserApi 和 IUserLoginApi
[HttpHost("http://localhost:5000/")]publicinterfaceIUserLoginApi{[HttpPost("/users")]Task<HttpResponseMessage>LoginAsync([JsonContent]Account account);}
然后实现自动登录的 CookieAuthorizationHandler
publicclassAutoRefreshCookieHandler:CookieAuthorizationHandler{privatereadonlyIUserLoginApi api;publicAutoRefreshCookieHandler(IUserLoginApi api){this.api = api;}/// <summary>/// 登录并刷新Cookie/// </summary>/// <returns>返回登录响应消息</returns>protectedoverrideTask<HttpResponseMessage>RefreshCookieAsync(){returnthis.api.LoginAsync(newAccount{
account ="admin",
password ="123456"});}}
最后,注册 IUserApi、IUserLoginApi,并为 IUserApi 配置 AutoRefreshCookieHandler
services
.AddHttpApi<IUserLoginApi>();
services
.AddHttpApi<IUserApi>().AddHttpMessageHandler(s =>newAutoRefreshCookieHandler(s.GetService<IUserLoginApi>()));
现在,调用 IUserApi 的任意接口,只要响应的状态码为 401,就触发 IUserLoginApi 登录,然后将登录得到的 cookie 来重试请求接口,最终响应为正确的结果。你也可以重写 CookieAuthorizationHandler 的 IsUnauthorizedAsync(HttpResponseMessage)方法来指示响应是未授权状态。
13.OAuths&Token
使用 WebApiClientCore.Extensions.OAuths 扩展,轻松支持 token 的获取、刷新与应用。
13.1 对象与概念
对象用途ITokenProviderFactorytokenProvider 的创建工厂,提供通过 HttpApi 接口类型获取或创建 tokenProviderITokenProvidertoken 提供者,用于获取 token,在 token 的过期后的头一次请求里触发重新请求或刷新 tokenOAuthTokenAttributetoken 的应用特性,使用 ITokenProviderFactory 创建 ITokenProvider,然后使用 ITokenProviderOAuthTokenHandler属于 http 消息处理器,功能与 OAuthTokenAttribute 一样,除此之外,如果因为意外的原因导致服务器仍然返回未授权(401 状态码),其还会丢弃旧 token,申请新 token 来重试一次请求。
13.2 OAuth 的 Client 模式
13.2.1 客户端
1、注册接口
// 为接口注册与配置Client模式的tokenProvider
services.AddClientCredentialsTokenProvider<IUserApi>(o =>{
o.Endpoint =newUri("http://localhost:6000/api/tokens");
o.Credentials.Client_id ="clientId";
o.Credentials.Client_secret ="xxyyzz";});
2、使用 OAuthToken 特性
OAuthTokenAttribute 属于 WebApiClientCore 框架层,很容易操控请求内容和响应模型,比如将 token 作为表单字段添加到既有请求表单中,或者读取响应消息反序列化之后对应的业务模型都非常方便,但它不能在请求内部实现重试请求的效果。在服务器颁发 token 之后,如果服务器的 token 丢失了,使用 OAuthTokenAttribute 会得到一次失败的请求,本次失败的请求无法避免。
默认
/// <summary>/// 用户操作接口/// </summary>[OAuthToken]publicinterfaceIUserApi{...}
自定义
classUriQueryTokenAttribute:OAuthTokenAttribute{protectedoverridevoidUseTokenResult(ApiRequestContext context,TokenResult tokenResult){
context.HttpContext.RequestMessage.AddUrlQuery("mytoken", tokenResult.Access_token);}}[UriQueryToken]publicinterfaceIUserApi{...}
3、使用 OAuthTokenHandler
OAuthTokenHandler 的强项是支持在一个请求内部里进行多次尝试,在服务器颁发 token 之后,如果服务器的 token 丢失了,OAuthTokenHandler 在收到 401 状态码之后,会在本请求内部丢弃和重新请求 token,并使用新 token 重试请求,从而表现为一次正常的请求。但 OAuthTokenHandler 不属于 WebApiClientCore 框架层的对象,在里面只能访问原始的 HttpRequestMessage 与 HttpResponseMessage,如果需要将 token 追加到 HttpRequestMessage 的 Content 里,这是非常困难的,同理,如果不是根据 http 状态码(401 等)作为 token 无效的依据,而是使用 HttpResponseMessage 的 Content 对应的业务模型的某个标记字段,也是非常棘手的活。
默认
// 注册接口时添加OAuthTokenHandler
services
.AddHttpApi<IUserApi>().AddOAuthTokenHandler();
自定义
classUriQueryOAuthTokenHandler:OAuthTokenHandler{/// <summary>/// token应用的http消息处理程序/// </summary>/// <param name="tokenProvider">token提供者</param>publicUriQueryOAuthTokenHandler(ITokenProvider tokenProvider):base(tokenProvider){}/// <summary>/// 应用token/// </summary>/// <param name="request"></param>/// <param name="tokenResult"></param>protectedoverridevoidUseTokenResult(HttpRequestMessage request,TokenResult tokenResult){// var builder = new UriBuilder(request.RequestUri);// builder.Query += "mytoken=" + Uri.EscapeDataString(tokenResult.Access_token);// request.RequestUri = builder.Uri;var uriValue =newUriValue(request.RequestUri).AddQuery("myToken", tokenResult.Access_token);
request.RequestUri = uriValue.ToUri();}}// 注册接口时添加UriQueryOAuthTokenHandler
services
.AddHttpApi<IUserApi>().AddOAuthTokenHandler((s, tp)=>newUriQueryOAuthTokenHandler(tp));
13.2.2 服务端
1、token获取
[ApiController][Route("api/[controller]")]publicclassTokensController:ControllerBase{[HttpPost]publicTokenResultCreateToken([FromForm]ClientCredentials credentials){returnnewTokenResult{
Access_token =$"Access_token_{credentials.Client_id}_{credentials.Client_secret}",
Expires_in =60*60,
Id_token ="id",
Token_type ="Bearer"};}}
2、接口认证
publicclassTokenFilterAttribute:Attribute,IAsyncActionFilter{publicasyncTaskOnActionExecutionAsync(ActionExecutingContext context,ActionExecutionDelegate next){if(context.HttpContext.Request.Headers.ContainsKey("Authorization")){awaitnext();}else{
context.Result =newUnauthorizedResult();}}}
[ApiController][Route("api/[controller]")][TokenFilter]publicclassUsersController:ControllerBase{[HttpGet("{account}")]publicUserGet(string account){returnnewUser{ Account = account, Password ="password"};}}
13.3 多接口共享的 TokenProvider
[OAuthToken]publicinterfaceIBaidu{}publicinterfaceIBaidu_XXX_Api:IBaidu{[HttpGet]TaskxxxAsync();}publicinterfaceIBaidu_YYY_Api:IBaidu{[HttpGet]TaskyyyAsync();}// 注册与配置password模式的token提者选项
services.AddPasswordCredentialsTokenProvider<IBaidu>(o =>{
o.Endpoint =newUri("http://localhost:5000/api/tokens");
o.Credentials.Client_id ="clientId";
o.Credentials.Client_secret ="xxyyzz";
o.Credentials.Username ="username";
o.Credentials.Password ="password";});
13.4 自定义 TokenProvider
1、自定义 TokenProvider接口
publicinterfaceITokenApi{[HttpPost("http://xxx.com/token")]Task<TokenResult>RequestTokenAsync([Parameter(Kind.Form)]string clientId,[Parameter(Kind.Form)]string clientSecret);}
2、注册自定义tokenProvider
// 为接口注册自定义tokenProvider
services.AddTokenProvider<IUserApi>(s =>{return s.GetService<ITokenApi>().RequestTokenAsync("id","secret");});
3、注册自定义tokenProvider
// 为接口注册CustomTokenProvider
services.AddTokenProvider<IUserApi, CustomTokenProvider>();classCustomTokenProvider:TokenProvider{publicCustomTokenProvider(IServiceProvider serviceProvider):base(serviceProvider){}protectedoverrideTask<TokenResult>RequestTokenAsync(IServiceProvider serviceProvider){return serviceProvider.GetService<ITokenApi>().RequestTokenAsync("id","secret");}protectedoverrideTask<TokenResult>RefreshTokenAsync(IServiceProvider serviceProvider,string refresh_token){returnthis.RequestTokenAsync(serviceProvider);}}
14.NewtonsoftJson 处理 json
默认的基础包是不包含 NewtonsoftJson 功能的,需要额外引用WebApiClientCore.Extensions.NewtonsoftJson 这个扩展包。
// ConfigureNewtonsoftJson
services.AddHttpApi<IUserApi>().ConfigureNewtonsoftJson(o =>{
o.JsonSerializeOptions.NullValueHandling = NullValueHandling.Ignore;});
使用[JsonNetReturn]替换内置的[JsonReturn],[JsonNetContent]替换内置[JsonContent]
// ConfigureNewtonsoftJson
services.AddHttpApi<IUserApi>().ConfigureNewtonsoftJson(o =>{
o.JsonSerializeOptions.NullValueHandling = NullValueHandling.Ignore;});
15.JsonRpc 调用
开发者可能遇到 JsonRpc 调用的接口,由于该协议不是很流行,WebApiClientCore 将该功能的支持作为 WebApiClientCore.Extensions.JsonRpc 扩展包提供。使用[JsonRpcMethod]修饰 Rpc 方法,使用[JsonRpcParam]修饰 Rpc 参数 即可。
[HttpHost("http://localhost:5000/jsonrpc")]publicinterfaceIUserApi{[JsonRpcMethod("add")]ITask<JsonRpcResult<User>>AddAsync([JsonRpcParam]string name,[JsonRpcParam]int age,CancellationToken token =default);}
POST /jsonrpc HTTP/1.1
Host: localhost:5000
User-Agent: WebApiClientCore/1.0.6.0
Accept: application/json; q=0.01, application/xml; q=0.01
Content-Type: application/json-rpc
{"jsonrpc":"2.0","method":"add","params":["laojiu",18],"id":1}
16.动态 Host
16.1 直接传入绝对目标的方式
[LoggingFilter]publicinterfaceIDynamicHostDemo{[HttpGet]ITask<HttpResponseMessage>ByUrlString([Uri]string urlString);}
16.2 直接传入绝对目标的方式
[LoggingFilter][UriFilter]//可以放在interface级别publicinterfaceIDynamicHostDemo{[HttpGet][UriFilter]//也可以放在Method(Action)级别ITask<HttpResponseMessage>ByFilter();//也可以选择在配置接口时通过GlobalFilter添加}/// <summary>///用来处理动态Uri的拦截器/// </summary>publicclassUriFilterAttribute:ApiFilterAttribute{publicoverrideTaskOnRequestAsync(ApiRequestContext context){var options = context.HttpContext.HttpApiOptions;//获取注册时为服务配置的服务名
options.Properties.TryGetValue("serviceName",outobject serviceNameObj);string serviceName = serviceNameObj asstring;IServiceProvider sp = context.HttpContext.ServiceProvider;HostProvider hostProvider = sp.GetRequiredService<HostProvider>();string host = hostProvider.ResolveService(serviceName);HttpApiRequestMessage requestMessage = context.HttpContext.RequestMessage;//和原有的Uri组合并覆盖原有Uri//并非一定要这样实现,只要覆盖了RequestUri,即完成了替换
requestMessage.RequestUri = requestMessage.MakeRequestUri(newUri(host));return Task.CompletedTask;}publicoverrideTaskOnResponseAsync(ApiResponseContext context){//不处理响应的信息return Task.CompletedTask;}}publicclassHostProvider{publicstringResolveService(string name){string servicehost=string.Empty;//TODO get service hostreturn servicehost;}}
16.3 通过 ApiActionAttribute
[LoggingFilter][ServiceName("baiduService")]//可以放在interface级别publicinterfaceIDynamicHostDemo{[HttpGet][ServiceName("baiduService")]//也可以放在Method(Action)级别ITask<HttpResponseMessage>ByAttribute();}/// <summary>/// 表示对应的服务名/// </summary>publicclassServiceNameAttribute:ApiActionAttribute{publicServiceNameAttribute(string name){
Name = name;
OrderIndex =int.MinValue;}publicstring Name {get;set;}publicoverrideasyncTaskOnRequestAsync(ApiRequestContext context){await Task.CompletedTask;IServiceProvider sp = context.HttpContext.ServiceProvider;HostProvider hostProvider = sp.GetRequiredService<HostProvider>();//服务名也可以在接口配置时挂在Properties中string host = hostProvider.ResolveService(this.Name);HttpApiRequestMessage requestMessage = context.HttpContext.RequestMessage;//和原有的Uri组合并覆盖原有Uri//并非一定要这样实现,只要覆盖了RequestUri,即完成了替换
requestMessage.RequestUri = requestMessage.MakeRequestUri(newUri(host));}}publicclassHostProvider{publicstringResolveService(string name){string servicehost=string.Empty;//TODO get service hostreturn servicehost;}}
17.客户端代码生成器
17.1 安装工具
dotnet tool install WebApiClientCore.OpenApi.SourceGenerator -g
17.2 使用工具
运行以下命令,会将对应的 WebApiClientCore 的接口定义代码文件输出到当前目录的 output 文件夹下
#举例
WebApiClientCore.OpenApi.SourceGenerator -o https://petstore.swagger.io/v2/swagger.json
命令介绍
-o OpenApi,--openapi=OpenApi Required. openApi的json本地文件路径或远程Uri地址
-n Namespace,--namespace=Namespace 代码的命名空间,如WebApiClientCore
--help
版权归原作者 愚公搬代码 所有, 如有侵权,请联系我们删除。