在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求Make HTTP requests using IHttpClientFactory in ASP.NET Core

作者:Glenn CondronRyan NowakSteve GordonRick AndersonKirk LarkinBy Glenn Condron, Ryan Nowak, Steve Gordon, Rick Anderson, and Kirk Larkin

可以注册 IHttpClientFactory 并将其用于配置和创建应用中的 HttpClient 实例。An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. IHttpClientFactory 的优势如下:IHttpClientFactory offers the following benefits:

  • 提供一个中心位置,用于命名和配置逻辑 HttpClient 实例。Provides a central location for naming and configuring logical HttpClient instances. 例如,可注册和配置名为 github 的客户端,使其访问 GitHubFor example, a client named github could be registered and configured to access GitHub. 可以注册一个默认客户端用于一般性访问。A default client can be registered for general access.
  • 通过 HttpClient 中的委托处理程序来编码出站中间件的概念。Codifies the concept of outgoing middleware via delegating handlers in HttpClient. 提供基于 Polly 的中间件的扩展,以利用 HttpClient 中的委托处理程序。Provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient.
  • 管理基础 HttpClientMessageHandler 实例的池和生存期。Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. 自动管理可避免手动管理 HttpClient 生存期时出现的常见 DNS(域名系统)问题。Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes.
  • (通过 ILogger)添加可配置的记录体验,以处理工厂创建的客户端发送的所有请求。Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

查看或下载示例代码如何下载)。View or download sample code (how to download).

此主题版本中的示例代码使用 System.Text.Json 来对 HTTP 响应中返回的 JSON 内容进行反序列化。The sample code in this topic version uses System.Text.Json to deserialize JSON content returned in HTTP responses. 对于使用 Json.NETReadAsAsync<T> 的示例,请使用版本选择器选择此主题的 2.x 版本。For samples that use Json.NET and ReadAsAsync<T>, use the version selector to select a 2.x version of this topic.

消耗模式Consumption patterns

在应用中可以通过以下多种方式使用 IHttpClientFactoryThere are several ways IHttpClientFactory can be used in an app:

最佳方法取决于应用要求。The best approach depends upon the app's requirements.

基本用法Basic usage

可以通过调用 AddHttpClient 来注册 IHttpClientFactoryIHttpClientFactory can be registered by calling AddHttpClient:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();
        // Remaining code deleted for brevity.

可以使用依赖项注入 (DI) 来请求 IHttpClientFactoryAn IHttpClientFactory can be requested using dependency injection (DI). 以下代码使用 IHttpClientFactory 来创建 HttpClient 实例:The following code uses IHttpClientFactory to create an HttpClient instance:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/aspnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            Branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }
    }
}

像前面的示例一样,使用 IHttpClientFactory 是重构现有应用的好方法。Using IHttpClientFactory like in the preceding example is a good way to refactor an existing app. 这不会影响 HttpClient 的使用方式。It has no impact on how HttpClient is used. 在现有应用中创建 HttpClient 实例的位置,使用对 CreateClient 的调用替换这些匹配项。In places where HttpClient instances are created in an existing app, replace those occurrences with calls to CreateClient.

命名客户端Named clients

在以下情况下,命名客户端是一个不错的选择:Named clients are a good choice when:

  • 应用需要 HttpClient 的许多不同用法。The app requires many distinct uses of HttpClient.
  • 许多 HttpClient 具有不同的配置。Many HttpClients have different configuration.

可以在 Startup.ConfigureServices 中注册时指定命名 HttpClient 的配置:Configuration for a named HttpClient can be specified during registration in Startup.ConfigureServices:

services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    // Github API versioning
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    // Github requires a user-agent
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

在上述代码中,客户端配置如下:In the preceding code the client is configured with:

  • 基址为 https://api.github.com/The base address https://api.github.com/.
  • 使用 GitHub API 需要的两个标头。Two headers required to work with the GitHub API.

CreateClientCreateClient

每次调用 CreateClient 时:Each time CreateClient is called:

  • 创建 HttpClient 的新实例。A new instance of HttpClient is created.
  • 调用配置操作。The configuration action is called.

要创建命名客户端,请将其名称传递到 CreateClient 中:To create a named client, pass its name into CreateClient:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "repos/aspnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            PullRequests = await JsonSerializer.DeserializeAsync
                    <IEnumerable<GitHubPullRequest>>(responseStream);
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

在上述代码中,请求不需要指定主机名。In the preceding code, the request doesn't need to specify a hostname. 代码可以仅传递路径,因为采用了为客户端配置的基址。The code can pass just the path, since the base address configured for the client is used.

类型化客户端Typed clients

类型化客户端:Typed clients:

  • 提供与命名客户端一样的功能,不需要将字符串用作密钥。Provide the same capabilities as named clients without the need to use strings as keys.
  • 在使用客户端时提供 IntelliSense 和编译器帮助。Provides IntelliSense and compiler help when consuming clients.
  • 提供单个位置来配置特定 HttpClient 并与其进行交互。Provide a single location to configure and interact with a particular HttpClient. 例如,可以使用单个类型化客户端:For example, a single typed client might be used:
    • 对于单个后端终结点。For a single backend endpoint.
    • 封装处理终结点的所有逻辑。To encapsulate all logic dealing with the endpoint.
  • 使用 DI 且可以被注入到应用中需要的位置。Work with DI and can be injected where required in the app.

类型化客户端在构造函数中接受 HttpClient 参数:A typed client accepts an HttpClient parameter in its constructor:

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        var response = await Client.GetAsync(
            "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<GitHubIssue>>(responseStream);
    }
}

若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。If you would like to see code comments translated to languages other than English, let us know in this GitHub discussion issue.

在上述代码中:In the preceding code:

  • 配置转移到了类型化客户端中。The configuration is moved into the typed client.
  • HttpClient 对象公开为公共属性。The HttpClient object is exposed as a public property.

可以创建特定于 API 的方法来公开 HttpClient 功能。API-specific methods can be created that expose HttpClient functionality. 例如,创建 GetAspNetDocsIssues 方法来封装代码以检索未解决的问题。For example, the GetAspNetDocsIssues method encapsulates code to retrieve open issues.

以下代码调用 Startup.ConfigureServices 中的 AddHttpClient 来注册类型化客户端类:The following code calls AddHttpClient in Startup.ConfigureServices to register a typed client class:

services.AddHttpClient<GitHubService>();

使用 DI 将类型客户端注册为暂时客户端。The typed client is registered as transient with DI. 在上述代码中,AddHttpClientGitHubService 注册为暂时性服务。In the preceding code, AddHttpClient registers GitHubService as a transient service. 此注册使用工厂方法执行以下操作:This registration uses a factory method to:

  1. 创建 HttpClient 的实例。Create an instance of HttpClient.
  2. 创建 GitHubService 的实例,将 HttpClient 的实例传入其构造函数。Create an instance of GitHubService, passing in the instance of HttpClient to its constructor.

可以直接插入或使用类型化客户端:The typed client can be injected and consumed directly:

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

可以在 Startup.ConfigureServices 中注册时指定类型化客户端的配置,而不是在类型化客户端的构造函数中指定:The configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

可以将 HttpClient 封装在类型化客户端中,The HttpClient can be encapsulated within a typed client. 定义一个在内部调用 HttpClient 实例的方法,而不是将其公开为属性:Rather than exposing it as a property, define a method which calls the HttpClient instance internally:

public class RepoService
{
    // _httpClient isn't exposed publicly
    private readonly HttpClient _httpClient;

    public RepoService(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<IEnumerable<string>> GetRepos()
    {
        var response = await _httpClient.GetAsync("aspnet/repos");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<string>>(responseStream);
    }
}

在上述代码中,HttpClient 存储在私有字段中。In the preceding code, the HttpClient is stored in a private field. 通过公共 GetRepos 方法访问 HttpClientAccess to the HttpClient is by the public GetRepos method.

生成的客户端Generated clients

IHttpClientFactory 可结合第三方库(例如 Refit)使用。IHttpClientFactory can be used in combination with third-party libraries such as Refit. Refit 是.NET 的 REST 库。Refit is a REST library for .NET. 它将 REST API 转换为实时接口。It converts REST APIs into live interfaces. RestService 动态生成该接口的实现,使用 HttpClient 进行外部 HTTP 调用。An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

定义了接口和答复来代表外部 API 及其响应:An interface and a reply are defined to represent the external API and its response:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

可以添加类型化客户端,使用 Refit 生成实现:A typed client can be added, using Refit to generate the implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddControllers();
}

可以在必要时使用定义的接口,以及由 DI 和 Refit 提供的实现:The defined interface can be consumed where necessary, with the implementation provided by DI and Refit:

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

