高性能で拡張性の高い宣言型HTTPクライアントライブラリ-WebApiClientCore

高性能で拡張性の高い宣言型HTTPクライアントライブラリ-WebApiClientCore

WebApiClient.JIT/AOTのnetcoreバージョンは、高性能でスケーラブルな宣言型HTTPクライアントライブラリであり、特にマイクロサービスからのrestfulリソース要求や様々な変形HTTPインターフェイス要求に適しています。

最后更新 2023/09/06 12:26
老九
预计阅读 21 分钟
分类
.NET
标签
.NET C# AOT 建築設計の構造 Web API

WebApiClientCore

WebApiClient.JIT/AOT的.NET Core 版本,集高性能高可扩展性于一体的声明式 http 客户端库,特别适用于微服务的 restful 资源请求,也适用于各种畸形 http 接口请求。

NuGet

パッケージ名: 説明する。 NuGet
WebApiClientCore ベースパック NuGet
WebApiClientCore.Extensions.OAuths OAuth拡張パック NuGet
WebApiClientCore.Extensions.NewtonsoftJson Json. Net拡张パック NuGet
WebApiClientCore.Extensions.JsonRpc JsonRpc拡張パッケージの呼び出し{{JsonRpcかくちょうぱっけーじ}} NuGet
WebApiClientCore.OpenApi.SourceGenerator ローカルまたはリモートのOpenApiドキュメントを解析し、WebApiClientCoreインターフェイスコードを生成するdotnetツールを解析する NuGet

使い方はこちら

[HttpHost("http://localhost:5000/")]
public interface IUserApi
{
    [HttpGet("api/users/{id}")]
    Task<User> GetAsync(string id);

    [HttpPost("api/users")]
    Task<User> PostAsync([JsonContent] User user);
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpApi<IUserApi>();
}

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

QQグループのサポート

825135345

グループに入る際に WebApiClient を明記してください。質問をする前に、以下の残りのドキュメントをよくお読みください。著者の不必要な繰り返し回答時間を消費しないようにしてください。

コンパイル時の構文解析

WebApiClientCore.Analyzersはコーディング時の構文解析とプロンプトを提供し、宣言されたインターフェイスは空のメソッドのIHttpApiインターフェイスを継承し、構文解析が有効になり、開発者はこの機能をオンにすることを推奨します。

例えば、[Header]プロパティはInterface、Method、Parameterの3つの場所で宣言できますが、正しいコンストラクタを使用しないと実行時に例外がスローされます。構文解析機能により、インターフェイス宣言時に不適切な構文が使用されません。

/// <summary>
/// 记得要实现IHttpApi
/// </summary>
public interface IUserApi : IHttpApi
{
    ...
}

インターフェイスの構成とオプション

每个接口的选项对应为HttpApiOptions,选项名称为接口的完整名称,也可以通过 HttpApi.GetName()方法获取得到。

IHttpClientBuilderの設定

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

JSONの設定ファイル

{
  "IUserApi": {
    "HttpHost": "http://www.webappiclient.com/",
    "UseParameterPropertyValidate": false,
    "UseReturnValuePropertyValidate": false,
    "JsonSerializeOptions": {
      "IgnoreNullValues": true,
      "WriteIndented": false
    }
  }
}

IServiceCollectionでの設定

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

データ検证

パラメータ値の検証

パラメータ値では、値を検証するValidationAttribute属性修飾をサポートします。

public interface IUserApi
{
    [HttpGet("api/users/{email}")]
    Task<User> GetAsync([EmailAddress, Required] string email);
}

引数または戻りモデル属性検证

public interface IUserApi
{
    [HttpPost("api/users")]
    Task<User> PostAsync([Required][XmlContent] User user);
}

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

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

一般的な組み込み機能

組み込み機能は、フレームワーク内で提供される機能の一部であり、一般的な状況下でのさまざまなアプリケーションにすぐに使用できます。もちろん、開発者は実際のアプリケーションで特定のシナリオのニーズを満たす機能を記述し、カスタム機能をインターフェイス、メソッド、パラメータに変更することもできます。

ReturnプロパティReturnぷろぱてぃ

