ABP Vnext UI 原理 自动API 和C#动态api客户端
自动Api控制器
当你从领域层以及数据持久层中查询出了数据并且 将他们转交给应用层进行显示时 你发现 显示之前经过HttpApi层的中转 还要托管再Web项目中
1.手动api控制器
第一步 为什么要有这个Httpapi层 UI界面为了实现应用层接口的通用 如果将web何应用层直接依赖 第三方就没有可用的直接webapi可以调用 你发现你还需要在写一个webapi 所以我们应该创建一个专门的接口层让web直接去托管他 这样不仅web可以直接快速部署也可以供第三方直接访问接口
第二步 在Httpapi层中创建控制器 依赖公共应用层 只依赖了接口 这时候你发现了 那么如果只依赖接口是肯定不行的 两种办法:插件加载实现类 以及 web托管(我们这次选择这个 并用httpapi进行暴露接口在host托管项目中你可以看见依赖了实现)
我想强调一点的是 这其实是一种延迟的思想
为什么需要把业务上的控制器独立出来成一个项目 而不是直接和web主机的控制器放在一起
业务角度: 原因就是为了做成模块化 为了在所有模块中进行复用 强调一点 模块化就是为了复用 如果和主机放在一起 就不能复用 所以我们只能把他做成独立的项目拆分出来
代码角度:复用控制器 在abp框架中 通过Abp.ASP.NETCOREMVC 模块复用 我们现在已知的httpapi的作用是 UI将商品给到hhtpapi 然后通过他委托给servers逻辑 在这里 我们是不是可以直接通过servers 就能生成api呢?通过他的名称 生成 这样我们就不用自己写控制器了
第三步:自动api的生成:在应用实现层中 继承 IRemoteService 接口 即可使用abp 自动api控制器 如果你不想让某个服务暴露 你可以给某个服务上 加上 特性 [RemoteService(IsEnabled =false)] 默认为True 即可
第四步 解析 abp是如何 通过applicaitionserver 自动生成控制:在托管主机的模块类中 有代码
1 private void ConfigureConventionalControllers() 2 { 3 Configure<AbpAspNetCoreMvcOptions>(options => 4 { 5 options.ConventionalControllers.Create(typeof(EBusinessApplicationModule).Assembly, options => { 6 7 options.RootPath = "My.EBusiness"; 8 }); 9 }); 10 }
加载当前项目自动生成控制器
options.RootPath = "My.EBusiness"; 此选项可以规定api名称

具体加载 依赖Application 通过自动api控制器加载方法 把servers 转化为控制器 运行一下 红框中就是自动生成的控制器 以及指定名称

我们看源码 究竟是如何生成的?
我们找到 Volo.Abp.Asp.NetCore.MVC--》Volo->Abp->ASPNETCORE->MVC->Conventions 在该模块中 有生成api的各种约定
当你的servers继承了 IRemoteService 接口时 他会将该类中 方法 以Get post Update delete 生成