出站请求中间件Outgoing request middleware

HttpClient 具有委托处理程序的概念,这些委托处理程序可以链接在一起,处理出站 HTTP 请求。HttpClient has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactoryIHttpClientFactory:

  • 简化定义应用于各命名客户端的处理程序。Simplifies defining the handlers to apply for each named client.

  • 支持注册和链接多个处理程序,以生成出站请求中间件管道。Supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. 每个处理程序都可以在出站请求前后执行工作。Each of these handlers is able to perform work before and after the outgoing request. 此模式:This pattern:

    • 类似于 ASP.NET Core 中的入站中间件管道。Is similar to the inbound middleware pipeline in ASP.NET Core.

    • 提供一种机制来管理有关 HTTP 请求的横切关注点,例如:Provides a mechanism to manage cross-cutting concerns around HTTP requests, such as:

      • 缓存caching
      • 错误处理error handling
      • 序列化serialization
      • 日志记录logging

创建委托处理程序:To create a delegating handler:

public class ValidateHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("X-API-KEY"))
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(
                    "You must supply an API key header called X-API-KEY")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

上述代码检查请求中是否存在 X-API-KEY 标头。The preceding code checks if the X-API-KEY header is in the request. 如果缺失 X-API-KEY,则返回 BadRequestIf X-API-KEY is missing, BadRequest is returned.

可使用 Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler 将多个处理程序添加到 HttpClient 的配置中:More than one handler can be added to the configuration for an HttpClient with Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ValidateHeaderHandler>();

    services.AddHttpClient("externalservice", c =>
    {
        // Assume this is an "external" service which requires an API KEY
        c.BaseAddress = new Uri("https://localhost:5001/");
    })
    .AddHttpMessageHandler<ValidateHeaderHandler>();

    // Remaining code deleted for brevity.

在上述代码中通过 DI 注册了 ValidateHeaderHandlerIn the preceding code, the ValidateHeaderHandler is registered with DI. IHttpClientFactory 为每个处理程序创建单独的 DI 作用域。The IHttpClientFactory creates a separate DI scope for each handler. 处理程序可依赖于任何作用域的服务。Handlers can depend upon services of any scope. 处理程序依赖的服务会在处置处理程序时得到处置。Services that handlers depend upon are disposed when the handler is disposed.

注册后可以调用 AddHttpMessageHandler,传入标头的类型。Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

可以按处理程序应该执行的顺序注册多个处理程序。Multiple handlers can be registered in the order that they should execute. 每个处理程序都会覆盖下一个处理程序,直到最终 HttpClientHandler 执行请求:Each handler wraps the next handler until the final HttpClientHandler executes the request:

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

使用以下方法之一将每个请求状态与消息处理程序共享:Use one of the following approaches to share per-request state with message handlers:

使用基于 Polly 的处理程序Use Polly-based handlers

IHttpClientFactory 与第三方库 Polly 集成。IHttpClientFactory integrates with the third-party library Polly. Polly 是适用于 .NET 的全面恢复和临时故障处理库。Polly is a comprehensive resilience and transient fault-handling library for .NET. 开发人员通过它可以表达策略,例如以流畅且线程安全的方式处理重试、断路器、超时、Bulkhead 隔离和回退。It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

提供了扩展方法,以实现将 Polly 策略用于配置的 HttpClient 实例。Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Polly 扩展支持将基于 Polly 的处理程序添加到客户端。The Polly extensions support adding Polly-based handlers to clients. Polly 需要 Microsoft.Extensions.Http.Polly NuGet 包。Polly requires the Microsoft.Extensions.Http.Polly NuGet package.

处理临时故障Handle transient faults

错误通常在暂时执行外部 HTTP 调用时发生。Faults typically occur when external HTTP calls are transient. AddTransientHttpErrorPolicy 允许定义一个策略来处理暂时性错误。AddTransientHttpErrorPolicy allows a policy to be defined to handle transient errors. 使用 AddTransientHttpErrorPolicy 配置的策略处理以下响应:Policies configured with AddTransientHttpErrorPolicy handle the following responses:

AddTransientHttpErrorPolicy 提供对 PolicyBuilder 对象的访问权限,该对象配置为处理表示可能的临时故障的错误:AddTransientHttpErrorPolicy provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient<UnreliableEndpointCallerService>()
        .AddTransientHttpErrorPolicy(p => 
            p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

    // Remaining code deleted for brevity.

上述代码中定义了 WaitAndRetryAsync 策略。In the preceding code, a WaitAndRetryAsync policy is defined. 请求失败后最多可以重试三次,每次尝试间隔 600 ms。Failed requests are retried up to three times with a delay of 600 ms between attempts.

动态选择策略Dynamically select policies

提供了扩展方法来添加基于 Polly 的处理程序,例如 AddPolicyHandlerExtension methods are provided to add Polly-based handlers, for example, AddPolicyHandler. 以下 AddPolicyHandler 重载检查请求以确定要应用的策略:The following AddPolicyHandler overload inspects the request to decide which policy to apply:

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

在上述代码中,如果出站请求为 HTTP GET,则应用 10 秒超时。In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. 其他所有 HTTP 方法应用 30 秒超时。For any other HTTP method, a 30-second timeout is used.

添加多个 Polly 处理程序Add multiple Polly handlers

这对嵌套 Polly 策略很常见:It's common to nest Polly policies:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

在上面的示例中:In the preceding example:

  • 添加了两个处理程序。Two handlers are added.
  • 第一个处理程序使用 AddTransientHttpErrorPolicy 添加重试策略。The first handler uses AddTransientHttpErrorPolicy to add a retry policy. 若请求失败,最多可重试三次。Failed requests are retried up to three times.
  • 第二个 AddTransientHttpErrorPolicy 调用添加断路器策略。The second AddTransientHttpErrorPolicy call adds a circuit breaker policy. 如果尝试连续失败了 5 次,则会阻止后续外部请求 30 秒。Further external requests are blocked for 30 seconds if 5 failed attempts occur sequentially. 断路器策略处于监控状态。Circuit breaker policies are stateful. 通过此客户端进行的所有调用都共享同样的线路状态。All calls through this client share the same circuit state.

从 Polly 注册表添加策略Add policies from the Polly registry

管理常用策略的一种方法是一次性定义它们并使用 PolicyRegistry 注册它们。An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry.

在以下代码中:In the following code:

public void ConfigureServices(IServiceCollection services)
{           
    var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(10));
    var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(30));
    
    var registry = services.AddPolicyRegistry();

    registry.Add("regular", timeout);
    registry.Add("long", longTimeout);
    
    services.AddHttpClient("regularTimeoutHandler")
        .AddPolicyHandlerFromRegistry("regular");

    services.AddHttpClient("longTimeoutHandler")
       .AddPolicyHandlerFromRegistry("long");

    // Remaining code deleted for brevity.

有关 IHttpClientFactory 和 Polly 集成的详细信息,请参阅 Polly WikiFor more information on IHttpClientFactory and Polly integrations, see the Polly wiki.

HttpClient 和生存期管理HttpClient and lifetime management

每次对 IHttpClientFactory 调用 CreateClient 都会返回一个新 HttpClient 实例。A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. 每个命名客户端都创建一个 HttpMessageHandlerAn HttpMessageHandler is created per named client. 工厂管理 HttpMessageHandler 实例的生存期。The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory 将工厂创建的 HttpMessageHandler 实例汇集到池中,以减少资源消耗。IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. 新建 HttpClient 实例时,可能会重用池中的 HttpMessageHandler 实例(如果生存期尚未到期的话)。An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

由于每个处理程序通常管理自己的基础 HTTP 连接,因此需要池化处理程序。Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. 创建超出必要数量的处理程序可能会导致连接延迟。Creating more handlers than necessary can result in connection delays. 部分处理程序还保持连接无期限地打开,这样可以防止处理程序对 DNS(域名系统)更改作出反应。Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS (Domain Name System) changes.

处理程序的默认生存期为两分钟。The default handler lifetime is two minutes. 可在每个命名客户端上重写默认值:The default value can be overridden on a per named client basis:

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

    // Remaining code deleted for brevity.

HttpClient 实例通常可视为无需处置的 .NET 对象 。HttpClient instances can generally be treated as .NET objects not requiring disposal. 处置既取消传出请求,又保证在调用 Dispose 后无法使用给定的 HttpClient 实例。Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory 跟踪和处置 HttpClient 实例使用的资源。IHttpClientFactory tracks and disposes resources used by HttpClient instances.

