admin 管理员组

文章数量: 887007

🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏

文章目录

  • 🚀前言
  • 🚀一、HTTP请求组件WebApiClientCore
    • 🔎1.WebApiClientCore简介
    • 🔎2.WebApiClientCore的基本使用
  • 🚀二、WebApiClientCore概念解析
    • 🔎1.编译时语法分析
    • 🔎2.配置
      • 🦋2.1 IHttpClientBuilder 配置
      • 🦋2.2 IServiceCollection 配置
    • 🔎3.数据验证
      • 🦋3.1 参数值验证
      • 🦋3.2 模型属性验证
    • 🔎4.内置特性
      • 🦋4.1 内置特性位置
      • 🦋4.2 ReturnAttribute
      • 🦋4.3 ActionAttribute
      • 🦋4.4 ParameterAttribute
      • 🦋4.5 FilterAttribute
      • 🦋4.6 自解释参数类型
    • 🔎5.请求声明
      • 🦋5.1 参数解析
      • 🦋5.2 CancellationToken 参数
      • 🦋5.3 ContentType CharSet
      • 🦋5.4 Accpet ContentType
      • 🦋5.5 PATCH 请求
        • ☀️5.5.1 PATCH 请求介绍
        • ☀️5.5.2 PATCH 请求案例
      • 🦋5.6 非模型请求
        • ☀️5.6.1 原始文本
        • ☀️5.6.2 原始 json
        • ☀️5.6.3 原始 xml
        • ☀️5.6.4 原始表单内容
      • 🦋5.7 自定义自解释的参数类型
    • 🔎6.响应处理
      • 🦋6.1 缺省配置值
      • 🦋6.2 Json 优先
      • 🦋6.3 禁用 json
      • 🦋6.4 原始类型返回值
      • 🦋6.5 响应内容缓存
        • ☀️6.5.1 声明缓存
        • ☀️6.5.2 自定义缓存
          • 🌈6.5.2.1 自定义实现
          • 🌈6.5.2.2 redis实现实现
    • 🔎7.日志
      • 🦋7.1 默认日志
      • 🦋7.2 自定义日志
    • 🔎8.文件下载
      • 🦋8.1 客户端
      • 🦋8.2 服务端
    • 🔎9.接口声明
    • 🔎10.请求条件性重试
    • 🔎11.异常和异常处理
    • 🔎12.适配畸形接口
      • 🦋12.1 不友好的参数名别名
      • 🦋12.2 Form 的某个字段为 json 文本
      • 🦋12.3 Form 提交嵌套的模型
      • 🦋12.4 响应未指明 ContentType
      • 🦋12.5 类签名参数或 apikey 参数
      • 🦋12.6 表单字段排序
      • 🦋12.7 自定义请求内容与响应内容解析
      • 🦋12.8 HttpMessageHandler 配置
        • ☀️12.8.1 Http 代理配置
        • ☀️12.8.2 客户端证书配置
        • ☀️12.8.3 维持 CookieContainer 不变
        • ☀️12.8.4 Cookie 过期自动刷新
    • 🔎13.OAuths&Token
      • 🦋13.1 对象与概念
      • 🦋13.2 OAuth 的 Client 模式
        • ☀️13.2.1 客户端
        • ☀️13.2.2 服务端
      • 🦋13.3 多接口共享的 TokenProvider
      • 🦋13.4 自定义 TokenProvider
    • 🔎14.NewtonsoftJson 处理 json
    • 🔎15.JsonRpc 调用
    • 🔎16.动态 Host
      • 🦋16.1 直接传入绝对目标的方式
      • 🦋16.2 直接传入绝对目标的方式
      • 🦋16.3 通过 ApiActionAttribute
    • 🔎17.客户端代码生成器
      • 🦋17.1 安装工具
      • 🦋17.2 使用工具
  • 🚀感谢:给读者的一封信


🚀前言

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/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 的基本使用:

  1. 安装 WebApiClientCore 库

通过 NuGet 包管理器或 .NET CLI 安装 WebApiClientCore 库。

  1. 创建接口

创建一个接口,用于定义 HTTP 请求。每个方法对应一种 HTTP 请求方式,并包含请求的地址、参数、请求头等信息。

[HttpHost("https://localhost:7030")]  // 设置主机地址
public interface IUserApi : 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);
}
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}
  1. 创建 WebApiClient 实例

Console控制台项目中使用

using ConsoleTest;
using Microsoft.Extensions.DependencyInjection;
using WebApiClientCore;

//无依赖注入的环境需要自行创建
IServiceCollection services = new ServiceCollection();
services.AddHttpApi<IUserApi>();
var serviceprovider = services.BuildServiceProvider();
var client = serviceprovider.GetService<IUserApi>();

WebApi项目中使用

