Httpclient 该实例已经启动了一个或多个请求。只能在发送第一个请求之前修改属性

Posted

技术标签:

【中文标题】Httpclient 该实例已经启动了一个或多个请求。只能在发送第一个请求之前修改属性【英文标题】:Httpclient This instance has already started one or more requests. Properties can only be modified before sending the first request 【发布时间】:2018-07-23 12:13:04 【问题描述】:

我正在 .Net Core 2.1 中创建一个应用程序,并且我正在使用 http 客户端进行 Web 请求。问题是我必须发送并行调用以节省时间,为此我正在使用 Task.WhenAll() 方法,但是当我点击此方法时,我收到错误“此实例已启动一个或多个请求。只能修改属性在发送第一个请求之前”以前我使用的是 RestSharp,一切都很好,但我想使用 httpclient.代码如下:

public async Task<User> AddUser(string email)

    var url = "user/";
    _client.BaseAddress = new Uri("https://myWeb.com/");
    _client.DefaultRequestHeaders.Accept.Clear();
    _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants."application/json"));
    _client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
    var json = new email = email 
    var response = await _client.PostAsJsonAsync(url,json);
    if (response .IsSuccessStatusCode)
     ....

这里是构造函数:

private readonly HttpClient _httpClient;

public UserRepository(HttpClient httpClient)
         
    _httpClient = httpClient;

方法调用:

var user1 = AddUser("user@user.com");
var user2 = AddUser("test@test.com");

await Task.WhenAll(user1, user2);

这里是启动配置:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

那么我做错了什么?我需要用 AddTransient() 更改 AddSingleton 还是有任何其他问题。还有一个问题,我需要在响应后使用 _client.Dispose() 吗,因为我遵循的教程没有使用 dispose 方法,所以我对此有点困惑。

【问题讨论】:

每次调用都不要更改基地址和默认标头 我应该在启动时添加基址吗? 你的单身人士是 &lt;IHttpContextAccessor, HttpContextAccessor&gt; 而不是 &lt;HttpClient&gt; 有什么原因吗? 是的,这样我得到了这个错误:“尝试激活'时无法解析类型''的服务” 您可能想查看Flurl。它使从请求级标头到不记名令牌再到 HttpClient 单例管理的一切都变得更加容易。 【参考方案1】:

HttpClient.DefaultRequestHeaders(和BaseAddress)在您提出任何请求之前只能设置一次。 HttpClient 只有在使用后不对其进行修改才能安全地用作单例。

不要设置DefaultRequestHeaders,而是在您发送的每个HttpRequestMessage 上设置标头。

var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent("...", Encoding.UTF8, "application/json");
var response = await _client.SendAsync(request, CancellationToken.None);

用您的 JSON 替换 "..."

【讨论】:

这一切都很好,但我真的被 Cookie 困住了。它们在访问 API 时并不常见,但有时在抓取时需要。因此,我想控制 cookie 容器并将其设置为带有一些请求的新容器。如果我们想重用 HttpClient,这似乎是不可能的 @GarrGodfrey 可以按请求禁用 cookie,但是为一组请求使用单独的 cookie 容器需要单独的 HttpClient 和单独的 SocketsHttpHandler,因此您将无法在这种情况下无论如何都要使用单例HttpClient。您可以将其与IHttpClientFactory 合并。如果您想获得更详细的答案,请提出单独的问题。 首次使用后更改UseCookies 的值失败,与更改BaseAddress 的错误相同。 HttpClient 可能只是麻烦多于它的价值。【参考方案2】:

也许我的两分钱会帮助某人。

我在调试应用程序时刷新页面时遇到了这个问题。

我使用的是单例,但每次刷新时,它都试图设置基地址。所以我只是把它包装在一个检查中,看看是否已经设置了基地址。

我的问题是,它试图设置 baseAddress,即使它已经设置好了。你不能用 httpClient 来做到这一点。

if (_httpClient.BaseAddress == null)

    _httpClient.BaseAddress = new Uri(baseAddress);

【讨论】:

这实际上帮助了我很多,这正是我所面临的。【参考方案3】:

此问题是由为同一 httpclient 实例重置 BaseAddress 和标头引起的。

我试过了

if (_httpClient.BaseAddress == null) 

但我并不热衷于此。

在我看来,更好的解决方案是使用 httpclientFactory。这将终止并在 httpclient 使用后对其实例进行垃圾收集。

private readonly IHttpClientFactory _httpClientFactory;
public Foo (IHttpClientFactory httpClientFactory) 

_httpClientFactory = httpClientFactory;

public  httpresponse Bar ()

_httpClient = _httpClientFactory.CreateClient(command.ClientId);

using var response = await _httpclient.PostAsync(uri,content);
return response;
// here as there is no more reference to the _httpclient, the garbage collector will clean 
// up the _httpclient and release that instance. Next time the method is called a new 
// instance of the _httpclient is created


【讨论】:

喜欢这里的工厂。应该注意它需要依赖注入,这可能不适合所有用例。【参考方案4】:

当您在消息中添加请求 url 和标头时效果很好,而不是在客户端。所以最好不要分配给BaseAddress 或标头DefaultRequestHeaders,如果您将它们用于许多请求。

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

httpClient.SendAsync(msg);

【讨论】:

以上是关于Httpclient 该实例已经启动了一个或多个请求。只能在发送第一个请求之前修改属性的主要内容,如果未能解决你的问题,请参考以下文章

具有不同身份验证标头的 HttpClient 单个实例

使用依赖注入管理多个 HttpClient 实例

Android 无法实例化一个或多个类

每个服务端点的 HttpClient 实例化

多个对象实例化预防?

如何在 Typescript 类中创建 Angular 5 HttpClient 实例