在.NET 8中使用HttpClient的正确方法?

问题描述 投票:0回答:1

我有一个 .NET 8 应用程序,我的应用程序不断使用

HttpClient
向服务器发送请求。每当我认为我掌握了使用
HttpClient
的“正确”方法时,我就会开始怀疑自己。

首先,我们假设这是一个长时间运行的应用程序,一次运行数天收集数据,每隔几秒将读数发送到服务器。

我们还假设我关心端口耗尽和可能出现的任何 DNS 问题。

因此,我创建了一个名为(对于本示例)

MyHttpClient
的类。我这样做的原因是能够传入类型参数并自动将响应反序列化为该类型。无论如何,这并不重要。但在此示例中,
MyHttpClient
包含
HttpClient
对象,如下所示。

我将 http 客户端添加到

IServiceCollection

在下面的示例中,我需要设置处理程序生命周期吗?

此示例是否解决了端口耗尽和 DNS 相关问题?或者,我完全错过了这里的要点吗?

如有任何反馈,我们将不胜感激....

App.xaml.cs:

private void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<MyHttpClient>("MyHttpClient", x =>
    {
        x.Timeout = TimeSpan.FromSeconds(5);
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        // allow to connect to self-signed certificate ssl
        var handler = new HttpClientHandler();
        handler.ClientCertificateOptions = ClientCertificateOption.Manual;
        handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true;
        return handler;
    });
}

MyHttpClient

public class MyHttpClient
{
    private HttpClient _httpClient;

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

    public async Task<string> GetStringAsync(string url, TimeSpan timeout)
    {
        try
        {
            _httpClient.Timeout = timeout;

            CancellationToken cancellationToken = default;
            using (var response = await _httpClient.GetAsync(url, cancellationToken))
            {
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
        }
        catch
        {
            return null;
        }
    }
}

SomeService
中的用法:

public class SomeService
{
    private readonly MyHttpClient httpClient;

    public SomeService(MyHttpClient httpClient)
    {
        this.httpClient = httpClient;
    }

    public async Task<string> TestMe()
    {
        return await httpClient.GetStringAsync("https://example.com/api/boblawlaw", TimeSpan.FromSeconds(4));
    }
}
c# .net-core dependency-injection dotnet-httpclient
1个回答
0
投票
The Naive Way To Use HttpClient
The simplest way to work with the HttpClient is to just create a new instance, set the required properties and use it to send requests.

What could possibly go wrong?

HttpClient instances are meant to be long-lived, and reused throughout the lifetime of the application.

Each instance uses its own connection pool for isolation purposes, but also to prevent port exhaustion. If a server is under high load, and your application is constantly creating new connections, it could lead to exhausting the available ports. This will cause an exception at runtime, when trying to send a request.

So how can you avoid this?

public class GitHubService
{
    private readonly GitHubSettings _settings;

    public GitHubService(IOptions<GitHubSettings> settings)
    {
        _settings = settings.Value;
    }

    public async Task<GitHubUser?> GetUserAsync(string username)
    {
        var client = new HttpClient();

        client.DefaultRequestHeaders.Add("Authorization", _settings.GitHubToken);
        client.DefaultRequestHeaders.Add("User-Agent", _settings.UserAgent);
        client.BaseAddress = new Uri("https://api.github.com");

        GitHubUser? user = await client
            .GetFromJsonAsync<GitHubUser>($"users/{username}");

        return user;
    }
}
The Smart Way To Create HttpClient Using IHttpClientFactory
Instead of managing the HttpClient lifetime yourself, you can use an IHttpClientFactory to create the HttpClient instance.

Simply call the CreateClient method and use the returned HttpClient instance to send your HTTP requests.

Why is this a better approach?

The expensive part of the HttpClient is the actual message handler - HttpMessageHandler. Each HttpMessageHandler has an internal HTTP connection pool that can be reused.

The IHttpClientFactory will cache the HttpMessageHandler and reuse it when creating a new HttpClient instance.

An important note here is that HttpClient instances created by IHttpClientFactory are meant to be short-lived.

public class GitHubService
{
    private readonly GitHubSettings _settings;
    private readonly IHttpClientFactory _factory;

    public GitHubService(
        IOptions<GitHubSettings> settings,
        IHttpClientFactory factory)
    {
        _settings = settings.Value;
        _factory = factory;
    }

    public async Task<GitHubUser?> GetUserAsync(string username)
    {
        var client = _factory.CreateClient();

        client.DefaultRequestHeaders.Add("Authorization", _settings.GitHubToken);
        client.DefaultRequestHeaders.Add("User-Agent", _settings.UserAgent);
        client.BaseAddress = new Uri("https://api.github.com");

        GitHubUser? user = await client
            .GetFromJsonAsync<GitHubUser>($"users/{username}");

        return user;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.