保持各个 HttpClient 实例长时间处于活动状态是在 IHttpClientFactory 推出前使用的常见模式。Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. 迁移到 IHttpClientFactory 后,就无需再使用此模式。This pattern becomes unnecessary after migrating to IHttpClientFactory.

IHttpClientFactory 的替代项Alternatives to IHttpClientFactory

通过在启用了 DI 的应用中使用 IHttpClientFactory,可避免:Using IHttpClientFactory in a DI-enabled app avoids:

  • 通过共用 HttpMessageHandler 实例,解决资源耗尽问题。Resource exhaustion problems by pooling HttpMessageHandler instances.
  • 通过定期循环 HttpMessageHandler 实例,解决 DNS 过时问题。Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

此外,还有其他方法使用生命周期长的 SocketsHttpHandler 实例来解决上述问题。There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • 在应用启动时创建 SocketsHttpHandler 的实例,并在应用的整个生命周期中使用它。Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • 根据 DNS 刷新时间,将 PooledConnectionLifetime 配置为适当的值。Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • 根据需要,使用 new HttpClient(handler, disposeHandler: false) 创建 HttpClient 实例。Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

上述方法使用 IHttpClientFactory 解决问题的类似方式解决资源管理问题。The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • SocketsHttpHandlerHttpClient 实例之间共享连接。The SocketsHttpHandler shares connections across HttpClient instances. 此共享可防止套接字耗尽。This sharing prevents socket exhaustion.
  • SocketsHttpHandler 会根据 PooledConnectionLifetime 循环连接,避免出现 DNS 过时问题。The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookieCookies

共用 HttpMessageHandler 实例将导致共享 CookieContainer 对象。The pooled HttpMessageHandler instances results in CookieContainer objects being shared. 意外的 CookieContainer 对象共享通常会导致错误的代码。Unanticipated CookieContainer object sharing often results in incorrect code. 对于需要 Cookie 的应用,请考虑执行以下任一操作:For apps that require cookies, consider either:

  • 禁用自动 Cookie 处理Disabling automatic cookie handling
  • 避免 IHttpClientFactoryAvoiding IHttpClientFactory

调用 ConfigurePrimaryHttpMessageHandler 以禁用自动 Cookie 处理:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            UseCookies = false,
        };
    });

LoggingLogging

通过 IHttpClientFactory 创建的客户端记录所有请求的日志消息。Clients created via IHttpClientFactory record log messages for all requests. 在日志记录配置中启用合适的信息级别可以查看默认日志消息。Enable the appropriate information level in the logging configuration to see the default log messages. 仅在跟踪级别包含附加日志记录(例如请求标头的日志记录)。Additional logging, such as the logging of request headers, is only included at trace level.

用于每个客户端的日志类别包含客户端名称。The log category used for each client includes the name of the client. 例如,名为 MyNamedClient 的客户端记录类别为“System.Net.Http.HttpClient.MyNamedClient.LogicalHandler”的消息 。A client named MyNamedClient, for example, logs messages with a category of "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". 后缀为 LogicalHandler 的消息在请求处理程序管道外部发生。Messages suffixed with LogicalHandler occur outside the request handler pipeline. 在请求时,在管道中的任何其他处理程序处理请求之前记录消息。On the request, messages are logged before any other handlers in the pipeline have processed it. 在响应时,在任何其他管道处理程序接收响应之后记录消息。On the response, messages are logged after any other pipeline handlers have received the response.

日志记录还在请求处理程序管道内部发生。Logging also occurs inside the request handler pipeline. 在 MyNamedClient 示例中,这些消息的日志类别为“System.Net.Http.HttpClient.MyNamedClient.ClientHandler” 。In the MyNamedClient example, those messages are logged with the log category "System.Net.Http.HttpClient.MyNamedClient.ClientHandler". 在请求时,在所有其他处理程序运行后,以及刚好要发出请求之前记录消息。For the request, this occurs after all other handlers have run and immediately before the request is sent. 在响应时,此日志记录包含响应在通过处理程序管道被传递回去之前的状态。On the response, this logging includes the state of the response before it passes back through the handler pipeline.

在管道内外启用日志记录,可以检查其他管道处理程序做出的更改。Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. 这可能包含对请求标头的更改,或者对响应状态代码的更改。This may include changes to request headers or to the response status code.

通过在日志类别中包含客户端名称,可以对特定的命名客户端筛选日志。Including the name of the client in the log category enables log filtering for specific named clients.

配置 HttpMessageHandlerConfigure the HttpMessageHandler

控制客户端使用的内部 HttpMessageHandler 的配置是有必要的。It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

在添加命名客户端或类型化客户端时,会返回 IHttpClientBuilderAn IHttpClientBuilder is returned when adding named or typed clients. ConfigurePrimaryHttpMessageHandler 扩展方法可以用于定义委托。The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. 委托用于创建和配置客户端使用的主要 HttpMessageHandlerThe delegate is used to create and configure the primary HttpMessageHandler used by that client:

public void ConfigureServices(IServiceCollection services)
{            
    services.AddHttpClient("configured-inner-handler")
        .ConfigurePrimaryHttpMessageHandler(() =>
        {
            return new HttpClientHandler()
            {
                AllowAutoRedirect = false,
                UseDefaultCredentials = true
            };
        });

    // Remaining code deleted for brevity.

在控制台应用中使用 IHttpClientFactoryUse IHttpClientFactory in a console app

在控制台中,将以下包引用添加到项目中:In a console app, add the following package references to the project:

如下示例中:In the following example:

  • IHttpClientFactory 已在泛型主机的服务容器中注册。IHttpClientFactory is registered in the Generic Host's service container.
  • MyService 从服务创建客户端工厂实例,用于创建 HttpClientMyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient 用于检索网页。HttpClient is used to retrieve a webpage.
  • Main 可创建作用域来执行服务的 GetPage 方法,并将网页内容的前 500 个字符写入控制台。Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();

                logger.LogError(ex, "An error occurred.");
            }
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

标头传播中间件Header propagation middleware

标头传播是一个 ASP.NET Core 中间件,可将 HTTP 标头从传入请求传播到传出 HTTP 客户端请求。Header propagation is an ASP.NET Core middleware to propagate HTTP headers from the incoming request to the outgoing HTTP Client requests. 使用标头传播:To use header propagation:

  • 引用 Microsoft.AspNetCore.HeaderPropagation 包。Reference the Microsoft.AspNetCore.HeaderPropagation package.

  • Startup 中配置中间件和 HttpClientConfigure the middleware and HttpClient in Startup:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    
        services.AddHttpClient("MyForwardingClient").AddHeaderPropagation();
        services.AddHeaderPropagation(options =>
        {
            options.Headers.Add("X-TraceId");
        });
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseHttpsRedirection();
    
        app.UseHeaderPropagation();
    
        app.UseRouting();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  • 客户端在出站请求中包含配置的标头:The client includes the configured headers on outbound requests:

    var client = clientFactory.CreateClient("MyForwardingClient");
    var response = client.GetAsync(...);
    

其他资源Additional resources

作者:Glenn CondronRyan NowakSteve GordonBy Glenn Condron, Ryan Nowak, and Steve Gordon

可以注册 IHttpClientFactory 并将其用于配置和创建应用中的 HttpClient 实例。An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. 这能带来以下好处:It offers the following benefits:

  • 提供一个中心位置,用于命名和配置逻辑 HttpClient 实例。Provides a central location for naming and configuring logical HttpClient instances. 例如,可注册和配置 github 客户端,使其访问 GitHubFor example, a github client can be registered and configured to access GitHub. 可以注册一个默认客户端用于其他用途。A default client can be registered for other purposes.
  • 通过委托 HttpClient 中的处理程序整理出站中间件的概念,并提供适用于基于 Polly 的中间件的扩展来利用概念。Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • 管理基础 HttpClientMessageHandler 实例的池和生存期,避免在手动管理 HttpClient 生存期时出现常见的 DNS 问题。Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • (通过 ILogger)添加可配置的记录体验,以处理工厂创建的客户端发送的所有请求。Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

查看或下载示例代码如何下载View or download sample code (how to download)

消耗模式Consumption patterns

在应用中可以通过以下多种方式使用 IHttpClientFactoryThere are several ways IHttpClientFactory can be used in an app:

它们之间不存在严格的优先级。None of them are strictly superior to another. 最佳方法取决于应用的约束条件。The best approach depends upon the app's constraints.

基本用法Basic usage

Startup.ConfigureServices 方法中,通过在 IServiceCollection 上调用 AddHttpClient 扩展方法可以注册 IHttpClientFactoryThe IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection, inside the Startup.ConfigureServices method.

services.AddHttpClient();

