當前位置:
首頁 > 最新 > net的retrofit-WebApiClient庫深入篇

net的retrofit-WebApiClient庫深入篇

前言

本篇文章的內容是對上一篇.net的retrofit--WebApiClient庫的深層次補充,你可能需要先閱讀上一篇才能理解此篇文章。本文將詳細地講解WebApiClient的原理,結合實際項目中可能遇到的問題進行使用說明。


WebApiClient是開源在github上的一個httpClient客戶端庫,內部基於HttpClient開發,是一個只需要定義c#介面(interface),並打上相關特性,即可非同步調用http-api的框架 ,支持.net framework4.5+、netcoreapp2.0和netstandard2.0。


當序列化一個多屬性的模型時,FormatOptions可以約束DateTime類型的屬性值轉換為字元串的格式,也可以指定屬性名是為CamelCase。


首次獲取HttpClient實例時,HttpClient的實例將被創建,HttpClient屬性是一個IHttpClient介面,是對HttpClient對象的包裝,它的Handler暴露出與HttpClient關聯的HttpClientHandler對象。


GlobalFilters用於添加全局過濾器,這些過濾器不需要使用硬編碼修飾於介面,而是通過配置傳輸給介面的實例,適用於介面定義的項目和介面調用的項目分離開的項目結構。

在實例化HttpApiConfig之後,當不再使用時,應該顯性地調用Dispose釋放資源;

對於1.1的例子,如果myWebApi實現了IDisposable介面,調用myWebApi.Dispose()也會將HttpApiConfig的HttpClient屬性也釋放;

對於var myWebApi = HttpApiClient.Create


1 創建介面實現類

當調用WebApiClient.Create時,內部使用Emit創建介面的實現類,該實現類為介面的每個方法實現為:獲取方法信息和調用參數值傳給攔截器(IApiInterceptor)處理;

2 攔截器創建ITask任務

IApiInterceptor收到方法的調用時,根據方法信息和參數值創建Api描述對象ApiActionDescriptor,然後將和HttpApiConfig實例和ApiActionDescriptor包裝成ITask任務對象並返回;

3 等待調用者執行請求

當調用者await ITask 或 await ITask.InvokeAsync()時,創建ApiActionContext並按照順序執行ApiActionContext里描述的各種Attribute,這些Attribue影響著ApiActionContext的HttpRequestMessage等屬性對象,然後使用HttpClient發送這個HttpRequestMessage對象,得到HttpResponseMessage,最後將HttpResponseMessage的Content轉換為介面的返回值;

///

/// 非同步執行api

///

///

上下文

///

public async Task ExecuteAsync(ApiActionContext context)

{

var apiAction = context.ApiActionDescriptor;

var globalFilters = context.HttpApiConfig.GlobalFilters;

foreach (var actionAttribute in apiAction.Attributes)

{

await actionAttribute.BeforeRequestAsync(context);

}

foreach (var parameter in apiAction.Parameters)

{

foreach (var parameterAttribute in parameter.Attributes)

{

await parameterAttribute.BeforeRequestAsync(context, parameter);

}

}

foreach (var filter in globalFilters)

{

await filter.OnBeginRequestAsync(context);

}

foreach (var filter in apiAction.Filters)

{

await filter.OnBeginRequestAsync(context);

}

await this.SendAsync(context);

foreach (var filter in globalFilters)

{

await filter.OnEndRequestAsync(context);

}

foreach (var filter in apiAction.Filters)

{

await filter.OnEndRequestAsync(context);

}

return await apiAction.Return.Attribute.GetTaskResult(context);

}


WebApiClient內置很多特性,包含介面級、方法級、參數級的,他們分別是實現了IApiActionAttribute介面、IApiActionFilterAttribute介面、IApiParameterAttribute介面、IApiParameterable介面和IApiReturnAttribute介面的一個或多個介面。一般情況下內置的特性就足以夠用,但實際項目中,你可能會遇到個別特殊的場景,需要自己實現一些特性或過濾器,主要用來操控請求上下文的RequestMessage對象,影響請求對象。