プロパティの名前 機能の記述 備考:コメント
RawReturnAttribute 元の型の戻り値の処理 デフォルトも有効です。
JsonReturnAttribute JSONモデルの戻り値の処理 デフォルトも有効です。
XmlReturnAttribute Xmlモデルの戻り値の処理 デフォルトも有効です。

一般的なAction 特性

プロパティの名前 機能の記述 備考:コメント
HttpHostAttribute HTTP絶対完全ホストドメイン名 Options構成より優先度が低い
HttpGetAttribute Getリクエストメソッドとパスの宣言 null、绝対、相対パスのサポート
HttpPostAttribute Postリクエストメソッドとパスの定義 null、绝対、相対パスのサポート
HttpPutAttribute Putリクエストメソッドとパスの宣言 null、绝対、相対パスのサポート
HttpDeleteAttribute Deleteリクエストメソッドとパスの宣言 null、绝対、相対パスのサポート
HeaderAttribute リクエストヘッダーの宣言 定数値の値
TimeoutAttribute タイムアウト時間の宣言 定数値の値
FormFieldAttribute Formフォームのフィールドと値の宣言 定数キーと値
FormDataTextAttribute FormDataフォームのフィールドと値の宣言 定数キーと値

一般的なParameterプロパティ

プロパティの名前 機能の記述 備考:コメント
PathQueryAttribute URLパスパラメータまたはqueryパラメータとしてのパラメータ値のキー値ペアの特性 デフォルトプロパティのパラメータは、デフォルトでこのプロパティになります
FormContentAttribute x-www-form-urlencodedフォームとしてのパラメータ値のキーと値のペア
FormDataContentAttribute multipart/form-dataフォームとしてのパラメータ値のキーと値のペア
JsonContentAttribute 要求されたJSONコンテンツへのパラメータ値のシリアル化
XmlContentAttribute パラメータ値は要求されたxmlコンテンツにシリアル化されます
UriAttribute 要求URIとしてのパラメータ値 修飾できるのは最初のパラメータのみ
ParameterAttribute 集約的な要求パラメータ宣言 微粒子構成はサポートされていません
HeaderAttribute 要求ヘッダーとしてのパラメータ値
TimeoutAttribute タイムアウト時間としてのパラメータ値 HttpClientのTimeoutプロパティより大きい値は指定できません。
FormFieldAttribute フォームフォームのフィールドと値としてのパラメータ値 単纯型パラメータのみサポート
FormDataTextAttribute FormDataフォームのフィールドと値としてのパラメータ値 単纯型パラメータのみサポート

Filterプロパティ

プロパティの名前 機能の記述 備考:コメント
ApiFilterAttribute Filterプロパティ抽象クラス{{Filterぷろぱてぃ抽象クラス}}
LoggingFilterAttribute リクエストとレスポンスのコンテンツの出力をログのフィルタとして

自己解釈パラメータのタイプ

タイプ名 機能の記述 備考:コメント
FormDataFile form-dataのファイル項目 プロパティの修飾は不要で、FileInfo型と同等です。
JsonPatchDocument JsonPatchがドキュメントを要求する場合 特性変更不要です

ウリ·ステッチ·ルール

すべてのUriスプライシングは、Uri Uri baseUri Uri relative Uriというコンストラクタによって生成される。

/结尾的 baseUri

  • http://a.com/ + b/c/d = http://a.com/b/c/d
  • http://a.com/path1/ + b/c/d = http://a.com/path1/b/c/d
  • http://a.com/path1/path2/ + b/c/d = http://a.com/path1/path2/b/c/d

不带/结尾的 baseUri

  • http://a.com + b/c/d = http://a.com/b/c/d
  • http://a.com/path1 + b/c/d = http://a.com/b/c/d
  • http://a.com/path1/path2 + b/c/d = http://a.com/path1/b/c/d

事实上http://a.comhttp://a.com/是完全一样的,他们的 path 都是/,所以才会表现一样。为了避免低级错误的出现,请使用的标准 baseUri 书写方式,即使用/作为 baseUri 的结尾的第一种方式。

フォームコレクションの処理

OpenApiによると、コレクションはUriのクエリまたはフォームで5つの表現をサポートしています。

  • Csv //コンマ区切り
  • Ssv //スペース区切り
  • Tsv //バックスラッシュ区切り
  • Pipes //縦棒で区切る
  • Multi //同じ名前の複数のキーと値のペア。