注册后,在可以使用依赖关系注入 (DI) 注入服务的任何位置,代码都能接受 IHttpClientFactoryOnce registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). IHttpClientFactory 可用于创建 HttpClient 实例:The IHttpClientFactory can be used to create an HttpClient instance:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://api.github.com/repos/aspnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

以这种方式使用 IHttpClientFactory 适合重构现有应用。Using IHttpClientFactory in this fashion is a good way to refactor an existing app. 这不会影响 HttpClient 的使用方式。It has no impact on the way HttpClient is used. 在当前创建 HttpClient 实例的位置,使用对 CreateClient 的调用替换这些匹配项。In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

命名客户端Named clients

如果应用需要有许多不同的 HttpClient 用法(每种用法的配置都不同),可以视情况使用命名客户端 。If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. 可以在 HttpClient 中注册时指定命名 Startup.ConfigureServices 的配置。Configuration for a named HttpClient can be specified during registration in Startup.ConfigureServices.

services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    // Github API versioning
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    // Github requires a user-agent
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

上面的代码调用 AddHttpClient,同时提供名称“github” 。In the preceding code, AddHttpClient is called, providing the name github. 此客户端应用了一些默认配置,也就是需要基址和两个标头来使用 GitHub API。This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

每次调用 CreateClient 时,都会创建 HttpClient 的新实例,并调用配置操作。Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

要使用命名客户端,可将字符串参数传递到 CreateClientTo consume a named client, a string parameter can be passed to CreateClient. 指定要创建的客户端的名称:Specify the name of the client to be created:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "repos/aspnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            PullRequests = await response.Content
                .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

在上述代码中,请求不需要指定主机名。In the preceding code, the request doesn't need to specify a hostname. 可以仅传递路径,因为采用了为客户端配置的基址。It can pass just the path, since the base address configured for the client is used.

类型化客户端Typed clients

类型化客户端:Typed clients:

  • 提供与命名客户端一样的功能,不需要将字符串用作密钥。Provide the same capabilities as named clients without the need to use strings as keys.
  • 在使用客户端时提供 IntelliSense 和编译器帮助。Provides IntelliSense and compiler help when consuming clients.
  • 提供单个位置来配置特定 HttpClient 并与其进行交互。Provide a single location to configure and interact with a particular HttpClient. 例如,单个类型化客户端可能用于单个后端终结点,并封装此终结点的所有处理逻辑。For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • 使用 DI 且可以被注入到应用中需要的位置。Work with DI and can be injected where required in your app.

类型化客户端在构造函数中接受 HttpClient 参数:A typed client accepts an HttpClient parameter in its constructor:

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept", 
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent", 
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        var response = await Client.GetAsync(
            "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<GitHubIssue>>();

        return result;
    }
}

在上述代码中,配置转移到了类型化客户端中。In the preceding code, the configuration is moved into the typed client. HttpClient 对象公开为公共属性。The HttpClient object is exposed as a public property. 可以定义公开 HttpClient 功能的特定于 API 的方法。It's possible to define API-specific methods that expose HttpClient functionality. GetAspNetDocsIssues 方法从 GitHub 存储库封装查询和分析最新待解决问题所需的代码。The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

要注册类型化客户端,可在 Startup.ConfigureServices 中使用通用的 AddHttpClient 扩展方法,指定类型化客户端类:To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices, specifying the typed client class:

services.AddHttpClient<GitHubService>();

使用 DI 将类型客户端注册为暂时客户端。The typed client is registered as transient with DI. 可以直接插入或使用类型化客户端:The typed client can be injected and consumed directly:

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

根据你的喜好,可以在 Startup.ConfigureServices 中注册时指定类型化客户端的配置,而不是在类型化客户端的构造函数中指定:If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

可以将 HttpClient 完全封装在类型化客户端中。It's possible to entirely encapsulate the HttpClient within a typed client. 不是将它公开为属性,而是可以提供公共方法,用于在内部调用 HttpClientRather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

public class RepoService
{
    // _httpClient isn't exposed publicly
    private readonly HttpClient _httpClient;

    public RepoService(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<IEnumerable<string>> GetRepos()
    {
        var response = await _httpClient.GetAsync("aspnet/repos");

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<string>>();

        return result;
    }
}

在上述代码中,HttpClient 存储未私有字段。In the preceding code, the HttpClient is stored as a private field. 进行外部调用的所有访问都经由 GetRepos 方法。All access to make external calls goes through the GetRepos method.

生成的客户端Generated clients

IHttpClientFactory 可结合其他第三方库(例如 Refit)使用。IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit 是.NET 的 REST 库。Refit is a REST library for .NET. 它将 REST API 转换为实时接口。It converts REST APIs into live interfaces. RestService 动态生成该接口的实现,使用 HttpClient 进行外部 HTTP 调用。An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

定义了接口和答复来代表外部 API 及其响应:An interface and a reply are defined to represent the external API and its response:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

可以添加类型化客户端,使用 Refit 生成实现:A typed client can be added, using Refit to generate the implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("https://localhost:5001");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddMvc();
}

可以在必要时使用定义的接口,以及由 DI 和 Refit 提供的实现:The defined interface can be consumed where necessary, with the implementation provided by DI and Refit:

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

出站请求中间件Outgoing request middleware

HttpClient 已经具有委托处理程序的概念,这些委托处理程序可以链接在一起,处理出站 HTTP 请求。HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory 可以轻松定义处理程序并应用于每个命名客户端。The IHttpClientFactory makes it easy to define the handlers to apply for each named client. 它支持注册和链接多个处理程序,以生成出站请求中间件管道。It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. 每个处理程序都可以在出站请求前后执行工作。Each of these handlers is able to perform work before and after the outgoing request. 此模式类似于 ASP.NET Core 中的入站中间件管道。This pattern is similar to the inbound middleware pipeline in ASP.NET Core. 此模式提供了一种用于管理围绕 HTTP 请求的横切关注点的机制,包括缓存、错误处理、序列化以及日志记录。The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

要创建处理程序,请定义一个派生自 DelegatingHandler 的类。To create a handler, define a class deriving from DelegatingHandler. 重写 SendAsync 方法,在将请求传递至管道中的下一个处理程序之前执行代码:Override the SendAsync method to execute code before passing the request to the next handler in the pipeline:

public class ValidateHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("X-API-KEY"))
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(
                    "You must supply an API key header called X-API-KEY")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

上述代码定义了基本处理程序。The preceding code defines a basic handler. 它检查请求中是否包含 X-API-KEY 头。It checks to see if an X-API-KEY header has been included on the request. 如果标头缺失,它可以避免 HTTP 调用,并返回合适的响应。If the header is missing, it can avoid the HTTP call and return a suitable response.

在注册期间可将一个或多个标头添加到 HttpClient 的配置中。During registration, one or more handlers can be added to the configuration for an HttpClient. 此任务通过 IHttpClientBuilder 上的扩展方法完成。This task is accomplished via extension methods on the IHttpClientBuilder.

services.AddTransient<ValidateHeaderHandler>();

