【转】在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求

摘要:
view=aspnetcore-5.0#consumption-patterns在ASP.NETCore中使用IHttpClientFactory发出HTTP请求2021/01/21作者:KirkLarkin、SteveGordon、GlennCondron和RyanNowak。可以注册IHttpClientFactory并将其用于配置和创建应用中的HttpClient实例。提供基于Polly的中间件的扩展,以利用HttpClient中的委托处理程序。管理基础HttpClientMessageHandler实例的池和生存期。消耗模式在应用中可以通过以下多种方式使用IHttpClientFactory:基本用法命名客户端类型化客户端生成的客户端最佳方法取决于应用要求。这不会影响HttpClient的使用方式。在现有应用中创建HttpClient实例的位置,使用对CreateClient的调用替换这些匹配项。

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#consumption-patterns

在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求
  • 2021/01/21
    • 【转】在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求第1张
    • 【转】在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求第2张

作者:Kirk LarkinSteve GordonGlenn CondronRyan Nowak

可以注册IHttpClientFactory并将其用于配置和创建应用中的HttpClient实例。IHttpClientFactory的优势如下:

  • 提供一个中心位置,用于命名和配置逻辑HttpClient实例。 例如,可注册和配置名为 github 的客户端,使其访问GitHub。 可以注册一个默认客户端用于一般性访问。
  • 通过HttpClient中的委托处理程序来编码出站中间件的概念。 提供基于 Polly 的中间件的扩展,以利用HttpClient中的委托处理程序。
  • 管理基础HttpClientMessageHandler实例的池和生存期。 自动管理可避免手动管理HttpClient生存期时出现的常见 DNS(域名系统)问题。
  • (通过ILogger)添加可配置的记录体验,以处理工厂创建的客户端发送的所有请求。

查看或下载示例代码如何下载)。

此主题版本中的示例代码使用System.Text.Json来对 HTTP 响应中返回的 JSON 内容进行反序列化。 对于使用Json.NETReadAsAsync<T>的示例,请使用版本选择器选择此主题的 2.x 版本。

消耗模式

在应用中可以通过以下多种方式使用IHttpClientFactory

最佳方法取决于应用要求。

基本用法

可以通过调用AddHttpClient来注册IHttpClientFactory

C#
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)来请求IHttpClientFactory。 以下代码使用IHttpClientFactory来创建HttpClient实例:

C#
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/dotnet/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是重构现有应用的好方法。 这不会影响HttpClient的使用方式。 在现有应用中创建HttpClient实例的位置,使用对CreateClient的调用替换这些匹配项。

命名客户端

在以下情况下,命名客户端是一个不错的选择:

  • 应用需要HttpClient的许多不同用法。
  • 许多HttpClient具有不同的配置。

可以在Startup.ConfigureServices中注册时指定命名HttpClient的配置:

C#
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");
});

在上述代码中,客户端配置如下:

  • 基址为https://api.github.com/
  • 使用 GitHub API 需要的两个标头。

CreateClient

每次调用CreateClient时:

  • 创建HttpClient的新实例。
  • 调用配置操作。

要创建命名客户端,请将其名称传递到CreateClient中:

C#
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/dotnet/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>();
        }
    }
}

在上述代码中,请求不需要指定主机名。 代码可以仅传递路径,因为采用了为客户端配置的基址。

类型化客户端

类型化客户端:

  • 提供与命名客户端一样的功能,不需要将字符串用作密钥。
  • 在使用客户端时提供 IntelliSense 和编译器帮助。
  • 提供单个位置来配置特定HttpClient并与其进行交互。 例如,可以使用单个类型化客户端:
    • 对于单个后端终结点。
    • 封装处理终结点的所有逻辑。
  • 使用 DI 且可以被注入到应用中需要的位置。

类型化客户端在构造函数中接受HttpClient参数:

C#
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()
    {
        return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
          "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
    }
}

在上述代码中:

  • 配置转移到了类型化客户端中。
  • HttpClient对象公开为公共属性。

