前言

本篇文章的内容是对上一篇.net的retrofit--WebApiClient库的深层次补充,你可能需要先阅读上一篇才能理解此篇文章。本文将详细地讲解WebApiClient的原理,结合实际项目中可能遇到的问题进行使用说明。

库简介

WebApiClient是开源在github上的一个httpClient客户端库,内部基于HttpClient开发,是一个只需要定义c#接口(interface),并打上相关特性,即可异步调用http-api的框架 ,支持.net framework4.5+、netcoreapp2.0和netstandard2.0。

1. HttpApiConfig的使用

1.1 创建HttpApiConfig

var config = new HttpApiConfig
{
// 请求的域名,会覆盖[HttpHost]特性
HttpHost = new Uri("http://www.webapiclient.com"),
};
var myWebApi = HttpApiClient.Create<MyWebApi>(config);

1.2 HttpApiConfig.FormatOptions

当序列化一个多属性的模型时,FormatOptions可以约束DateTime类型的属性值转换为字符串的格式,也可以指定属性名是为CamelCase。

1.3 HttpApiConfig.HttpClient

首次获取HttpClient实例时,HttpClient的实例将被创建,HttpClient属性是一个IHttpClient接口,是对HttpClient对象的包装,它的Handler暴露出与HttpClient关联的HttpClientHandler对象。

1.4 HttpApiConfig.GlobalFilters

GlobalFilters用于添加全局过滤器,这些过滤器不需要使用硬编码修饰于接口,而是通过配置传输给接口的实例,适用于接口定义的项目和接口调用的项目分离开的项目结构。

1.5 HttpApiConfig的生命周期

  • 在实例化HttpApiConfig之后,当不再使用时,应该显性地调用Dispose释放资源;
  • 对于1.1的例子,如果myWebApi实现了IDisposable接口,调用myWebApi.Dispose()也会将HttpApiConfig的HttpClient属性也释放;
  • 对于var myWebApi = HttpApiClient.Create()不传入config的,内部将自动创建一个config实例,与myWebApi关联,myWebApi.Dispose时,config实例也被释放,但外部是获取不到config实例的;

2.WebApiClient执行流程

  • 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转换为接口的返回值;

/// <summary>
/// 异步执行api
/// </summary>
/// <param name="context">上下文</param>
/// <returns></returns>
public async Task<object> 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);
}

3.使用自定义特性

WebApiClient内置很多特性,包含接口级、方法级、参数级的,他们分别是实现了IApiActionAttribute接口、IApiActionFilterAttribute接口、IApiParameterAttribute接口、IApiParameterable接口和IApiReturnAttribute接口的一个或多个接口。一般情况下内置的特性就足以够用,但实际项目中,你可能会遇到个别特殊的场景,需要自己实现一些特性或过滤器,主要用来操控请求上下文的RequestMessage对象,影响请求对象。

3.1 自定义IApiParameterAttribute例子

举个例子:比如,服务端要求使用x-www-form-urlencoded提交,由于接口设计不合理,目前要求是提交:fieldX= {X}的json文本&fieldY={Y}的json文本 这里{X}和{Y}都是一个多字段的Model,我们对应的接口是这样设计的:

[HttpHost("/upload")]
ITask<bool> UploadAsync(
[FormField][AliasAs("fieldX")] string xJson,
[FormField][AliasAs("fieldY")] string yJson);

显然,我们接口参数为string类型的范围太广,没有约束性,我们希望是这样子:

[HttpHost("/upload")]
ITask<bool> UploadAsync([FormFieldJson] X fieldX, [FormFieldJson] Y fieldY);

现在我们为这种特殊场景实现一个[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);
}
}

3.2 自定义过滤器

举个例子:我们需要为每个请求的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
{
...
}

3.3 自定义全局过滤器

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<MyWebApi>(config);

4. DataAnnotations

在一些场景中,你的模型与服务需要的数据模块可能不是全部吻合,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; }
}

5. 了解ITask对象

5.1 await ITask

await ITask,实际是调用了ITask.GetAwaiter()方法,等于同于ITask.InvokeAsync().GetAwaiter()方法。所以await ITask等同于await ITask.InvokeAsync()

5.2 ITask的InvokeAsync方法

InvokeAsync()返回Task对象,实际是http请求的任务对象。一个ITask实例,可以多次调用InvokeAsync()方法,完成多次一模一样的请求。ITask的很多扩展,是对InvokeAsync方法调用的包装而得到。

5.3 ITask的Retry和Handle

Retry本质上是对ITask的InvokeAsync的包装,实际思想是当符合某种条件时,就多调用一次InvokeAsync方法,达到重试提交请求的目的。

Handle也是对ITask的InvokeAsync的包装,使用try catch对InvokeAsync方法封装为新的委托,当捕获到符合条件的异常类型时,就返回某种结果。

var result = await myWebApi.TestAsync()
.Retry(3, i => TimeSpan.FromSeconds(i))
.WhenCatch<Exception>()
.HandleAsDefaultWhenException();

以上可以解读为,当遇到异常时,再重试请求,累计重试3次还是异常的话,处理为返回null值,期间总共最多请求了4次。

5.4 同步请求