Get Post Put Delet 方法 截取具体方法名 做成api的名称 以及获取相应的参数 /api/app/product 其中api不能更改 app可以通过上述 选项进行修改 后面具体方法名称以及方法参数 则是abo自动解析
思考 :既然这样能够自动生成 为何我们还要写一个Httpapit呢 明明可以自动生成 :
答案:为了能够实现个性化的api 所以 abp在此 既考虑了 复用 又考虑到了 个性化 以及特性对servers 过滤加上 [RemoteService(IsEnabled =false)]
动态C#Api客户端 abp核心模块 之 HttpApi.Client
什么是 动态C#Api客户端:对应用servers接口的动态代理 目的 解决客户端调用服务端复杂性的问题;
上一节中 我们暴露的是webapi接口 如果我们使用webapi调用服务时 每增加一个需求 就会增加一个 类去调用服务 者极大的违背了 开闭原则
第一步 为什么要有HttpClient 如果我们只想让我们的系统通过webapi 或者grpc/rpc进行远程调用 就需要使用HttpClient了 也就是说 如果我们整个项目只需要做成webapi的形式 那我们只需要选择一个就行了 并通过web托管 细心的同学可能想到 既然Httpapi 和Httpclient 都是可以做远程通信为何要重复做一件事呢? 实际上在Httpapi.Client 是直接可以使用RPC直接进行远程调用的 当然也可进行托管 而Httpapi则是 真正的Webapi项目( 页面上接收参数有复用 一对多的关系)
如何使用C#动态api客户端:创建一个 客户端以调用 我们的服务
实现C#api动态客户端的前提 1:OA项目 是个模块项目 2:让OA项目 依赖Httpapi.Client 并且在OA中引入Httpapi.Client
既然普通的Http调用违背了开闭原则 那我们该如何调用呢? 在Abp 的HttpApi.Client的加持下 使用IOC注入application.Contracts 的接口 直接进行具体的方法调用 就能完成调用
启动测试 发现 接口变成了代理类而不是原来的接口 这个代理类就是 Httpclient创建的
C#动态api客户端 原理分析:
原理可以总结一句话 当你调用接口时httpclient 拦截执并通过http形式执行你接口对应服务 可以通过日志输出可以明显看见 他替我们完成了最为繁琐的一步
1.OA--依赖--》Httpapi.Client -----依赖 --》application.Contracts 这样会让OA间接依赖 application.Contracts 才能使用IOC进行获取 但是 依赖的时接口 我们肯定不能直接调用;----------》动态代理
2.此时需要为接口创建代理:此时 abp 的HttpApi.Client基于application.Contracts 的接口 创建了代理(提前透露 所谓的代理 其实就是获取服务名称 然后拼接出真正的Url 使用HttpClient 去调用 获取返回结果)--------》拼接URL 并且通过httpclient进行调用
源码分析
首先动态代理的代码在项目HttpApi.Client 模块类中
其中
AddHttpClientProxies 就是创建代理的具体方法
public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddHttpClientProxies( typeof(EBusinessApplicationContractsModule).Assembly, RemoteServiceName ); }
查看源码 FRAMEWORK-->SRC--->Volo.Abp.Http.clien-->Extensions--->Dependencyinjetion-->
1 public static IServiceCollection AddStaticHttpClientProxies( 2 [NotNull] this IServiceCollection services, 3 [NotNull] Assembly assembly, 4 [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName) 5 { 6 Check.NotNull(services, nameof(assembly)); 7 8 var serviceTypes = assembly.GetTypes().Where(IsSuitableForClientProxying).ToArray(); 9 10 foreach (var serviceType in serviceTypes) 11 { 12 AddHttpClientFactory(services, remoteServiceConfigurationName); 13 14 services.Configure<AbpHttpClientOptions>(options => 15 { 16 options.HttpClientProxies[serviceType] = new HttpClientProxyConfig(serviceType, remoteServiceConfigurationName); 17 }); 18 } 19 20 return services; 21 }

1 public static IServiceCollection AddHttpClientProxy( 2 [NotNull] this IServiceCollection services, 3 [NotNull] Type type, 4 [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName, 5 bool asDefaultService = true) 6 { 7 Check.NotNull(services, nameof(services)); 8 Check.NotNull(type, nameof(type)); 9 Check.NotNullOrWhiteSpace(remoteServiceConfigurationName, nameof(remoteServiceConfigurationName)); 10 11 AddHttpClientFactory(services, remoteServiceConfigurationName); 12 13 services.Configure<AbpHttpClientOptions>(options => 14 { 15 options.HttpClientProxies[type] = new HttpClientProxyConfig(type, remoteServiceConfigurationName); 16 }); 17 18 var interceptorType = typeof(DynamicHttpProxyInterceptor<>).MakeGenericType(type); 19 services.AddTransient(interceptorType); 20 21 var interceptorAdapterType = typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(interceptorType); 22 23 var validationInterceptorAdapterType = 24 typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(typeof(ValidationInterceptor)); 25 26 if (asDefaultService) 27 { 28 services.AddTransient( 29 type, 30 serviceProvider => ProxyGeneratorInstance 31 .CreateInterfaceProxyWithoutTarget( 32 type, 33 (IInterceptor)serviceProvider.GetRequiredService(validationInterceptorAdapterType), 34 (IInterceptor)serviceProvider.GetRequiredService(interceptorAdapterType) 35 ) 36 ); 37 } 38 39 services.AddTransient( 40 typeof(IHttpClientProxy<>).MakeGenericType(type), 41 serviceProvider => 42 { 43 var service = ProxyGeneratorInstance 44 .CreateInterfaceProxyWithoutTarget( 45 type, 46 (IInterceptor)serviceProvider.GetRequiredService(validationInterceptorAdapterType), 47 (IInterceptor)serviceProvider.GetRequiredService(interceptorAdapterType) 48 ); 49 50 return Activator.CreateInstance( 51 typeof(HttpClientProxy<>).MakeGenericType(type), 52 service 53 ); 54 }); 55 56 return services; 57 }
AddHttpClientProxy
你会发现 这个方法 就做了一件事
context.Services.AddHttpClientProxies( typeof(EBusinessApplicationContractsModule).Assembly, RemoteServiceName
从
EBusinessApplicationContractsModule 此程序集中加载所有类型 通过for循环 调用AddHttpClientProxy方法 创建所有的代理 具体依赖来自于
Volo.abp.castle.core此模块将在后期文章中详解
这样我们可以想一想 C#Api动态客户端有什么缺陷?
不能跨语言 仅仅局限于ABp框架中 甚至是.NET5中
这就有了自动api了
动态客户端对内调用 自动api向外提供访问
三五八团楚云飞的小Tips 模块为了复用:业务模块组件之 数据库持久层
如何再数据持久层中切换数据库 首先你可以查看源码 abp封装了MIc 的EntitiyFramework框架
再AbpEntitiyFramework中 我们默认使用的是Mysql 如何切换到Sqlserver 只需要下载组件 然后再数据持久层
修改 Options.UseSqlserver();即可