id = new string []{"001""002"}のような値の場合、PathQueryAttributeとFormContentAttributeの処理後は次のようになります。

CollectionFormat Data
[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

ellation

各インターフェイスは、リクエストのキャンセル操作をサポートする1つ以上のCancellationToken型のパラメータの宣言をサポートします。CancellationToken.Noneは決してキャンセルしないことを意味し、CancellationTokenSourceを作成してCancellationTokenを提供します。

[HttpGet("api/users/{id}")]
ITask<User> GetAsync([Required]string id, CancellationToken token = default);

ContentType CharSet

非フォームボディコンテンツの場合、デフォルトまたはデフォルトのcharset値はUTF 8エンコーディングに対応し、サーバーの要件に応じてエンコーディングを調整できます。

Attribute ContentType
[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

Accpet ContentType

これは、JSONやXMLなど、クライアントがサーバーに返すことを期待するコンテンツ形式を制御します。

デフォルトの構成値

デフォルトの構成は[JsonReturn 0.01 XmlReturn 0.01]で、対応する要求accept値は Accept: application/json; q=0.01, application/xml; q=0.01

ジェイソン·ファースト

在 Interface 或 Method 上显式地声明[JsonReturn],请求 accept 变为Accept: application/json, application/xml; q=0.01

JSONの禁止

在 Interface 或 Method 上声明[JsonReturn(Enable = false)],请求变为Accept: application/xml; q=0.01

リクエストとレスポンスのログ

在整个 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);
}

ログ出力先のカスタマイズ

class MyLoggingAttribute : LoggingFilterAttribute
{
    protected override Task WriteLogAsync(ApiResponseContext context, LogMessage logMessage)
    {
        xxlogger.Log(logMessage.ToIndentedString(spaceCount: 4));
        return Task.CompletedTask;
    }
}

[MyLogging]
public interface IUserApi
{
}

元の型戻り値

インターフェイスの戻り値が次の型で宣言された場合、プリミティブ型と呼ばれ、RawReturnAttributeによって処理されます。

戻り値の型 説明書の作成
Task 応答メッセージに関心がない
Task<HttpResponseMessage> 元のレスポンス·メッセージ·タイプ
Task<Stream> オリジナルレスポンスフロー
Task<byte[]> raw responseバイナリ·データ
Task<string> 元のレスポンス·メッセージ·テキスト

インターフェイス宣言の例

Petstoreインターフェイス

这个 OpenApi 文档在petstore.swagger.io,代码为使用 WebApiClientCore.OpenApi.SourceGenerator 工具将其 OpenApi 文档反向生成得到

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

IOAuthClientインターフェイス

このインターフェイスはWebApiClientCore. Extensions.OAuths.IOAuthClient.csコードで宣言されています。

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

条件付き再試行を要求する

ITask<>非同期宣言を使用すると、Retryの拡張があり、Retryの条件は特定の例外をキャッチしたり、レスポンスモデルに適合したりすることができる。

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

例外処理と例外処理

インターフェイスをリクエストすると、どんな例外が発生しても、最終的にHttpRequestExceptionがスローされます。HttpRequestExceptionの内部例外は実際の特定の例外であり、内部例外として設計されている理由は、内部例外のスタック情報を完全に保存するためです。

WebApiClient 内部の多くの例外はApiException抽象例外に基づいています。つまり、多くの場合、投げられる例外はApiExceptionに固有のHttpRequestExceptionです。

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)
{
    // 异常
}

PATCH要求

JSONパッチは、クライアントがサーバに存在するリソースを部分的に更新できるように設計された標準的な相互作用であり、RFC6902で詳細に説明されているJSONパッチは、一般的には以下のポイントがあります。

  1. HTTP PATCHリクエストメソッドの使用;
  2. リクエストbodyは複数の操作を記述するJSONの内容です。
  3. リクエストのContent-Typeはapplication/json-patch+jsonです。

Patchメソッドの宣言

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

JsonPatchDocumentのインスタンス化

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

要求内容の内容

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.com"}]

レスポンス·コンテンツのキャッシュ