services.AddHttpClient("externalservice", c =>
{
    // Assume this is an "external" service which requires an API KEY
    c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();

在上述代码中通过 DI 注册了 ValidateHeaderHandlerIn the preceding code, the ValidateHeaderHandler is registered with DI. IHttpClientFactory 为每个处理程序创建单独的 DI 作用域。The IHttpClientFactory creates a separate DI scope for each handler. 处理程序可依赖于任何作用域的服务。Handlers are free to depend upon services of any scope. 处理程序依赖的服务会在处置处理程序时得到处置。Services that handlers depend upon are disposed when the handler is disposed.

注册后可以调用 AddHttpMessageHandler,传入标头的类型。Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

可以按处理程序应该执行的顺序注册多个处理程序。Multiple handlers can be registered in the order that they should execute. 每个处理程序都会覆盖下一个处理程序,直到最终 HttpClientHandler 执行请求:Each handler wraps the next handler until the final HttpClientHandler executes the request:

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

使用以下方法之一将每个请求状态与消息处理程序共享:Use one of the following approaches to share per-request state with message handlers:

  • 使用 HttpRequestMessage.Properties 将数据传递到处理程序。Pass data into the handler using HttpRequestMessage.Properties.
  • 使用 IHttpContextAccessor 访问当前请求。Use IHttpContextAccessor to access the current request.
  • 创建自定义 AsyncLocal 存储对象以传递数据。Create a custom AsyncLocal storage object to pass the data.

使用基于 Polly 的处理程序Use Polly-based handlers

IHttpClientFactory 与一个名为 Polly 的热门第三方库集成。IHttpClientFactory integrates with a popular third-party library called Polly. Polly 是适用于 .NET 的全面恢复和临时故障处理库。Polly is a comprehensive resilience and transient fault-handling library for .NET. 开发人员通过它可以表达策略,例如以流畅且线程安全的方式处理重试、断路器、超时、Bulkhead 隔离和回退。It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

提供了扩展方法,以实现将 Polly 策略用于配置的 HttpClient 实例。Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Polly 扩展:The Polly extensions:

  • 支持将基于 Polly 的处理程序添加到客户端。Support adding Polly-based handlers to clients.
  • 安装了 Microsoft.Extensions.Http.Polly NuGet 包后可使用该扩展。Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. ASP.NET Core 共享框架中不包括该包。The package isn't included in the ASP.NET Core shared framework.

处理临时故障Handle transient faults

大多数常见错误在暂时执行外部 HTTP 调用时发生。Most common faults occur when external HTTP calls are transient. 包含了一种简便的扩展方法,该方法名为 AddTransientHttpErrorPolicy,允许定义策略来处理临时故障。A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. 使用这种扩展方法配置的策略可以处理 HttpRequestException、HTTP 5xx 响应以及 HTTP 408 响应。Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

AddTransientHttpErrorPolicy 扩展可在 Startup.ConfigureServices 内使用。The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. 该扩展可以提供 PolicyBuilder 对象的访问权限,该对象配置为处理表示可能的临时故障的错误:The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

services.AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

上述代码中定义了 WaitAndRetryAsync 策略。In the preceding code, a WaitAndRetryAsync policy is defined. 请求失败后最多可以重试三次,每次尝试间隔 600 ms。Failed requests are retried up to three times with a delay of 600 ms between attempts.

动态选择策略Dynamically select policies

存在其他扩展方法,可以用于添加基于 Polly 的处理程序。Additional extension methods exist which can be used to add Polly-based handlers. 这类扩展的其中一个是 AddPolicyHandler,它具备多个重载。One such extension is AddPolicyHandler, which has multiple overloads. 一个重载允许在定义要应用的策略时检查该请求:One overload allows the request to be inspected when defining which policy to apply:

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

在上述代码中,如果出站请求为 HTTP GET,则应用 10 秒超时。In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. 其他所有 HTTP 方法应用 30 秒超时。For any other HTTP method, a 30-second timeout is used.

添加多个 Polly 处理程序Add multiple Polly handlers

通过嵌套 Polly 策略来增强功能是很常见的:It's common to nest Polly policies to provide enhanced functionality:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

在上述示例中,添加两个处理程序。In the preceding example, two handlers are added. 第一个使用 AddTransientHttpErrorPolicy 扩展添加重试策略。The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. 若请求失败,最多可重试三次。Failed requests are retried up to three times. 第二个调用 AddTransientHttpErrorPolicy 添加断路器策略。The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. 如果尝试连续失败了五次,则会阻止后续外部请求 30 秒。Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. 断路器策略处于监控状态。Circuit breaker policies are stateful. 通过此客户端进行的所有调用都共享同样的线路状态。All calls through this client share the same circuit state.

从 Polly 注册表添加策略Add policies from the Polly registry

管理常用策略的一种方法是一次性定义它们并使用 PolicyRegistry 注册它们。An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. 提供了一种扩展方法,可以使用注册表中的策略添加处理程序:An extension method is provided which allows a handler to be added using a policy from the registry:

var registry = services.AddPolicyRegistry();

registry.Add("regular", timeout);
registry.Add("long", longTimeout);

services.AddHttpClient("regulartimeouthandler")
    .AddPolicyHandlerFromRegistry("regular");

在上面的代码中,两个策略在 PolicyRegistry 添加到 ServiceCollection 中时进行注册。In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. 若要使用注册表中的策略,请使用 AddPolicyHandlerFromRegistry 方法,同时传递要应用的策略的名称。To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

要进一步了解 IHttpClientFactory 和 Polly 集成,请参考 Polly WikiFurther information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient 和生存期管理HttpClient and lifetime management

每次对 IHttpClientFactory 调用 CreateClient 都会返回一个新 HttpClient 实例。A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. 每个命名的客户端都具有一个 HttpMessageHandlerThere's an HttpMessageHandler per named client. 工厂管理 HttpMessageHandler 实例的生存期。The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory 将工厂创建的 HttpMessageHandler 实例汇集到池中,以减少资源消耗。IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. 新建 HttpClient 实例时,可能会重用池中的 HttpMessageHandler 实例(如果生存期尚未到期的话)。An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

由于每个处理程序通常管理自己的基础 HTTP 连接,因此需要池化处理程序。Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. 创建超出必要数量的处理程序可能会导致连接延迟。Creating more handlers than necessary can result in connection delays. 部分处理程序还保持连接无期限地打开,这样可以防止处理程序对 DNS 更改作出反应。Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

处理程序的默认生存期为两分钟。The default handler lifetime is two minutes. 可在每个命名客户端上重写默认值。The default value can be overridden on a per named client basis. 要重写该值,请在创建客户端时在返回的 IHttpClientBuilder 上调用 SetHandlerLifetimeTo override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

services.AddHttpClient("extendedhandlerlifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

无需处置客户端。Disposal of the client isn't required. 处置既取消传出请求,又保证在调用 Dispose 后无法使用给定的 HttpClient 实例。Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory 跟踪和处置 HttpClient 实例使用的资源。IHttpClientFactory tracks and disposes resources used by HttpClient instances. HttpClient 实例通常可视为无需处置的 .NET 对象。The HttpClient instances can generally be treated as .NET objects not requiring disposal.

保持各个 HttpClient 实例长时间处于活动状态是在 IHttpClientFactory 推出前使用的常见模式。Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. 迁移到 IHttpClientFactory 后,就无需再使用此模式。This pattern becomes unnecessary after migrating to IHttpClientFactory.

IHttpClientFactory 的替代项Alternatives to IHttpClientFactory

通过在启用了 DI 的应用中使用 IHttpClientFactory,可避免:Using IHttpClientFactory in a DI-enabled app avoids:

  • 通过共用 HttpMessageHandler 实例,解决资源耗尽问题。Resource exhaustion problems by pooling HttpMessageHandler instances.
  • 通过定期循环 HttpMessageHandler 实例,解决 DNS 过时问题。Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

此外,还有其他方法使用生命周期长的 SocketsHttpHandler 实例来解决上述问题。There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • 在应用启动时创建 SocketsHttpHandler 的实例,并在应用的整个生命周期中使用它。Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • 根据 DNS 刷新时间,将 PooledConnectionLifetime 配置为适当的值。Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • 根据需要,使用 new HttpClient(handler, disposeHandler: false) 创建 HttpClient 实例。Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

上述方法使用 IHttpClientFactory 解决问题的类似方式解决资源管理问题。The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • SocketsHttpHandlerHttpClient 实例之间共享连接。The SocketsHttpHandler shares connections across HttpClient instances. 此共享可防止套接字耗尽。This sharing prevents socket exhaustion.
  • SocketsHttpHandler 会根据 PooledConnectionLifetime 循环连接,避免出现 DNS 过时问题。The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookieCookies

共用 HttpMessageHandler 实例将导致共享 CookieContainer 对象。The pooled HttpMessageHandler instances results in CookieContainer objects being shared. 意外的 CookieContainer 对象共享通常会导致错误的代码。Unanticipated CookieContainer object sharing often results in incorrect code. 对于需要 Cookie 的应用,请考虑执行以下任一操作:For apps that require cookies, consider either:

  • 禁用自动 Cookie 处理Disabling automatic cookie handling
  • 避免 IHttpClientFactoryAvoiding IHttpClientFactory

调用 ConfigurePrimaryHttpMessageHandler 以禁用自动 Cookie 处理:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            UseCookies = false,
        };
    });

LoggingLogging

通过 IHttpClientFactory 创建的客户端记录所有请求的日志消息。Clients created via IHttpClientFactory record log messages for all requests. 在日志记录配置中启用合适的信息级别可以查看默认日志消息。Enable the appropriate information level in your logging configuration to see the default log messages. 仅在跟踪级别包含附加日志记录(例如请求标头的日志记录)。Additional logging, such as the logging of request headers, is only included at trace level.

用于每个客户端的日志类别包含客户端名称。The log category used for each client includes the name of the client. 例如,名为“MyNamedClient” 的客户端使用 System.Net.Http.HttpClient.MyNamedClient.LogicalHandler 类别来记录消息。A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. 后缀为 LogicalHandler 的消息在请求处理程序管道外部发生。Messages suffixed with LogicalHandler occur outside the request handler pipeline. 在请求时,在管道中的任何其他处理程序处理请求之前记录消息。On the request, messages are logged before any other handlers in the pipeline have processed it. 在响应时,在任何其他管道处理程序接收响应之后记录消息。On the response, messages are logged after any other pipeline handlers have received the response.

