ABP vNext系列文章04---DynamicClient动态代理
一、动态代理在ABP系统中的应用
1、它主要在做什么事件
之前开发系统想要在后台调用别的服务都是用HttpClient发起请求,在abp vnext中不需要我们这样做了,
你只要知道服务调用的接口方法,就像调用本地代码一样调用接口,abp会动态生成一个http请求
这样,我们就引出了一个概念叫:动态 C# API 客户端
定义:ABP可以自动创建C# API 客户端代理来调用远程HTTP服务(REST APIS).通过这种方式,你不需要通过 HttpClient
或者其他低级的HTTP功能调用远程服务并获取数据.
2、如何申明一个远程的api接口
定义一个接口,只有一个条件:必须实现IRemoteService
接口:
public interface IBookAppService : IApplicationService { Task<List<BookDto>> GetListAsync(); }
为了能自动被发现,你的接口需要实现IRemoteService
接口.由于IApplicationService
继承自IRemoteService
接口.所以IBookAppService
完全满足这个条件.
在你的服务中实现这个类,你可以使用auto API controller system将你的服务暴漏为一个REST API 端点.
3、添加 Volo.Abp.Http.Client nuget包
Install-Package Volo.Abp.Http.Client
增加的层为 HttpApi.Client
[DependsOn(typeof(AbpHttpClientModule))] //添加依赖 public class MyClientAppModule : AbpModule { }
4、创建客户端代理了.
例如:
[DependsOn( typeof(AbpHttpClientModule), //用来创建客户端代理 typeof(MyApplicationContractsModule) //包含应用服务接口 )] public class MyClientAppModule : AbpModule {
private const string ProductRemoteServiceName = "ProductService"; public override void ConfigureServices(ServiceConfigurationContext context) { //创建动态客户端代理 context.Services.AddHttpClientProxies( typeof(MyApplicationContractsModule).Assembly,ProductRemoteServiceName ); } }
AddHttpClientproxies
方法获得一个程序集,找到这个程序集中所有的服务接口,创建并注册代理类.我们所有要调用的接口都是程序集 MyApplicationContractsModule 中的。
5、修改配置文件
appsettings.json
文件中的RemoteServices
节点被用来设置默认的服务地址.下面是最简单的配置:
{ "RemoteServices": { "ProductService": { "BaseUrl": "http://localhost:8012/", "UseCurrentAccessToken": "true" }, } }
当设置 UseCurrentAccessToken 为true时说明请求是可以传送token的。那此时还要引用一个nuget包:Volo.Abp.Http.Client.IdentityModel,注意是要xxx.Host项目下引用,而不是xxx.HttpApi.Client层
并增加依赖:[DependsOn(typeof(AbpHttpClientIdentityModelModule))]
6、如何使用
可以直接在你的类中使用,就像调用本地接口一样,IBookAppService就是远程要调用的服务
public class MyService : ITransientDependency { private readonly IBookAppService _bookService; public MyService(IBookAppService bookService) { _bookService = bookService; } public async Task DoIt() { var books = await _bookService.GetListAsync(); foreach (var book in books) { Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); } } }
二、源码解析
在第一部分6节中,在使用注入接口IBookAppService时,在调用时其实是为我们生成一个代理接口去访问远程的api,也就是说当你调用接口时httpclient 拦截执并通过http形式执行你接口对应服务
我们找到和系统集成的地方,在项目HttpApi.Client 模块类中:
那我们就从这里开始源码的分析吧。此段代码是将模块中所有程序集和远程的地址传了进去,进入这个扩展方法AddHttpClientProxies,
扩展方法写在类ServiceCollectionHttpClientProxyExtensions中:
其中代码段:var serviceTypes = assembly.GetTypes().Where(IsSuitableForClientProxying).ToArray();就是从程序集中取出所有的符合要求的类,符合什么要求呢,我们再看看IsSuitableForClientProxying(ps:没想到方法在where中还可以这么调用,get了)
在这个方法中我们看到了一个非常亲切的接口IRemoteService,这就是我们自动api为什么要实现这个接口的原因了。
拿到我们所有符合要求的类后,通过foreach遍历,将符合要求的类,远程连接的地址继续往下传,调用方法AddHttpClientProxy,我们进这个方法看看做了些什么
可以看到,主要是将我们的接口,动态的去创建了一个实实,然后注入到容器中。
其中有一条经常用到的代码:typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(interceptorType);
作用是为泛型类指定泛型类型,也就是原来是AbpAsyncDeterminationInterceptor<T>,泛型T不明确,现在明确了为 interceptorType
有一段非常重要也是核心的代码:
该方法主要是给我们的接口类Type(例如:IBookAppService ),动态的创建了一个实现类,并注入到ioc中,这样,我们在其它类中注入时,就有了该实例。
动态创建类是在第三方库中实现的,比较复杂,暂不研究,有兴趣可以参考开源:Https://github.com/castleproject/Core