本文内容
显示另外 2 个
备注
此 API 是实验性的。 它可能会在后续版本的库中更改,并且无法保证向后兼容性。
HttpClient是使用 REST API 的好方法,但存在挑战。 其中一个挑战是需要写入以使用 API 的样板代码量。 本文介绍了如何使用Microsoft.Extensions.Http.AutoClient NuGet 包修饰接口并生成 HTTP 客户端依赖项。 AutoClient 的基础源生成器生成接口的实现,以及用于将其注册到依赖项注入容器中的扩展方法。 此外,AutoClient 会为每个 HTTP 请求生成遥测数据,该请求随Microsoft.Extensions.Http.Telemetry一起发送。
使用 AutoClientAttribute
AutoClientAttribute负责触发 AutoClient 生成器以发出修饰接口的相应实现。 它接受要从IHttpClientFactory中检索的HttpClient的httpClientName
。 请考虑以下接口定义:
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(httpClientName: "GeneratedClient")]
public interface IProductClient
{
}
提示
接口名称必须以I
开头。 该名称去除了前导I
,并用作遥测RequestMetadata的RequestMetadata.DependencyName。 如果名称以Api
或Client
结尾,则排除这些名称。 例如,如果接口命名为IProductClient
,则依赖项名称为Product
。
要替代计算的依赖项名称,请使用AutoClientAttribute的customDependencyName
参数。
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(
httpClientName: "GeneratedClient",
customDependencyName: "Widget Service")]
public interface IWidgetClient
{
}
备注
上述两个代码示例都会导致编译警告,因为接口未定义任何 HTTP 方法。 该警告由 AutoClient 生成器发出,提醒发出实现是无用的。
使用谓词属性定义 HTTP 方法
空接口不是有用的抽象。 要定义 HTTP 方法,必须使用 HTTP 谓词属性。 每个 HTTP 方法都会返回Task<TResult>,其中T
是以下类型之一:
当 HTTP 响应的内容类型未application/json
且方法的返回类型不是Task<string>
时,会引发异常。
HTTP 谓词属性
使用以下属性之一定义 HTTP 方法:
- Microsoft.Extensions.Http.AutoClient.GetAttribute
- Microsoft.Extensions.Http.AutoClient.PostAttribute
- Microsoft.Extensions.Http.AutoClient.PutAttribute
- Microsoft.Extensions.Http.AutoClient.PatchAttribute
- Microsoft.Extensions.Http.AutoClient.DeleteAttribute
- Microsoft.Extensions.Http.AutoClient.HeadAttribute
- Microsoft.Extensions.Http.AutoClient.OptionsAttribute
每个属性都需要path
参数,该参数路由到基础 REST API,并且它应相对于HttpClient.BaseAddress。 path
不能包含查询字符串参数,而是使用QueryAttribute。 从遥测的角度来看,path
用作RequestMetadata.RequestRoute。
使用任何谓词属性修饰的 HTTP 方法必须具有CancellationToken参数,并且它应是定义的最后一个参数。 CancellationToken
参数用于取消 HTTP 请求。
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient("GeneratedClient")]
public interface IUserClient
{
[Get("/api/users")]
public Task<User[]> GetUsersAsync(
CancellationToken cancellationToken = default);
}
前面的代码:
- 使用GetAttribute定义 HTTP 方法。
path
为/api/users
。- 该方法返回Task<TResult>,其中
T
是User[]
。 - 此方法接受可选的CancellationToken参数,该参数在未提供参数时分配给
default
。
路由参数
URL 可能包含路由参数,例如"/api/users/{userId}"
。 要定义路由参数,方法还必须接受名称相同的参数(本例中为userId
):
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(nameof(IRouteParameterUserClient), "User Service")]
public interface IRouteParameterUserClient
{
[Get("/api/users/{userId}")]
public Task<User> GetUserAsync(
string userId,
CancellationToken cancellationToken = default);
}
在上述代码中:
GetUserAsync
方法具有名为userId
的路由参数。userId
参数用于请求的path
,替换{userId}
占位符。
遥测请求名称
方法名称用作RequestMetadata.RequestName。 如果方法名称包含Async
后缀,会将其删除。 例如,名为GetUsersAsync
的方法计算为"GetUsers"
。
要替代名称,请使用 HTTP 谓词属性的每个属性的RequestName
属性。
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(nameof(IRequestNameUserClient), "User Service")]
public interface IRequestNameUserClient
{
[Get("/api/users", RequestName = "CustomRequestName")]
public Task<List<User>> GetUsersAsync(
CancellationToken cancellationToken = default);
}
HTTP 有效负载
要使用请求发送 HTTP 有效负载,请在方法的参数上使用BodyAttribute。 如果不向其传递任何参数,它会将内容类型视为 JSON,在发送之前对参数进行序列化。 否则,请定义显式BodyContentType并在BodyAttribute内使用它。
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(nameof(IPayloadUserClient), "User Service")]
public interface IPayloadUserClient
{
[Post("/api/users")]
public Task<User> CreateUserAsync(
// The content type is JSON
// The parameter is serialized before sending
[Body] User user,
CancellationToken cancellationToken = default);
[Put("/api/users/{userId}/displayName")]
public Task<User> UpdateDisplayNameAsync(
string userId,
// The content type is text/plain
// The parameter is sent as is
[Body(BodyContentType.TextPlain)] string displayName,
CancellationToken cancellationToken = default);
}
HTTP 头
可通过两种方式使用 HTTP 请求发送标头。 其中一个方式最适合永不更改值的标头(静态标头)。 另一种方式是基于方法的参数更改的标头。
静态标头
要定义静态标头,请在接口定义上使用StaticHeaderAttribute。 将标头名称和值传递给其构造函数。
还可以在方法中一起使用多个StaticHeaderAttribute。 在方法上使用StaticHeader
属性时,该 HTTP 标头仅针对该方法发送,而接口级StaticHeader
属性针对所有方法发送。
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(nameof(IStaticHeaderUserClient), "User Service")]
[StaticHeader("X-ForAllRequests", "GlobalHeaderValue")]
public interface IStaticHeaderUserClient
{
[Get("/api/users")]
[StaticHeader("X-ForJustThisRequest", "RequestHeaderValue")]
public Task<List<User>> GetUsersAsync(
CancellationToken cancellationToken = default);
}
参数标头
使用HeaderAttribute定义基于参数的标头,可以从方法的属性接收标头的值。 将标头名称传递给其构造函数。
该参数可以是任何类型。 当标头类型不是string
时,对参数的值调用.ToString()
方法。
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(nameof(IParameterHeaderUserClient), "User Service")]
public interface IParameterHeaderUserClient
{
[Get("/api/users")]
public Task<List<User>> GetUsersAsync(
[Header("X-MyHeader")] string myHeader,
CancellationToken cancellationToken = default);
}
查询参数
使用方法参数上的QueryAttribute定义查询参数。 所有类型都有效,并且查询值依赖于.ToString()
方法,以在不是string
类型时获取参数的值。
QueryAttribute.Key从参数的名称分配。
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(nameof(IQueryUserClient))]
public interface IQueryUserClient
{
[Get("/api/users")]
public Task<List<User>> GetUsersAsync(
[Query] string search,
CancellationToken cancellationToken = default);
}
GetUsersAsync
方法生成 HTTP 请求,其 URL 格式为/api/users?search={search}
。 此格式用作遥测的RequestMetadata.RequestRoute。
如果需要更改查询键,可以调用key
基于参数的构造函数,QueryAttribute(String)。
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(nameof(ICustomQueryUserClient))]
public interface ICustomQueryUserClient
{
[Get("/api/users")]
public Task<List<User>> GetUsersAsync(
[Query("customQueryKey")] string search,
CancellationToken cancellationToken = default);
}
GetUsersAsync
方法生成 HTTP 请求(URL 格式类似于/api/users?customQueryKey={customQueryKey}
),因为密钥名称被替代为customQueryKey
。
依赖项注入挂钩
除了接口的实现外,生成扩展方法以在依赖项注入容器中注册客户端。 生成的扩展方法的名称与接口名称相同,将前导I
替换为Add
。
例如,请考虑以下接口定义:
C#复制
using Microsoft.Extensions.Http.AutoClient;
[AutoClient(nameof(ICompleteUserClient))]
[StaticHeader("User-Agent", "dotnet-auto-client sample")]
public interface ICompleteUserClient
{
[Get("users")]
public Task<User[]> GetAllUsersAsync(
CancellationToken cancellationToken = default);
[Get("users")]
public Task<User[]> GetUserByNameAsync(
[Query] string name,
CancellationToken cancellationToken = default);
[Get("users/{userId}")]
public Task<User> GetUserByIdAsync(
int userId,
CancellationToken cancellationToken = default);
[Post("users")]
[StaticHeader("X-CustomHeader", "custom-value")]
public Task<HttpResponseMessage> CreateUserAsync(
[Body(BodyContentType.ApplicationJson)] User user,
CancellationToken cancellationToken = default);
[Delete("user/{userId}")]
public Task<User> DeleteUserAsync(
int userId,
[Header("If-None-Match")] string eTag,
CancellationToken cancellationToken = default);
}
虽然生成器发出ICompleteUserClient
接口的实现,但它还会在IServiceCollection
上生成AddCompleteUserClient
扩展方法。 请考虑以下示例Program.cs代码:
C#复制
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateEmptyApplicationBuilder(null);
// Add a named HTTP client "ICompleteUserClient".
builder.Services.AddHttpClient(nameof(ICompleteUserClient), options =>
{
options.BaseAddress = new("https://jsonplaceholder.typicode.com");
});
builder.Services.AddCompleteUserClient(options =>
{
options.JsonSerializerOptions = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true
};
});
builder.Services.AddSingleton<UserService>();
using IHost host = builder.Build();
UserService service = host.Services.GetRequiredService<UserService>();
await service.ProcessUsersAsync();
host.Run();
在前面的示例代码中:
- Host.CreateEmptyApplicationBuilder用于创建HostApplicationBuilder。
- 从HostApplicationBuilder.Services属性中检索IServiceCollection,以调用AddHttpClient(IServiceCollection, String, Action<HttpClient>)扩展方法。
- 使用要注册的HttpClient的名称和用于配置HttpClient实例的委托调用
AddHttpClient
扩展方法。
- 使用要注册的HttpClient的名称和用于配置HttpClient实例的委托调用
- 调用
AddCompleteUserClient
扩展方法以注册ICompleteUserClient
接口及其实现。
可以将客户端注入服务构造函数以使用客户端:
C#复制
using System.Net.Http.Json;
internal sealed class UserService(ICompleteUserClient userClient)
{
public async Task ProcessUsersAsync()
{
// Create a new user.
HttpResponseMessage response = await userClient.CreateUserAsync(new(
Id: null, /* Is populated upon successful HTTP POST when creating user */
Name: "Ada Lovelace",
Username: "ada.lovelace",
Email: "1st-computer-programmer@example.com",
Address: new(
Street: "123 Engineer Lane",
Suite: null,
City: "London",
ZipCode: "EC1A",
Geo: new(
Lat: 51.509865m, Lng: -0.118092m)),
Phone: "+1234567890",
Website: "www.example.com",
Company: new(
Name: "Babbage, LLC.",
CatchPhrase: "works on my machine",
Bs: "This is the future")));
User? createdUser = await response.Content.ReadFromJsonAsync<User>();
Console.WriteLine($"""
CreateUserAsync: Created user
'{createdUser}'...
""");
// Get user by id.
User receivedUser = await userClient.GetUserByIdAsync(7);
Console.WriteLine($"""
GetUserAsync: Received user
'{receivedUser}'...
""");
// Get list of all users.
User[] allUsers = await userClient.GetAllUsersAsync();
Console.WriteLine($"""
GetUsersAsync: Received a total of {allUsers.Length} users...
""");
}
}
有关详细信息,请参阅.NET 依赖项注入。