HttpClient目前没提供任何的同步请求方法,所以WebApiClient的请求也是一样,如果遇到必须使用同步的场景,可以暂时使用 ITask.GetAwaiter().GetResult()方法等待结果。

.net的retrofit--WebApiClient库深入篇的更多相关文章

  1. Rxjava, RxAndroid, Retrofit 等库的使用

    RxJava的基本用法: 关于 unSubscribe() 的调用问题: There is no need to unsubscribe in onCompleted. Take a look at  ...

  2. [置顶] android利用jni调用第三方库——第二篇——编写库android程序直接调用第三方库libhello.so

    0:前言 1:本文主要作为丙方android公司的身份来写 2:作者有不对的地方,请指出,谢谢 [第一篇:android利用jni调用第三方库——编写库libhello.so] [第二篇:androi ...

  3. WebApiClient库支持AOT

    1 库简介 WebApiClient是开源在github上的一个http客户端库,内部基于HttpClient开发,只需要定义c#接口(interface),并打上相关特性,即可异步调用http-ap ...

  4. android调用第三方库——第二篇——编写库android程序直接调用第三方库libhello.so (转载)

    转自:http://blog.csdn.net/jiuyueguang/article/details/9449737 版权声明:本文为博主原创文章,未经博主允许不得转载. 0:前言 1:本文主要作为 ...

  5. android调用第三方库——第一篇 (转载)

    转自:http://blog.csdn.net/jiuyueguang/article/details/9447245 版权声明:本文为博主原创文章,未经博主允许不得转载. 0:前言: 这两天一直在研 ...

  6. iOS - 第三方库总结篇

    Swift版本点击这里 欢迎加入QQ群交流: 594119878 About A curated list of iOS objective-C ecosystem. How to Use Simpl ...

  7. Swift 必备开发库 (高级篇) (转)

    1.CryptoSwift swift加密库, 支持md5,sha1,sha224,sha256... github地址: https://github.com/krzyzanowskim/Crypt ...

  8. 如何发布带静态资源的库——android 篇

    1.首先要使用 android sdk 提供的命令行工具处理已有的项目: cd YourProjectDir android update project -p ./ 2.上一步生成的 build.x ...

  9. Swift 必备开发库 (高级篇)

    1.CryptoSwift swift加密库, 支持md5,sha1,sha224,sha256... github地址: https://github.com/krzyzanowskim/Crypt ...

随机推荐

  1. python集合增删改查,深拷贝浅拷贝

    集合 集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的.以下是集合最重要的两点: 去重,把一个列表变成集合,就自动去重了. 关系 ...

  2. 在Windows上运行Spark程序

    一.下载Saprk程序 https://d3kbcqa49mib13.cloudfront.net/spark-2.1.1-bin-hadoop2.7.tgz 解压到d:\spark-2.1.1-bi ...

  3. spring cloud sidecar

    用spring cloud sidecar的整合异构语言,以前做过没有做笔记,现在再做由于各种坑又浪费了一天,这里记一下 首先是官网:http://cloud.spring.io/spring-clo ...

  4. iOS 数据储存--SQLite 操作数据库-FMDB,sqlite数据类型,保存图片,demo

    1.SQLite 语句中 数据类型的储存 /* 不区分大小写 char(长度).字符串 NULL. 空值 INTEGER. 整型 REAL.浮点型 TEXT.文本类型 BLOB. 二进制类型,用来存储 ...

  5. RabbitMQ阻塞读取时数据时,关闭channel引起的问题和解决方案

    项目场景: 最近在项目中使用了RabbitMq,其中有一个功能必须能随时切断RabbitMq的coumser.第一时间写出来的代码如下: 伪代码: while(flag){ QueueingConsu ...

  6. linux系统下,安装centos7.0系统,配置网卡出现的问题(与centos5.x、centos6.x版本,有差异)

    1.新建虚拟机时,自己下载的是centos64系统,选择系统时,默认选择centos,而未选择centos64位,导致犯了一个低级错误,导致后面网卡安装一直有问题 2.查看ip命令与centos5.x ...

  7. 谈谈CommonsChunkPlugin抽取公共模块

    引言 webpack插件CommonsChunkPlugin的主要作用是抽取webpack项目入口chunk的公共部分,具体的用法就不做过多介绍,不太了解可以参考webpack官网介绍: 该插件是we ...

  8. 数据库入门(以MySQL为例)

    一.数据库中的概念 1.数据库是用户存放数据.访问数据.操作数据的存储仓库,用户的各种数据被有组织地存放在数据库中.可以随时被有权限的用户查询.统计.添加.删除.和修改.可以说,数据库是长期存储在计算 ...

  9. bzoj 4345: [POI2016]Korale

    Description 有n个带标号的珠子,第i个珠子的价值为a[i].现在你可以选择若干个珠子组成项链(也可以一个都不选),项链的价值为所有珠子的价值和.现在给所有可能的项链排序,先按权值从小到大排 ...

  10. c# 了解c# 面向对象

    C#是微软公司发布的一种面向对象的.运行于.NET Framework之上的高级程序设计语言.并定于在微软职业开发者论坛(PDC)上登台亮相.C#是微软公司研究员Anders Hejlsberg的最新 ...