WebApiClient

WebApiClientNCC开源社区的一个项目,是目前微服务里http接口调用的一把锋利尖刀,项目早期设计与开发的时候,是基于.netframework的,然后慢慢加入netstandard和netcoreapp多个框架的支持,设计能力出众,AOP能力唾手可得易如反掌。

WebApiClientCore

WebApiClient很优秀,它将不同框架不同平台都实现了统一的api;WebApiClient不够优秀,它在.netcore下完全可以更好,但它不得不兼容.net45开始所有框架而有所牺牲。所以WebApiClientCore横空出世,它是WebApiClient.JIT的.netcore替代版本,目前尚属于alpha阶段,计划只支持.netcore平台,并紧密与.netcore新特性紧密结合。

WebApiClientCore的变化

  • 使用System.Text.Json替换Json.net,提升序列化性能
  • 移除HttpApiFactory和HttApiConfig功能,使用Microsoft.Extensions.Http的HttpClientFactory
  • 移除AOT功能,仅保留依赖于Emit的运行时代理
  • 高效的ActionInvoker,对返回Task<>和ITask<>作不同处理
  • 所有特性都都变成中间件,基于管道编排各个特性并生成Action执行委托
  • 良好设计的HttpContext、ApiRequestContext、ApiParameterContext和ApiResponseContext

WebApiClientCore执行流程设计

1 接口代理类生成设计

Cretate<THttpApi>() -> BuildProxyType() -> CreateProxyInstance(ActionInterceptor)

1.1 HttpApiProxyTypeBuilder

在HttpApi.Create()时,先调用HttpApiProxyTypeBuilder来生成THttpApi接口的代理类,HttpApiProxyTypeBuilder是基于Emit方案,Build出来的代理类在每个方法调用时触发一次拦截器ActionInterceptor的Intercept()方法,将调用参数传给拦截器。

1.2 HttpApiProxyBuilder

给定一个代理类的类型(Type),快速生成代理类的实例,这个Builder实际是生成并保存了代理类构造器的高效调用委托,属于反射优化。

2 ActionInterceptor的设计

ActionInterceptor.Intercept(MethodInfo) -> CreateActionInvoker() -> ActionInvoker.Invoke()

ActionInterceptor在拦截到方法调用时,根据方法的MethodInfo信息,创建ActionInvoker,然后调用ActionInvoker.Invoke()执行。当然,ActionInvoker并不是总是创建的,因为它的创建是有成本的,ActionInterceptor使用了缓存ActionInvoker的方案。

2.1 MultiplexedActionInvoker

WebApiClientCore支持加Task<>和ITask<>两种异步声明,MultiplexedActionInvoker实际上包装了ActionInvoker和ActionTask两个字段,当声明为Task<>时,调用ActionInvoker执行,当声明为ITask<>是,返回创建实现了ITask<>接口的ActionTask实例。

2.2 ActionInvoker

ActionInvoker是一个ApiActionDescriptor的执行器,其实现了IActionInvoker.Invoke(ServiceContext context, object[] arguments)接口。关于Descriptor的设计模式,我们在asp.netcore的各种AtionContext里可以发现,有了ApiActionDescriptor,再给它各个参数值,Action就很容易执行起来了。

3 RequestDelegate生成设计

ActionInvoker在拿到各个参数值之后,并不是直接从ApiActionDescriptor查找各个特性来执行,而是在执行前就把执行流程编译好,得到一个执行委托,这个委托叫RequestDelegate,其原型为Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)。抽象成传入请求上下文件,返回响应上下文,当真正执行时,调用这个委托即可。如果你熟悉asp.netcore,那么应该很容易理解下面代码的思路:

