Использование HttpClient во внешней библиотеке для ASP.NET Сердечник

#c# #.net-core #asp.net-core-webapi #webapi #httpclientfactory

Вопрос:

Введение

Я разрабатываю SDK (это просто оболочка для REST API). Этот SDK внутренне использует HttpClient для выполнения запросов к API. SDK ориентирован на стандарт .NET 2.1. Вот упрощенный код.

 public class SomeSdk 
{
    private readonly HttpClient _httpClient;

    public SomeSdk(<some-settings>) 
    {
        _httpClient = new HttpClient(<some-settings>);
    }

    public async Task<string> GetSomethingFromApiAsync() 
    {
        return await _httpClient.GetStringAsync(...);
    }
}
 

Вопрос

SDK используется другими разработчиками в ASP.NET Основные проекты. Мне известно о проблемах с HttpClient, и я знаю, что MS DOCS рекомендует вводить HttpClientFactory, чтобы избежать исключения SocketException и отразить изменения DNS.

Первое решение, которое приходит на ум, — позволить пользователю передать HttpClient (то есть из HttpClientFactory) в конструкторе SDK или другими методами. Подобный этому.

 public async Task<string> GetSomethingFromApiAsync(HttpClient clientFromFactory) 
{
    return await clientFromFactory.GetStringAsync(...);
}
 

Однако существует проблема, связанная с тем, что SDK настраивает HttpClient перед его использованием (прокси, тайм-аут и т.д.). Поэтому его инициализацию следует оставить в конструкторе SDK.

Вопрос

Мое текущее решение состоит в том, чтобы внедрить SDK в качестве синглтона в ASP.NET Основные проекты и примите проблемы отражения DNS. Я все еще хотел бы спросить, есть ли кто-нибудь, кто порекомендовал бы мне лучший подход к этой проблеме.

Большое тебе спасибо, Адам.

Ответ №1:

Лучший способ-использовать HttpClientFactory для создания экземпляров HttpClient.

HttpClientFactory может создавать новые экземпляры HttpClient и управлять ими, но также при создании новых экземпляров HttpClient он не создает новый обработчик сообщений, а берет его из пула. Затем он использует этот обработчик сообщений для отправки запросов в API. Время жизни обработчика по умолчанию равно двум минутам, и в течение этого времени любой запрос на новый HttpClient может повторно использовать существующий обработчик сообщений и соединение. Это означает, что нам не нужно создавать новый обработчик сообщений для каждого запроса, а также не нужно открывать новое соединение, что предотвращает проблему исчерпания сокета.

С помощью HttpClientHandler мы можем централизовать конфигурацию нашего HttpClient. Если вы повторите одну и ту же конфигурацию в каждом классе службы с помощью HttpClientHandler, вы можете предотвратить это.

Примеры того, как его использовать, вы можете найти здесь https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0

Ответ №2:

Лучший способ создать экземпляр HttpClient-использовать DI.
Итак, предположим, что в вашем проекте SDK есть такой класс

 public interface ISomeSdk
{
    Task<string> GetSomethingFromApiAsync();
}

public class SomeSdk: ISomeSdk
{
    private readonly HttpClient _httpClient;

    public SomeSdk(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> GetSomethingFromApiAsync()
    {
        return await _httpClient.GetStringAsync("");
    }
}
 

вы можете легко добавить метод расширения, чтобы зарегистрировать его через DI (здесь, в проекте SDK)

 public static class HttpClientExtensions
{
    public static void RegisterSdk(this IServiceCollection services)
    {
        services.AddHttpClient<ISomeSdk, SomeSdk>(client =>
        {
            client.Timeout = TimeSpan.FromSeconds(60);
            client.BaseAddress = new Uri("https://google.com");
        });
    }
}
 

после этого в стартовом проекте этот класс может быть добавлен при регистрации в DI

 public void ConfigureServices(IServiceCollection services)
{
    services.RegisterSdk();
    //...
 

Что позволяет использовать его всякий раз, когда это необходимо

 public class SomeController : ControllerBase
{
    private readonly ISomeSdk _someSdk;

    public SomeController(ISomeSdk someSdk)
    {
        _someSdk = someSdk;
    }