WebApiClientCore使用說明

前言

我是WebApiClient庫的做者,目前在開發其.netcore版本,在整理其readme後,想一想一來這部份內容可能對你們有用,二來興許能給WebApiClient帶人更多人氣,因此將readme做爲博客在此發表。git

WebApiClientCore

WebApiClient.JIT的.netcore版本,基於HttpClient的高性能與高可擴展性於一體的聲明式Http客戶端庫,特別適用於微服務的restful資源請求,也適用於各類非標準的http接口請求。github

PackageReference

<PackageReference Include="WebApiClientCore" Version="1.0.0-beta1" />

項目緣由

  1. WebApiClient很優秀,它將不一樣框架不一樣平臺都實現了統一的api
  2. WebApiClient不夠優秀,它在.netcore下徹底能夠更好,但它不得不兼容.net45開始全部框架而有所犧牲

相對變化

  • 使用System.Text.Json替換Json.net,提高序列化性能
  • 移除HttpApiFactory和HttApiConfig功能,使用Microsoft.Extensions.Http的HttpClientFactory
  • 移除AOT功能,僅保留依賴於Emit的運行時代理
  • 高效的ActionInvoker,對返回Task<>和ITask<>做不一樣處理
  • 全部特性都都變成中間件,基於管道編排各個特性並生成Action執行委託
  • 良好設計的HttpContext、ApiRequestContext、ApiParameterContext和ApiResponseContext

Benchmark

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

聲明式接口定義

  • 支持Task、Task<>和ITask<>三種異步返回
  • 支持模型自動轉換爲Xml、Json、Form、和FormData共4種請求格式的內容
  • 支持HttpResponseMessage、byte[]、string和Stream原生類型返回內容
  • 支持原生HttpContent(好比StringContent)類型直接作爲請求參數
  • 內置豐富的能知足各類環境的經常使用特性(ActionAttribute和ParameterAttribute)
  • 內置經常使用的FormDataFile等參數類型,同時支持自定義IApiParameter參數類型做爲參數值
  • 支持用戶自定義IApiActionAttribute、IApiParameterAttribue、IApiReturnAttribute和IApiFilterAttribute

如下聲明的代碼爲使用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排除。異步

Accpet ContentType

這個用於控制客戶端但願服務器返回什麼樣的內容格式,好比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);

OAuths&Token

使用WebApiClientCore.Extensions.OAuths擴展,輕鬆支持token的獲取、刷新與應用

1 註冊相應類型的TokenProvider

// 爲接口註冊與配置token提者選項
services.AddClientCredentialsTokenProvider<IpetApi>(o =>
{
    o.Endpoint = new Uri("http://localhost:6000/api/tokens");
    o.Credentials.Client_id = "clientId";
    o.Credentials.Client_secret = "xxyyzz";
});

2 聲明對應的Token特性

/// <summary>
/// 用戶操做接口
/// </summary>
[ClientCredentialsToken]
public interface IpetApi
{
    ...
}

3 其它操做

清空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
{
    ...
}
相關文章
相關標籤/搜索