HttpClient - 此实例已经启动

wai*_*rit 19 c# asp.net-core

我在api中使用http客户端获取此异常.

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

我把我的服务注入了

services.AddSingleton<HttpClient>()
Run Code Online (Sandbox Code Playgroud)

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

编辑:我的用法

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;
   }
 }
Run Code Online (Sandbox Code Playgroud)

Nic*_*ico 46

这是HttpClient .Net Core Source类的设计.

这里有趣的方法是CheckDisposedOrStarted().

private void CheckDisposedOrStarted()
{
     CheckDisposed();
     if (_operationStarted)
     {
         throw new InvalidOperationException(SR.net_http_operation_started);
     }
}
Run Code Online (Sandbox Code Playgroud)

现在在设置属性时调用它

  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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在为什么我使用和界面?这是通过使用依赖注入和IoC完成的,我们可以非常轻松地"交换"应用程序的各个部分.现在,而不是尝试访问HttpClientFactory我们访问IHttpClientFactory.

services.AddScoped<IHttpClientFactory, HttpClientFactory>();
Run Code Online (Sandbox Code Playgroud)

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

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

readonly IHttpClientFactory _httpClientFactory;

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

这里的关键是.

  1. 工厂负责生成客户端实例并管理默认值.
  2. 我们要求接口而不是实现.这有助于我们保持组件断开连接,并允许更模块化的设计.
  3. 该服务已注册为Scoped实例.单身人士有他们的用途,但在这种情况下,你更有可能想要一个范围的实例.

每个请求创建一次范围生命周期服务.

  • 每个请求创建一个新客户端将导致端口耗尽. (5认同)
  • 人们习惯于复制不好的例子,请把它拿下来. (4认同)

Tra*_*her 9

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

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

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

...

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

如果没有一致的默认值,则不应使用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);
Run Code Online (Sandbox Code Playgroud)