项目

静态 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 文件夹下生成以下文件:

generated-static-client-proxies

  • 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 文件夹下生成以下文件:

generated-static-client-proxies

  • 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 方法。

另请参阅

在本文档中