public void ConfigureServices(IServiceCollection services)
{
  // 注册并配置
  services.AddHttpApi(typeof(IUserApi), o =>
  {
      o.UseLogging = Environment.IsDevelopment();
      o.HttpHost = new Uri("http://localhost:7030/");
  });
  //注册,然后配置
  services.AddHttpApi<IUserApi>().ConfigureHttpApi(o =>
  {
      o.UseLogging = Environment.IsDevelopment();
      o.HttpHost = new Uri("http://localhost:7030/");
  });
  //添加全局配置
  services.AddWebApiClient().ConfigureHttpApi(o =>
  {
      o.UseLogging = Environment.IsDevelopment();
      o.HttpHost = new Uri("http://localhost:7030/");
  });
}

public class MyService
{
    private readonly IUserApi userApi;
    public MyService(IUserApi userApi)
    {
        this.userApi = userApi;
    }

    public async Task GetAsync(){
        //使用接口
        var user=await userApi.GetAsync(100);
    }
}
  1. 发送请求

通过调用接口中的方法,发送 HTTP 请求。

Console.WriteLine(await client.GetUserAsync());
Console.WriteLine(await client.GetUserAsync(1));
Console.WriteLine(await client.AddUserAsync(new User { Id = 2, Name = "愚公搬代码" }));
Console.WriteLine(await client.UpdateUserAsync(1, new User { 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>
public interface IUserApi : IHttpApi
{
    ...
}

🔎2.配置

调用services.AddHttpApi()即可完成接口注册, 每个接口的选项对应为HttpApiOptions,选项名称通过 HttpApi.GetName()方法获取得到。

配置文件的 json

{
  "IUserApi": {
    "HttpHost": "http://www.webappiclient/",
    "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(new JsonDateTimeConverter("yyyy-MM-dd HH:mm:ss"));
    });

🦋2.2 IServiceCollection 配置

services
    .ConfigureHttpApi<IUserApi>(Configuration.GetSection(nameof(IUserApi)))
    .ConfigureHttpApi<IUserApi>(o =>
    {
        // 符合国情的不标准时间格式,有些接口就是这么要求必须不标准
        o.JsonSerializeOptions.Converters.Add(new JsonDateTimeConverter("yyyy-MM-dd HH:mm:ss"));
    });

🔎3.数据验证

🦋3.1 参数值验证

public interface IUserApi : 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}")]
    Task DeleteAsync([Required] string account);
}
public interface IUserApi_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}")]
    Task DeleteAsync([Required] string account);
}

🦋3.2 模型属性验证

/// <summary>
/// 表示用户模型
/// </summary>
public class User
{
    [Required]
    [StringLength(10, MinimumLength = 1)]
    public string Account { get; set; }

    [Required]
    [StringLength(10, MinimumLength = 1)]
    public string Password { get; set; }

    public string NickName { get; set; }

    [JsonDateTime("yyyy年MM月dd日")]
    public DateTime? BirthDay { get; set; }

    public Gender Gender { get; set; }

    [JsonIgnore]
    public string Email { get; set; }
}

/// <summary>
/// 性别
/// </summary>
public enum Gender
{
    Female = 0,
    Male = 1
}

🔎4.内置特性

内置特性是指在一些编程框架或库中,预定义的特性(Attribute),用于标记类、方法、属性等元素,以提供一些额外的功能或行为。这些内置特性可以在编写代码时直接使用,而无需自己编写或引入额外的代码。

在WebApiClientCore中,内置特性是指一些预定义的特性(Attribute),用于对HTTP请求和响应进行描述和控制。这些特性可以方便地设置请求头、请求参数、请求内容、响应内容等。使用这些内置特性,可以让我们更加高效地开发和调试HTTP API客户端,减少重复开发的工作量。

🦋4.1 内置特性位置

[IApiFilterAttribute]/*作用于接口内所有方法的FilterAttribute*/
[IApiReturnAttribute]/*作用于接口内所有方法的ReturnAttribute*/
public interface DemoApiInterface
{

    [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.4 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.5 FilterAttribute

FilterAttribute表示可以在WebApiClientCore的请求管道的不同阶段添加各种过滤器,以实现各种功能。

特性名称功能描述备注
ApiFilterAttributeFilter 特性抽象类
LoggingFilterAttribute请求和响应内容的输出为日志的过滤器

🦋4.6 自解释参数类型

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)]
public ITask<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,指示操作已成功完成。

public interface IUserApi
{
    [HttpPatch("api/users/{id}")]
    Task<UserInfo> PatchAsync(string id, JsonPatchDocument<User> doc);
}

var doc = new JsonPatchDocument<User>();
doc.Replace(item => item.Account, "laojiu");
doc.Replace(item => item.Email, "laojiu@qq");

请求内容

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":"laojiu@qq"}]
☀️5.5.2 PATCH 请求案例