日志记录还在请求处理程序管道内部发生。Logging also occurs inside the request handler pipeline. 在“MyNamedClient” 示例中,这些消息是针对日志类别 System.Net.Http.HttpClient.MyNamedClient.ClientHandler 进行记录。In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler. 在请求时,在所有其他处理程序运行后,以及刚好在通过网络发出请求之前记录消息。For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. 在响应时,此日志记录包含响应在通过处理程序管道被传递回去之前的状态。On the response, this logging includes the state of the response before it passes back through the handler pipeline.

在管道内外启用日志记录,可以检查其他管道处理程序做出的更改。Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. 例如,其中可能包含对请求标头的更改,或者对响应状态代码的更改。This may include changes to request headers, for example, or to the response status code.

通过在日志类别中包含客户端名称,可以在必要时对特定的命名客户端筛选日志。Including the name of the client in the log category enables log filtering for specific named clients where necessary.

配置 HttpMessageHandlerConfigure the HttpMessageHandler

控制客户端使用的内部 HttpMessageHandler 的配置是有必要的。It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

在添加命名客户端或类型化客户端时,会返回 IHttpClientBuilderAn IHttpClientBuilder is returned when adding named or typed clients. ConfigurePrimaryHttpMessageHandler 扩展方法可以用于定义委托。The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. 委托用于创建和配置客户端使用的主要 HttpMessageHandlerThe delegate is used to create and configure the primary HttpMessageHandler used by that client:

services.AddHttpClient("configured-inner-handler")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            AllowAutoRedirect = false,
            UseDefaultCredentials = true
        };
    });

在控制台应用中使用 IHttpClientFactoryUse IHttpClientFactory in a console app

在控制台中,将以下包引用添加到项目中:In a console app, add the following package references to the project:

如下示例中:In the following example:

  • IHttpClientFactory 已在泛型主机的服务容器中注册。IHttpClientFactory is registered in the Generic Host's service container.
  • MyService 从服务创建客户端工厂实例,用于创建 HttpClientMyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient 用于检索网页。HttpClient is used to retrieve a webpage.
  • Main 可创建作用域来执行服务的 GetPage 方法,并将网页内容的前 500 个字符写入控制台。Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();

                logger.LogError(ex, "An error occurred.");
            }
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

其他资源Additional resources

作者:Glenn CondronRyan NowakSteve GordonBy Glenn Condron, Ryan Nowak, and Steve Gordon

可以注册 IHttpClientFactory 并将其用于配置和创建应用中的 HttpClient 实例。An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. 这能带来以下好处:It offers the following benefits:

  • 提供一个中心位置,用于命名和配置逻辑 HttpClient 实例。Provides a central location for naming and configuring logical HttpClient instances. 例如,可注册和配置 github 客户端,使其访问 GitHubFor example, a github client can be registered and configured to access GitHub. 可以注册一个默认客户端用于其他用途。A default client can be registered for other purposes.
  • 通过委托 HttpClient 中的处理程序整理出站中间件的概念,并提供适用于基于 Polly 的中间件的扩展来利用概念。Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • 管理基础 HttpClientMessageHandler 实例的池和生存期,避免在手动管理 HttpClient 生存期时出现常见的 DNS 问题。Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • (通过 ILogger)添加可配置的记录体验,以处理工厂创建的客户端发送的所有请求。Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

查看或下载示例代码如何下载View or download sample code (how to download)

先决条件Prerequisites

面向.NET Framework 的项目要求安装 Microsoft.Extensions.Http NuGet 包。Projects targeting .NET Framework require installation of the Microsoft.Extensions.Http NuGet package. 面向 .NET Core 且引用 Microsoft.AspNetCore.App 元包的项目已经包括 Microsoft.Extensions.Http 包。Projects that target .NET Core and reference the Microsoft.AspNetCore.App metapackage already include the Microsoft.Extensions.Http package.

消耗模式Consumption patterns

在应用中可以通过以下多种方式使用 IHttpClientFactoryThere are several ways IHttpClientFactory can be used in an app:

它们之间不存在严格的优先级。None of them are strictly superior to another. 最佳方法取决于应用的约束条件。The best approach depends upon the app's constraints.

基本用法Basic usage

Startup.ConfigureServices 方法中,通过在 IServiceCollection 上调用 AddHttpClient 扩展方法可以注册 IHttpClientFactoryThe IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection, inside the Startup.ConfigureServices method.

services.AddHttpClient();

注册后,在可以使用依赖关系注入 (DI) 注入服务的任何位置,代码都能接受 IHttpClientFactoryOnce registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). IHttpClientFactory 可用于创建 HttpClient 实例:The IHttpClientFactory can be used to create an HttpClient instance:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://api.github.com/repos/aspnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

以这种方式使用 IHttpClientFactory 适合重构现有应用。Using IHttpClientFactory in this fashion is a good way to refactor an existing app. 这不会影响 HttpClient 的使用方式。It has no impact on the way HttpClient is used. 在当前创建 HttpClient 实例的位置,使用对 CreateClient 的调用替换这些匹配项。In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

命名客户端Named clients

如果应用需要有许多不同的 HttpClient 用法(每种用法的配置都不同),可以视情况使用命名客户端 。If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. 可以在 HttpClient 中注册时指定命名 Startup.ConfigureServices 的配置。Configuration for a named HttpClient can be specified during registration in Startup.ConfigureServices.

services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    // Github API versioning
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    // Github requires a user-agent
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

上面的代码调用 AddHttpClient,同时提供名称“github” 。In the preceding code, AddHttpClient is called, providing the name github. 此客户端应用了一些默认配置,也就是需要基址和两个标头来使用 GitHub API。This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

每次调用 CreateClient 时,都会创建 HttpClient 的新实例,并调用配置操作。Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

要使用命名客户端,可将字符串参数传递到 CreateClientTo consume a named client, a string parameter can be passed to CreateClient. 指定要创建的客户端的名称:Specify the name of the client to be created:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "repos/aspnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            PullRequests = await response.Content
                .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

在上述代码中,请求不需要指定主机名。In the preceding code, the request doesn't need to specify a hostname. 可以仅传递路径,因为采用了为客户端配置的基址。It can pass just the path, since the base address configured for the client is used.

类型化客户端Typed clients

类型化客户端:Typed clients:

  • 提供与命名客户端一样的功能,不需要将字符串用作密钥。Provide the same capabilities as named clients without the need to use strings as keys.
  • 在使用客户端时提供 IntelliSense 和编译器帮助。Provides IntelliSense and compiler help when consuming clients.
  • 提供单个位置来配置特定 HttpClient 并与其进行交互。Provide a single location to configure and interact with a particular HttpClient. 例如,单个类型化客户端可能用于单个后端终结点,并封装此终结点的所有处理逻辑。For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • 使用 DI 且可以被注入到应用中需要的位置。Work with DI and can be injected where required in your app.

类型化客户端在构造函数中接受 HttpClient 参数:A typed client accepts an HttpClient parameter in its constructor:

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept", 
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent", 
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        var response = await Client.GetAsync(
            "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<GitHubIssue>>();

        return result;
    }
}

在上述代码中,配置转移到了类型化客户端中。In the preceding code, the configuration is moved into the typed client. HttpClient 对象公开为公共属性。The HttpClient object is exposed as a public property. 可以定义公开 HttpClient 功能的特定于 API 的方法。It's possible to define API-specific methods that expose HttpClient functionality. GetAspNetDocsIssues 方法从 GitHub 存储库封装查询和分析最新待解决问题所需的代码。The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

要注册类型化客户端,可在 Startup.ConfigureServices 中使用通用的 AddHttpClient 扩展方法,指定类型化客户端类:To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices, specifying the typed client class:

services.AddHttpClient<GitHubService>();

使用 DI 将类型客户端注册为暂时客户端。The typed client is registered as transient with DI. 可以直接插入或使用类型化客户端:The typed client can be injected and consumed directly:

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

根据你的喜好,可以在 Startup.ConfigureServices 中注册时指定类型化客户端的配置,而不是在类型化客户端的构造函数中指定:If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

可以将 HttpClient 完全封装在类型化客户端中。It's possible to entirely encapsulate the HttpClient within a typed client. 不是将它公开为属性,而是可以提供公共方法,用于在内部调用 HttpClientRather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

