2016-09-02 1 views
1

Я хорошо прочитал, как экземпляры HttpClient следует использовать как можно больше, возможно даже на протяжении всего жизненного цикла приложения. Для полноты, вот несколько ресурсов, которые я основывая свои утверждения на:Повторное использование HttpClient в веб-приложении, когда требуется новый заголовок авторизации по каждому запросу

У меня есть несколько вопросов относительно этого:

  1. Как создать экземпляр приложения HttpClient с областью приложения в ASP.NET MVC для совместного использования среди всех запросов? Предположим, что на картинке нет контейнера IoC, поэтому я не могу просто привязать его в области Singleton с помощью container-name-here и называть его днем. Как мне это сделать? вручную? "
  2. Кроме того, веб-сервис я взаимодействующий с требует нового маркера авторизации на каждый запрос, так что даже если придумать способом сделать # 1 выше, как мне поставить новый заголовок авторизации на каждом запрос, чтобы он не сталкивался с потенциальными множественными параллельными запросами (исходящими от разных пользователей и еще чего-то)? Я понимаю, что HttpClient довольно поточно-защищен сам по себе, когда дело доходит до GetAsync и других методов, но установка DefaultAuthorizationHeaders для меня не является потокобезопасной, не так ли?
  3. Как я могу сохранить его под контроль?

Это как мой код в прогресс выглядит до сих пор (в несколько упрощенной форме для краткости здесь):

public class MyHttpClientWrapper : IDisposable 
{ 
    private readonly HttpClient _httpClient; 
    private readonly TokenManager _tokenManager; 

    public HttpServiceClient(HttpClient httpClient, TokenManager tokenManager) 
    { 
     _httpClient = httpClient; 
     _tokenManager = tokenManager; 

     _httpClient.BaseAddress = new Uri("https://someapp/api/"); 
     _httpClient.DefaultRequestHeaders.Accept.Add(new 
      MediaTypeWithQualityHeaderValue("application/json")); 
    } 

    public string GetDataByQuery(string query) 
    { 
     _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "amx", _tokenManager.GetNewAuthorizationCode()); 

     var response = _httpClient.GetAsync(query).Result; 
     return response.Content.ReadAsStringAsync().Result; 
    } 

    public void Dispose() 
    { 
     HttpClient?.Dispose(); 
    } 
} 

Side Примечание: Я использую инъекции зависимостей здесь, но не обязательно IoC (по причинам, не имеющим отношения к этому обсуждению).

ответ

0

После некоторого дополнительного чтения в Интернете, я пришел с этим:

public class MyHttpClientWrapper 
{ 
    private static readonly HttpClient _httpClient; 
    private readonly TokenManager _tokenManager; 

    static MyHttpClientWrapper() 
    { 
     // Initialize the static http client: 
     _httpClient = new HttpClient(); 
     _httpClient.BaseAddress = new Uri("https://someapp/api/"); 
     _httpClient.DefaultRequestHeaders.Accept.Add(
      new MediaTypeWithQualityHeaderValue("application/json")); 
    } 

    public HttpServiceClient(TokenManager tokenManager) 
    { 
     _tokenManager = tokenManager;   
    } 

    public string GetDataByQuery(string query) 
    { 
     _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "amx", _tokenManager.GetNewAuthorizationCode()); 

     var response = _httpClient.GetAsync(query).Result; 
     return response.Content.ReadAsStringAsync().Result; 
    } 

} 

Единственная проблема в том, что это не блок-проверяемым. У меня нет способа заменить клиент http поддельным. Я мог бы инкапсулировать _httpClient в свойство и сделать его нечитаемым. Таким образом, httpclient может быть перезаписан из модульного теста через средство настройки свойств. Я не уверен, что я влюблен в это решение.

Другая идея - сделать ленивую инициализацию статического _httpClient через свойство, но я не уверен, что это лучше.

Любые мысли об этих идеях? Любые другие мысли?

+1

Предполагая, что ваше приложение асинхронно, что произойдет, если несколько потоков войдут в GetDataByQuery, и все они меняют DefaultRequestHeaders.Авторизация? Я считаю, что вы можете создать объект HttpRequestMessage, установить на нем заголовок авторизации и, наконец, вызвать _httpClient.SendAsync (requestMessage) – raRaRa

+0

Да, хорошая точка. В какой-то момент я понял одно и то же, и начал делать то, что вы там описали. – Jiveman

0

Я решил сделать это несколько иначе, так что я могу позволить тестировать единицы измерения. Я использую вложение собственности здесь, чтобы позволить переопределять Http Client в модульных тестах. Но в производственном коде оно просто само инициализировалось (лениво) при первом доступе к свойству Client.

public class MyHttpClientWrapper 
{ 
    private static readonly object ThreadLock = new object(); 
    private static HttpClient _httpClient; 
    private readonly TokenManager _tokenManager; 

    public Client 
    { 
     get 
     { 
      if (_httpClient != null) return _httpClient; 

      // Initialize http client for the first time, and lock for thread-safety 
      lock (ThreadLock) 
      { 
       // Double check 
       if (_httpClient != null) return _httpClient; 

       _httpClient = new HttpClient(); 
       InitClient(_httpClient); 
       return _httpClient; 
      } 

     } 
     set 
     { 
      // primarily used for unit-testing 
      _httpClient = value; 
      InitClient(_httpClient); 
     } 
    } 

    private void InitClient(HttpClient httpClient) 
    { 
     httpClient.BaseAddress = new Uri("https://someapp/api/"); 
     httpClient.DefaultRequestHeaders.Accept.Add(
      new MediaTypeWithQualityHeaderValue("application/json")); 
    } 


    public HttpServiceClient(TokenManager tokenManager) 
    { 
     _tokenManager = tokenManager;   
    } 

    public string GetDataByQuery(string query) 
    { 
     Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "amx", _tokenManager.GetNewAuthorizationCode()); 

     var response = _httpClient.GetAsync(query).Result; 
     return response.Content.ReadAsStringAsync().Result; 
    } 

} 
Смежные вопросы