可以创建特定于 API 的方法来公开HttpClient功能。 例如,创建GetAspNetDocsIssues方法来封装代码以检索未解决的问题。

以下代码调用Startup.ConfigureServices中的AddHttpClient来注册类型化客户端类:

C#
services.AddHttpClient<GitHubService>();

使用 DI 将类型客户端注册为暂时客户端。 在上述代码中,AddHttpClientGitHubService注册为暂时性服务。 此注册使用工厂方法执行以下操作:

  1. 创建HttpClient的实例。
  2. 创建GitHubService的实例,将HttpClient的实例传入其构造函数。

可以直接插入或使用类型化客户端:

C#
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中注册时指定类型化客户端的配置,而不是在类型化客户端的构造函数中指定:

C#
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封装在类型化客户端中, 定义一个在内部调用HttpClient实例的方法,而不是将其公开为属性:

C#
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存储在私有字段中。 通过公共GetRepos方法访问HttpClient

生成的客户端

IHttpClientFactory可结合第三方库(例如Refit)使用。 Refit 是.NET 的 REST 库。 它将 REST API 转换为实时接口。RestService动态生成该接口的实现,使用HttpClient进行外部 HTTP 调用。

定义了接口和答复来代表外部 API 及其响应:

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

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

可以添加类型化客户端,使用 Refit 生成实现:

C#
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 提供的实现:

C#
[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();
    }
}

发出 POST、PUT 和 DELETE 请求

在前面的示例中,所有 HTTP 请求均使用 GET HTTP 谓词。HttpClient还支持其他 HTTP 谓词,其中包括:

  • POST
  • PUT
  • 删除
  • PATCH

有关受支持的 HTTP 谓词的完整列表,请参阅HttpMethod

下面的示例演示如何发出 HTTP POST 请求:

C#
public async Task CreateItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

在前面的代码中,CreateItemAsync方法:

HttpClient还支持其他类型的内容。 例如,MultipartContentStreamContent。 有关受支持的内容的完整列表,请参阅HttpContent

下面的示例演示了一个 HTTP PUT 请求:

C#
public async Task SaveItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

前面的代码与 POST 示例非常相似。SaveItemAsync方法调用PutAsync而不是PostAsync

下面的示例演示了一个 HTTP DELETE 请求:

C#
public async Task DeleteItemAsync(long itemId)
{
    using var httpResponse =
        await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

    httpResponse.EnsureSuccessStatusCode();
}

在前面的代码中,DeleteItemAsync方法调用DeleteAsync。 由于 HTTP DELETE 请求通常不包含正文,因此DeleteAsync方法不提供接受HttpContent实例的重载。

要详细了解如何将不同的 HTTP 谓词用于HttpClient,请参阅HttpClient

出站请求中间件

HttpClient具有委托处理程序的概念,这些委托处理程序可以链接在一起,处理出站 HTTP 请求。IHttpClientFactory:

  • 简化定义应用于各命名客户端的处理程序。

  • 支持注册和链接多个处理程序,以生成出站请求中间件管道。 每个处理程序都可以在出站请求前后执行工作。 此模式:

    • 类似于 ASP.NET Core 中的入站中间件管道。
    • 提供一种机制来管理有关 HTTP 请求的横切关注点,例如:
      • 缓存
      • 错误处理
      • 序列化
      • 日志记录

创建委托处理程序:

C#
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标头。 如果缺失X-API-KEY,则返回BadRequest

可使用Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler将多个处理程序添加到HttpClient的配置中:

C#
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 注册了ValidateHeaderHandler。 注册后可以调用AddHttpMessageHandler,传入标头的类型。

可以按处理程序应该执行的顺序注册多个处理程序。 每个处理程序都会覆盖下一个处理程序,直到最终HttpClientHandler执行请求:

C#
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>();

在出站请求中间件中使用 DI

