Abp Vnext 动态(静态)API客户端源码解析
- 服务端:根据接口定义方法的签名生成路由,并暴露Api。
- 客户端:根据接口定义方法的签名生成请求,通过HTTPClient调用。
一.动态API客户端
context.Services.AddHttpClientProxies(
typeof(IdentityApplicationContractsModule).Assembly, //接口层程序集
RemoteServiceName //远程服务名称
);
public static IServiceCollection AddHttpClientProxy(this IServiceCollection services, Type type, string remoteServiceConfigurationName = "Default", bool asDefaultService = true)
{
/*省略一些代码...*/
Type type2 = typeof(DynamicHttpProxyInterceptor<>).MakeGenericType(type); //拦截器
services.AddTransient(type2);
Type interceptorAdapterType = typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(type2);
Type validationInterceptorAdapterType = typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(typeof(ValidationInterceptor));
if (asDefaultService)
{
//生成代理,依赖注入到容器
services.AddTransient(type, (IServiceProvider serviceProvider) => ProxyGeneratorInstance.CreateInterfaceProxyWithoutTarget(type, (IInterceptor)serviceProvider.GetRequiredService(validationInterceptorAdapterType), (IInterceptor)serviceProvider.GetRequiredService(interceptorAdapterType)));
}
services.AddTransient(typeof(IHttpClientProxy<>).MakeGenericType(type), delegate (IServiceProvider serviceProvider)
{
//生成代理,通过HttpClientProxy封装,依赖注入到容器
object obj = ProxyGeneratorInstance.CreateInterfaceProxyWithoutTarget(type, (IInterceptor)serviceProvider.GetRequiredService(validationInterceptorAdapterType), (IInterceptor)serviceProvider.GetRequiredService(interceptorAdapterType));
return Activator.CreateInstance(typeof(HttpClientProxy<>).MakeGenericType(type), obj);
});
return services;
}
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
{
var context = new ClientProxyRequestContext(
await GetActionApiDescriptionModel(invocation), //获取Api描述信息
invocation.ArgumentsDictionary,
typeof(TService));
if (invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty())
{
await InterceptorClientProxy.CallRequestAsync(context);
}
else
{
var returnType = invocation.Method.ReturnType.GenericTypeArguments[0];
var result = (Task)CallRequestAsyncMethod
.MakeGenericMethod(returnType)
.Invoke(this, new object[] { context });
invocation.ReturnValue = await GetResultAsync(result, returnType); //调用CallRequestAsync泛型方法
}
}
protected virtual async Task<ActionApiDescriptionModel> GetActionApiDescriptionModel(IAbpMethodInvocation invocation)
{
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? //获取远程服务名称
throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}.");
var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName);//获取远程服务端点配置
var client = HttpClientFactory.Create(clientConfig.RemoteServiceName); //创建HttpClient
return await ApiDescriptionFinder.FindActionAsync(
client,
remoteServiceConfig.BaseUrl, //远程服务地址
typeof(TService),
invocation.Method
);
}
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:44388"
},
"XXXDemo":{
"BaseUrl": "http://localhost:44345"
}
},
public async Task<ActionApiDescriptionModel> FindActionAsync(
HttpClient client,
string baseUrl,
Type serviceType,
MethodInfo method)
{
var apiDescription = await GetApiDescriptionAsync(client, baseUrl); //获取Api描述信息并缓存结果
//TODO: Cache finding?
var methodParameters = method.GetParameters().ToArray();
foreach (var module in apiDescription.Modules.Values)
{
foreach (var controller in module.Controllers.Values)
{
if (!controller.Implements(serviceType)) //不继承接口跳过,所以写控制器为什么需要要继承服务接口的作用之一便在于此
{
continue;
}
foreach (var action in controller.Actions.Values)
{
if (action.Name == method.Name && action.ParametersOnMethod.Count == methodParameters.Length) //签名是否匹配
{
/*省略部分代码 */
}
}
}
}
throw new AbpException($"Could not found remote action for method: {method} on the URL: {baseUrl}");
}
public virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionAsync(HttpClient client, string baseUrl)
{
return await Cache.GetAsync(baseUrl, () => GetApiDescriptionFromServerAsync(client, baseUrl)); //缓存结果
}
protected virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionFromServerAsync(
HttpClient client,
string baseUrl)
{
//构造请求信息
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
baseUrl.EnsureEndsWith('/') + "api/abp/api-definition"
);
AddHeaders(requestMessage); //添加请求头
var response = await client.SendAsync( //发送请求并获取响应结果
requestMessage,
CancellationTokenProvider.Token
);
if (!response.IsSuccessStatusCode)
{
throw new AbpException("Remote service returns error! StatusCode = " + response.StatusCode);
}
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<ApplicationApiDescriptionModel>(content, DeserializeOptions);
return result;
}
public virtual async Task<T> CallRequestAsync<T>(ClientProxyRequestContext requestContext)
{
return await base.RequestAsync<T>(requestContext);
}
public virtual async Task<HttpContent> CallRequestAsync(ClientProxyRequestContext requestContext)
{
return await base.RequestAsync(requestContext);
}
protected virtual async Task<HttpContent> RequestAsync(ClientProxyRequestContext requestContext)
{
//获取远程服务名称
var clientConfig = ClientOptions.Value.HttpClientProxies.GetOrDefault(requestContext.ServiceType) ?? throw new AbpException($"Could not get HttpClientProxyConfig for {requestContext.ServiceType.FullName}.");
//获取远程服务端点配置
var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName);
var client = HttpClientFactory.Create(clientConfig.RemoteServiceName);
var apiVersion = await GetApiVersionInfoAsync(requestContext); //获取API版本
var url = remoteServiceConfig.BaseUrl.EnsureEndsWith('/') + await GetUrlWithParametersAsync(requestContext, apiVersion); //拼接完整的url
var requestMessage = new HttpRequestMessage(requestContext.Action.GetHttpMethod(), url) //构造HTTP请求信息
{
Content = await ClientProxyRequestPayloadBuilder.BuildContentAsync(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion)
};
AddHeaders(requestContext.Arguments, requestContext.Action, requestMessage, apiVersion); //添加请求头
if (requestContext.Action.AllowAnonymous != true) //是否需要认证
{
await ClientAuthenticator.Authenticate( //认证
new RemoteServiceHttpClientAuthenticateContext(
client,
requestMessage,
remoteServiceConfig,
clientConfig.RemoteServiceName
)
);
}
HttpResponseMessage response;
try
{
response = await client.SendAsync( //发送请求
requestMessage,
HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/,
GetCancellationToken(requestContext.Arguments)
);
}
return response.Content;
}
public override async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
{
if (context.RemoteService.GetUseCurrentAccessToken() != false)
{
var accessToken = await GetAccessTokenFromHttpContextOrNullAsync(); //获取当前登录用户Token
if (accessToken != null)
{
context.Request.SetBearerToken(accessToken);
return;
}
}
await base.Authenticate(context);
}
[DependsOn(typeof(AbpHttpClientIdentityModelWebModule))]
"UseCurrentAccessToken": "true"
}
}
protected virtual void AddHeaders(
IReadOnlyDictionary<string, object> argumentsDictionary,
ActionApiDescriptionModel action,
HttpRequestMessage requestMessage,
ApiVersionInfo apiVersion)
{
/*省略代码/*
//TenantId
if (CurrentTenant.Id.HasValue)
{
//TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key
requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString());
}
/*省略代码/*
}
要点

二.静态API客户端
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityRoleAppService), typeof(IdentityRoleClientProxy))]
public partial class IdentityRoleClientProxy : ClientProxyBase<IIdentityRoleAppService>, IIdentityRoleAppService
{
public virtual async Task<ListResultDto<IdentityRoleDto>> GetAllListAsync()
{
return await RequestAsync<ListResultDto<IdentityRoleDto>>(nameof(GetAllListAsync));
}
}
protected virtual async Task RequestAsync(string methodName, ClientProxyRequestTypeValue arguments = null)
{
await RequestAsync(BuildHttpProxyClientProxyContext(methodName, arguments));
}
protected virtual ClientProxyRequestContext BuildHttpProxyClientProxyContext(string methodName, ClientProxyRequestTypeValue arguments = null)
{
if (arguments == null)
{
arguments = new ClientProxyRequestTypeValue();
}
var methodUniqueName = $"{typeof(TService).FullName}.{methodName}.{string.Join("-", arguments.Values.Select(x => TypeHelper.GetFullNameHandlingNullableAndGenerics(x.Key)))}";
var action = ClientProxyApiDescriptionFinder.FindAction(methodUniqueName); //获取调用方法的API描述信息
if (action == null)
{
throw new AbpException($"The API description of the {typeof(TService).FullName}.{methodName} method was not found!");
}
var actionArguments = action.Parameters.GroupBy(x => x.NameOnMethod).ToList();
if (action.SupportedVersions.Any())
{
//TODO: make names configurable
actionArguments.RemoveAll(x => x.Key == "api-version" || x.Key == "apiVersion");
}
return new ClientProxyRequestContext( //封装未远程调用上下文
action,
actionArguments
.Select((x, i) => new KeyValuePair<string, object>(x.Key, arguments.Values[i].Value))
.ToDictionary(x => x.Key, x => x.Value),
typeof(TService));
}
private ApplicationApiDescriptionModel GetApplicationApiDescriptionModel()
{
var applicationApiDescription = ApplicationApiDescriptionModel.Create();
var fileInfoList = new List<IFileInfo>();
GetGenerateProxyFileInfos(fileInfoList);
foreach (var fileInfo in fileInfoList)
{
using (var streamReader = new StreamReader(fileInfo.CreateReadStream()))
{
var content = streamReader.ReadToEnd();
var subApplicationApiDescription = JsonSerializer.Deserialize<ApplicationApiDescriptionModel>(content);
foreach (var module in subApplicationApiDescription.Modules)
{
if (!applicationApiDescription.Modules.ContainsKey(module.Key))
{
applicationApiDescription.AddModule(module.Value);
}
}
}
}
return applicationApiDescription;
}
private void GetGenerateProxyFileInfos(List<IFileInfo> fileInfoList, string path = "")
{
foreach (var directoryContent in VirtualFileProvider.GetDirectoryContents(path))
{
if (directoryContent.IsDirectory)
{
GetGenerateProxyFileInfos(fileInfoList, directoryContent.PhysicalPath);
}
else
{
if (directoryContent.Name.EndsWith("generate-proxy.json"))
{
fileInfoList.Add(VirtualFileProvider.GetFileInfo(directoryContent.GetVirtualOrPhysicalPathOrNull()));
}
}
}
}
要点
总结
动态API客户端
静态API客户端
最后
ABPVNEXT框架 QQ交流群:655362692
Abp Vnext 动态(静态)API客户端源码解析的更多相关文章
- Netty5客户端源码解析
Netty5客户端源码解析 今天来分析下netty5的客户端源码,示例代码如下: import io.netty.bootstrap.Bootstrap; import io.netty.channe ...
- Spring中AOP相关的API及源码解析
Spring中AOP相关的API及源码解析 本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configuration注解? 谈谈Spring ...
- FileZilla客户端源码解析
FileZilla客户端源码解析 FTP是TCP/IP协议组的协议,有指令通路和数据通路两条通道.一般来说,FTP标准命令TCP端口号是21,Port方式数据传输端口是20. FileZilla作为p ...
- JDK1.8 动态代理机制及源码解析
动态代理 a) jdk 动态代理 Proxy, 核心思想:通过实现被代理类的所有接口,生成一个字节码文件后构造一个代理对象,通过持有反射构造被代理类的一个实例,再通过invoke反射调用被代理类实例的 ...
- 自定义Visual Studio.net Extensions 开发符合ABP vnext框架代码生成插件[附源码]
介绍 我很早之前一直在做mvc5 scaffolder的开发功能做的已经非常完善,使用代码对mvc5的项目开发效率确实能成倍的提高,就算是刚进团队的新成员也能很快上手,如果你感兴趣 可以参考 http ...
- .Net Core 中间件之静态文件(StaticFiles)源码解析
一.介绍 在介绍静态文件中间件之前,先介绍 ContentRoot和WebRoot概念. ContentRoot:指web的项目的文件夹,包括bin和webroot文件夹. WebRoot:一般指Co ...
- Spark streaming技术内幕6 : Job动态生成原理与源码解析
原创文章,转载请注明:转载自 周岳飞博客(http://www.cnblogs.com/zhouyf/) Spark streaming 程序的运行过程是将DStream的操作转化成RDD的操作,S ...
- 6.Spark streaming技术内幕 : Job动态生成原理与源码解析
原创文章,转载请注明:转载自 周岳飞博客(http://www.cnblogs.com/zhouyf/) Spark streaming 程序的运行过程是将DStream的操作转化成RDD的操作, ...
- Zookeeper ZAB协议-客户端源码解析
因为在Zookeeper的底层源码中大量使用了NIO,线程和阻塞队列,在了解之前对前面这些有个基础会更容易理解 ZAB 是Zookeeper 的一种原子广播协议,用于支持Zookeeper 的分布式协 ...
- Soul API 网关源码解析 02
如何读开源项目:对着文档跑demo,对着demo看代码,懂一点就开始试,有问题了问社区. 今日目标: 1.运行examples下面的 http服务 2.学习文档,结合divde插件,发起http请求s ...
随机推荐
- 一文快速回顾 Java 操作数据库的方式-JDBC
前言 数据库的重要性不言而喻,不管是什么系统,什么应用软件,也不管它们是 Windows 上的应用程序,还是 Web 应用程序,存储(持久化)和查询(检索)数据都是核心的功能. 大家学习数据库时,比如 ...
- Javaweb学习笔记第八弹
继续MyBatis学习 SQL语句警告提示问题 产生原因:IDEA和数据库没有建立连接,不识别表的信息 解决方式:在IDEA中配置MySQL数据库连接 在IDEA的Maven项目中,如果想要直接通过I ...
- ElasticSearch 实现分词全文检索 - id、ids、prefix、fuzzy、wildcard、range、regexp 查询
目录 ElasticSearch 实现分词全文检索 - 概述 ElasticSearch 实现分词全文检索 - ES.Kibana.IK安装 ElasticSearch 实现分词全文检索 - Rest ...
- Python批量采集百度资讯文章,如何自定义采集日期范围
01 引言 大家好!蜡笔小曦有个朋友是做能源相关工作的,她想要有一个工具以天为单位持续地采集百度资讯中能源相关的文章进行留存和使用. 其中有个需求点是说能够自定义采集的开始日期和结束日期,这样更加灵活 ...
- 实践解析丨如何通过 WebAssembly 在 Web 进行实时视频人像分割
5 月 15 日,声网Agora 高级架构师高纯参加了 WebAssambly 社区举办的第一场线下活动"WebAssembly Meetup",并围绕声网Agora 在 Web ...
- 开源不易、安全慎行,中国软件如何走向文明?丨RTE 技术环境月报 202205
各位开发者小伙伴: 这里是 2022 年第 5 期的 RTE<技术环境月报>--致力于成为对大家"有用"的 Highlight 看板--每月初通过 RTC 开发者社区( ...
- Mathematica做数字图像处理中的ImageConvolve练习
目录 ImageConvolve练习 original right sobel 垂直边检测 top sobel 水平边检测 通过一个平方内核的卷积使图像变平滑 类似ConstantArray[1, { ...
- 【备忘录】 主定理 Master Theorem (转载)
备忘录 https://zhuanlan.zhihu.com/p/113406812
- 记一次 .NET 某传感器采集系统 线程爆高分析
一:背景 1. 讲故事 前段时间有位朋友微信找到我,说他的程序使用 hsl 库之后,采集 plc 时内存溢出,让我帮忙看一下怎么回事,哈哈,貌似是分析之旅中的第二次和 hsl 打交道,既然找到我,那就 ...
- JVM的垃圾收集算法
介绍分代收集理论和几种垃圾收集算法的思想及其发展过程. 分代收集理论 当前商业虚拟机的垃圾收集器,大多数都遵循了 "分代收集"(Generational Collection)的理 ...