CacheAttribute機能を構成するメソッドは、このレスポンス内容をキャッシュし、次回、期待される条件を満たせば、リモートサーバに要求するのではなく、IResponseCacheProviderからキャッシュ内容を取得し、開発者はResponseCacheProviderを実装できます。

キャッシュ·プロパティの宣言

public interface IUserApi
{
    // 缓存一分钟
    [Cache(60 * 1000)]
    [HttpGet("api/users/{account}")]
    ITask<HttpResponseMessage> GetAsync([Required]string account);
}

默认缓存条件:URL(如http://abc.com/a)和指定的请求 Header 一致。 如果需要类似[CacheByPath]这样的功能,可直接继承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)
    {
        return Task.FromResult(context.HttpContext.RequestMessage.RequestUri.AbsolutePath);
    }
}

カスタム·キャッシュ·プロバイダ

デフォルトのキャッシュプロバイダはインメモリキャッシュです。キャッシュを別のストレージ場所に保存したい場合は、カスタムキャッシュプロバイダと、デフォルトのキャッシュプロバイダを置き換えるために登録する必要があります。

public class RedisResponseCacheProvider : IResponseCacheProvider
{
    public string Name => nameof(RedisResponseCacheProvider);

    public Task<ResponseCacheResult> GetAsync(string key)
    {
        throw new NotImplementedException();
    }

    public Task SetAsync(string key, ResponseCacheEntry entry, TimeSpan expiration)
    {
        throw new NotImplementedException();
    }
}

// 注册RedisResponseCacheProvider
var services = new ServiceCollection();
services.AddSingleton<IResponseCacheProvider, RedisResponseCacheProvider>();

モデル以外の要求

生のformテキストコンテンツ、生のjsonテキストコンテンツ、さらにはSystem. Net. Http.HttpContentオブジェクトがあり、これらの生の内部リクエストをリモートリモートに送信するだけで済みます。

オリジナルのテキスト

[HttpPost]
Task PostAsync([RawStringContent("txt/plain")] string text);

[HttpPost]
Task PostAsync(StringContent text);

オリジナルのJSON

[HttpPost]
Task PostAsync([RawJsonContent] string json);

オリジナルXML

[HttpPost]
Task PostAsync([RawXmlContent] string xml);

オリジナルフォームコンテンツ

[HttpPost]
Task PostAsync([RawFormContent] string form);

カスタム解釈されるパラメータ·タイプのカスタマイズ

顔比較インタフェースのような特定の制限条件下では、入力モデルと伝送モデルは必ずしも等価ではありません。

** サーバー側で必要なJSONモデル **

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

** クライアントが期待するビジネスモデル **

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

モデルインスタンスを構築するときにBitmapオブジェクトを渡し、転送するときにBitmapのbase 64値になりたいので、IApiParameterインターフェイスを実装するようにFaceModelを変更します。

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 jsonContent = new JsonContent();
        context.HttpContext.RequestMessage.Content = jsonContent;

        var options = context.HttpContext.HttpApiOptions.JsonSerializeOptions;
        var serializer = context.HttpContext.ServiceProvider.GetJsonSerializer();
        serializer.Serialize(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());
    }
}

最後に、改善されたFaceModelを使用してリクエストを行います。

public interface IFaceApi
{
    [HttpPost("/somePath")]
    Task<HttpResponseMessage> PostAsync(FaceModel faces);
}

リクエストコンテンツとレスポンスコンテンツのカスタマイズ

強型付き結果モデルにデシリアライズされる一般的なXMLまたはJSONレスポンスに加えて、GoogleのProtoBufバイナリなどの他のバイナリプロトコルレスポンスに遭遇する可能性があります。

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;
    }
}
カスタム応答コンテンツ解決機能
public class ProtobufReturnAttribute : ApiReturnAttribute
{
    public ProtobufReturnAttribute(string acceptContentType = "application/x-protobuf")
        : base(new MediaTypeWithQualityHeaderValue(acceptContentType))
    {
    }

    public override async Task SetResultAsync(ApiResponseContext context)
    {
        if (context.ApiAction.Return.DataType.IsRawType == false)
        {
            var stream = await context.HttpContext.ResponseMessage.Content.ReadAsStreamAsync();
            context.Result = Serializer.NonGeneric.Deserialize(context.ApiAction.Return.DataType.Type, stream);
        }
    }
}

