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

 

posted @ 2022-08-16 13:49  爱生活,爱代码  阅读(567)  评论(0编辑  收藏  举报