Зарегистрируйте HttpClient с ограниченной областью действия в .NET Core 2

У меня есть приложение веб-API NET Core 2. Во время процесса мне нужно вызвать API клиента A, чтобы получить некоторые данные. Поэтому я использую HttpClient для его вызова. Клиент A также требует, чтобы я передавал идентификатор пользователя и пароль в заголовке.

Поэтому вместо прямого введения HttpClient у меня есть оболочка вокруг HttpClient, как показано ниже

public class ClientA : IClientA
{
    private readonly HttpClient _httpClient;

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

    public async Task<string> GetData()
    {
        return await _httpClient.HttpGetAsync("someurl");
    }
 }

Затем используйте ClientA в службе

  public class MyService :IMyService
  {
      private readonly IClientA _clientA

      public MyService(IClientA clientA)
      {
           _clientA= clientA
      }

      public void DoSomethig()
      {
          _clientA.GetData();
      }           
  }

Потом все регистрирую в Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IMyService, MyService>();           

        services.AddScoped(factory =>
        {
            Func<Task<IClientA>> provider = async () =>
            {
                using (var dbContext = factory.GetService<MyDBContext>())
                {
                    // get userid and password from database here

                    var httpClient = new HttpClient();
                    httpClient.DefaultRequestHeaders.Add("UserId",userid);
                    httpClient.DefaultRequestHeaders.Add("Password",password);
                    return new ClientA(httpClient);
                }
            };

            return provider;
        });
    }

Однако я получаю сообщение об ошибке

System.InvalidOperationException: не удалось разрешить службу для типа «System.Net.Http.HttpClient» при попытке активировать «XXXXXXXXX.ClientA». в Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites (тип serviceType, тип implementationType, ISet1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, ISet1 callSiteChain)

оставшееся исключение удалено для краткости

Обратите внимание, что во время регистрации я создаю новый экземпляр HttpClient и передаю его классу ClientA, потому что мне нужно установить идентификатор пользователя и пароль.

Чтобы избавиться от указанной выше ошибки, я могу зарегистрировать HttpClient с идентификатором пользователя и паролем с помощью платформы DI, и я думаю, что это сработает.

Однако в этом случае, если есть другой клиент, ClientB, который принимает HttpClient, тогда инфраструктура DI внедрит тот же httpclient, у которого есть идентификатор пользователя и пароль. и это создаст проблему безопасности, потому что ClientB увидит учетные данные ClientA в заголовках запросов.

   public class ClientB(HttpClient client)
   {
        private readonly _httpClient;
        public class ClientB(HttpClient client)
        {
           _httpClient = client;
        }

        public string CallClientB(string url)
        {
            // here ClientB will receive ClientA's credentials
            return await _httpClient.HttpGetAsync(url);
        }
   }

person LP13    schedule 05.03.2018    source источник
comment
Что-то не так с этой регистрацией. Вы регистрируете функцию, используемую для создания клиента, а не сам клиент.   -  person Nkosi    schedule 06.03.2018
comment
yeeeks .. @Nkosi, вы правы .. скопируйте проблему с пастой на моей стороне.   -  person LP13    schedule 06.03.2018
comment
Нет необходимости вводить HttpClient. Это служебный класс. При необходимости вы должны вручную создавать экземпляры. Во всяком случае, введите параметры, необходимые HttpClient, используя шаблон параметров, например. IOptions<MyHttpClientOptions>.   -  person Brad    schedule 06.03.2018


Ответы (2)


Вы не хотите создавать экземпляр httpclient в контексте с заданной областью, который создает экземпляр httpclient для каждого запроса, что не является рекомендуемым шаблоном использования для этого класса. (плохо масштабируется). https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

Создайте синглтон с отдельным интерфейсом для каждого клиента (при условии небольшого количества клиентов) - возможно, с требованием безопасности доступа кода в его реализации, в зависимости от вашей настройки (олицетворение личности включено?)

Это будет: а) хорошо масштабироваться, б) запускаться только один раз для каждого клиента на экземпляр / запуск приложения и в) принудительно проверять доступ для использования.

Кроме того, этот ответ связан с вашими требованиями к заголовку и относится к нему - один экземпляр HttpClient с разными заголовками аутентификации

person Nathan    schedule 06.03.2018
comment
Регистрация одноэлементного экземпляра HttpClient может быть приемлемой, но в случае, если вам нужно использовать заголовки Http запроса и ответа, вы должны продолжать очищать заголовки Http после каждого запроса / ответа. В противном случае они будут кэшироваться, поскольку HttpClient в синглтоне - person LP13; 18.02.2019
comment
@ LP13 Вы можете иметь синглтон, а затем использовать метод SendAsync с запросом, который конкретно касается требуемых заголовков (сохраняет заголовки отдельно / отделенными от синглтона). например: HttpRequestMessage request = new HttpRequestMessage (HttpMethod.Delete, url); request.Headers.Authorization = new AuthenticationHeaderValue (Bearer, userSpecifcAccessToken); HttpResponseMessage response = ожидание клиента.SendAsync (запрос); (клиент является одноэлементным / статическим экземпляром HttpClient) - person Nathan; 19.02.2019

решил мою проблему

   services.AddScoped<IClientA>(factory =>
    {            
            var dbContext = factory.GetService<MyDBContext>();                
                // get userid and password from database here

                var httpClient = new HttpClient();
                httpClient.DefaultRequestHeaders.Add("UserId",userid);
                httpClient.DefaultRequestHeaders.Add("Password",password);
                return new ClientA(httpClient);
    });
person LP13    schedule 05.03.2018
comment
это не тот же код. Исходный код перенастраивал функцию, и этот фактически возвращает IClientA - person LP13; 06.03.2018
comment
Я думал, что для разработчиков код более понятен и чем набор предложений :) буду иметь это в виду - person LP13; 25.04.2018
comment
Из-за того, что регистрация ограничена, этот код по-прежнему создает экземпляр http-клиента для каждого пользовательского запроса, что означает, что он не будет работать под нагрузкой. (см. ссылку в моем ответе). - person Nathan; 28.09.2019
comment
Это серьезный антипаттерн, и он дестабилизирует ваше приложение, если с ним будет достаточно нагрузки! Это может сработать, если ваше приложение не обрабатывает много запросов, но никто не должен использовать это решение, если их приложение обрабатывает больше. - person Ravior; 14.01.2020
comment
Пожалуйста, не создавайте HttpClient всех типов, используйте для этого IHttpClientFactory. - person Daniel Botero Correa; 21.03.2020