下面是一个使用WebApiClientCore中JsonPatchDocument的完整客户端请求和服务端响应流程案例:

  1. 定义实体类和JsonPatchDocument类

假设我们有一个名为Person的实体类,包含属性Name和Age。我们可以定义一个UpdatePerson类,包含Name和Age属性的部分更新信息。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class UpdatePerson
{
    public string Name { get; set; }
    public int? Age { get; set; }
}
  1. 创建API接口类

创建一个继承自HttpApi的API接口类,并定义一个Patch方法,用于将UpdatePerson类的信息应用到Person实体类中。

public interface IPersonApi : IHttpApi
{
    [Patch("/api/persons/{id}")]
    Task<Person> PatchPersonAsync(int id, [JsonPatch(typeof(UpdatePerson))]JsonPatchDocument<UpdatePerson> patchDoc);
}

public class PersonApi : HttpApi, IPersonApi
{
    public PersonApi(IHttpClientFactory httpClientFactory) : base(httpClientFactory)
    {
    }

    public async Task<Person> PatchPersonAsync(int id, JsonPatchDocument<UpdatePerson> patchDoc)
    {
        return await PatchAsync<Person>($"/api/persons/{id}", patchDoc);
    }
}
  1. 创建API服务

为了测试这个API接口,我们需要创建一个API服务。在这个例子中,我们使用ASP.NET Core创建一个Web API服务,供客户端测试使用。

[Route("api/[controller]")]
[ApiController]
public class PersonsController : ControllerBase
{
    private readonly List<Person> _persons = new List<Person>
    {
        new Person { Name = "Tom", Age = 25 },
        new Person { Name = "Jerry", Age = 30 }
    };
    
    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        var person = _persons.FirstOrDefault(p => p.Id == id);
        if (person == null)
        {
            return NotFound();
        }

        return Ok(person);
    }

    [HttpPatch("{id}")]
    public IActionResult Patch(int id, [FromBody]JsonPatchDocument<UpdatePerson> patchDoc)
    {
        var person = _persons.FirstOrDefault(p => p.Id == id);
        if (person == null)
        {
            return NotFound();
        }

        patchDoc.ApplyTo(person);

        return Ok(person);
    }
}

在上面的代码中,我们创建了一个名为PersonsController的ASP.NET Core Web API控制器。控制器包含一个Get方法用于获取指定ID的Person实体类,以及一个Patch方法用于更新指定ID的Person实体类。注意,在Patch方法中,我们使用JsonPatchDocument.ApplyTo方法将更新文档应用到Person实体类中。

  1. 发送请求并获取响应

现在,我们可以使用PersonApi类中的PatchPersonAsync方法发送请求,更新指定ID的Person实体类,并获取更新后的数据。

using (var client = new HttpClient())
{
    client.BaseAddress = new Uri("http://localhost:5000");

    var api = RestService.For<IPersonApi>(client);

    var patchDoc = new JsonPatchDocument<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]
Task PostAsync([RawStringContent("txt/plain")] string text);
[HttpPost]
Task PostAsync(StringContent text);
☀️5.6.2 原始 json
[HttpPost]
Task PostAsync([RawJsonContent] string json);
☀️5.6.3 原始 xml
[HttpPost]
Task PostAsync([RawXmlContent] string xml);
☀️5.6.4 原始表单内容
[HttpPost]
Task PostAsync([RawFormContent] string form);

🦋5.7 自定义自解释的参数类型

1、服务端要求的 json 模型

{
  "image1": "图片1的base64",
  "image2": "图片2的base64"
}

2、客户端期望的业务模型

class FaceModel
{
    public Bitmap Image1 {get; set;}
    public Bitmap Image2 {get; set;}
}

3、构造转换

class FaceModel : IApiParameter
{
    public Bitmap Image1 { get; set; }

    public Bitmap Image2 { get; set; }


    public Task OnRequestAsync(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 = new JsonContent(model,options);
    }

    private static string GetImageBase64(Bitmap image)
    {
        using var stream = new MemoryStream();
        image.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
        return Convert.ToBase64String(stream.ToArray());
    }
}

4、使用

