.net的retrofit--WebApiClient底層篇

前言

本篇文章的內容是WebApiClient底層說明,也是WebApiClient系列接近尾聲的一篇文章,若是你沒有閱讀過以前的的相關文章,可能會以爲本文章的內容斷層,WebApiClient系列文章索引:html

庫簡介

WebApiClient是開源在github上的一個httpClient客戶端庫,內部基於HttpClient開發,是一個只須要定義c#接口(interface),並打上相關特性,便可異步調用http-api的框架 ,支持.net framework4.5+、netcoreapp2.0和netstandard2.0。
WebApiClient是我2017年看到java的retrofit庫以後,決心給.net打造的一款面向切面的httpclient客戶端庫,在開發過程當中陸續發現.net下也有一些HttpClient包裝庫,WebApiClient庫雖然是後起,卻有其它庫所沒有不少優秀特徵:java

  • 原生的支持面向切面編程;
  • 內置豐富的特性,支持自定義特性;
  • 靈活和Filter、GlobalFilter和IParameterable;
  • 功能強大的序列化工具;
  • 與外部HttpClientHandler無縫銜接;
  • 獨一無二的請求異常條件重試功能和異常處理鏈式語法功能

1. HttpRequestMessage簡說

System.Net.Http.HttpRequestMessage表示一個請求消息,通常而言它包含一個完整的請求數據,主要由請求頭和請求體組成,對於Post、Delete、Put等請求,其Content屬性包含提交的數據體內容。git

WebApiClient的ApiActionContex對象有個RequestMessage對象,是派生於HttpRequestMessage,同時實現了多個Addxxx方法用於給其屬性Content對象添加數據內容。github

2. HttpClientHandler簡說

System.Net.Http.HttpClientHandler是一個與tcp層相關的對象,負責與遠程服務器進行tcp鏈接,將HttpRequestMessage轉換爲http請求包發送給服務端,並等待服務端的響應。(以上這段話是我瞎說,僅供討論)通常的網絡相關配置的證書、代理和認證等在都在這裏能夠配置。web

3. HttpClient簡說

System.Net.Http.HttpClient必須與HttpClientHandler關聯才能使用,一個HttpRequestMessage通過HttpClient以後,HttpClient的一些默認配置會影響到它,好比默認請求頭等。HttpClient是使用關聯的HttpClientHandler將HttpRequestMessage發送出去,也就是說,徹底能夠跳過HttpClient而使用HttpClientHandler來發送請求,方法是寫一個類,繼承於HttpClientHandler並公開一個方法,調用基類的SendAsync方法就能夠發送請求。編程

4. WebApiClient庫的HttpClient配置

WebApiClient庫是對HttpClient的封裝,全部的配置項在HttpApiConfig對象, 實例化HttpApiConfig對象時有個構造器能夠傳入IHttpClient的實例,而IHttpClient是對System.Net.Http.HttpClient的一個包裝接口定義,WebApiClient.Defaults.HttpClient是對IHttpClient接口的一個實現,才下代碼是WebApiCient與System.Net.Http.HttpClient的一個銜接:json

IHttpClient client = new WebApiClient.Defaults.HttpClient();
var config = new WebApiClient.HttpApiConfig(client);

5. IHttpClient接口

5.1 IHttpClient的接口定義

/// <summary>
/// 定義HttpClient的接口
/// </summary>
public interface IHttpClient : IDisposable
{
    /// <summary>
    /// 獲取關聯的Http處理對象
    /// </summary>
    HttpClientHandler Handler { get; }

    /// <summary>
    /// 獲取默認的請求頭管理對象
    /// </summary>
    HttpRequestHeaders DefaultRequestHeaders { get; }

    /// <summary>
    /// 異步發送請求
    /// </summary>
    /// <param name="request">請求消息</param>
    /// <returns></returns>
    Task<HttpResponseMessage> SendAsync(HttpApiRequestMessage request);
    
    ...
}

5.2 IHttpClient的接口意圖

IHttpClient接口意圖將System.Net.Http.HttpClient實例和System.Net.Http.HttpClientHandler實例進行組合封裝,隱藏底層的一些細節,同時描述了HttpClient和HttpClientHandler不可分割的關係,其默認實現對象WebApiClient.Defaults.HttpClient將System.Net.Http.HttpClient難用的幾個功能也封裝了一次:好比設置Cookie和設置代理等。c#

5.3 更換WebApiClient.Defaults.HttpClient關聯的HttpClientHandler

