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

57

我正在使用.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方法,所以我有点困惑。


1
每次调用时不要更改基地址和默认标头。 - p3tch
我应该在启动时添加baseaddress吗? - Ask
你的单例为什么是<IHttpContextAccessor,HttpContextAccessor>而不是只有<HttpClient> - p3tch
是的,以那种方式我一直收到这个错误:“在尝试激活“”时无法解析类型为""的服务”。 - Ask
你可能想看一下Flurl。它可以让从请求级别的头部到令牌管理再到HttpClient单例管理等所有事情变得更加容易。 - Todd Menier
5个回答

92

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 替换 "{...}"


这些都很好,但我真的卡在了Cookies上。它们在访问API时并不是很常见,但有时在爬取数据时需要使用。因此,我想控制Cookie容器,并将其设置为一些请求中的新容器。如果我们想要重用HttpClient,则似乎这是不可能的。 - Garr Godfrey
1
@GarrGodfrey 禁用每个请求的 cookie 是可能的,但是为一组请求拥有单独的 cookie 容器需要使用一个单独的 HttpClient 和一个单独的 SocketsHttpHandler,因此在这种情况下您将无法使用单例 HttpClient。您可以尝试使用 IHttpClientFactory 进行池化。如果您想要更详细的答案,请提出一个单独的问题。 - George Helyar
更改“UseCookies”值在第一次使用后失败,并出现与更改“BaseAddress”相同的错误。 HttpClient可能只是让人感到更麻烦,而不值得这样做。 - Garr Godfrey

18
也许我的建议可以帮到别人。 在调试应用程序时,我遇到了这个问题。 之前我一直在使用单例,但每次刷新页面时,它都会尝试设置基地址。所以我只是加了一个检查,看看基地址是否已经设定好了。 对我来说问题就在于,它在已经设置好基地址的情况下仍然尝试设置基地址。而使用httpClient是无法这么做的。
if (_httpClient.BaseAddress == null)
{
    _httpClient.BaseAddress = new Uri(baseAddress);
}

2
这实际上帮了我很多,正是我所面临的问题。 - Möoz

6
该问题是由于重置`httpclient`实例的`BaseAddress`和`headers`引起的。
我尝试过:
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
}


喜欢在这里使用工厂。但应该注意它需要依赖注入,这可能不适用于所有用例。 - AndrewGentry

3
当您在消息中添加请求URL和标头时,而不是在客户端中添加时,它可以很好地工作。因此,最好不要将其分配给BaseAddress或标头DefaultRequestHeaders(如果您将它们用于多个请求)。
HttpRequestMessage msg = new HttpRequestMessage {
    Method = HttpMethod.Put,
    RequestUri = new Uri(url),
    Headers = httpRequestHeaders;
};

httpClient.SendAsync(msg);

0
这个问题已经存在一段时间了,但是我刚刚遇到了在 Parallel.ForEachAsync 循环内调用客户端的问题。
根据 Mali Tbt 的答案解决了问题。
只需在构造函数中注入 IHttpClientFactory 并在每次迭代中使用它创建一个客户端实例即可。
适用于 OP 情况:
private readonly IHttpClientFactory _clientFactory;

// Constructor
public UserRepository(IHttpClientFactory httpClientFactory)
  {         
      _clientFactory = httpClientFactory;
  }

// calling method
public async Task<User> AddUser(string email)
{
    var url = "user/";
    var client = _clientFactory.CreateClient("UserRepository");

    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)
    { ....

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接