HttpClient – 此实例已经启动

我在api中使用http客户端获取此exception。

执行请求时发生了未处理的exception。 System.InvalidOperationException:此实例已启动一个或多个请求。 只能在发送第一个请求之前修改属性。

我把我的服务注入了

services.AddSingleton() 

我认为单身是我最好的选择 。 可能是我的问题?

编辑:我的用法

 class ApiClient { private readonly HttpClient _client; public ApiClient(HttpClient client) { _client = client; } public async Task 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; } } 

这是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); } } 

现在为什么我使用和界面? 这是通过使用dependency injection和IoC完成的,我们可以非常轻松地“交换”应用程序的各个部分。 现在,我们不是试图访问HttpClientFactory而是访问IHttpClientFactory

 services.AddScoped(); 

现在,在您的类,服务或控制器中,您将请求工厂接口并生成实例。

 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实例。 单身人士有他们的用途但在这种情况下你更有可能想要一个范围的实例。

每个请求创建一次Scoped生命周期服务。

单身人士是正确的方法。 使用范围或瞬态将阻止连接池并导致性能下降和端口耗尽。

如果您具有一致的默认值,则可以在注册服务时初始化一次:

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