.NET的Retrofit--WebAPIClient库深入篇

2018 年 1 月 28 日 DotNet

 (点击上方蓝字,可快速关注我们)


来源:jiulang

cnblogs.com/kewei/p/8302382.html


前言


本篇文章的内容是对上一篇《.NET的Retrofit--WebAPIClient库》的深层次补充,你可能需要先阅读上一篇才能理解此篇文章。


本文将详细地讲解WebAPIClient的原理,结合实际项目中可能遇到的问题进行使用说明。


库简介


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


一、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


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

}


三、使用自定义特性


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


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

}


五、了解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()方法等待结果。


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 


淘口令复制以下红色内容,再打开手淘即可购买

范品社,使用¥极客T恤¥抢先预览(长按复制整段文案,打开手机淘宝即可进入活动内容)

登录查看更多
0

相关内容

【2020新书】实战R语言4,323页pdf
专知会员服务
98+阅读 · 2020年7月1日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
115+阅读 · 2020年5月10日
【SIGMOD2020-腾讯】Web规模本体可扩展构建
专知会员服务
28+阅读 · 2020年4月12日
【2020新书】Kafka实战:Kafka in Action,209页pdf
专知会员服务
65+阅读 · 2020年3月9日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
83+阅读 · 2019年11月25日
《DeepGCNs: Making GCNs Go as Deep as CNNs》
专知会员服务
30+阅读 · 2019年10月17日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
神器Cobalt Strike3.13破解版
黑白之道
12+阅读 · 2019年3月1日
百度开源项目OpenRASP快速上手指南
黑客技术与网络安全
5+阅读 · 2019年2月12日
在浏览器中使用tensorflow.js进行人脸识别的JavaScript API
人工智能头条
6+阅读 · 2018年7月2日
Heterogeneous Deep Graph Infomax
Arxiv
12+阅读 · 2019年11月19日
Arxiv
11+阅读 · 2018年9月28日
Rapid Customization for Event Extraction
Arxiv
7+阅读 · 2018年9月20日
VIP会员
相关VIP内容
【2020新书】实战R语言4,323页pdf
专知会员服务
98+阅读 · 2020年7月1日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
115+阅读 · 2020年5月10日
【SIGMOD2020-腾讯】Web规模本体可扩展构建
专知会员服务
28+阅读 · 2020年4月12日
【2020新书】Kafka实战:Kafka in Action,209页pdf
专知会员服务
65+阅读 · 2020年3月9日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
83+阅读 · 2019年11月25日
《DeepGCNs: Making GCNs Go as Deep as CNNs》
专知会员服务
30+阅读 · 2019年10月17日
相关资讯
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
神器Cobalt Strike3.13破解版
黑白之道
12+阅读 · 2019年3月1日
百度开源项目OpenRASP快速上手指南
黑客技术与网络安全
5+阅读 · 2019年2月12日
在浏览器中使用tensorflow.js进行人脸识别的JavaScript API
人工智能头条
6+阅读 · 2018年7月2日
Top
微信扫码咨询专知VIP会员