/// <summary>
/// 提供Action的调用链委托创建
/// </summary>
static class RequestDelegateBuilder
{
/// <summary>
/// 创建执行委托
/// </summary>
/// <returns></returns>
public static Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)
{
var requestHandler = BuildRequestHandler(apiAction);
var responseHandler = BuildResponseHandler(apiAction); return async request =>
{
await requestHandler(request).ConfigureAwait(false);
var response = await SendRequestAsync(request).ConfigureAwait(false);
await responseHandler(response).ConfigureAwait(false);
return response;
};
} /// <summary>
/// 创建请求委托
/// </summary>
/// <param name="apiAction"></param>
/// <returns></returns>
private static InvokeDelegate<ApiRequestContext> BuildRequestHandler(ApiActionDescriptor apiAction)
{
var builder = new PipelineBuilder<ApiRequestContext>(); // 参数验证特性验证和参数模型属性特性验证
builder.Use(next => context =>
{
var validateProperty = context.HttpContext.Options.UseParameterPropertyValidate;
foreach (var parameter in context.ApiAction.Parameters)
{
var parameterValue = context.Arguments[parameter.Index];
ApiValidator.ValidateParameter(parameter, parameterValue, validateProperty);
}
return next(context);
}); // action特性请求前执行
foreach (var attr in apiAction.Attributes)
{
builder.Use(attr.OnRequestAsync);
} // 参数特性请求前执行
foreach (var parameter in apiAction.Parameters)
{
var index = parameter.Index;
foreach (var attr in parameter.Attributes)
{
builder.Use(async (context, next) =>
{
var ctx = new ApiParameterContext(context, index);
await attr.OnRequestAsync(ctx, next).ConfigureAwait(false);
});
}
} // Return特性请求前执行
foreach (var @return in apiAction.Return.Attributes)
{
if (@return.Enable == true)
{
builder.Use(@return.OnRequestAsync);
}
} // Filter请求前执行
foreach (var filter in apiAction.FilterAttributes)
{
if (filter.Enable == true)
{
builder.Use(filter.OnRequestAsync);
}
} return builder.Build();
} /// <summary>
/// 创建响应委托
/// </summary>
/// <param name="apiAction"></param>
/// <returns></returns>
private static InvokeDelegate<ApiResponseContext> BuildResponseHandler(ApiActionDescriptor apiAction)
{
var builder = new PipelineBuilder<ApiResponseContext>(); // Return特性请求后执行
foreach (var @return in apiAction.Return.Attributes)
{
if (@return.Enable == false)
{
continue;
} builder.Use(async (context, next) =>
{
if (context.ResultStatus == ResultStatus.None)
{
await @return.OnResponseAsync(context, next).ConfigureAwait(false);
}
else
{
await next().ConfigureAwait(false);
}
});
} // 验证Result是否ok
builder.Use(next => context =>
{
try
{
ApiValidator.ValidateReturnValue(context.Result, context.HttpContext.Options.UseReturnValuePropertyValidate);
}
catch (Exception ex)
{
context.Exception = ex;
}
return next(context);
}); // Filter请求后执行
foreach (var filter in apiAction.FilterAttributes)
{
if (filter.Enable == true)
{
builder.Use(filter.OnResponseAsync);
}
} return builder.Build();
} /// <summary>
/// 执行http请求
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static async Task<ApiResponseContext> SendRequestAsync(ApiRequestContext context)
{
try
{
var apiCache = new ApiCache(context);
var cacheValue = await apiCache.GetAsync().ConfigureAwait(false); if (cacheValue != null && cacheValue.Value != null)
{
context.HttpContext.ResponseMessage = cacheValue.Value;
}
else
{
using var cancellation = CreateLinkedTokenSource(context);
var response = await context.HttpContext.Client.SendAsync(context.HttpContext.RequestMessage, cancellation.Token).ConfigureAwait(false); context.HttpContext.ResponseMessage = response;
await apiCache.SetAsync(cacheValue?.Key, response).ConfigureAwait(false);
}
return new ApiResponseContext(context);
}
catch (Exception ex)
{
return new ApiResponseContext(context) { Exception = ex };
}
} /// <summary>
/// 创建取消令牌源
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static CancellationTokenSource CreateLinkedTokenSource(ApiRequestContext context)
{
if (context.CancellationTokens.Count == 0)
{
return CancellationTokenSource.CreateLinkedTokenSource(CancellationToken.None);
}
else
{
var tokens = context.CancellationTokens.ToArray();
return CancellationTokenSource.CreateLinkedTokenSource(tokens);
}
}
}

