• C# 使用 REST API HTTP 客户端生成器


    本文内容

    1. 使用 AutoClientAttribute
    2. 使用谓词属性定义 HTTP 方法
    3. HTTP 有效负载
    4. HTTP 头

    显示另外 2 个

     备注

    API 是实验性的。 它可能会在后续版本的库中更改,并且无法保证向后兼容性。

    HttpClient是使用 REST API 的好方法,但存在挑战。 其中一个挑战是需要写入以使用 API 的样板代码量。 本文介绍了如何使用Microsoft.Extensions.Http.AutoClient NuGet 包修饰接口并生成 HTTP 客户端依赖项。 AutoClient 的基础源生成器生成接口的实现,以及用于将其注册到依赖项注入容器中的扩展方法。 此外,AutoClient 会为每个 HTTP 请求生成遥测数据,该请求随Microsoft.Extensions.Http.Telemetry一起发送。

    使用 AutoClientAttribute

    AutoClientAttribute负责触发 AutoClient 生成器以发出修饰接口的相应实现。 它接受要从IHttpClientFactory中检索的HttpClienthttpClientName。 请考虑以下接口定义:

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(httpClientName: "GeneratedClient")]
    3. public interface IProductClient
    4. {
    5. }

     提示

    接口名称必须以I开头。 该名称去除了前导I,并用作遥测RequestMetadataRequestMetadata.DependencyName。 如果名称以ApiClient结尾,则排除这些名称。 例如,如果接口命名为IProductClient,则依赖项名称为Product

    要替代计算的依赖项名称,请使用AutoClientAttributecustomDependencyName参数。

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(
    3. httpClientName: "GeneratedClient",
    4. customDependencyName: "Widget Service")]
    5. public interface IWidgetClient
    6. {
    7. }

     备注

    上述两个代码示例都会导致编译警告,因为接口未定义任何 HTTP 方法。 该警告由 AutoClient 生成器发出,提醒发出实现是无用的。

    使用谓词属性定义 HTTP 方法

    空接口不是有用的抽象。 要定义 HTTP 方法,必须使用 HTTP 谓词属性。 每个 HTTP 方法都会返回Task,其中T是以下类型之一:

    返回类型说明
    Task响应的原始内容以字符串形式返回。
    TaskT是任何可序列化的类型时,响应内容从 JSON 反序列化并返回。
    Task如果需要HttpResponseMessage本身(如从HttpClient.SendAsync中返回),请使用此类型。

    当 HTTP 响应的内容类型未application/json且方法的返回类型不是Task时,会引发异常。

    HTTP 谓词属性

    使用以下属性之一定义 HTTP 方法:

    每个属性都需要path参数,该参数路由到基础 REST API,并且它应相对于HttpClient.BaseAddress。 path不能包含查询字符串参数,而是使用QueryAttribute。 从遥测的角度来看,path用作RequestMetadata.RequestRoute

    使用任何谓词属性修饰的 HTTP 方法必须具有CancellationToken参数,并且它应是定义的最后一个参数。 CancellationToken参数用于取消 HTTP 请求。

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient("GeneratedClient")]
    3. public interface IUserClient
    4. {
    5. [Get("/api/users")]
    6. public Task GetUsersAsync(
    7. CancellationToken cancellationToken = default);
    8. }

    前面的代码:

    • 使用GetAttribute定义 HTTP 方法。
    • path 为 /api/users
    • 该方法返回Task,其中TUser[]
    • 此方法接受可选的CancellationToken参数,该参数在未提供参数时分配给default

    路由参数

    URL 可能包含路由参数,例如"/api/users/{userId}"。 要定义路由参数,方法还必须接受名称相同的参数(本例中为userId):

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(nameof(IRouteParameterUserClient), "User Service")]
    3. public interface IRouteParameterUserClient
    4. {
    5. [Get("/api/users/{userId}")]
    6. public Task GetUserAsync(
    7. string userId,
    8. CancellationToken cancellationToken = default);
    9. }

    在上述代码中:

    • GetUserAsync方法具有名为userId的路由参数。
    • userId参数用于请求的path,替换{userId}占位符。

    遥测请求名称

    方法名称用作RequestMetadata.RequestName。 如果方法名称包含Async后缀,会将其删除。 例如,名为GetUsersAsync的方法计算为"GetUsers"

    要替代名称,请使用 HTTP 谓词属性的每个属性的RequestName属性。

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(nameof(IRequestNameUserClient), "User Service")]
    3. public interface IRequestNameUserClient
    4. {
    5. [Get("/api/users", RequestName = "CustomRequestName")]
    6. public Task<List<User>> GetUsersAsync(
    7. CancellationToken cancellationToken = default);
    8. }

    HTTP 有效负载

    要使用请求发送 HTTP 有效负载,请在方法的参数上使用BodyAttribute。 如果不向其传递任何参数,它会将内容类型视为 JSON,在发送之前对参数进行序列化。 否则,请定义显式BodyContentType并在BodyAttribute内使用它。

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(nameof(IPayloadUserClient), "User Service")]
    3. public interface IPayloadUserClient
    4. {
    5. [Post("/api/users")]
    6. public Task<User> CreateUserAsync(
    7. // The content type is JSON
    8. // The parameter is serialized before sending
    9. [Body] User user,
    10. CancellationToken cancellationToken = default);
    11. [Put("/api/users/{userId}/displayName")]
    12. public Task<User> UpdateDisplayNameAsync(
    13. string userId,
    14. // The content type is text/plain
    15. // The parameter is sent as is
    16. [Body(BodyContentType.TextPlain)] string displayName,
    17. CancellationToken cancellationToken = default);
    18. }

    HTTP 头

    可通过两种方式使用 HTTP 请求发送标头。 其中一个方式最适合永不更改值的标头(静态标头)。 另一种方式是基于方法的参数更改的标头。

    静态标头

    要定义静态标头,请在接口定义上使用StaticHeaderAttribute。 将标头名称和值传递给其构造函数。

    还可以在方法中一起使用多个StaticHeaderAttribute。 在方法上使用StaticHeader属性时,该 HTTP 标头仅针对该方法发送,而接口级StaticHeader属性针对所有方法发送。

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(nameof(IStaticHeaderUserClient), "User Service")]
    3. [StaticHeader("X-ForAllRequests", "GlobalHeaderValue")]
    4. public interface IStaticHeaderUserClient
    5. {
    6. [Get("/api/users")]
    7. [StaticHeader("X-ForJustThisRequest", "RequestHeaderValue")]
    8. public Task> GetUsersAsync(
    9. CancellationToken cancellationToken = default);
    10. }

    参数标头

    使用HeaderAttribute定义基于参数的标头,可以从方法的属性接收标头的值。 将标头名称传递给其构造函数。

    该参数可以是任何类型。 当标头类型不是string时,对参数的值调用.ToString()方法。

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(nameof(IParameterHeaderUserClient), "User Service")]
    3. public interface IParameterHeaderUserClient
    4. {
    5. [Get("/api/users")]
    6. public Task> GetUsersAsync(
    7. [Header("X-MyHeader")] string myHeader,
    8. CancellationToken cancellationToken = default);
    9. }

    查询参数

    使用方法参数上的QueryAttribute定义查询参数。 所有类型都有效,并且查询值依赖于.ToString()方法,以在不是string类型时获取参数的值。

    QueryAttribute.Key从参数的名称分配。

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(nameof(IQueryUserClient))]
    3. public interface IQueryUserClient
    4. {
    5. [Get("/api/users")]
    6. public Task<List<User>> GetUsersAsync(
    7. [Query] string search,
    8. CancellationToken cancellationToken = default);
    9. }

    GetUsersAsync方法生成 HTTP 请求,其 URL 格式为/api/users?search={search}。 此格式用作遥测的RequestMetadata.RequestRoute

    如果需要更改查询键,可以调用key基于参数的构造函数,QueryAttribute(String)

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(nameof(ICustomQueryUserClient))]
    3. public interface ICustomQueryUserClient
    4. {
    5. [Get("/api/users")]
    6. public Task<List<User>> GetUsersAsync(
    7. [Query("customQueryKey")] string search,
    8. CancellationToken cancellationToken = default);
    9. }

    GetUsersAsync方法生成 HTTP 请求(URL 格式类似于/api/users?customQueryKey={customQueryKey}),因为密钥名称被替代为customQueryKey

    依赖项注入挂钩

    除了接口的实现外,生成扩展方法以在依赖项注入容器中注册客户端。 生成的扩展方法的名称与接口名称相同,将前导I替换为Add

    例如,请考虑以下接口定义:

    C#复制

    1. using Microsoft.Extensions.Http.AutoClient;
    2. [AutoClient(nameof(ICompleteUserClient))]
    3. [StaticHeader("User-Agent", "dotnet-auto-client sample")]
    4. public interface ICompleteUserClient
    5. {
    6. [Get("users")]
    7. public Task GetAllUsersAsync(
    8. CancellationToken cancellationToken = default);
    9. [Get("users")]
    10. public Task GetUserByNameAsync(
    11. [Query] string name,
    12. CancellationToken cancellationToken = default);
    13. [Get("users/{userId}")]
    14. public Task GetUserByIdAsync(
    15. int userId,
    16. CancellationToken cancellationToken = default);
    17. [Post("users")]
    18. [StaticHeader("X-CustomHeader", "custom-value")]
    19. public Task CreateUserAsync(
    20. [Body(BodyContentType.ApplicationJson)] User user,
    21. CancellationToken cancellationToken = default);
    22. [Delete("user/{userId}")]
    23. public Task DeleteUserAsync(
    24. int userId,
    25. [Header("If-None-Match")] string eTag,
    26. CancellationToken cancellationToken = default);
    27. }

    虽然生成器发出ICompleteUserClient接口的实现,但它还会在IServiceCollection上生成AddCompleteUserClient扩展方法。 请考虑以下示例Program.cs代码:

    C#复制

    1. using System.Text.Json;
    2. using Microsoft.Extensions.DependencyInjection;
    3. using Microsoft.Extensions.Hosting;
    4. HostApplicationBuilder builder = Host.CreateEmptyApplicationBuilder(null);
    5. // Add a named HTTP client "ICompleteUserClient".
    6. builder.Services.AddHttpClient(nameof(ICompleteUserClient), options =>
    7. {
    8. options.BaseAddress = new("https://jsonplaceholder.typicode.com");
    9. });
    10. builder.Services.AddCompleteUserClient(options =>
    11. {
    12. options.JsonSerializerOptions = new(JsonSerializerDefaults.Web)
    13. {
    14. PropertyNameCaseInsensitive = true
    15. };
    16. });
    17. builder.Services.AddSingleton<UserService>();
    18. using IHost host = builder.Build();
    19. UserService service = host.Services.GetRequiredService<UserService>();
    20. await service.ProcessUsersAsync();
    21. host.Run();

    在前面的示例代码中:

    可以将客户端注入服务构造函数以使用客户端:

    C#复制

    1. using System.Net.Http.Json;
    2. internal sealed class UserService(ICompleteUserClient userClient)
    3. {
    4. public async Task ProcessUsersAsync()
    5. {
    6. // Create a new user.
    7. HttpResponseMessage response = await userClient.CreateUserAsync(new(
    8. Id: null, /* Is populated upon successful HTTP POST when creating user */
    9. Name: "Ada Lovelace",
    10. Username: "ada.lovelace",
    11. Email: "1st-computer-programmer@example.com",
    12. Address: new(
    13. Street: "123 Engineer Lane",
    14. Suite: null,
    15. City: "London",
    16. ZipCode: "EC1A",
    17. Geo: new(
    18. Lat: 51.509865m, Lng: -0.118092m)),
    19. Phone: "+1234567890",
    20. Website: "www.example.com",
    21. Company: new(
    22. Name: "Babbage, LLC.",
    23. CatchPhrase: "works on my machine",
    24. Bs: "This is the future")));
    25. User? createdUser = await response.Content.ReadFromJsonAsync<User>();
    26. Console.WriteLine($"""
    27. CreateUserAsync: Created user
    28. '{createdUser}'...
    29. """);
    30. // Get user by id.
    31. User receivedUser = await userClient.GetUserByIdAsync(7);
    32. Console.WriteLine($"""
    33. GetUserAsync: Received user
    34. '{receivedUser}'...
    35. """);
    36. // Get list of all users.
    37. User[] allUsers = await userClient.GetAllUsersAsync();
    38. Console.WriteLine($"""
    39. GetUsersAsync: Received a total of {allUsers.Length} users...
    40. """);
    41. }
    42. }

    有关详细信息,请参阅.NET 依赖项注入

  • 相关阅读:
    UE4 C++ ActionRoguelike开发记录
    SQLZOO 7 More JOIN
    [Hive] CTE 通用表达式 WITH关键字
    【服务治理】服务熔断、服务降级、服务限流、流量削峰、错峰
    小程序毕设作品之微信二手交易小程序毕业设计成品(3)后台功能
    html与css知识点
    设计模式-适配器-笔记
    满级大牛 HuaWei 首次出这份 598 页【网络协议全彩手册】,建议大家收藏
    10款最赞的ROS机器人操作系统课程+为何ROS不是必须的
    通过这些API,开发者可以在自己的应用程序中嵌入电商功能,为用户提供便捷的购物体验
  • 原文地址:https://blog.csdn.net/lvmingzhou/article/details/134162860