静态 C# API 客户端代理
ABP 可以生成用于调用远程 HTTP 服务(REST API)的 C# API 客户端代理代码。通过这种方式,您无需处理 HttpClient 和其他低级细节来调用远程服务并获取结果。
静态 C# 代理自动为您处理以下事宜:
- 通过考虑 HTTP 方法、路由、查询字符串参数、请求负载和其他细节,将 C# 方法调用 映射到远程服务器的 HTTP 调用。
- 通过向 HTTP 头添加访问令牌来验证 HTTP 客户端。
- 与 JSON 进行序列化和反序列化。
- 处理 HTTP API 版本控制。
- 将关联 ID、当前租户 ID 和当前文化区域添加到请求中。
- 正确处理服务器发送的错误消息并抛出适当的异常。
任何类型的 .NET 客户端都可以使用此系统来使用您的 HTTP API。
静态代理与动态客户端代理
ABP 提供两种类型的客户端代理生成系统。本文档解释了静态客户端代理,它会在您的开发时生成客户端代码。您也可以查看 动态 C# API 客户端代理 文档,以了解如何使用运行时生成的代理。
开发时(静态)客户端代理生成具有性能优势,因为它不需要在运行时获取 HTTP API 定义。但是,每当您更改 API 端点定义时,都应重新生成客户端代理代码。另一方面,动态客户端代理在运行时生成,并提供更便捷的开发体验。
服务接口
您的服务/控制器应实现一个在服务器和客户端之间共享的接口。因此,首先在一个共享库项目中定义服务接口,如果您使用启动模板创建了解决方案,则通常在 Application.Contracts 项目中定义。
示例:
public interface IBookAppService : IApplicationService
{
Task<List<BookDto>> GetListAsync();
}
您的接口应实现
IRemoteService接口以被自动发现。由于IApplicationService继承了IRemoteService接口,因此上面的IBookAppService满足此条件。
在您的服务应用程序中实现此类。您可以使用 自动 API 控制器系统 将服务公开为 REST API 端点。
带契约或不带契约
Without Contracts(不带契约)方式依赖于目标服务的 application.contracts 包,因此它们可以重用 DTO 和其他相关类。但是,当我们想要创建完全独立开发和部署的微服务时,这可能会有问题。我们希望即使不依赖目标服务的 application.contracts 包,也能使用静态代理生成。
With Contracts(带契约)方式会在客户端生成所有的类/枚举/其他类型(包括应用服务接口),这也是 generate-proxy 命令的默认行为。
客户端代理生成
首先,将 Volo.Abp.Http.Client nuget 包添加到您的客户端项目:
dotnet add package Volo.Abp.Http.Client
然后将 AbpHttpClientModule 依赖项添加到您的模块中:
[DependsOn(typeof(AbpHttpClientModule))] //添加依赖
public class MyClientAppModule : AbpModule
{
}
现在,可以配置应用程序以进行静态客户端代理生成。
带契约示例
[DependsOn(
typeof(AbpHttpClientModule), //用于创建客户端代理
typeof(AbpVirtualFileSystemModule) //虚拟文件系统
)]
public class MyClientAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 准备静态客户端代理生成
context.Services.AddStaticHttpClientProxies(
typeof(MyClientAppModule).Assembly
);
// 将生成的 app-generate-proxy.json 包含在虚拟文件系统中
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyClientAppModule>();
});
}
}
不带契约示例
[DependsOn(
typeof(AbpHttpClientModule), //用于创建客户端代理
typeof(AbpVirtualFileSystemModule), //虚拟文件系统
typeof(BookStoreApplicationContractsModule) //包含应用服务接口
)]
public class MyClientAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 准备静态客户端代理生成
context.Services.AddStaticHttpClientProxies(
typeof(BookStoreApplicationContractsModule).Assembly
);
// 将生成的 app-generate-proxy.json 包含在虚拟文件系统中
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyClientAppModule>();
});
}
}
AddStaticHttpClientProxies 方法获取一个程序集,在给定程序集中查找所有服务接口,并为静态客户端代理生成做准备。
应用程序启动模板 已为 动态 客户端代理生成预配置,位于
HttpApi.Client项目中。如果您想切换到静态客户端代理,请在您HttpApi.Client项目的模块类中将context.Services.AddHttpClientProxies更改为context.Services.AddStaticHttpClientProxies。
端点配置
默认情况下,使用 appsettings.json 文件中的 RemoteServices 部分来获取远程服务地址。最简单的配置如下所示:
{
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:53929/"
}
}
}
有关更详细的配置,请参阅下面的 AbpRemoteServiceOptions 部分。
代码生成
生成客户端代理代码时,服务器端必须已启动并正在运行。因此,请运行您的应用程序,使其在 端点配置 部分中配置的 BaseUrl 上提供 HTTP API。
在客户端项目(.csproj)的根文件夹中打开命令行终端,并键入以下命令:
带契约
abp generate-proxy -t csharp -u http://localhost:53929/
如果您尚未安装,应安装 ABP CLI。
此命令应在 ClientProxies 文件夹下生成以下文件:
BookClientProxy.Generated.cs是本例中实际生成的代理类。BookClientProxy是一个partial类,您可以在其中编写自定义代码(ABP 不会覆盖它)。IBookAppService.cs是应用服务接口。BookDto.cs是应用服务使用的 DTO 类。app-generate-proxy.json包含有关远程 HTTP 端点的信息,因此 ABP 可以正确执行 HTTP 请求。此文件必须在您的项目中配置为嵌入式资源,以便虚拟文件系统可以找到它。
不带契约
abp generate-proxy -t csharp -u http://localhost:53929/ --without-contracts
此命令应在 ClientProxies 文件夹下生成以下文件:
BookClientProxy.Generated.cs是本例中实际生成的代理类。BookClientProxy是一个partial类,您可以在其中编写自定义代码(ABP 不会覆盖它)。app-generate-proxy.json包含有关远程 HTTP 端点的信息,因此 ABP 可以正确执行 HTTP 请求。此文件必须在您的项目中配置为嵌入式资源,以便虚拟文件系统可以找到它。
generate-proxy命令仅为应用程序中定义的 API 生成代理。如果您正在开发一个模块化应用程序,可以指定-m(或--module)参数来指定要为其生成代理的模块。有关其他选项,请参阅 ABP CLI 文档中的 generate-proxy 部分。
使用方法
使用客户端代理非常简单。只需在客户端应用程序代码中注入服务接口:
public class MyService : ITransientDependency
{
private readonly IBookAppService _bookService;
public MyService(IBookAppService bookService)
{
_bookService = bookService;
}
public async Task DoItAsync()
{
var books = await _bookService.GetListAsync();
foreach (var book in books)
{
Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}");
}
}
}
此示例注入了上面定义的 IBookAppService 服务接口。每当客户端调用服务方法时,静态客户端代理实现都会进行 HTTP 调用。
配置
AbpRemoteServiceOptions
默认情况下,AbpRemoteServiceOptions 会自动从 appsettings.json 设置。或者,您可以在模块的 ConfigureServices 方法中配置它以进行设置或覆盖。示例:
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Configure<AbpRemoteServiceOptions>(options =>
{
options.RemoteServices.Default =
new RemoteServiceConfiguration("http://localhost:53929/");
});
//...
}
多个远程服务端点
上面的示例已配置了“Default”远程服务端点。您可能为不同的服务配置不同的端点(例如在微服务架构中,每个微服务都有不同的端点)。在这种情况下,您可以在配置文件中添加其他端点:
{
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:53929/"
},
"BookStore": {
"BaseUrl": "http://localhost:48392/"
}
}
}
AddStaticHttpClientProxies 方法可以接收一个额外的参数用于指定远程服务名称。示例:
context.Services.AddStaticHttpClientProxies(
typeof(BookStoreApplicationContractsModule).Assembly,
remoteServiceConfigurationName: "BookStore"
);
remoteServiceConfigurationName 参数与通过 AbpRemoteServiceOptions 配置的服务端点匹配。如果未定义 BookStore 端点,则回退到 Default 端点。
远程服务配置提供程序
在某些情况下,您可能需要获取特定远程服务的远程服务配置。为此,您可以使用 IRemoteServiceConfigurationProvider 接口。
示例:获取“BookStore”远程服务的远程服务配置
public class MyService : ITransientDependency
{
private readonly IRemoteServiceConfigurationProvider _remoteServiceConfigurationProvider;
public MyService(IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider)
{
_remoteServiceConfigurationProvider = remoteServiceConfigurationProvider;
}
public async Task GetRemoteServiceConfiguration()
{
var configuration = await _remoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync("BookStore");
Console.WriteLine(configuration.BaseUrl);
}
}
重试/失败逻辑与 Polly 集成
如果您想为客户端代理的失败远程 HTTP 调用添加重试逻辑,可以在模块类的 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))
)
);
});
});
}
此示例使用了 Microsoft.Extensions.Http.Polly 包。您还需要导入 Polly 命名空间 (using Polly;) 才能使用 WaitAndRetryAsync 方法。
抠丁客




