HttpClient - 此实例已经启动

时间:2017-02-14 20:33:43

标签: c# asp.net-core

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

  

执行请求时发生了未处理的异常。   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;
   }
 }

3 个答案:

答案 0 :(得分:40)

这是班级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
  4. 因此,如果您计划重复使用HttpClient实例,则应设置一个预设这3个属性的实例,并且所有用途必须 NOT 修改这些属性。

    另外,您可以创建工厂或使用简单的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完成的,我们可以轻松地交换&#34;部分应用程序非常容易。现在,我们不是试图访问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实例。单身人士有他们的用途,但在这种情况下,你更有可能想要一个范围的实例。
    4.   

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

答案 1 :(得分:7)

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

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

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

答案 2 :(得分:0)

最好在消息中添加请求的 url 和标头,而不是在客户端上。除非万不得已,否则最好不要使用 BaseAddressDefaultRequestHeaders

HttpRequestMessage yourmsg = new HttpRequestMessage {
    Method = HttpMethod.Put,
    RequestUri = new Uri(url),
    Headers = httpRequestHeaders;
};


httpClient.SendAsync(yourmsg);

它适用于为多个请求重用单个 HttpClient