我是WebApiClient庫的做者,目前在開發其.netcore
版本,在整理其readme後,想一想一來這部份內容可能對你們有用,二來興許能給WebApiClient帶人更多人氣,因此將readme做爲博客在此發表。git
WebApiClient.JIT的.netcore版本,基於HttpClient的高性能與高可擴展性於一體的聲明式Http客戶端庫,特別適用於微服務的restful資源請求,也適用於各類非標準的http接口請求。github
<PackageReference Include="WebApiClientCore" Version="1.0.0-beta1" />
System.Text.Json
替換Json.net
,提高序列化性能Microsoft.Extensions.Http的HttpClientFactory
WebApiClientCore、WebApiClient.JIT與原生HttpClient的性能比較,相比原生的HttpClient,WebApiClientCore幾乎沒有性能損耗。web
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.778 (1903/May2019Update/19H1)
Intel Core i3-4150 CPU 3.50GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.202
[Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
DefaultJob : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJITjson
Method | Mean | Error | StdDev |
---|---|---|---|
WebApiClient_GetAsync | 279.479 us | 22.5466 us | 64.3268 us |
WebApiClientCore_GetAsync | 25.298 us | 0.4953 us | 0.7999 us |
HttpClient_GetAsync | 2.849 us | 0.0568 us | 0.1393 us |
WebApiClient_PostAsync | 25.942 us | 0.3817 us | 0.3188 us |
WebApiClientCore_PostAsync | 13.462 us | 0.2551 us | 0.6258 us |
HttpClient_PostAsync | 4.515 us | 0.0866 us | 0.0926 us |
如下聲明的代碼爲使用
WebApiClientCore.Extensions.OpenApi
工具將openApi文檔反向生成獲得api
namespace Petstore { /// <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> [HttpPost("pet")] Task AddPetAsync([Required] [JsonContent] Pet body, CancellationToken token = default); /// <summary> /// Update an existing pet /// </summary> /// <param name="body">Pet object that needs to be added to the store</param> [HttpPut("pet")] Task<HttpResponseMessage> UpdatePetAsync([Required] [JsonContent] Pet body, CancellationToken token = default); /// <summary> /// Finds Pets by status /// </summary> /// <param name="status">Status values that need to be considered for filter</param> /// <returns>successful operation</returns> [HttpGet("pet/findByStatus")] ITask<List<Pet>> FindPetsByStatusAsync([Required] IEnumerable<Anonymous> status); /// <summary> /// Finds Pets by tags /// </summary> /// <param name="tags">Tags to filter by</param> /// <returns>successful operation</returns> [Obsolete] [HttpGet("pet/findByTags")] ITask<List<Pet>> FindPetsByTagsAsync([Required] [PathQuery] IEnumerable<string> tags); /// <summary> /// Find pet by ID /// </summary> /// <param name="petId">ID of pet to return</param> /// <returns>successful operation</returns> [HttpGet("pet/{petId}")] ITask<Pet> GetPetByIdAsync([Required] long petId); /// <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> [HttpPost("pet/{petId}")] Task UpdatePetWithFormAsync([Required] long petId, [FormContent] string name, [FormContent] string status); /// <summary> /// Deletes a pet /// </summary> /// <param name="api_key"></param> /// <param name="petId">Pet id to delete</param> [HttpDelete("pet/{petId}")] Task DeletePetAsync([Header("api_key")] string api_key, [Required] long petId); /// <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> /// <returns>successful operation</returns> [LoggingFilter(Enable = false)] [HttpPost("pet/{petId}/uploadImage")] ITask<ApiResponse> UploadFileAsync([Required] long petId, [FormDataContent] string additionalMetadata, FormDataFile file); } }
另外一個例子是
WebApiClientCore.Extensions.OAuths.IOAuthClient
接口聲明服務器
using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using WebApiClientCore.Attributes; namespace WebApiClientCore.Extensions.OAuths { /// <summary> /// 定義Token客戶端的接口 /// </summary> [LoggingFilter] [XmlReturn(Enable = false)] [JsonReturn(EnsureMatchAcceptContentType = false, EnsureSuccessStatusCode = false)] public interface IOAuthClient { /// <summary> /// 以client_credentials受權方式獲取token /// </summary> /// <param name="endpoint">token請求地址</param> /// <param name="credentials">身份信息</param> /// <returns></returns> [HttpPost] [FormField("grant_type", "client_credentials")] Task<TokenResult> RequestTokenAsync([Required, Uri] Uri endpoint, [Required, FormContent] ClientCredentials credentials); /// <summary> /// 以password受權方式獲取token /// </summary> /// <param name="endpoint">token請求地址</param> /// <param name="credentials">身份信息</param> /// <returns></returns> [HttpPost] [FormField("grant_type", "password")] Task<TokenResult> RequestTokenAsync([Required, Uri] Uri endpoint, [Required, FormContent] PasswordCredentials credentials); /// <summary> /// 刷新token /// </summary> /// <param name="endpoint">token請求地址</param> /// <param name="credentials">身份信息</param> /// <returns></returns> [HttpPost] [FormField("grant_type", "refresh_token")] Task<TokenResult> RefreshTokenAsync([Required, Uri] Uri endpoint, [Required, FormContent] RefreshTokenCredentials credentials); } }
服務註冊restful
var services = new ServiceCollection(); services.AddHttpApi<IPetApi>(o => { o.UseParameterPropertyValidate = true; o.UseReturnValuePropertyValidate = false; o.KeyValueSerializeOptions.IgnoreNullValues = true; o.HttpHost = new Uri("http://localhost:6000/"); });
服務獲取app
public class MyService { private readonly IpetApi petApi; // 構造器注入IpetApi public MyService(IpetApi petApi) { tihs.petApi = petApi; } }
在整個Interface或某個Method上聲明[LoggingFilter],便可把請求和響應的內容輸出到LoggingFactory中。框架
若是要排除某個Method不打印日誌(好比大流量傳輸接口),在方法上聲明[LoggingFilter(Enable = false)],便可將本Method排除。異步
這個用於控制客戶端但願服務器返回什麼樣的內容格式,好比json或xml,默認的配置值是Accept: application/json; q=0.01, application/xml; q=0.01
若是想json優先,能夠在Interface或Method上聲明[JsonReturn],請求變爲Accept: application/json, application/xml; q=0.01
若是想禁用其中一種,好比禁用xml,能夠在Interface或Method上聲明[XmlReturn(Enable = false)],請求變爲Accept: application/json; q=0.01
使用ITask<>異步聲明,就有Retry的擴展,Retry的條件能夠爲捕獲到某種Exception或響應模型符合某種條件。
var result = await youApi.GetModelAsync(id: "id001") .Retry(maxCount: 3) .WhenCatch<Exception>() .WhenResult(r => r.ErrorCode > 0);
使用WebApiClientCore.Extensions.OAuths
擴展,輕鬆支持token的獲取、刷新與應用
// 爲接口註冊與配置token提者選項 services.AddClientCredentialsTokenProvider<IpetApi>(o => { o.Endpoint = new Uri("http://localhost:6000/api/tokens"); o.Credentials.Client_id = "clientId"; o.Credentials.Client_secret = "xxyyzz"; });
/// <summary> /// 用戶操做接口 /// </summary> [ClientCredentialsToken] public interface IpetApi { ... }
清空Token,未過時的token也強制刷新
var providers = serviceProvider.GetServices<ITokenProvider>(); foreach(var item in providers) { // 強制清除token以支持下次獲取到新的token item.ClearToken(); }
自定義Token應用,獲得token值,怎麼用本身說了算
class MyTokenAttribute : ClientCredentialsTokenAttribute { protected override void UseTokenResult(ApiRequestContext context, TokenResult tokenResult) { context.HttpContext.RequestMessage.Headers.TryAddWithoutValidation("xxx-header", tokenResult.Access_token); } } /// <summary> /// 用戶操做接口 /// </summary> [MyToken] public interface IpetApi { ... }