2016-09-02 67 views
1

我已經閱讀了很多關於如何儘可能重用HttpClient實例的問題,甚至可能在整個應用程序生命週期中。爲了完整起見,這裏有一些資源是我立足於我的發言:當每個請求需要新的授權標頭時,在Web應用程序中重用HttpClient

我有幾個關於這個問題:

  1. 如何創建HttpClient的應用程序範圍實例在ASP.NET MVC中被所有請求共享?假設圖片中沒有IoC容器,所以我不能只將它與Singleton作用域綁定在container-name-here並稱之爲一天。我該怎麼做「手動?」
  2. 此外,我與交互的Web服務需要在一個新授權令牌各要求,所以即使想出了一個辦法做到#1以上,如何在每提供一個新的授權頭請求,以便它不會與潛在的多個併發請求(來自不同用戶以及不同的請求)相沖突?我的理解是,當涉及到GetAsync和其他方法時,HttpClient本身是相當線程安全的,但設置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(); 
    } 
} 

邊注:我使用依賴注入這裏,但不一定是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客戶端。但是在生產代碼中,它只會在首次訪問客戶端屬性時進行自我初始化(懶惰)。

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; 
    } 

} 
相關問題