IHttpClientFactory创建新的委托处理程序时,它使用 DI 来完成处理程序的构造函数参数。IHttpClientFactory为每个处理程序创建单独的 DI 范围,当处理程序使用限定范围的服务时,这可能导致意外的行为。

例如,请考虑下面的接口及其实现,它将任务表示为带有标识符OperationId的操作:

C#
public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

顾名思义,使用限定范围的生存期向 DI 注册IOperationScoped

C#
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<TodoContext>(options =>
        options.UseInMemoryDatabase("TodoItems"));

    services.AddHttpContextAccessor();

    services.AddHttpClient<TodoClient>((sp, httpClient) =>
    {
        var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;

        // For sample purposes, assume TodoClient is used in the context of an incoming request.
        httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
                                         httpRequest.Host, httpRequest.PathBase));
        httpClient.Timeout = TimeSpan.FromSeconds(5);
    });

    services.AddScoped<IOperationScoped, OperationScoped>();
    
    services.AddTransient<OperationHandler>();
    services.AddTransient<OperationResponseHandler>();

    services.AddHttpClient("Operation")
        .AddHttpMessageHandler<OperationHandler>()
        .AddHttpMessageHandler<OperationResponseHandler>()
        .SetHandlerLifetime(TimeSpan.FromSeconds(5));

    services.AddControllers();
    services.AddRazorPages();
}

以下委托处理程序消耗并使用IOperationScoped来设置传出请求的X-OPERATION-ID标头:

C#
public class OperationHandler : DelegatingHandler
{
    private readonly IOperationScoped _operationService;

    public OperationHandler(IOperationScoped operationScoped)
    {
        _operationService = operationScoped;
    }

    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);

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

HttpRequestsSample下载] 中,导航到/Operation并刷新页面。 每个请求的请求范围值发生更改,但处理程序范围值仅每 5 秒钟更改一次。

处理程序可依赖于任何作用域的服务。 处理程序依赖的服务会在处置处理程序时得到处置。

使用以下方法之一将每个请求状态与消息处理程序共享:

使用基于 Polly 的处理程序

IHttpClientFactory与第三方库Polly集成。 Polly 是适用于 .NET 的全面恢复和临时故障处理库。 开发人员通过它可以表达策略,例如以流畅且线程安全的方式处理重试、断路器、超时、Bulkhead 隔离和回退。

提供了扩展方法,以实现将 Polly 策略用于配置的HttpClient实例。 Polly 扩展支持将基于 Polly 的处理程序添加到客户端。 Polly 需要Microsoft.Extensions.Http.PollyNuGet 包。

处理临时故障

错误通常在暂时执行外部 HTTP 调用时发生。AddTransientHttpErrorPolicy允许定义一个策略来处理暂时性错误。 使用AddTransientHttpErrorPolicy配置的策略处理以下响应:

AddTransientHttpErrorPolicy提供对PolicyBuilder对象的访问权限,该对象配置为处理表示可能的临时故障的错误:

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

    // Remaining code deleted for brevity.

上述代码中定义了WaitAndRetryAsync策略。 请求失败后最多可以重试三次,每次尝试间隔 600 ms。

动态选择策略

提供了扩展方法来添加基于 Polly 的处理程序,例如AddPolicyHandler。 以下AddPolicyHandler重载检查请求以确定要应用的策略:

C#
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 秒超时。 其他所有 HTTP 方法应用 30 秒超时。

添加多个 Polly 处理程序

这对嵌套 Polly 策略很常见:

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

在上面的示例中:

  • 添加了两个处理程序。
  • 第一个处理程序使用AddTransientHttpErrorPolicy添加重试策略。 若请求失败,最多可重试三次。
  • 第二个AddTransientHttpErrorPolicy调用添加断路器策略。 如果尝试连续失败了 5 次,则会阻止后续外部请求 30 秒。 断路器策略处于监控状态。 通过此客户端进行的所有调用都共享同样的线路状态。

从 Polly 注册表添加策略