public interface IFaceApi
{
    [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)]
 public class CacheByAbsolutePathAttribute : ApiCacheAttribute
 {
     public CacheByPathAttribute(double expiration) : base(expiration)
     {
     }

     public override Task<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来实现。下面是一个示例代码:

  1. 创建一个自定义的缓存提供程序实现IResponseCacheProvider接口:
public class MyResponseCacheProvider : IResponseCacheProvider
{
    private readonly Dictionary<string, byte[]> cache = new Dictionary<string, byte[]>();
    
    public Task<byte[]> GetAsync(string key)
    {
        if (cache.ContainsKey(key)) return Task.FromResult(cache[key]);
        return Task.FromResult<byte[]>(null);
    }

    public Task SetAsync(string key, byte[] value, TimeSpan expirationTime)
    {
        cache[key] = value;
        return Task.CompletedTask;
    }
}
  1. 在WebApiClientCore配置中注册自定义的缓存提供程序:
services
    .AddHttpApi<MyHttpApi>()
    .ConfigureHttpApiOptions<MyHttpApiOptions>(options =>
    {
        options.ResponseCacheProvider = new MyResponseCacheProvider();
    });
  1. 在HttpClient请求中添加缓存头信息:
[HttpGet("weatherforecast")]
[Cache(60)] // 设置缓存时间为60秒
Task<string[]> GetWeatherForecastAsync();
🌈6.5.2.2 redis实现实现

WebApiClientCore的缓存功能可以通过自定义IResponseCacheProvider来实现,Redis是一个高性能的缓存数据库,可以作为WebApiClientCore的缓存提供程序。下面是示例代码:

  1. 引入StackExchange.Redis包:
<ItemGroup>
  <PackageReference Include="WebApiClient" Version="4.5.9" />
  <PackageReference Include="StackExchange.Redis" Version="2.2.4" />
</ItemGroup>
  1. 创建一个自定义的缓存提供程序实现IResponseCacheProvider接口,并使用StackExchange.Redis作为缓存后端:
using StackExchange.Redis;

public class RedisResponseCacheProvider : IResponseCacheProvider
{
    private readonly ConnectionMultiplexer redis;
    
    public RedisResponseCacheProvider()
    {
        redis = ConnectionMultiplexer.Connect("localhost");
    }

    public async Task<byte[]> GetAsync(string key)
    {
        var database = redis.GetDatabase();
        return await database.StringGetAsync(key);
    }

    public async Task SetAsync(string key, byte[] value, TimeSpan expirationTime)
    {
        var database = redis.GetDatabase();
        await database.StringSetAsync(key, value, expirationTime);
    }
}
  1. 在WebApiClientCore配置中注册自定义的缓存提供程序:
services
    .AddHttpApi<MyHttpApi>()
    .ConfigureHttpApiOptions<MyHttpApiOptions>(options =>
    {
        options.ResponseCacheProvider = new RedisResponseCacheProvider();
    });
  1. 在HttpClient请求中添加缓存头信息:
[HttpGet("weatherforecast")]
[Cache(60)] // 设置缓存时间为60秒
Task<string[]> GetWeatherForecastAsync();

🔎7.日志

🦋7.1 默认日志

WebApiClientCore还提供了一个带特性的日志过滤器LoggingFilterAttribute,可以通过在接口方法上标记特性来记录请求和响应的日志信息。下面是使用和查看默认日志过滤器特性的示例代码:

  1. 引入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>
  1. 在Program.cs中配置Serilog:
using Serilog;
using Serilog.Events;

public class Program
{
    public static void Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .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();
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            .UseSerilog();
}
  1. 在接口方法上标记特性来记录请求和响应的日志信息:
[LoggingFilter]
[HttpHost("https://api.example")]
public interface IMyHttpApi : IHttpApi
{
    [HttpGet("/example")]
    Task<string> GetExampleAsync();
}
  1. 启动应用程序并执行请求后,在log.txt文件中查看日志信息。

以上就是WebApiClientCore默认日志过滤器特性的使用和查看的示例代码。需要注意的是,LoggingFilterAttribute默认会记录请求和响应的详细信息,如果需要自定义日志格式和布局,可以参考示例代码中自定义日志过滤器的实现方式。

注意:在整个 Interface 或某个 Method 上声明[LoggingFilter],即可把请求和响应的内容输出到 LoggingFactory 中。如果要排除某个 Method 不打印日志,在该 Method 上声明[LoggingFilter(Enable = false)],即可将本 Method 排除。

[LoggingFilter]
public interface IUserApi
{
    [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)]
public class MyLoggingFilterAttribute : LoggingFilterAttribute
{
    private readonly ILogger logger;

    public MyLoggingFilterAttribute()
    {
        this.logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger("WebApiClientCore");
    }

    protected override async Task WriteLogAsync(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 客户端

public interface IUserApi
{
    [HttpGet("/files/{fileName}"]
    Task<HttpResponseMessage> DownloadAsync(string fileName);
}
using System.Net.Http

var response = await userApi.DownloadAsync('123.zip');
using var fileStream = File.OpenWrite("123.zip");
await response.SaveAsAsync(fileStream);

🦋8.2 服务端

[HttpGet("/download")]
public IActionResult GetFile(string fileName)
{
    var filePath = Path.Combine(_configuration["FileFolderPath"], fileName);
    if (System.IO.File.Exists(filePath))
    {
        var fileStream = System.IO.File.OpenRead(filePath);
        return File(fileStream, "application/octet-stream", filename);
    }

    return NotFound();
}

🔎9.接口声明

/// <summary>
/// Everything about your Pets
/// </summary>
[LoggingFilter]
[HttpHost("https://petstore.swagger.io/v2/")]
public interface IPetApi : 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")]
    Task AddPetAsync([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")]
    Task UpdatePetAsync([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}")]
    Task UpdatePetWithFormAsync([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}")]
    Task DeletePetAsync([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 或响应模型符合某种条件。

public interface IUserApi
{
    [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 is ApiInvalidConfigException configException)
{
    // 请求配置异常
}
catch (HttpRequestException ex) when (ex.InnerException is ApiResponseStatusException statusException)
{
    // 响应状态码异常
}
catch (HttpRequestException ex) when (ex.InnerException is ApiException apiException)
{
    // 抽象的api异常
}
catch (HttpRequestException ex) when (ex.InnerException is SocketException socketException)
{
    // socket连接层异常
}
catch (HttpRequestException ex)
{
    // 请求异常
}
catch (Exception ex)
{
    // 异常
}

🔎12.适配畸形接口

🦋12.1 不友好的参数名别名

AliasAs是一个属性特性,它用于给参数或返回值指定一个别名。它的作用是当使用HttpClient的POST或PUT方法发送一个对象时,可以用这个别名来指定对象的属性名称。

public interface IDeformedApi
{
    [HttpGet("api/users")]
    ITask<string> GetAsync([AliasAs("field-Name")] string fieldName);
}

🦋12.2 Form 的某个字段为 json 文本

JsonFormField是WebApiClientCore中的一个类,用于在发送请求时添加一个Json格式的表单字段。

可以使用JsonFormField来将一个Json格式的对象转换为表单字段添加到请求中。

字段
field1someValue
field2{“name”:“sb”,“age”:18}
class Field2
{
    public string Name {get; set;}

    public int Age {get; set;}
}
public interface IDeformedApi
{
    Task PostAsync([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编码方式来避免错误。

字段
filed1someValue
field2.namesb
field2.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)]
public interface IDeformedApi
{
}

🦋12.5 类签名参数或 apikey 参数

WebApiClientCore是一个非常强大的开源.NET库,它可以轻松地生成使用RESTful API的异步.NET客户端代码。

ApiFilterAttribute是WebApiClientCore库中的一个特性,它提供了在请求发出前、后或请求发生异常时执行自定义代码的能力。其中,OnRequestAsync方法签名是ApiFilterAttribute的重要方法之一。

OnRequestAsync方法签名允许开发人员在请求执行前、后或请求发生异常时执行自定义代码。该方法返回值为一个Task对象,可以在该方法中进行异步操作。开发人员可以使用这些方法来实现各种自定义逻辑,例如日志记录、身份验证、缓存控制等等。

在WebApiClientCore中,开发人员可以通过继承ApiFilterAttribute类,并重写OnRequestAsync方法签名实现自定义逻辑。例如:

public class MyApiFilterAttribute : ApiFilterAttribute
{
    public override Task OnRequestAsync(ApiRequestContext context)
    {
        // 在请求执行前的逻辑
        var signService = context.HttpContext.ServiceProvider.GetService<SignService>();
        var sign = signService.SignValue(DateTime.Now);
        context.HttpContext.RequestMessage.AddUrlQuery("sign", sign);
        return Task.CompletedTask;
    }

    public override Task OnResponseAsync(ApiResponseContext context)
    {
        // 在请求执行后的逻辑
        return Task.CompletedTask;
    }

    public override Task OnExceptionAsync(ApiExceptionContext context)
    {
        // 在请求发生异常时的逻辑
        return Task.CompletedTask;
    }
}

可以看到,在MyApiFilterAttribute中,我们重写了OnRequestAsync、OnResponseAsync和OnExceptionAsync方法签名。在这些方法中,我们可以执行各种自定义逻辑,例如记录请求日志、验证请求头、设置缓存等。

开发人员可以在WebApiClientCore的客户端接口中应用这些特性。例如:

[HttpHost("https://api.example")]
[MyApiFilterAttribute]
public interface IMyApiClient : IHttpApiClient
{
    [HttpGet("users/{id}")]
    Task<User> GetUserAsync(int id);
}

在上面的代码中,我们将MyApiFilterAttribute应用到了IMyApiClient接口上,这就意味着在执行IMyApiClient中的任何方法时,都会执行我们定义的自定义逻辑。同时,我们还设置了HttpHost特性,这定义了API的主机地址。通过这种方式,我们可以轻松地为我们的客户端API添加自定义逻辑和主机地址信息。

🦋12.6 表单字段排序

在WebApiClientCore中,我们可以使用FormContentAttribute来指定表单内容。它可以将对象转换为表单数据,以便在HTTP POST请求中使用。

而表单字段排序则是指在发送表单数据时,按照字母顺序对表单字段进行排序。这样做有利于提高表单数据的可读性和可维护性,同时还可以保证数据的顺序一致性,避免出现因字段顺序不同而导致的问题。

WebApiClientCore中的FormContentAttribute已经实现了表单字段排序功能。当使用FormContentAttribute时,它会自动对对象属性按照字母顺序进行排序,然后将其转换为表单数据。

class SortedFormContentAttribute : FormContentAttribute
{
    protected override IEnumerable<KeyValue> SerializeToKeyValues(ApiParameterContext context)
    {
        这里可以排序、加上其它衍生字段等
        return base.SerializeToKeyValues(context).OrderBy(item => item.Key);
    }
}

public interface IDeformedApi
{
    [HttpGet("/path")]
    Task<HttpResponseMessage> PostAsync([SortedFormContent] Model model);
}

🦋12.7 自定义请求内容与响应内容解析

1、自定义请求内容处理特性

public class ProtobufContentAttribute : HttpContentAttribute
{
    public string ContentType { get; set; } = "application/x-protobuf";

    protected override Task SetHttpContentAsync(ApiParameterContext context)
    {
        var stream = new MemoryStream();
        if (context.ParameterValue != null)
        {
            Serializer.NonGeneric.Serialize(stream, context.ParameterValue);
            stream.Position = 0L;
        }

        var content = new StreamContent(stream);
        content.Headers.ContentType = new MediaTypeHeaderValue(this.ContentType);
        context.HttpContext.RequestMessage.Content = content;
        return Task.CompletedTask;
    }
}

2、自定义响应内容解析特性

public class ProtobufReturnAttribute : ApiReturnAttribute
{
    public ProtobufReturnAttribute(string acceptContentType = "application/x-protobuf")
        : base(new MediaTypeWithQualityHeaderValue(acceptContentType))
    {
    }

    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);
    }
}

3、应用相关自定义特性

[ProtobufReturn]
public interface IProtobufApi
{
    [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文件(如果你使用控制台应用程序)中,你可以进行如下配置:

using WebApiClientCore;
using WebApiClientCore.Extensions.HttpClientFactory;
using System.Net.Http;

// ...

// 配置WebApiClientCore
services.AddHttpApi<IUserProfileApi>(c =>
{
    c.HttpHost = new Uri("https://your-api-url");
    
    // 配置主要的HttpMessageHandler
    c.ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.UseProxy = true,
        // 在这里进行自定义配置,例如设置代理
        handler.Proxy = new WebProxy("http://your-proxy-url");
        // 配置凭据(用户名和密码)
        handler.Credentials = new NetworkCredential("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中配置客户端证书,你可以使用HttpClientHandlerClientCertificates属性。以下是一个示例,展示了如何配置客户端证书:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using WebApiClientCore;
using WebApiClientCore.Attributes;
using WebApiClientCore.Extensions.HttpClientFactory;
using WebApiClientCore.HttpContents;

class Program
{
    static async Task Main()
    {
        var services = new ServiceCollection();
        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}");
        }
    }

    static void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpApi<IUserProfileApi>(c =>
        {
            c.HttpHost = new Uri("https://your-api-url");

            // 配置主要的HttpMessageHandler,设置客户端证书
            c.ConfigurePrimaryHttpMessageHandler(() =>
            {
                var handler = new HttpClientHandler();

                // 配置客户端证书
                var certificate = new X509Certificate2("path-to-client-certificate.pfx", "certificate-password");
                handler.ClientCertificates.Add(certificate);

                return handler;
            });
        });
    }
}

public class UserProfile
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

[HttpHost("https://your-api-url")]
public interface IUserProfileApi : IHttpApi
{
    [HttpGet("/api/userprofile")]
    ITask<HttpResponseMessage> GetUserInfoAsync();
}

在上述示例中,我们首先创建了一个HttpClientHandler实例并配置了客户端证书。配置客户端证书时,我们使用X509Certificate2类加载证书文件(.pfx 格式)并提供证书密码。然后,我们将证书添加到ClientCertificates属性中。

☀️12.8.3 维持 CookieContainer 不变

要在WebApiClientCore的ConfigurePrimaryHttpMessageHandler中配置CookieContainer,你可以使用HttpClientHandlerCookieContainer属性。以下是一个示例,展示了如何配置CookieContainer以处理和管理HTTP请求中的Cookie:

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using WebApiClientCore;
using WebApiClientCore.Attributes;
using WebApiClientCore.Extensions.HttpClientFactory;
using WebApiClientCore.HttpContents;

class Program
{
    static async Task Main()
    {
        var services = new ServiceCollection();
        ConfigureServices(services);

        var serviceProvider = services.BuildServiceProvider();
        var api = serviceProvider.GetRequiredService<IUserProfileApi>();

        try
        {
            // 第一次发送请求,将在响应中接收和处理Cookie
            var 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}");
            }

            // 第二次发送请求,将包含之前响应中接收到的Cookie
            var 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}");
        }
    }

    static void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpApi<IUserProfileApi>(c =>
        {
            c.HttpHost = new Uri("https://your-api-url");

            // 配置主要的HttpMessageHandler,设置CookieContainer
            c.ConfigurePrimaryHttpMessageHandler(() =>
            {
                var handler = new HttpClientHandler();

                // 创建一个新的CookieContainer
                handler.CookieContainer = new CookieContainer();

                return handler;
            });
        });
    }
}

public class UserProfile
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

[HttpHost("https://your-api-url")]
public interface IUserProfileApi : 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/")]
public interface IUserLoginApi
{
    [HttpPost("/users")]
    Task<HttpResponseMessage> LoginAsync([JsonContent] Account account);
}

然后实现自动登录的 CookieAuthorizationHandler

public class AutoRefreshCookieHandler : CookieAuthorizationHandler
{
    private readonly IUserLoginApi api;

    public AutoRefreshCookieHandler(IUserLoginApi api)
    {
        this.api = api;
    }

    /// <summary>
    /// 登录并刷新Cookie
    /// </summary>
    /// <returns>返回登录响应消息</returns>
    protected override Task<HttpResponseMessage> RefreshCookieAsync()
    {
        return this.api.LoginAsync(new Account
        {
            account = "admin",
            password = "123456"
        });
    }
}

最后,注册 IUserApi、IUserLoginApi,并为 IUserApi 配置 AutoRefreshCookieHandler

services
    .AddHttpApi<IUserLoginApi>();

services
    .AddHttpApi<IUserApi>()
    .AddHttpMessageHandler(s => new AutoRefreshCookieHandler(s.GetService<IUserLoginApi>()));

现在,调用 IUserApi 的任意接口,只要响应的状态码为 401,就触发 IUserLoginApi 登录,然后将登录得到的 cookie 来重试请求接口,最终响应为正确的结果。你也可以重写 CookieAuthorizationHandler 的 IsUnauthorizedAsync(HttpResponseMessage)方法来指示响应是未授权状态。

🔎13.OAuths&Token

使用 WebApiClientCore.Extensions.OAuths 扩展,轻松支持 token 的获取、刷新与应用。

🦋13.1 对象与概念

对象用途
ITokenProviderFactorytokenProvider 的创建工厂,提供通过 HttpApi 接口类型获取或创建 tokenProvider
ITokenProvidertoken 提供者,用于获取 token,在 token 的过期后的头一次请求里触发重新请求或刷新 token
OAuthTokenAttributetoken 的应用特性,使用 ITokenProviderFactory 创建 ITokenProvider,然后使用 ITokenProvider
OAuthTokenHandler属于 http 消息处理器,功能与 OAuthTokenAttribute 一样,除此之外,如果因为意外的原因导致服务器仍然返回未授权(401 状态码),其还会丢弃旧 token,申请新 token 来重试一次请求。

🦋13.2 OAuth 的 Client 模式

☀️13.2.1 客户端

1、注册接口

// 为接口注册与配置Client模式的tokenProvider
services.AddClientCredentialsTokenProvider<IUserApi>(o =>
{
    o.Endpoint = new Uri("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]
public interface IUserApi
{
    ...
}

自定义

class UriQueryTokenAttribute : OAuthTokenAttribute
{
    protected override void UseTokenResult(ApiRequestContext context, TokenResult tokenResult)
    {
        context.HttpContext.RequestMessage.AddUrlQuery("mytoken", tokenResult.Access_token);
    }
}

[UriQueryToken]
public interface IUserApi
{
    ...
}

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();

自定义

class UriQueryOAuthTokenHandler : OAuthTokenHandler
{
    /// <summary>
    /// token应用的http消息处理程序
    /// </summary>
    /// <param name="tokenProvider">token提供者</param>
    public UriQueryOAuthTokenHandler(ITokenProvider tokenProvider)
        : base(tokenProvider)
    {
    }

    /// <summary>
    /// 应用token
    /// </summary>
    /// <param name="request"></param>
    /// <param name="tokenResult"></param>
    protected override void UseTokenResult(HttpRequestMessage request, TokenResult tokenResult)
    {
        // var builder = new UriBuilder(request.RequestUri);
        // builder.Query += "mytoken=" + Uri.EscapeDataString(tokenResult.Access_token);
        // request.RequestUri = builder.Uri;

        var uriValue = new UriValue(request.RequestUri).AddQuery("myToken", tokenResult.Access_token);
        request.RequestUri = uriValue.ToUri();
    }
}


// 注册接口时添加UriQueryOAuthTokenHandler
services
    .AddHttpApi<IUserApi>()
    .AddOAuthTokenHandler((s, tp) => new UriQueryOAuthTokenHandler(tp));
☀️13.2.2 服务端

1、token获取

[ApiController]
[Route("api/[controller]")]
public class TokensController : ControllerBase
{
    [HttpPost]
    public TokenResult CreateToken([FromForm] ClientCredentials credentials)
    {
        return new TokenResult
        {
            Access_token = $"Access_token_{credentials.Client_id}_{credentials.Client_secret}",
            Expires_in = 60 * 60,
            Id_token = "id",
            Token_type = "Bearer"
        };
    }
}

2、接口认证

public class TokenFilterAttribute : Attribute, IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (context.HttpContext.Request.Headers.ContainsKey("Authorization"))
        {
            await next();
        }
        else
        {
            context.Result = new UnauthorizedResult();
        }
    }
}
[ApiController]
[Route("api/[controller]")]
[TokenFilter]
public class UsersController : ControllerBase
{
    [HttpGet("{account}")]
    public User Get(string account)
    {  
        return new User { Account = account, Password = "password" };
    }
}

🦋13.3 多接口共享的 TokenProvider

[OAuthToken]
public interface IBaidu
{
}

public interface IBaidu_XXX_Api : IBaidu
{
    [HttpGet]
    Task xxxAsync();
}

public interface IBaidu_YYY_Api : IBaidu
{
    [HttpGet]
    Task yyyAsync();
}

// 注册与配置password模式的token提者选项
services.AddPasswordCredentialsTokenProvider<IBaidu>(o =>
{
    o.Endpoint = new Uri("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接口

public interface ITokenApi
{
    [HttpPost("http://xxx/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>();

class CustomTokenProvider : TokenProvider
{
    public CustomTokenProvider(IServiceProvider serviceProvider)
        : base(serviceProvider)
    {
    }

    protected override Task<TokenResult> RequestTokenAsync(IServiceProvider serviceProvider)
    {
        return serviceProvider.GetService<ITokenApi>().RequestTokenAsync("id", "secret");
    }

    protected override Task<TokenResult> RefreshTokenAsync(IServiceProvider serviceProvider, string refresh_token)
    {
        return this.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")]
public interface IUserApi
{
    [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]
public interface IDynamicHostDemo
{
    [HttpGet]
    ITask<HttpResponseMessage> ByUrlString([Uri] string urlString);
}

🦋16.2 直接传入绝对目标的方式

[LoggingFilter]
[UriFilter]//可以放在interface级别
public interface IDynamicHostDemo
{
    [HttpGet]
    [UriFilter]//也可以放在Method(Action)级别
    ITask<HttpResponseMessage> ByFilter();

    //也可以选择在配置接口时通过GlobalFilter添加
}
/// <summary>
///用来处理动态Uri的拦截器
/// </summary>
public class UriFilterAttribute : ApiFilterAttribute
{
    public override Task OnRequestAsync(ApiRequestContext context)
    {
        var options = context.HttpContext.HttpApiOptions;
        //获取注册时为服务配置的服务名
        options.Properties.TryGetValue("serviceName", out object serviceNameObj);
        string serviceName = serviceNameObj as string;
        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(new Uri(host));
        return Task.CompletedTask;
    }

    public override Task OnResponseAsync(ApiResponseContext context)
    {
        //不处理响应的信息
        return Task.CompletedTask;
    }
}
public class HostProvider
{
    public string ResolveService(string name)
    {
        string servicehost=string.Empty;
        //TODO get service host
        return servicehost;
    }
}

🦋16.3 通过 ApiActionAttribute

[LoggingFilter]
[ServiceName("baiduService")]//可以放在interface级别
public interface IDynamicHostDemo
{
    [HttpGet]
    [ServiceName("baiduService")]//也可以放在Method(Action)级别
    ITask<HttpResponseMessage> ByAttribute();
}
/// <summary>
/// 表示对应的服务名
/// </summary>
public class ServiceNameAttribute : ApiActionAttribute
{
    public ServiceNameAttribute(string name)
    {
        Name = name;
        OrderIndex = int.MinValue;
    }

    public string Name { get; set; }

    public override async Task OnRequestAsync(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(new Uri(host));
    }
}

public class HostProvider
{
    public string ResolveService(string name)
    {
        string servicehost=string.Empty;
        //TODO get service host
        return 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 

🚀感谢:给读者的一封信

亲爱的读者,

我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。

如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。

我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。

如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。

再次感谢您的阅读和支持!

最诚挚的问候, “愚公搬代码”

本文标签: 愚公 求之 案例 工具 系列