1、前言

时间飞快,转眼半年,碌碌无为,眼下就要三十而立,回想三年前的今天,我将NetworkSocket库开放到github,一直在更新与学习,不求有这个库能有多好,只求自己在过程能成长,将领悟到一些思想应用到库里面去。今天,我来给大家介绍半年前在github开放的WebApiClient这个库,正如NetworkSocket一样,它正在渐渐从渺小变得强大,从简单变得抽象、易用、可高度扩展,它将带你进入不一个和以往完全不同风格的调用http接口的世界。

2、编程风格

2.1传统调用风格

一般的,我们需要new 一个HttpClient实例,然后准备请求url、请求body的HttpContent,然后发送,等待接收,解析回复内容.....

这些都是需要一行一行代码来实现,代码里不仅表现了“做什么(What)”,而且更多表现出“如何(How)”完成工作这样的实现细节,大概想想代码像下面:

/// <summary>
/// 更新用户信息
/// 使用application/json提交
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
async static Task<UserInfo> UpdateAsync(UserInfo user)
{
var httpClient = new HttpClient();
var serializer = new JavaScriptSerializer(); var json = serializer.Serialize(user);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var url = "http://localhost:9999/webapi/user/updateWithJson"; var response = await httpClient.PostAsync(url, content); var resJson = await response.Content.ReadAsStringAsync();
var resUser = serializer.Deserialize<UserInfo>(resJson);
return resUser;
}

传统What How思想代码

服务端任何接口的小变化,都直接影响到我们接口调用的某行具体代码,如果有100个接口,这代码的维护也不小。

2.2WebApiClient风格

WebApiClient的设计可以解放使用者的劳动力,只需要使用者根据http接口来定义一份.Net的interface,在接口里描述你想要什么(what i wan),但不需要实现这份interface(how it do),大概如下:

[HttpHost("http://localhost:9999")]
public interface UserApi
{
[HttpPost("/webapi/user/UpdateWithJson")]
Task<UserInfo> UpdateWithJsonAsync([JsonContent] UserInfo user);
}

那么,接口的实现者给谁来完成呢?给WebApiClient来完成,它很聪明,知道你想要什么,这个正像我们写一条sql:select * from table一样,只有what,没有how。

3、使用层设计

3.1接口与服务端设计一致

使用者编写的interface,可以与服务端接近完全一致,在编写接口或文档对照方面相当容易。

3.2使用Attribute标记描述“干什么”

上面的[HttpPost]和[JsonContent],用来标记是干什么(不是怎么干)

3.3没有了,只剩下调用接口了

var userApi = new HttpApiClient().Implement<UserApi>();
var resUser = await userApi.UpdateWithJsonAsync(user);

4、架构层设计

使用Castle来动态实现interface的实例,并获得实例方法调用的拦截,在拦截层,一一调用与方法相关标记的Atribute,Attribute是真正的逻辑实现者,每个Attribute只关注自己应该做什么。

4.1、ApiActionContext

ApiActionContext用于描述接口的详细信息以及接口周边的其它信息,在拦截interface的实例某方法之后,都生成一份ApiActionContext实例,但实例的很多属性是缓存中获取的,任何特性在执行的时候,都可以访问和修改这个ApiActionContext。

4.2、Attribute标记分类

1、IHttpActionAttribute (ApiActionContext)         // 与Api方法相关

2、IApiParameterAttribute (ApiActionContext)    // 与 api参数相关

3、IApiActionFilterAttribute (ApiActionContext)   // 与Api请求前后有关

4、IApiReturnAttribute (ApiActionContext)          // 与api返回值相关

这4个Attribute接口有着各自的职责,前三者一个共同的目标:构造和影响一个请求内容对象HttpRequestMessage,第4个的目标是:从回复的HttpResponseMessage中得到接口的返回值

4.3 Attribute的执行

在拦截器里,按照IApiActionAttribute > IApiParameterAttribute > IApiActionFilterAttribute > IApiReturnAttribute的顺序,将与方法的所有特性都执行,就可以完成接口的调用,大概实现如下:

/// <summary>
/// 异步执行api
/// </summary>
/// <param name="context">上下文</param>
/// <returns></returns>
private async Task<object> ExecuteInternalAsync(ApiActionContext context)
{
var apiAction = context.ApiActionDescriptor; 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 apiAction.Filters)
{
await filter.OnBeginRequestAsync(context);
} // 执行Http请求,获取回复对象
var httpClient = context.HttpClientContext.HttpClient;
context.ResponseMessage = await httpClient.SendAsync(context.RequestMessage); foreach (var filter in apiAction.Filters)
{
await filter.OnEndRequestAsync(context);
} return await apiAction.Return.Attribute.GetTaskResult(context);
}