WebApiClientCore的特性设计

WebApiClientCore的核心特性为以下4种,每种功能各不一样,在设计上使用了中间件的思想,每一步执行都可以获取到context对象和下一个中间件next对象,开发者在实现自定义Attribute时,可以选择性的进行短路设计。

1 IApiActionAttribute

表示Action执行前会调用,调用时接收到ApiRequestContext

/// <summary>
/// 定义ApiAction修饰特性的行为
/// </summary>
public interface IApiActionAttribute : IAttributeMultiplable
{
/// <summary>
/// 请求前
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一个执行委托</param>
/// <returns></returns>
Task OnRequestAsync(ApiRequestContext context, Func<Task> next);
}

2 IApiParameterAttribute

表示参数执行前会调用,调用时接收到ApiParameterContext

/// <summary>
/// 定义Api参数修饰特性的行为
/// </summary>
public interface IApiParameterAttribute
{
/// <summary>
/// 请求前
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一个执行委托</param>
/// <returns></returns>
Task OnRequestAsync(ApiParameterContext context, Func<Task> next);
}

3 IApiReturnAttribute

执行前和执行后都会收到,设置为上下文的Result或Exception,会短路执行

/// <summary>
/// 定义回复内容处理特性的行为
/// </summary>
public interface IApiReturnAttribute : IAttributeMultiplable, IAttributeEnable
{
/// <summary>
/// 请求前
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一个执行委托</param>
/// <returns></returns>
Task OnRequestAsync(ApiRequestContext context, Func<Task> next); /// <summary>
/// 响应后
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一个执行委托</param>
/// <returns></returns>
Task OnResponseAsync(ApiResponseContext context, Func<Task> next);
}

5 IApiFilterAttribute

执行前和执行后都会收到,在IApiReturnAttribute之后执行

/// <summary>
/// 定义ApiAction过滤器修饰特性的行为
/// </summary>
public interface IApiFilterAttribute : IAttributeMultiplable, IAttributeEnable
{
/// <summary>
/// 请求前
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一个执行委托</param>
/// <returns></returns>
Task OnRequestAsync(ApiRequestContext context, Func<Task> next); /// <summary>
/// 响应后
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一个执行委托</param>
/// <returns></returns>
Task OnResponseAsync(ApiResponseContext context, Func<Task> next);
}

结束语

代码可以写得很烂,但设计必须高大上,希望WebApiClientCore可以在声明式客户端领域继续引领其它开源库,同时让使用它的开发者为之赞叹。

如果你希望为望WebApiClientCore出力,可以Fork它然后pull request,和我一起完善单元测试,或编写多语言资源文件,或者加入一些更好的代码设计。

