.net的retrofit--WebApiClient库深入篇

前言

本篇文章的内容是对上一篇.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

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

相关内容: 

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


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322301.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sentinel(三)之如何使用

转载自 Sentinel如何使用 简介 Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard&#xff0c;但是结合 Dashboard 可以取得最好的效果。 这篇文章主要介绍 Sentinel 核心库的使用。如果希望有一个最快最直接的了解&#xff0c;可以参考 新手指…

jzoj4485-[GDOI 2016 Day1]第一题 中学生数学题【数学】

正题 题目大意 给出n0,k,p0n_0,k,p_0n0​,k,p0​ 然后有两问&#xff0c;求 ⌊n0−kp⌋(p−p0)\lfloor n_0-kp\rfloor (p-p_0)⌊n0​−kp⌋(p−p0​)的最大值&#xff0c;ppp为任意实数。⌊n0−kp1⌋(p1−p0)(⌊n0−kp2⌋−n1)(p2−p0)\lfloor n_0-kp_1\rfloor (p_1-p_0)(\lf…

ASP.NET Core中使用IOC三部曲(二.采用Autofac来替换IOC容器,并实现属性注入)

上一篇ASP.NET Core中使用IOC三部曲(一.使用ASP.NET Core自带的IOC容器) ,我们说过ASP.NET Core中自带的IOC容器是属于轻量级的,功能并不是很多,只是提供了基础功能而已..所以今天我们主要讲讲如何采用Autofac来替换IOC容器,并实现属性注入注意:本文需要读者理解DI IOC并使用过…

Sentinel(四)之工作主流程

转载自 工作主流程 Overview 在 Sentinel 里面&#xff0c;所有的资源都对应一个资源名称&#xff08;resourceName&#xff09;&#xff0c;每次资源调用都会创建一个 Entry 对象。Entry 可以通过对主流框架的适配自动创建&#xff0c;也可以通过注解的方式或调用 SphU API…

常用的依赖

<!--指定版本--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.3</version><relativePath/></parent><!--引入插件lombok 自动的set…

在 .NET Core 中的并发编程

原文地址:http://www.dotnetcurry.com/dotnet/1360/concurrent-programming-dotnet-core今天我们购买的每台电脑都有一个多核心的 CPU&#xff0c;允许它并行执行多个指令。操作系统通过将进程调度到不同的内核来发挥这个结构的优点。然而&#xff0c;还可以通过异步 I/O 操作和…

P5007-DDOSvoid的疑惑【树形dp】

正题 题目链接:https://www.luogu.org/problemnew/show/P5007 题目大意 一棵树每个点有点权(若T0T0T0点权为1&#xff0c;若T1T1T1点权为该点的编号)。求所有不包含祖先关系的集合的点权之和的和。 解题思路 设fif_ifi​表示iii的子树的集合点权之和&#xff0c;gig_igi​表…

配置YML整合Mybatis-映射文件配置

YML基本配置 #端口配置 server:port: 8090#配置数据源 spring:datasource:#如果使用高版本驱动 则添加cjdriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/jt?serverTimezoneGMT%2B8&useUnicodetrue&characterEncodingutf8&autoRe…

Sentinel(五)之流量控制

转载自 流量控制 概述 流量控制&#xff08;flow control&#xff09;&#xff0c;其原理是监控应用流量的 QPS 或并发线程数等指标&#xff0c;当达到指定的阈值时对流量进行控制&#xff0c;以避免被瞬时的流量高峰冲垮&#xff0c;从而保障应用的高可用性。 FlowSlot 会…

jzoj4382-[GDOI2016模拟3.11]历史【并查集】

正题 题目大意 有若干个事件 Kc:cK\ c:cK c:c值更改并且清除生气状态Rxy:R\ x\ y:R x y:新建一条x′x&#x27;x′到y′y&#x27;y′的边&#xff0c;若当前处于生气状态则x(x′n−c)%n,y(y′n−c)%nx(x&#x27;n-c)\%n,y(y&#x27;n-c)\%nx(x′n−c)%n,y(y′n−c)%n&a…

MP实现分页查询

/*** 需求: 实现商品分页查询* param pageResult* return*/Overridepublic PageResult getItemList(PageResult pageResult) {IPage page new Page(pageResult.getPageNum(),pageResult.getPageSize());QueryWrapper<Item> queryWrapper new QueryWrapper<>();//…

ASP.NET Core中使用IOC三部曲(三.采用替换后的Autofac来实现AOP拦截)

上一篇ASP.NET Core中使用IOC三部曲(二.采用Autofac来替换IOC容器,并实现属性注入)我们讲了如何将默认的容器替换为Autofac,并使用属性注入.那么这一篇我们就来讲讲如何利用Autofac实现我们的AOP(面向切面编程) .1.引用正确的库来实现AOP既然是跨平台,那么在asp.net core因为采…

Sentinel(六)之集群流控

转载自 集群流控 介绍 为什么要使用集群流控呢&#xff1f;假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50&#xff0c;但机器数可能很多&#xff08;比如有 100 台&#xff09;。这时候我们很自然地就想到&#xff0c;找一个 server 来专门来统计总的调用量&#…

P4597-序列sequence【堆】

正题 题目链接:https://www.luogu.org/problemnew/show/P4597 题目大意 一个长度为nnn的序列&#xff0c;每次可以将一个数111或−1-1−1。要求 变成一个非降序列出现的数之前都在原序列 解题思路 我们考虑一下贪心&#xff0c;由于变成一个非降序列&#xff0c;所以肯定越…

Mybatis-Plus基本

Data AllArgsConstructor//全参构造 NoArgsConstructor//无参构造 Accessors(chain true)//链表模式 TableName("User")//映射数据表名 public class User implements Serializable {//序列化传输保证数据完整TableId(type IdType.UUID)//设定主键自增private Inte…

Sentinel(七)之网关限流

转载自 网关限流 Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。 Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块&#xff0c;此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑&#xff1a; GatewayFlowRule&…

P1941-飞扬的小鸟【dp】

正题 题目链接:https://www.luogu.org/problemnew/show/P1941 题目大意 n∗mn*mn∗m的场地&#xff0c;一只鸟&#xff0c;在iii格点击一次升XiX_iXi​格(可以点击多次)&#xff0c;不点击掉YiY_iYi​格。不能落地&#xff0c;最高mmm格&#xff0c;然后不能撞到地图上的一些管…

C# 7编程模式与实践

C# 7是一个重大更新&#xff0c;其中提供了很多有意思的新功能。虽然已有大量的文章介绍这些功能可以做什么&#xff0c;但是鲜有文章介绍应如何使用这些功能。本文将过一遍《.NET设计规范&#xff1a;.NET约定惯用法与模式》&#xff08;译者注&#xff1a;英文书名为“Framew…

过来瞅瞅学学

当需要对Date时间类型进行加减int类型分钟运算 Date date new Date();//Sun Mar 06 14:12:14 CST 2022int hour 25;int minutes 70;int second 3;SimpleDateFormat simpleDateFormat new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(simpleDat…

Sentinel(八)之熔断降级

转载自 熔断降级 概述 除了流量控制以外&#xff0c;对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块&#xff0c;可能是另外的一个远程服务、数据库&#xff0c;或者第三方 API 等。例如&#xff0c;支付的时候&#xff0c…