2関連するカスタム特性の適用

[ProtobufReturn]
public interface IProtobufApi
{
    [HttpPut("/users/{id}")]
    Task<User> UpdateAsync([Required, PathQuery] string id, [ProtobufContent] User user);
}

適応変形インターフェース

実際のアプリケーションシナリオでは、多くの場合、標準ではない設計の変形インタフェース、主に初期のインターフェイスのrestful 概念がない、我々はこれらのインターフェイスを区別して分析し、フレンドリーなクライアント呼び出しインターフェイスとしてパッケージする必要があります。

非友好的なパラメータ名のエイリアス

例如服务器要求一个 Query 参数的名字为field-Name,这个是 c#关键字或变量命名不允许的,我们可以使用[AliasAsAttribute]来达到这个要求:

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

然后最终请求 uri 变为 api/users/?field-name=fileNameValue

フォームのフィールドはJSONテキストです。

フィールドフィールド 価値観は
field1 someValue
field2

強い型モデルは

class Field2
{
    public string Name {get; set;}

    public int Age {get; set;}
}

通常、フィールド2のインスタンスjsonをシリアライズしてjsonテキストを取得し、フィールド2のstringプロパティに代入します。[JsonFormField]機能を使用すると、フィールド2のjsonシリアライズを自動化し、結果の文字列をフォームのフィールドとして取得するのに役立ちます。

public interface IDeformedApi
{
    Task PostAsync([FormField] string field1, [JsonFormField] Field2 field2)
}

Formネストされたモデルのコミット{{Formねすとされたもでるのをコミット}}

フィールドフィールド 価値観は
filed1 someValue
field2.name sb
field2.age 18

対応するJSONフォーマットは、

{
  "field1": "someValue",
  "filed2": {
    "name": "sb",
    "age": 18
  }
}

合理的には、複雑な入れ子構造を持つデータモデルではapplicaiton/jsonを使用する必要がありますが、インターフェイス要件はFormコミットを使用する必要があり、KeyValue Serialize Optionsを設定してこのフォーマット要件を満たすことができます。

services.AddHttpApi <
  IDeformedApi >
  ((o) => {
    o.KeyValueSerializeOptions.KeyNamingStyle = KeyNamingStyle.FullName;
  });

応答はContentTypeを示していません。

レスポンスの内容は肉眼ではjsonの内容に見えますが、サービスレスポンスヘッダーにはコンテンツがjsonであることをクライアントに伝えるContentTypeはありません。これは、クライアントがフォームまたはjsonを使用してコミットするときに、リクエストヘッダーでコンテンツ形式が何であるかをサーバーに伝えず、サーバーに同じ理由を推測させるようなものです。

解决办法是在 Interface 或 Method 声明[JsonReturn]特性,并设置其 EnsureMatchAcceptContentType 属性为 false,表示 ContentType 不是期望值匹配也要处理。

[JsonReturn(EnsureMatchAcceptContentType = false)]
public interface IDeformedApi
{
}

クラスシグネチャまたはapikeyパラメータ

例えば、各リクエストのURLには、リクエストのパラメータ値に関連付けられたsignと呼ばれるパラメータが動的に追加され、毎回計算する必要があります。

ApiFilterAttributeをカスタム化して独自のシグネチャを実装し、カスタムFilterをインターフェイスまたはメソッドに宣言することができます。

class SignFilterAttribute : 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;
    }
}

[SignFilter]
public interface IDeformedApi
{
    ...
}

HttpMessageHandlerの設定

Httpプロキシの設定

services
    .AddHttpApi<IUserApi>(o =>
    {
        o.HttpHost = new Uri("http://localhost:6000/");
    })
    .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
    {
        UseProxy = true,
        Proxy = new WebProxy
        {
            Address = new Uri("http://proxy.com"),
            Credentials = new NetworkCredential
            {
                UserName = "useranme",
                Password = "pasword"
            }
        }
    });

クライアント书

一部のサーバは、クライアントの接続を制限するためにHTTPS双方向認証を有効にし、発行した証明書を持つクライアントのみが接続できるようにします。

services
    .AddHttpApi<IUserApi>(o =>
    {
        o.HttpHost = new Uri("http://localhost:6000/");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(yourCert);
        return handler;
    });

