zoukankan      html  css  js  c++  java
  • .net的retrofit--WebApiClient底层篇

    前言

    本篇文章的内容是WebApiClient底层说明,也是WebApiClient系列接近尾声的一篇文章,如果你没有阅读过之前的的相关文章,可能会觉得本文章的内容断层,WebApiClient系列文章索引:

    库简介

    WebApiClient是开源在github上的一个httpClient客户端库,内部基于HttpClient开发,是一个只需要定义c#接口(interface),并打上相关特性,即可异步调用http-api的框架 ,支持.net framework4.5+、netcoreapp2.0和netstandard2.0。
    WebApiClient是我2017年看到java的retrofit库之后,决心给.net打造的一款面向切面的httpclient客户端库,在开发过程中陆续发现.net下也有一些HttpClient包装库,WebApiClient库虽然是后起,却有其它库所没有很多优秀特征:

    • 原生的支持面向切面编程;
    • 内置丰富的特性,支持自定义特性;
    • 灵活和Filter、GlobalFilter和IParameterable;
    • 功能强大的序列化工具;
    • 与外部HttpClientHandler无缝衔接;
    • 独一无二的请求异常条件重试功能和异常处理链式语法功能

    1. HttpRequestMessage简说

    System.Net.Http.HttpRequestMessage表示一个请求消息,一般而言它包含一个完整的请求数据,主要由请求头和请求体组成,对于Post、Delete、Put等请求,其Content属性包含提交的数据体内容。

    WebApiClient的ApiActionContex对象有个RequestMessage对象,是派生于HttpRequestMessage,同时实现了多个Addxxx方法用于给其属性Content对象添加数据内容。

    2. HttpClientHandler简说

    System.Net.Http.HttpClientHandler是一个与tcp层相关的对象,负责与远程服务器进行tcp连接,将HttpRequestMessage转换为http请求包发送给服务端,并等待服务端的响应。(以上这段话是我瞎说,仅供讨论)一般的网络相关配置的证书、代理和认证等在都在这里可以配置。

    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的一个衔接:

    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和设置代理等。

    5.3 更换WebApiClient.Defaults.HttpClient关联的HttpClientHandler

    一般而言,HttpClient没有多少扩展的价值,但HttpClientHandler就有很多扩展空间,其中System.Net.Http.WebRequestHandler也派生于HttpClientHandler,多了很一些配置的属性,很多时候,需要替换WebApiClient.Defaults.HttpClient的HttpClientHandler就可以,而不用从头实现IHttpClient接口,以下方式可以替换HttpClientHandler:

    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默认支持序列化以下类型:

    • 1、常用简单类型及其空类型(byte、int、short、long、doublue、flout、string、decimal、DateTime、Guid、enum、Version和Uri)
    • 2、支持IEnumerable递归拆解,默认最多16层
    • 3、KeyValuePair<,>的任意泛型
    • 4、多属性模型的第一层属性拆解

    如果你需要支持更多的类型,需要派生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();
            }
        }
    }
    
  • 相关阅读:
    2020学习 04 python 爬取某政府网站信件
    2020学习03
    2020学习02
    阅读笔记--《一线架构师实践指南》--Pre-architecture阶段
    重构“信息领域热词分析”,实现六种质量属性战术
    pycharm错误:Error updating package list: connect timed out解决
    python连接mysql数据库——编码问题
    阅读笔记--《大型网站技术架构》—02架构
    python查询MySQL数据库的表以及所有字段
    python连接mysql数据库——版本问题
  • 原文地址:https://www.cnblogs.com/kewei/p/8320247.html
Copyright © 2011-2022 走看看