public class RepoService
{
    // _httpClient isn't exposed publicly
    private readonly HttpClient _httpClient;

    public RepoService(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<IEnumerable<string>> GetRepos()
    {
        var response = await _httpClient.GetAsync("aspnet/repos");

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<string>>();

        return result;
    }
}

在上述代码中,HttpClient 存储未私有字段。In the preceding code, the HttpClient is stored as a private field. 进行外部调用的所有访问都经由 GetRepos 方法。All access to make external calls goes through the GetRepos method.

生成的客户端Generated clients

IHttpClientFactory 可结合其他第三方库(例如 Refit)使用。IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit 是.NET 的 REST 库。Refit is a REST library for .NET. 它将 REST API 转换为实时接口。It converts REST APIs into live interfaces. RestService 动态生成该接口的实现,使用 HttpClient 进行外部 HTTP 调用。An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

定义了接口和答复来代表外部 API 及其响应:An interface and a reply are defined to represent the external API and its response:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

可以添加类型化客户端,使用 Refit 生成实现:A typed client can be added, using Refit to generate the implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddMvc();
}

可以在必要时使用定义的接口,以及由 DI 和 Refit 提供的实现:The defined interface can be consumed where necessary, with the implementation provided by DI and Refit:

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

出站请求中间件Outgoing request middleware

HttpClient 已经具有委托处理程序的概念,这些委托处理程序可以链接在一起,处理出站 HTTP 请求。HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory 可以轻松定义处理程序并应用于每个命名客户端。The IHttpClientFactory makes it easy to define the handlers to apply for each named client. 它支持注册和链接多个处理程序,以生成出站请求中间件管道。It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. 每个处理程序都可以在出站请求前后执行工作。Each of these handlers is able to perform work before and after the outgoing request. 此模式类似于 ASP.NET Core 中的入站中间件管道。This pattern is similar to the inbound middleware pipeline in ASP.NET Core. 此模式提供了一种用于管理围绕 HTTP 请求的横切关注点的机制,包括缓存、错误处理、序列化以及日志记录。The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

要创建处理程序,请定义一个派生自 DelegatingHandler 的类。To create a handler, define a class deriving from DelegatingHandler. 重写 SendAsync 方法,在将请求传递至管道中的下一个处理程序之前执行代码:Override the SendAsync method to execute code before passing the request to the next handler in the pipeline:

public class ValidateHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("X-API-KEY"))
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(
                    "You must supply an API key header called X-API-KEY")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

上述代码定义了基本处理程序。The preceding code defines a basic handler. 它检查请求中是否包含 X-API-KEY 头。It checks to see if an X-API-KEY header has been included on the request. 如果标头缺失,它可以避免 HTTP 调用,并返回合适的响应。If the header is missing, it can avoid the HTTP call and return a suitable response.

在注册期间可将一个或多个标头添加到 HttpClient 的配置中。During registration, one or more handlers can be added to the configuration for an HttpClient. 此任务通过 IHttpClientBuilder 上的扩展方法完成。This task is accomplished via extension methods on the IHttpClientBuilder.

services.AddTransient<ValidateHeaderHandler>();

services.AddHttpClient("externalservice", c =>
{
    // Assume this is an "external" service which requires an API KEY
    c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();

在上述代码中通过 DI 注册了 ValidateHeaderHandlerIn the preceding code, the ValidateHeaderHandler is registered with DI. 处理程序必须 在 DI 中注册为暂时性服务且从不设置作用域。The handler must be registered in DI as a transient service, never scoped. 如果该处理程序注册为作用域服务,并且处理程序依赖的任何服务都是可释放的:If the handler is registered as a scoped service and any services that the handler depends upon are disposable:

  • 处理程序的服务可以在处理程序超出作用域之前被释放。The handler's services could be disposed before the handler goes out of scope.
  • 已释放的处理程序服务可导致处理程序失败。The disposed handler services causes the handler to fail.

注册后,可以调用 AddHttpMessageHandler,传入处理程序类型。Once registered, AddHttpMessageHandler can be called, passing in the handler type.

可以按处理程序应该执行的顺序注册多个处理程序。Multiple handlers can be registered in the order that they should execute. 每个处理程序都会覆盖下一个处理程序,直到最终 HttpClientHandler 执行请求:Each handler wraps the next handler until the final HttpClientHandler executes the request:

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

使用以下方法之一将每个请求状态与消息处理程序共享:Use one of the following approaches to share per-request state with message handlers:

  • 使用 HttpRequestMessage.Properties 将数据传递到处理程序。Pass data into the handler using HttpRequestMessage.Properties.
  • 使用 IHttpContextAccessor 访问当前请求。Use IHttpContextAccessor to access the current request.
  • 创建自定义 AsyncLocal 存储对象以传递数据。Create a custom AsyncLocal storage object to pass the data.

使用基于 Polly 的处理程序Use Polly-based handlers

IHttpClientFactory 与一个名为 Polly 的热门第三方库集成。IHttpClientFactory integrates with a popular third-party library called Polly. Polly 是适用于 .NET 的全面恢复和临时故障处理库。Polly is a comprehensive resilience and transient fault-handling library for .NET. 开发人员通过它可以表达策略,例如以流畅且线程安全的方式处理重试、断路器、超时、Bulkhead 隔离和回退。It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

提供了扩展方法,以实现将 Polly 策略用于配置的 HttpClient 实例。Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Polly 扩展:The Polly extensions:

  • 支持将基于 Polly 的处理程序添加到客户端。Support adding Polly-based handlers to clients.
  • 安装了 Microsoft.Extensions.Http.Polly NuGet 包后可使用该扩展。Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. ASP.NET Core 共享框架中不包括该包。The package isn't included in the ASP.NET Core shared framework.

处理临时故障Handle transient faults

大多数常见错误在暂时执行外部 HTTP 调用时发生。Most common faults occur when external HTTP calls are transient. 包含了一种简便的扩展方法,该方法名为 AddTransientHttpErrorPolicy,允许定义策略来处理临时故障。A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. 使用这种扩展方法配置的策略可以处理 HttpRequestException、HTTP 5xx 响应以及 HTTP 408 响应。Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

AddTransientHttpErrorPolicy 扩展可在 Startup.ConfigureServices 内使用。The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. 该扩展可以提供 PolicyBuilder 对象的访问权限,该对象配置为处理表示可能的临时故障的错误:The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

services.AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

上述代码中定义了 WaitAndRetryAsync 策略。In the preceding code, a WaitAndRetryAsync policy is defined. 请求失败后最多可以重试三次,每次尝试间隔 600 ms。Failed requests are retried up to three times with a delay of 600 ms between attempts.

动态选择策略Dynamically select policies

存在其他扩展方法,可以用于添加基于 Polly 的处理程序。Additional extension methods exist which can be used to add Polly-based handlers. 这类扩展的其中一个是 AddPolicyHandler,它具备多个重载。One such extension is AddPolicyHandler, which has multiple overloads. 一个重载允许在定义要应用的策略时检查该请求:One overload allows the request to be inspected when defining which policy to apply:

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

在上述代码中,如果出站请求为 HTTP GET,则应用 10 秒超时。In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. 其他所有 HTTP 方法应用 30 秒超时。For any other HTTP method, a 30-second timeout is used.

添加多个 Polly 处理程序Add multiple Polly handlers

通过嵌套 Polly 策略来增强功能是很常见的:It's common to nest Polly policies to provide enhanced functionality:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

在上述示例中,添加两个处理程序。In the preceding example, two handlers are added. 第一个使用 AddTransientHttpErrorPolicy 扩展添加重试策略。The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. 若请求失败,最多可重试三次。Failed requests are retried up to three times. 第二个调用 AddTransientHttpErrorPolicy 添加断路器策略。The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. 如果尝试连续失败了五次,则会阻止后续外部请求 30 秒。Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. 断路器策略处于监控状态。Circuit breaker policies are stateful. 通过此客户端进行的所有调用都共享同样的线路状态。All calls through this client share the same circuit state.

从 Polly 注册表添加策略Add policies from the Polly registry

管理常用策略的一种方法是一次性定义它们并使用 PolicyRegistry 注册它们。An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. 提供了一种扩展方法,可以使用注册表中的策略添加处理程序:An extension method is provided which allows a handler to be added using a policy from the registry:

var registry = services.AddPolicyRegistry();

registry.Add("regular", timeout);
registry.Add("long", longTimeout);

services.AddHttpClient("regulartimeouthandler")
    .AddPolicyHandlerFromRegistry("regular");

在上面的代码中,两个策略在 PolicyRegistry 添加到 ServiceCollection 中时进行注册。In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. 若要使用注册表中的策略,请使用 AddPolicyHandlerFromRegistry 方法,同时传递要应用的策略的名称。To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

要进一步了解 IHttpClientFactory 和 Polly 集成,请参考 Polly WikiFurther information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient 和生存期管理HttpClient and lifetime management

每次对 IHttpClientFactory 调用 CreateClient 都会返回一个新 HttpClient 实例。A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. 每个命名的客户端都具有一个 HttpMessageHandlerThere's an HttpMessageHandler per named client. 工厂管理 HttpMessageHandler 实例的生存期。The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory 将工厂创建的 HttpMessageHandler 实例汇集到池中,以减少资源消耗。IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. 新建 HttpClient 实例时,可能会重用池中的 HttpMessageHandler 实例(如果生存期尚未到期的话)。An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

由于每个处理程序通常管理自己的基础 HTTP 连接,因此需要池化处理程序。Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. 创建超出必要数量的处理程序可能会导致连接延迟。Creating more handlers than necessary can result in connection delays. 部分处理程序还保持连接无期限地打开,这样可以防止处理程序对 DNS 更改作出反应。Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

处理程序的默认生存期为两分钟。The default handler lifetime is two minutes. 可在每个命名客户端上重写默认值。The default value can be overridden on a per named client basis. 要重写该值,请在创建客户端时在返回的 IHttpClientBuilder 上调用 SetHandlerLifetimeTo override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

services.AddHttpClient("extendedhandlerlifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

无需处置客户端。Disposal of the client isn't required. 处置既取消传出请求,又保证在调用 Dispose 后无法使用给定的 HttpClient 实例。Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory 跟踪和处置 HttpClient 实例使用的资源。IHttpClientFactory tracks and disposes resources used by HttpClient instances. HttpClient 实例通常可视为无需处置的 .NET 对象。The HttpClient instances can generally be treated as .NET objects not requiring disposal.

保持各个 HttpClient 实例长时间处于活动状态是在 IHttpClientFactory 推出前使用的常见模式。Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. 迁移到 IHttpClientFactory 后,就无需再使用此模式。This pattern becomes unnecessary after migrating to IHttpClientFactory.

IHttpClientFactory 的替代项Alternatives to IHttpClientFactory

通过在启用了 DI 的应用中使用 IHttpClientFactory,可避免:Using IHttpClientFactory in a DI-enabled app avoids:

  • 通过共用 HttpMessageHandler 实例,解决资源耗尽问题。Resource exhaustion problems by pooling HttpMessageHandler instances.
  • 通过定期循环 HttpMessageHandler 实例,解决 DNS 过时问题。Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

此外,还有其他方法使用生命周期长的 SocketsHttpHandler 实例来解决上述问题。There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • 在应用启动时创建 SocketsHttpHandler 的实例,并在应用的整个生命周期中使用它。Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • 根据 DNS 刷新时间,将 PooledConnectionLifetime 配置为适当的值。Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • 根据需要,使用 new HttpClient(handler, disposeHandler: false) 创建 HttpClient 实例。Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

上述方法使用 IHttpClientFactory 解决问题的类似方式解决资源管理问题。The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • SocketsHttpHandlerHttpClient 实例之间共享连接。The SocketsHttpHandler shares connections across HttpClient instances. 此共享可防止套接字耗尽。This sharing prevents socket exhaustion.
  • SocketsHttpHandler 会根据 PooledConnectionLifetime 循环连接,避免出现 DNS 过时问题。The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookieCookies

共用 HttpMessageHandler 实例将导致共享 CookieContainer 对象。The pooled HttpMessageHandler instances results in CookieContainer objects being shared. 意外的 CookieContainer 对象共享通常会导致错误的代码。Unanticipated CookieContainer object sharing often results in incorrect code. 对于需要 Cookie 的应用,请考虑执行以下任一操作:For apps that require cookies, consider either:

  • 禁用自动 Cookie 处理Disabling automatic cookie handling
  • 避免 IHttpClientFactoryAvoiding IHttpClientFactory

调用 ConfigurePrimaryHttpMessageHandler 以禁用自动 Cookie 处理:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            UseCookies = false,
        };
    });

