项目

动态 C# API 客户端代理

ABP 可以动态创建 C# API 客户端代理来调用您的远程 HTTP 服务(REST 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 端点。

客户端代理生成

启动模板已在 HttpApi.Client 项目中预先配置了客户端代理生成。

如果您不使用启动模板,请在包含客户端项目的 .csproj 文件的文件夹中执行以下命令:

abp add-package Volo.Abp.Http.Client

如果您尚未安装,首先需要安装 ABP CLI。有关其他安装选项,请参阅包描述页面

现在,可以创建客户端代理了。示例:

[DependsOn(
    typeof(AbpHttpClientModule), //用于创建客户端代理
    typeof(BookStoreApplicationContractsModule) //包含应用服务接口
    )]
public class MyClientAppModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        //创建动态客户端代理
        context.Services.AddHttpClientProxies(
            typeof(BookStoreApplicationContractsModule).Assembly
        );
    }
}

AddHttpClientProxies 方法获取一个程序集,查找给定程序集中的所有服务接口,创建并注册代理类。

端点配置

默认情况下,appsettings.json 文件中的 RemoteServices 部分用于获取远程服务地址。最简单的配置如下所示:

{
  "RemoteServices": {
    "Default": {
      "BaseUrl": "http://localhost:53929/"
    } 
  } 
}

更详细的配置请参见下面的“AbpRemoteServiceOptions”部分。

使用

使用起来很简单。只需在客户端应用程序代码中注入服务接口:

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 服务接口。每当客户端调用服务方法时,动态客户端代理实现都会进行 HTTP 调用。

IHttpClientProxy 接口

虽然您可以像上面那样注入 IBookAppService 来使用客户端代理,但为了更显式的用法,您可以注入 IHttpClientProxy<IBookAppService>。在这种情况下,您将使用 IHttpClientProxy<T> 接口的 Service 属性。

配置

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/"
    } 
  } 
}

AddHttpClientProxies 方法可以获取一个额外的参数来指定远程服务名称。示例:

context.Services.AddHttpClientProxies(
    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);
    }
}

作为默认服务

当您为 IBookAppService 创建服务代理时,可以直接注入 IBookAppService 来使用代理客户端(如使用部分所示)。您可以将 asDefaultServices: false 传递给 AddHttpClientProxies 方法来禁用此功能。

context.Services.AddHttpClientProxies(
    typeof(BookStoreApplicationContractsModule).Assembly,
    asDefaultServices: false
);

仅当您的应用程序已经有一个服务的实现,并且您不希望用客户端代理覆盖/替换另一个实现时,才需要使用 asDefaultServices: false

如果禁用了 asDefaultServices,则只能使用 IHttpClientProxy<T> 接口来使用客户端代理。请参见上面的 IHttpClientProxy 接口部分。

重试/失败逻辑与 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 方法。

另请参阅

在本文档中