管理常用策略的一种方法是一次性定义它们并使用PolicyRegistry注册它们。

在以下代码中:

C#
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 Wiki

HttpClient 和生存期管理

每次对IHttpClientFactory调用CreateClient都会返回一个新HttpClient实例。 每个命名客户端都创建一个HttpMessageHandler。 工厂管理HttpMessageHandler实例的生存期。

IHttpClientFactory将工厂创建的HttpMessageHandler实例汇集到池中,以减少资源消耗。 新建HttpClient实例时,可能会重用池中的HttpMessageHandler实例(如果生存期尚未到期的话)。

由于每个处理程序通常管理自己的基础 HTTP 连接,因此需要池化处理程序。 创建超出必要数量的处理程序可能会导致连接延迟。 部分处理程序还保持连接无期限地打开,这样可以防止处理程序对 DNS(域名系统)更改作出反应。

处理程序的默认生存期为两分钟。 可在每个命名客户端上重写默认值:

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

    // Remaining code deleted for brevity.

HttpClient实例通常可视为无需处置的 .NET 对象。 处置既取消传出请求,又保证在调用Dispose后无法使用给定的HttpClient实例。IHttpClientFactory跟踪和处置HttpClient实例使用的资源。

保持各个HttpClient实例长时间处于活动状态是在IHttpClientFactory推出前使用的常见模式。 迁移到IHttpClientFactory后,就无需再使用此模式。

IHttpClientFactory 的替代项

通过在启用了 DI 的应用中使用IHttpClientFactory,可避免:

  • 通过共用HttpMessageHandler实例,解决资源耗尽问题。
  • 通过定期循环HttpMessageHandler实例,解决 DNS 过时问题。

此外,还有其他方法使用生命周期长的SocketsHttpHandler实例来解决上述问题。

  • 在应用启动时创建SocketsHttpHandler的实例,并在应用的整个生命周期中使用它。
  • 根据 DNS 刷新时间,将PooledConnectionLifetime配置为适当的值。
  • 根据需要,使用new HttpClient(handler, disposeHandler: false)创建HttpClient实例。

上述方法使用IHttpClientFactory解决问题的类似方式解决资源管理问题。

  • SocketsHttpHandlerHttpClient实例之间共享连接。 此共享可防止套接字耗尽。
  • SocketsHttpHandler会根据PooledConnectionLifetime循环连接,避免出现 DNS 过时问题。

Cookies

共用HttpMessageHandler实例将导致共享CookieContainer对象。 意外的CookieContainer对象共享通常会导致错误的代码。 对于需要 cookie 的应用,请考虑执行以下任一操作:

  • 禁用自动 cookie 处理
  • 避免IHttpClientFactory

调用ConfigurePrimaryHttpMessageHandler以禁用自动 cookie 处理:

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

Logging

通过IHttpClientFactory创建的客户端记录所有请求的日志消息。 在日志记录配置中启用合适的信息级别可以查看默认日志消息。 仅在跟踪级别包含附加日志记录(例如请求标头的日志记录)。

用于每个客户端的日志类别包含客户端名称。 例如,名为 MyNamedClient 的客户端记录类别为“System.Net.Http.HttpClient.MyNamedClient.LogicalHandler”的消息。 后缀为 LogicalHandler 的消息在请求处理程序管道外部发生。 在请求时,在管道中的任何其他处理程序处理请求之前记录消息。 在响应时,在任何其他管道处理程序接收响应之后记录消息。

日志记录还在请求处理程序管道内部发生。 在 MyNamedClient 示例中,这些消息的日志类别为“System.Net.Http.HttpClient.MyNamedClient.ClientHandler”。 在请求时,在所有其他处理程序运行后,以及刚好要发出请求之前记录消息。 在响应时,此日志记录包含响应在通过处理程序管道被传递回去之前的状态。

在管道内外启用日志记录,可以检查其他管道处理程序做出的更改。 这可能包含对请求标头的更改,或者对响应状态代码的更改。