走进WebApiClientCore的设计的更多相关文章

  1. WebApiClient性能参考

    1 文章目的 昨天写了走进WebApiClientCore的设计,介绍了WebApiClient的变化与设计之后,收到大家支持的.赞许的,还有好的建议和顾虑,比如WebApiClient性能怎么样,有 ...

  2. .NET框架设计(常被忽视的C#设计技巧)

    阅读目录: 1.开篇介绍 2.尽量使用Lambda匿名函数调用代替反射调用(走进声明式设计) 3.被忽视的特性(Attribute)设计方式 4.扩展方法让你的对象如虎添翼(要学会使用扩展方法的设计思 ...

  3. .NET框架设计—常被忽视的C#设计技巧

    .NET框架设计—常被忽视的C#设计技巧 阅读目录: 1.开篇介绍 2.尽量使用Lambda匿名函数调用代替反射调用(走进声明式设计) 3.被忽视的特性(Attribute)设计方式 4.扩展方法让你 ...

  4. 【转载】 .NET框架设计—常被忽视的C#设计技巧

    阅读目录: 1.开篇介绍 2.尽量使用Lambda匿名函数调用代替反射调用(走进声明式设计) 3.被忽视的特性(Attribute)设计方式 4.扩展方法让你的对象如虎添翼(要学会使用扩展方法的设计思 ...

  5. 走进MEasy的世界:基于STM32MP1的IOT参考设计

    前言:在万物互联快速发展的趋势下,板卡处理器性能.内存大小.接口外设等都是人们非常关心的硬件参数,但是如何让硬件的作用实现它的功能最大化,一套完善的软件支持尤为重要. 背景:随着HTML5技术的发展, ...

  6. 走进缓存的世界(三) - Memcache

    系列文章 走进缓存的世界(一) - 开篇 走进缓存的世界(二) - 缓存设计 走进缓存的世界(三) - Memcache 简介 Memcache是一个高性能的分布式内存对象缓存系统,用于动态Web应用 ...

  7. [C#] 走进异步编程的世界 - 在 GUI 中执行异步操作

    走进异步编程的世界 - 在 GUI 中执行异步操作 [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5877042.html 序 这是继<开始接 ...

  8. [转]DDD领域驱动设计基本理论知识总结

    领域驱动设计之领域模型 加一个导航,关于如何设计聚合的详细思考,见这篇文章. 2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity i ...

  9. 【开源】OSharp框架解说系列(5.1):EntityFramework数据层设计

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

随机推荐

  1. docker企业级镜像仓库Harbor管理

    Harbor概述 Harbor是由VMWare公司开源的容器镜像仓库.事实上,Harbor是在Docker Registry上进行了相应的企业级扩展,从而获得了更加广泛的应用,这些新的企业级特性包括: ...

  2. 点击 QTableView,触发事件

    Here is an example of how you can get a table cell's text when clicking on it. Suppose a QTableView ...

  3. [Windows] DiskPart commands

    https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/diskpart

  4. numpy库的学习笔记

    一.ndarray 1.numpy 库处理的最基础数据类型是由同种元素构成的多维数组(ndarray),简称“数组”. 2.ndarray是一个多维数组的对象,ndarray数组一般要求所有元素类型相 ...

  5. MySQL简介和安装

    一.关系型数据库初识 1.1 什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据.我 ...

  6. 听说你在从事前端开发?那这10个JavaScript的优化问题你不得不知道!

    JavaScript的高效优化一直都是我们前端开发中非常重要的工作,也是很多开发人员无法做好的一部分内容,今天我总结了10个优化问题,大家可以参考来做优化,这其中很多问题都是大家经常遇到的哦. ==和 ...

  7. 前端存储 (5) - service worker 离线存储

    service worker 离线存储 简介: 一般的网站 在我们无法访问的 时候 一般 回出现 如下 该网页无法访问 service worker 构建的网站不会出现这个错误,因为所有的 请求都是先 ...

  8. ASP.NET Core WebApi(01)项目建立

    前言:前一段时间学习了ASP.NET Core,决定写个简单的项目,旨在消化所学内容,并记录过程中遇到的问题.本章是第一篇,内容为项目的建立 一.准备工作 安装Visual Studio时,默认会安装 ...

  9. 虚拟机部署单机版kubernetes,minikube,docker

    # 目前公司用的是阿里云的容器服务 所以本地搭建个单机版 方便测试使用# VMware® Workstation 12 Pro 版本# 虚拟机环境配置:配置 2核 4G 网络桥接# 系统镜像: Cen ...

  10. ASP.NET Core MVC 如何获取请求的参数

    一次HTTP请求,就是一次标准IO操作.请求是I,是输入:响应式O,是输出.任何web开发框架,其实都是在干这两件事: 接受请求并进行解析获取参数 根据参数进行渲染并输出响应内容 所以我们学习一个框架 ...