CookieContainerの変更なし

リクエストされたインターフェイスがCookieを使用している場合は、Cookie ContainerインスタンスをHttpMessageHandlerのライフサイクルに従わないようにすることを検討してください。デフォルトのHttpMessageHandlerのライフサイクルは最短2分です。

var cookieContainer = new CookieContainer();
services
    .AddHttpApi<IUserApi>(o =>
    {
        o.HttpHost = new Uri("http://localhost:6000/");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.CookieContainer = cookieContainer;
        return handler;
    });

OAuths&Token

WebApiClientCore. Extensions.OAuths拡張機能を使用して、トークンの取得、更新、適用を簡単にサポートします。

オブジェクトと概念

オブジェクトオブジェクト 目的は
ITokenProviderFactory HttpApiインターフェイス型を介してtokenProviderを作成するtokenProviderの作成ファクトリー
ITokenProvider トークンを取得し、トークンの有効期限後の最初のリクエストで再リクエストまたはリフレッシュをトリガーするトークンプロバイダ。
OAuthTokenAttribute ITokenProviderFactoryを使用してITokenProviderFactoryを作成し、ITokenProviderを使用してトークンを取得し、最後にリクエストメッセージにトークンを適用するトークンアプリケーション機能。
OAuthTokenHandler HTTPメッセージ·ハンドラに属し、OAuthTokenAttributeと同じ機能を持ちますが、予期せぬ理由でサーバーがまだ不正な状態401ステータスコードを返した場合、古いトークンを破棄し、新しいトークンを要求してリクエストを再試行します。

OAuthのクライアントモデル

1インターフェイスのtokenProviderの登録
// 为接口注册与配置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トークンの適用
2.1 OAuthToken機能の使用

OAuthTokenAttributeはWebApiClientCoreフレームワーク層に属しており、既存のリクエストフォームにトークンをフォームフィールドとして追加したり、対応するビジネスモデルをデシリアライズした後にレスポンスメッセージを読み取ったりするなど、リクエスト内容とレスポンスモデルを簡単に操作できますが、リクエスト内でリクエストを再試行する効果を実現することはできません。サーバーがトークンを発行した後、サーバーのトークンが失われた場合、OAuthTokenAttributeを使用すると、回避できないリクエストが失敗しました。

/// <summary>
/// 用户操作接口
/// </summary>
[OAuthToken]
public interface IUserApi
{
    ...
}

OAuthTokenAttributeはデフォルトでトークンをAuthorizationリクエストヘッダーに配置します。インターフェイスがURIのクエリなど他の場所にトークンを配置する必要がある場合は、OAuthTokenAttributeをオーバーライドする必要があります。

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

[UriQueryToken]
public interface IUserApi
{
    ...
}
2.1 OAuthTokenHandlerの使用

OAuthTokenHandlerの強みは、リクエスト内で複数の試行をサポートすることです。サーバーがトークンを発行した後、サーバーのトークンが失われた場合、OAuthTokenHandlerは401ステータスコードを受信した後、このリクエスト内でトークンを破棄して再要求し、新しいトークンでリクエストを再試行し、通常のリクエストとして振る舞います。しかし,OAuthTokenHandlerはWebApiClientCoreフレームワーク層のオブジェクトに属しておらず,そこではオリジナルのHttpRequestMessageとHttpResponseMessageにしかアクセスできず,tokenをHttpRequestMessageのContentに追加する必要がある場合は非常に困難であり,同様に,httpステータスコード(401など)によるtoken無効の根拠ではなく,HttpResponseMessageのContent対応する業務モデルのあるタグフィールドを利用するのも大変な作業である.

// 注册接口时添加OAuthTokenHandler
services
    .AddHttpApi<IUserApi>()
    .AddOAuthTokenHandler();

OAuthTokenHandlerのデフォルト実装では、トークンをAuthorizationリクエストヘッダーに配置します。インターフェイスがURIのクエリなど他の場所にトークンを配置する必要がある場合は、OAuthTokenHandlerをオーバーライドする必要があります。

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


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

マルチインターフェイス共有TokenProvider

httpインターフェイスにベースインターフェイスを設定し、ベースインターフェイスのTokenProviderを設定することができます。例えば、以下のxxxとyyyインターフェイスはIBaiduに属します。