通过在日志类别中包含客户端名称,可以对特定的命名客户端筛选日志。

配置 HttpMessageHandler

控制客户端使用的内部HttpMessageHandler的配置是有必要的。

在添加命名客户端或类型化客户端时,会返回IHttpClientBuilderConfigurePrimaryHttpMessageHandler扩展方法可以用于定义委托。 委托用于创建和配置客户端使用的主要HttpMessageHandler

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

    // Remaining code deleted for brevity.

在控制台应用中使用 IHttpClientFactory

在控制台中,将以下包引用添加到项目中:

如下示例中:

  • IHttpClientFactory已在泛型主机的服务容器中注册。
  • MyService从服务创建客户端工厂实例,用于创建HttpClientHttpClient用于检索网页。
  • Main可创建作用域来执行服务的GetPage方法,并将网页内容的前 500 个字符写入控制台。
C#
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();

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

            Console.WriteLine(pageContent.Substring(0, 500));
        }
        catch (Exception ex)
        {
            var logger = host.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}";
            }
        }
    }
}

标头传播中间件

标头传播是一个 ASP.NET Core 中间件,可将 HTTP 标头从传入请求传播到传出 HTTP 客户端请求。 使用标头传播:

  • 引用Microsoft.AspNetCore.HeaderPropagation包。

  • Startup中配置中间件和HttpClient

    C#
    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();
        });
    }
    
  • 客户端在出站请求中包含配置的标头:

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

其他资源

免责声明:文章转载自《【转】在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇OSWatcher的使用ASP.NET Core2.1 你不得不了解的GDPR(Cookie处理)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

使用python asyncio+aiohttp做接口测试(TODO)

线程是操作系统层面的“并行”, 协程是应用程序层面的“并行”。 协程本质上就是:提供一个环境,保存一些需要等待的任务,当这些任务可以执行(等待结束)的时候,能够执行。再等待的过程中,程序可以执行别的任务。 asyncio是python3.4版本引入到标准库因此要注意python版本 我的python环境 Python 3.6.5 (v3.6.5:f59c0...

ISR吞吐性能问题

ISR大致可以分几类: Cisco 860、880、890 ISR1800 (fixed)、1800 (modular)、2800、3800 Series ISR1900、2900、3800、3900 Series ISR4K 每一代的设备,设备的性能肯定都不一样,本摘要,将主要记录ISR的吞吐性能问题。 1、下图主要体现的是800 Series和19...

c# async await 理解 结合并行处理

写两个方法 第一个 List<int> list = new List<int>(); int i= 0; do { i++; list.Add(i); } while (i< 10); Console.WriteLine("开始执行时间:" + DateTime.Now.ToString()); Parall...

RocketMQ的安装配置:配置jdk环境,配置RocketMQ环境,配置集群环境,配置rocketmq-console

RocketMQ的安装配置 演示虚拟机环境:Centos64-1 (D:linuxMorecentos6_64) root / itcast : 固定IP 192.168.52.128 一,配置JDK环境 1,解压jdk到指定的目录 tar -xvf jdk-8u171-linux-x64.tar.gz -C /usr/local cd /usr/loca...

给HttpClient添加Socks代理

本文描述http client使用socks代理过程中需要注意的几个方面:1,socks5支持用户密码授权;2,支持https;3,支持让代理服务器解析DNS; 使用代理创建Socket 从原理上来看,不管用什么http客户端(httpclient,okhttp),最终都要转换到java.net.Socket的创建上去,看到代码: package java...

并发编程概述--C#并发编程经典实例

优秀软件的一个关键特征就是具有并发性。过去的几十年,我们可以进行并发编程,但是难度很大。以前,并发性软件的编写、调试和维护都很难,这导致很多开发人员为图省事放弃了并发编程。新版.NET 中的程序库和语言特征,已经让并发编程变得简单多了。随着Visual Studio 2012 的发布,微软明显降低了并发编程的门槛。以前只有专家才能做并发编程,而今天,每一个...