LoggingLogging

通过 IHttpClientFactory 创建的客户端记录所有请求的日志消息。Clients created via IHttpClientFactory record log messages for all requests. 在日志记录配置中启用合适的信息级别可以查看默认日志消息。Enable the appropriate information level in your logging configuration to see the default log messages. 仅在跟踪级别包含附加日志记录(例如请求标头的日志记录)。Additional logging, such as the logging of request headers, is only included at trace level.

用于每个客户端的日志类别包含客户端名称。The log category used for each client includes the name of the client. 例如,名为“MyNamedClient” 的客户端使用 System.Net.Http.HttpClient.MyNamedClient.LogicalHandler 类别来记录消息。A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. 后缀为 LogicalHandler 的消息在请求处理程序管道外部发生。Messages suffixed with LogicalHandler occur outside the request handler pipeline. 在请求时,在管道中的任何其他处理程序处理请求之前记录消息。On the request, messages are logged before any other handlers in the pipeline have processed it. 在响应时,在任何其他管道处理程序接收响应之后记录消息。On the response, messages are logged after any other pipeline handlers have received the response.

日志记录还在请求处理程序管道内部发生。Logging also occurs inside the request handler pipeline. 在“MyNamedClient” 示例中,这些消息是针对日志类别 System.Net.Http.HttpClient.MyNamedClient.ClientHandler 进行记录。In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler. 在请求时,在所有其他处理程序运行后,以及刚好在通过网络发出请求之前记录消息。For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. 在响应时,此日志记录包含响应在通过处理程序管道被传递回去之前的状态。On the response, this logging includes the state of the response before it passes back through the handler pipeline.

在管道内外启用日志记录,可以检查其他管道处理程序做出的更改。Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. 例如,其中可能包含对请求标头的更改,或者对响应状态代码的更改。This may include changes to request headers, for example, or to the response status code.

通过在日志类别中包含客户端名称,可以在必要时对特定的命名客户端筛选日志。Including the name of the client in the log category enables log filtering for specific named clients where necessary.

配置 HttpMessageHandlerConfigure the HttpMessageHandler

控制客户端使用的内部 HttpMessageHandler 的配置是有必要的。It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

在添加命名客户端或类型化客户端时,会返回 IHttpClientBuilderAn IHttpClientBuilder is returned when adding named or typed clients. ConfigurePrimaryHttpMessageHandler 扩展方法可以用于定义委托。The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. 委托用于创建和配置客户端使用的主要 HttpMessageHandlerThe delegate is used to create and configure the primary HttpMessageHandler used by that client:

services.AddHttpClient("configured-inner-handler")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            AllowAutoRedirect = false,
            UseDefaultCredentials = true
        };
    });

在控制台应用中使用 IHttpClientFactoryUse IHttpClientFactory in a console app

在控制台中,将以下包引用添加到项目中:In a console app, add the following package references to the project:

如下示例中:In the following example:

  • IHttpClientFactory 已在泛型主机的服务容器中注册。IHttpClientFactory is registered in the Generic Host's service container.
  • MyService 从服务创建客户端工厂实例,用于创建 HttpClientMyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient 用于检索网页。HttpClient is used to retrieve a webpage.
  • Main 可创建作用域来执行服务的 GetPage 方法,并将网页内容的前 500 个字符写入控制台。Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();

                logger.LogError(ex, "An error occurred.");
            }
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

标头传播中间件Header propagation middleware

标头传播是一个社区支持的中间件,可将 HTTP 标头从传入请求传播到传出 HTTP 客户端请求。Header propagation is a community supported middleware to propagate HTTP headers from the incoming request to the outgoing HTTP Client requests. 使用标头传播:To use header propagation:

  • 引用 HeaderPropagation 包的社区支持的端口。Reference the community supported port of the package HeaderPropagation. ASP.NET Core 3.1 及更高版本支持 Microsoft.AspNetCore.HeaderPropagationASP.NET Core 3.1 and later supports Microsoft.AspNetCore.HeaderPropagation.

  • Startup 中配置中间件和 HttpClientConfigure the middleware and HttpClient in Startup:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    
        services.AddHttpClient("MyForwardingClient").AddHeaderPropagation();
        services.AddHeaderPropagation(options =>
        {
            options.Headers.Add("X-TraceId");
        });
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
    
        app.UseHeaderPropagation();
    
        app.UseMvc();
    }
    
  • 客户端在出站请求中包含配置的标头:The client includes the configured headers on outbound requests:

    var client = clientFactory.CreateClient("MyForwardingClient");
    var response = client.GetAsync(...);
    

其他资源Additional resources