2017-02-14 51 views
8

我在我的api中使用http客戶端得到了這個異常。HttpClient - 這個實例已經啓動了

執行請求時發生未處理的異常。 System.InvalidOperationException:此實例已經啓動一個或多個請求。屬性只能在發送第一個請求之前修改。

和我注入我的服務爲

services.AddSingleton<HttpClient>() 

我覺得單身是我最好的bet。可能是我的問題?

編輯:我使用

class ApiClient 
{ 
    private readonly HttpClient _client; 
    public ApiClient(HttpClient client) 
    { 
     _client = client; 
    } 

    public async Task<HttpResponseMessage> GetAsync(string uri) 
    { 
    _client.BaseAddress = new Uri("http://localhost:5001/"); 
    _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"); 
    var response = await _client.GetAsync(uri); 

    return response; 
    } 
} 
+2

也許發佈全班?我們目前不知道發生了什麼。 –

+0

使用'AddScoped'來代替按請求獲取不同的實例。 – Kalten

+0

消息很明確,一旦你設置了諸如BaseAddress之類的屬性併發出請求,你就不能隨後改變這些屬性。所以單身人士是好的,但只有當你設置屬性一次。 –

回答

13

這是類HttpClient .Net Core Source的設計。

這裏有趣的方法是CheckDisposedOrStarted()

private void CheckDisposedOrStarted() 
{ 
    CheckDisposed(); 
    if (_operationStarted) 
    { 
     throw new InvalidOperationException(SR.net_http_operation_started); 
    } 
} 

現在這個設置屬性

  1. BaseAddress
  2. Timeout
  3. MaxResponseContentBufferSize

所以,如果你打算重用HttpClient情況下,你應該在被稱爲設置一個預設這些的單個實例3屬性和所有用途必須不是修改這些屬性。

另外,您可以創建工廠或使用簡單的AddTransient(...)。請注意,AddScoped不適合此處,因爲您將按請求範圍接收相同的實例。

編輯基本廠

現在工廠無非就是負責向其他服務提供實例的服務更多。這是一個基本的工廠建立你HttpClient現在認識到這僅僅是最基本的,你可以擴展這個工廠爲什麼我用做你的願望和預先設置的HttpClient

public interface IHttpClientFactory 
{ 
    HttpClient CreateClient(); 
} 

public class HttpClientFactory : IHttpClientFactory 
{ 
    static string baseAddress = "http://example.com"; 

    public HttpClient CreateClient() 
    { 
     var client = new HttpClient(); 
     SetupClientDefaults(client); 
     return client; 
    } 

    protected virtual void SetupClientDefaults(HttpClient client) 
    { 
     client.Timeout = TimeSpan.FromSeconds(30); //set your own timeout. 
     client.BaseAddress = new Uri(baseAddress); 
    } 
} 

現在每個實例和接口?這是通過使用依賴注入和IoC完成的,我們可以非常容易地將部分應用程序輕鬆「交換」。現在,我們不是試圖訪問HttpClientFactory,而是訪問IHttpClientFactory

services.AddScoped<IHttpClientFactory, HttpClientFactory>(); 

現在在你的類,服務或控制器中,你會請求工廠接口並生成一個實例。

public HomeController(IHttpClientFactory httpClientFactory) 
{ 
    _httpClientFactory = httpClientFactory; 
} 

readonly IHttpClientFactory _httpClientFactory; 

public IActionResult Index() 
{ 
    var client = _httpClientFactory.CreateClient(); 
    //....do your code 
    return View(); 
} 

這裏的關鍵是。

  1. 工廠負責生成客戶端實例並管理默認值。
  2. 我們正在請求接口而不是實現。這有助於我們保持組件斷開連接並允許更多模塊化設計。
  3. 該服務被註冊爲一個Scoped實例。單身人士有他們的用途,但在這種情況下,你更可能想要一個範圍實例。

爲每個請求創建一次作用域生命期服務。

+0

謝謝。我會嘗試'AddTransient'。你能指出我在創造工廠方面的正確方向,所以我可以嘗試一下嗎?我是新來的這些東西。 –

+0

工廠只不過是一種服務來生成另一種服務或類。我將把一個非常基本的版本作爲編輯。 – Nico

+0

@Nico我有類似的問題https://stackoverflow.com/questions/47166325/httpclient-throws-error-with-the-rest-services?noredirect=1#comment81282610_47166325但不知道如果我需要設置超時財產在這裏? – xyz

1

單身是正確的方法。使用scoped或transient會阻止連接池並導致性能下降和端口耗盡。

如果你有一致的默認值,然後在該服務的註冊一次就可以初始化:

 var client = new HttpClient(); 
     client.BaseAddress = new Uri("http://example.com/"); 
     client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 
     services.AddSingleton<HttpClient>(client); 

...

 var incoming = new Uri(uri, UriKind.Relative); // Don't let the user specify absolute. 
     var response = await _client.GetAsync(incoming); 

如果沒有一致的默認值,然後BaseAddress和DefaultRequestHeaders不應該使用。改爲創建一個新的HttpRequestMessage:

 var incoming = new Uri(uri, UriKind.Relative); // Don't let the user specify absolute urls. 
     var outgoing = new Uri(new Uri("http://example.com/"), incoming); 
     var request = new HttpRequestMessage(HttpMethod.Get, outgoing); 
     request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 
     var response = await _client.SendAsync(request); 
相關問題