ABP.vNext 可以创建 C# API 客户端动态代理来调用远程 HTTP 服务 (REST API)。使用这种方式你无需处理 HttpClient 和其他细节即可调用远程服务并获取结果。
在ABP.vNext中,Service/Controller 应该实现一个在服务器和客户端之间共享的接口。因此,首先在共享库项目中定义服务接口,如果使用启动模板创建了解决方案,通常在 Application.Contracts 项目中,如下定义:
public interface IBookAppService : IApplicationService
{
Task<List<BookDto>> GetListAsync();
}
生成客户端代理
首先,要生成Client Proxy,你需要先通过Install-Package Volo.Abp.Http.Client
命令引入Volo.Abp.Http.Client
包。
其次需要为Module添加AbpHttpClientModule
依赖:
[DependsOn(typeof(AbpHttpClientModule))] //add the dependency
public class MyClientAppModule : AbpModule
{
}
接着,使用AddHttpClientProxies
生成动态代理
[DependsOn(
typeof(AbpHttpClientModule), //used to create client proxies
typeof(BookStoreApplicationContractsModule) //contains the application service interfaces
)]
public class MyClientAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//Create dynamic client proxies
context.Services.AddHttpClientProxies(
typeof(BookStoreApplicationContractsModule).Assembly
);
}
}
Endpoint 配置
添加完Client Proxy后,程序如何知道所要请求的Url呢,ABP.vNext项目中,需要对Endpoint进行配置,在appsettings.json
文件中添加如下配置:
{
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:53929/"
}
}
}
以上方式是最简单的配置方式,使用AbpRemoteServiceOptions
可以进行一些复杂的配置。AbpRemoteServiceOptions
默认从 appsettings.json
自动设置。当然,你可以在Module的 ConfigureServices 方法中对其进行设置。
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Configure<AbpRemoteServiceOptions>(options =>
{
options.RemoteServices.Default =
new RemoteServiceConfiguration("http://localhost:53929/");
});
//...
}
多个Endpoint配置
如果项目需要调用多个不同的服务,可以在appsettings.json
中进行如下配置:
{
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:53929/"
},
"BookStore": {
"BaseUrl": "http://localhost:48392/"
}
}
}
AddHttpClientProxies
方法可以获取远程服务名称的附加参数。
context.Services.AddHttpClientProxies(
typeof(BookStoreApplicationContractsModule).Assembly,
remoteServiceConfigurationName: "BookStore"
);
remoteServiceConfigurationName
参数匹配 AbpRemoteServiceOptions
配置的服务端点。如果未定义 BookStore 端点,则将回退到默认端点。
如何使用Client Proxy
最直接的使用方式,直接使用构造函数注入。在你的Client项目中,不应该直接引用ApplicationService的具体实现,而是只引用Contracts项目即可,下面的 IBookAppService 将发起Http请求调用。
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}");
}
}
}
需要注意的是,虽然可以像上面那样注入 IBookAppService 以使用客户端代理,但如果你的项目中有某个服务实现了该接口,注入时你想使用本地的实现,这时候可以将AddHttpClientProxies
中的属性AddHttpClientProxies
设置为 false
来禁用。
但可能在某个时候又想调用远程的API时,可以通过注入 IHttpClientProxy
context.Services.AddHttpClientProxies(
typeof(BookStoreApplicationContractsModule).Assembly,
asDefaultServices: false
);
失败重试
如果要为Client Proxy的远程 HTTP 调用添加失败重试逻辑,可以在Module类的 PreConfigureServices 方法中配置 AbpHttpClientBuilderOptions,如下配置使用 Polly 库在失败时重试 3 次。
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpHttpClientBuilderOptions>(options =>
{
options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>
{
clientBuilder.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3,
i => TimeSpan.FromSeconds(Math.Pow(2, i))
)
);
});
});
}