public interface IBaidu
{
}

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

[OAuthToken]
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";
});

TokenProviderの定義

拡張パッケージにはOAuthクライアントモードとパスワードモードの2つの標準トークンリクエストが組み込まれていますが、実装にはまだ多くのインターフェイスプロバイダがまだあり、TokenProviderをカスタマイズする必要があります。

public interface ITokenApi
{
    [HttpPost("http://xxx.com/token")]
    Task<TokenResult> RequestTokenAsync([Parameter(Kind.Form)] string clientId, [Parameter(Kind.Form)] string clientSecret);
}
TokenProviderによる委任

委任TokenProviderは最も簡単な実装の1つで、カスタムTokenProviderの実装ロジックとしてトークンを要求する委任を行います。

// 为接口注册自定义tokenProvider
services.AddTokeProvider<IUserApi>(s =>
{
    return s.GetService<ITokenApi>().RequestTokenAsync("id", "secret");
});
TokenProviderの実装
// 为接口注册CustomTokenProvider
services.AddTokeProvider<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);
    }
}
TokenProviderのオプションをカスタマイズ

各TokenProviderにはNameプロパティがあり、service.AddTokeProviderによって返されるITokenProviderBuilderのNameと同じ値です。Optionsの値はTokenProviderのGetOptionsValueメソッドを使用して読み取ることができ、Optionsの構成はITokenProviderBuilderのNameを使用して行います。

NewtonsoftJSON JSONを処理する

System.Text.Jsonは性能の利点からますます広く使われるようになることは否定できませんが、NewtonsoftJsonはそれでステージから離れることはありません。

System. Text. Jsonはデフォルトで厳密であり、呼び出し元に代わって推測や解釈を避け、決定論的な振る舞いを強調している。ライブラリはパフォーマンスとセキュリティのために特別に設計されている。Newtonsoft. Jsonはデフォルトでは柔軟で、デフォルト設定ではデシリアライズの問題はほとんどありませんが、これらの問題の多くは厳密でないjson構造や型宣言によって引き起こされます。

Expansion Pack

デフォルトのベースパッケージにはNewtonsoftJson機能は含まれておらず、WebApiClientCore.Extensions.NewtonsoftJson拡張パッケージへの追加参照が必要です。

配置[可选]#

// ConfigureNewtonsoftJson
services.AddHttpApi<IUserApi>().ConfigureNewtonsoftJson(o =>
{
    o.JsonSerializeOptions.NullValueHandling = NullValueHandling.Ignore;
});

特性の宣言

組み込みの[JsonReturn]を[JsonNetReturn]に置き換え、組み込みの[JsonContent]を[JsonNetContent]に置き換える

/// <summary>
/// 用户操作接口
/// </summary>
[JsonNetReturn]
public interface IUserApi
{
    [HttpPost("/users")]
    Task PostAsync([JsonNetContent] User user);
}

JsonRPC呼び出し

まれに、開発者がJsonRpc呼び出しのインターフェイスに遭遇する可能性がありますが、プロトコルはあまり普及していないため、WebApiClientCoreはWebApiClientCore.Extension.JsonRpc拡張パッケージとしてこの機能をサポートします。Rpcメソッドを[JsonRpcMethod]で修飾し、Rpcパラメータを[JsonRpcParam]で修飾する OKです。

JsonRPCの声明

[HttpHost("http://localhost:5000/jsonrpc")]
public interface IUserApi
{
    [JsonRpcMethod("add")]
    ITask<JsonRpcResult<User>> AddAsync([JsonRpcParam] string name, [JsonRpcParam] int age, CancellationToken token = default);
}

JsonRpcパケット

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}
Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 2026/02/07

AOTの使用経験

プロジェクトの最初から、新しい機能が追加されたり、新しい構文が使用されたりするたびに、AOTリリーステストを行うという良い習慣を身につける必要があります。

继续阅读
同分类 / 同标签 2025/02/25

NET 10プレビュー 1リリース

今日. NET 10 Preview 1がリリースされ、私は初めてダウンロードし、Avalonia UIプロジェクトとブログサイトをアップグレードし、前者の機能テストとAOTリリースは正常で、後者のデバッグは正常ですが、Dockerは一時的に失敗しました。

继续阅读