2017-02-14 50 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); 
相关问题