5、支持的功能特性

5.1方法或接口级特性

绝对主机域名:[HttpHost]

请求方式与路径:[HttpGet]、[HttpPost]、[HttpDelete]、[HttpPut]、[HttpHead]和[HttpOptions]

代理:[Proxy]

请求头:[Header]

返回值:[AutoReturn]、[JsonReturn]、[XmlReturn]

使用者可以自己扩充更多特性。

5.2 参数级特性

路径或query:[PathQuery]、[Url]

请求头:[Header]

请求Body:[HttpContent]、[JsonContent]、[XmlContent]、[FormContent]、[MulitpartConten]

使用者可以自己扩充更多特性。

5.3特殊参数类型

MulitpartFile类(表单文件)、Url类(请求地址)、Proxy类 (请求代理)

这些特殊参数类型在参数里,可以是本类型或本类型的集合,都会被执行

使用者可以自己扩充更多的特殊参数类型。

6、扩展能力

6.1 扩展特性

任何只要实现了IHttpActionAttribute、IApiParameterAttribute 、IApiActionFilterAttribute 、IApiReturnAttribute 之一或以上的特性,只要打在接口或参数上,就会得到调用,在调用里实现处理逻辑。

6.2 特殊参数扩展

任何实现了IApiParameterable接口的参数值,也会得到调用。

6.3 自定义xml/json序列化

HttpApiClient.Config.UseXmlFormatter(your formatter)

HttpApiClient.Config.UseJsonFormatter(your formatter)

6.4 自定义HttpClient上下文提供者

HttpApiClient.Config.UseHttpClientContextProvider(your provider)

你可以自己控制HttpClient的配置与生命周期

6.5 自定义过滤器

继承ApiActionFilterAttribute,可以实现自己的拦截器,作日志、授权什么的都可以;

在子类修改AllowMultiple属性与OrderIndex属性,可以实现特性的排序与是否在接口和方法上重复使用。

7、项目地址

https://github.com/xljiulang/WebApiClient

里面有一个demo,借助networksocket,http服务端与客户端都是同一个进程,调试过程非常方便,数据流向一目了然。

最后,如果你看哪个哪.net 的httpClient有更方便的调用,请@我,我马上模仿他。

我来给.Net设计一款HttpClient的更多相关文章

  1. 如何为编程爱好者设计一款好玩的智能硬件(十)——无线2.4G通信模块研究·一篇说完

    六.温湿度传感器DHT11驱动封装(下):如何为编程爱好者设计一款好玩的智能硬件(六)——初尝试·把温湿度给收集了(下)! 七.点阵字符型液晶显示模块LCD1602驱动封装(上):如何为编程爱好者设计 ...

  2. 如何为编程爱好者设计一款好玩的智能硬件(九)——LCD1602点阵字符型液晶显示模块驱动封装(下)

    六.温湿度传感器DHT11驱动封装(下):如何为编程爱好者设计一款好玩的智能硬件(六)——初尝试·把温湿度给收集了(下)! 七.点阵字符型液晶显示模块LCD1602驱动封装(上):如何为编程爱好者设计 ...

  3. 如何为编程爱好者设计一款好玩的智能硬件(八)——LCD1602点阵字符型液晶显示模块驱动封装(中)

    六.温湿度传感器DHT11驱动封装(下):如何为编程爱好者设计一款好玩的智能硬件(六)——初尝试·把温湿度给收集了(下)! 七.点阵字符型液晶显示模块LCD1602驱动封装(上):如何为编程爱好者设计 ...

  4. 如何为编程爱好者设计一款好玩的智能硬件(七)——LCD1602点阵字符型液晶显示模块驱动封装(上)

    当前进展: 一.我的构想:如何为编程爱好者设计一款好玩的智能硬件(一)——即插即用.积木化.功能重组的智能硬件模块构想 二.别人家的孩子:如何为编程爱好者设计一款好玩的智能硬件(二)——别人是如何设计 ...

  5. 如何为编程爱好者设计一款好玩的智能硬件(三)——该选什么样的MCU呢?

    一.我的构想:如何为编程爱好者设计一款好玩的智能硬件(一)——即插即用.积木化.功能重组的智能硬件模块构想 二.别人家的孩子:如何为编程爱好者设计一款好玩的智能硬件(二)——别人是如何设计硬件积木的! ...

  6. 如何设计一款优秀的短视频 SDK

    2017 年,短视频成为了创业的新风口,各种短视频 App 如雨后春笋般先后上线,视频越来越像文字.图片一样,成为每一个 App 不可或缺的一部分. 1. 包体一定要尽可能小 如何做到尽可能的减小 S ...

  7. .Net架构篇:思考如何设计一款实用的分布式监控系统?

    前言 无论从最早期的unix操作系统,还是曾经大行其道的单体式应用,还是现在日益流行的微服务架构,始终都离不开监控的身影.如windows的任务管理器,linux的top命令,都可以看作是监控的面板. ...

  8. 如何为Android上的产品设计一款合适的图标

    如 果你已经完成了你的app,你一定会马上向其它人宣布这件事情.但是你需要注意一个很重要的问题,那就是app的图标.你的图标可能在项目启动之 前就已经设计好了,但我不喜欢这样,如果app没有完成实际上 ...

  9. 设计一款兼容ST207和GD207的开发板

    在MCU的学习中,大部分人都是学习别人的开发板,例如正点原子.野火等,优点是有可靠的教程和代码,缺点是容易让人有种自己全部都学会的了错觉,听了课程编写了代码,运行正常. 这个时候,可以尝试自已做一块属 ...