通常而言,HttpClient沒有多少擴展的價值,但HttpClientHandler就有不少擴展空間,其中System.Net.Http.WebRequestHandler也派生於HttpClientHandler,多了很一些配置的屬性,不少時候,須要替換WebApiClient.Defaults.HttpClient的HttpClientHandler就能夠,而不用從頭實現IHttpClient接口,如下方式能夠替換HttpClientHandler:api

class MyHttpClient : WebApiClient.Defaults.HttpClient
{
    protected override HttpClientHandler CreateHttpClientHandler()
    {
        // or return your handler
        return new WebRequestHandler();
    }
} 

var config = new HttpApiConfig(new MyHttpClient());
var myWebApi = HttpApiClient.Create(config);

若是是外部的HttpClientHandler實例,可使用以下方式關聯:服務器

var client = new WebApiClient.Defaults.HttpClient(handler);
var config = new HttpApiConfig(client);
var myWebApi = HttpApiClient.Create(config);

6. 擴展JsonFormatter

WebApiClient.Defaults.JsonFormatter使用了json.net,每次序列化或反序列化時都會建立JsonSerializerSettings,能夠派生WebApiClient.Defaults.JsonFormatter返回自定義的JsonSerializerSettings:

class MyJsonFormatter : WebApiClient.Defaults.JsonFormatter
{
    protected override JsonSerializerSettings CreateSerializerSettings()
    {
        return new JsonSerializerSettings
        {
            // your setting
        };
    }
}


var config = new HttpApiConfig
{
    JsonFormatter = new MyJsonFormatter()
};
var myWebApi = HttpApiClient.Create(config);

7. 擴展WebApiClient.Defaults.KeyValueFormatter

KeyValueFormatter基於Middleware思想,內部由多個轉換器相連組成,隨着轉換器的增長,支持的類型也更多,KeyValueFormatter默認支持序列化如下類型:

  • 一、經常使用簡單類型及其空類型(byte、int、short、long、doublue、flout、string、decimal、DateTime、Guid、enum、Version和Uri)
  • 二、支持IEnumerable遞歸拆解,默認最多16層
  • 三、KeyValuePair<,>的任意泛型
  • 四、多屬性模型的第一層屬性拆解

若是你須要支持更多的類型,須要派生KeyValueFormatter增長功能:

class MyKeValueFormatter : WebApiClient.Defaults.KeyValueFormatter
{
    protected override IEnumerable<ConverterBase> GetConverters()
    {
        // 在原有轉換器以前插入DynamicObjectConverter
        var addin = new[] { new DynamicObjectConverter() };
        return addin.Concat(base.GetConverters());
    }
}

/// <summary>
/// 表示動態類型轉換器
/// </summary>
class DynamicObjectConverter : ConverterBase
{
    /// <summary>
    /// 執行轉換
    /// </summary>
    /// <param name="context">轉換上下文</param>
    /// <returns></returns>
    public override IEnumerable<KeyValuePair<string, string>> Invoke(ConvertContext context)
    {
        var dynamicObject = context.Data as DynamicObject;
        if (dynamicObject != null)
        {
            return from name in dynamicObject.GetDynamicMemberNames()
                   let value = this.GetValue(dynamicObject, name)
                   let ctx = new ConvertContext(name, value, context.Depths, context.Options)
                   select ctx.ToKeyValuePair();
        }

        return this.Next.Invoke(context);
    }

    /// <summary>
    /// 獲取動態類型的值
    /// </summary>
    /// <param name="dynamicObject">實例</param>
    /// <param name="name">名稱</param>
    /// <returns></returns>
    private object GetValue(DynamicObject dynamicObject, string name)
    {
        object value;
        var binder = new MemberBinder(name);
        dynamicObject.TryGetMember(binder, out value);
        return value;
    }

    /// <summary>
    /// 表示成員值的獲取綁定
    /// </summary>
    private class MemberBinder : GetMemberBinder
    {
        /// <summary>
        /// 鍵的信息獲取綁定
        /// </summary>
        /// <param name="key">鍵名</param>
        public MemberBinder(string key)
            : base(key, false)
        {
        }

        /// <summary>
        /// 在派生類中重寫時,若是沒法綁定目標動態對象,則執行動態獲取成員操做的綁定
        /// </summary>
        /// <param name="target"></param>
        /// <param name="errorSuggestion"></param>
        /// <returns></returns>
        public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
        {
            throw new NotImplementedException();
        }
    }
}
相關文章
相關標籤/搜索