舉個例子:比如,服務端要求使用x-www-form-urlencoded提交,由於介面設計不合理,目前要求是提交:fieldX= 的json文本&fieldY=的json文本 這裡和都是一個多欄位的Model,我們對應的介面是這樣設計的:

顯然,我們介面參數為string類型的範圍太廣,沒有約束性,我們希望是這樣子:

現在我們為這種特殊場景實現一個[FormFieldJson]的參數級特性,給每個參數修飾這個[FormFieldJson]後,參數就解釋為其序列化為Json的文本,做為表單的一個欄位內容:

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]

class FormFieldJson: Attribute, IApiParameterAttribute

{

public async Task BeforeRequestAsync(ApiActionContext context, ApiParameterDescriptor parameter)

{

var options = context.HttpApiConfig.FormatOptions;

var json = context.HttpApiConfig.JsonFormatter.Serialize(parameter.Value, options);

var fieldName = parameter.Name;

await context.RequestMessage.AddFormFieldAsync(fieldName, json);

}

}


舉個例子:我們需要為每個請求的url額外的動態添加一個叫sign的參數,這個sign可能和配置文件等有關係,而且每次都需要計算:

class SignFilter : ApiActionFilterAttribute

{

public override Task OnBeginRequestAsync(ApiActionContext context)

{

var sign = DateTime.Now.Ticks.ToString();

context.RequestMessage.AddUrlQuery("sign", sign);

return base.OnBeginRequestAsync(context);

}

}

[SignFilter]

public interface MyApi : IDisposable

{

...

}


class GlobalFilter : IApiActionFilter

{

public Task OnBeginRequestAsync(ApiActionContext context)

{

if (context.ApiActionDescriptor.Member.IsDefined(typeof(MyCustomAttribute), true))

{

// do something

}

return Task.CompletedTask;

}

public Task OnEndRequestAsync(ApiActionContext context)

{

return Task.CompletedTask;

}

}

// 通過配置項將全局過濾器傳給MyWebApi實例

var config = new HttpApiConfig();

config.GlobalFilters.Add(new GlobalFilter());

var myWebApi = HttpApiClient.Create(config);


在一些場景中,你的模型與服務需要的數據模塊可能不是全部吻合,DataAnnotations的功能可以非常方便實現兩者的對接,目前DataAnnotations只支持Json序列化和KeyValue序列化,xml序列化不受任何變化。

public class UserInfo

{

public string Account { get; set; }

// 別名

[AliasAs("a_password")]

public string Password { get; set; }

// 時間格式,優先順序最高

[DateTimeFormat("yyyy-MM-dd")]

public DateTime? BirthDay { get; set; }

// 忽略序列化

[IgnoreSerialized]

public string Email { get; set; }

// 時間格式

[DateTimeFormat("yyyy-MM-dd HH:mm:ss")]

public DateTime CreateTime { get; set; }

}


await ITask,實際是調用了ITask.GetAwaiter()方法,等於同於ITask.InvokeAsync().GetAwaiter()方法。所以await ITask等同於await ITask.InvokeAsync()


InvokeAsync()返回Task對象,實際是http請求的任務對象。一個ITask實例,可以多次調用InvokeAsync()方法,完成多次一模一樣的請求。ITask的很多擴展,是對InvokeAsync方法調用的包裝而得到。


Retry本質上是對ITask的InvokeAsync的包裝,實際思想是當符合某種條件時,就多調用一次InvokeAsync方法,達到重試提交請求的目的。

Handle也是對ITask的InvokeAsync的包裝,使用try catch對InvokeAsync方法封裝為新的委託,當捕獲到符合條件的異常類型時,就返回某種結果。

以上可以解讀為,當遇到異常時,再重試請求,累計重試3次還是異常的話,處理為返回null值,期間總共最多請求了4次。


HttpClient目前沒提供任何的同步請求方法,所以WebApiClient的請求也是一樣,如果遇到必須使用同步的場景,可以暫時使用 ITask.GetAwaiter().GetResult()方法等待結果。

原文地址:https://www.cnblogs.com/kewei/p/8302382.html


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 dotNET跨平台 的精彩文章:

NET Core單文件發布靜態編譯AOT CoreRT

TAG:dotNET跨平台 |