随机推荐

  1. Vmware虚拟机安装win7系统教程

    第一步:下载虚拟机 可以下载VMware虚拟机,这里用的是Vmware12专业版,百度网盘直通车密码:c3mt密钥:5A02H-AU243-TZJ49-GTC7K-3C61N 第二部:安装 第三部:做 ...

  2. 计蒜客模拟赛D2T2 蒜头君的排序:区间逆序对(移动端点) + 树状数组

    题目链接:https://nanti.jisuanke.com/t/16443 题意: 给你一个由1~n构成的正整数序列,有m组询问,每组询问要求输出[l , r]区间内的逆序对个数. 数据范围: 对 ...

  3. 关于JS中数组的分析操作

    JS数组的基础操作代码: <script type="text/javascript">        数组的三种定义    var arr1 = new Array( ...

  4. sqlite数据库之简单操作

    一 sqlite介绍 Sqlite是一种嵌入式数据库,类似于一个文件系统,是跟程序在一起的.跟mysql等数据库程序跟数据分离是不一样的. 应用场景:常用于保存本地配置,类似于本地文件系统,因此他内嵌 ...

  5. 关于Java中Eclipse运行卡顿、未响应,Cpu100%的快速处理办法

    1.与近几日我以及我的同事们遇到一件非常奇怪的问题,我们目前在实现一个小程序,但是不知为何,Eclipse突然变得很卡,以为是小问题最后闹到重启都没解决,于是 我开始查找造成这个现象的原因,发现这个程 ...

  6. virtual与static

    virtual与static不能同时作用于一个函数.根据面向对象的理论,virtual的成员函数是可以变子类覆盖的,是实现多态的重要手段.而static作用的成员函数表示该函数仅属于某个类. 下面是实 ...

  7. /bin,/sbin,/usr/sbin,/usr/bin 目录

    这些目录都是存放命令的,首先区别下/sbin和/bin: 从命令功能来看,/sbin 下的命令属于基本的系统命令,如shutdown,reboot,用于启动系统,修复系统,/bin下存放一些普通的基本 ...

  8. 记录-新建一个web应用的过程与曲折

    第一步/ 打开eclipse,菜单栏下,File–New–Other-,打开后找到web–Dynamic Web Project,然后单击Next. 解释一下,Dynamic ,动态的,变化的,Dyn ...

  9. vue.js基础知识篇(2):指令详解

    第三章:指令 1.语法 指令以v-打头,它的值限定为绑定表达式,它负责的是按照表达式的值应用某些行为到DOM上. 内部指令有v-show,v-else,v-model,v-repeat,v-for,v ...

  10. mapper.xml是怎样实现Dao层接口

    上午写了一个简单的 从xml读取信息实例化一个Bean对象.下午就开始想mybatis是怎么通过xml文件来实现dao层接口的,一开始想直接用Class.forName(String name)然后调 ...