1

How is it possible that I get this error message in the setup of a client when I use HttpClientFactory? Who is already using this client? I've been trying both like this as a named client and also by specifying the <Interface,Class> so it can add it as a transient - same error.

services.AddHttpClient("xxx", async client =>
{
  var tokenData = await jwtTokenProvider.GetTokenAsync(appSettingsSectionIdentity);
  if (tokenData is null || !tokenData.IsValid)
    throw new TechnicalErrorException("Can't get a token xxx");
  client.CancelPendingRequests();
  client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "xx");
  client.BaseAddress = new Uri(baseAddress);
}).AddPolicyHandler(PollyPolicys.xxx());
  • I've also tried to comment out that polly-policy, same behaviour
  • And I also added that CancelPendingRequests() but no luck
  • The TokenProvider has its own httpClient

client.BaseAddress is null here, and the baseaddress is set outside of this lambda.

The error message:

"This instance has already started one or more requests. Properties can only be modified before sending the first request"

enter image description here

And I simply request this client by:

var httpClient = _httpClientFactory.CreateClient("xxx");
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
steb
  • 436
  • 3
  • 12
  • Okey.. so this is interesting... by moving the `client.BaseAddress = new Uri(baseAddress)` to the top before requesting a token, it works.. sooo weird. – steb Feb 15 '23 at 13:00
  • Why do you call `client.CancelPendigRequests`? – Peter Csala Feb 15 '23 at 13:03
  • @PeterCsala I don't, that was just a desperate way to try everything to solve the problem. However, the error message say "has already started" so in this case (who ever is already using my client) it looked like a way to solve it.. :-) but it didn't.. – steb Feb 15 '23 at 13:18
  • Good to know. BTW are you aware of the fact that there is [no overload](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.httpclientfactoryservicecollectionextensions.addhttpclient?view=dotnet-plat-ext-7.0#microsoft-extensions-dependencyinjection-httpclientfactoryservicecollectionextensions-addhttpclient(microsoft-extensions-dependencyinjection-iservicecollection-system-string-system-action((system-iserviceprovider-system-net-http-httpclient)))) which anticipates an async `configureClient`? – Peter Csala Feb 15 '23 at 13:22
  • Could you please share with use the `GetToken` as well? – Peter Csala Feb 15 '23 at 13:35
  • Hmm, this sounds interesting, but I don't follow you there, where are you now? Should I avoid using an async lambda when adding a HttpClient? – steb Feb 15 '23 at 13:37
  • My GetToken is quite big, to cache the token, but the interesting part is basicaly using HttpClient client = _httpClientFactory.CreateClient(); var response = await client.PostAsync(url, postContent); – steb Feb 15 '23 at 13:41
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/251890/discussion-between-peter-csala-and-steb). – Peter Csala Feb 15 '23 at 13:45

1 Answers1

1

Just to capture here the essence of our discussion in the chat:

  • The GetToken was also using the HttpClient
  • Changing the order of the BaseAddress assignment and Header assignment helped
  • The configureClient should be a synchronous delegate not an async one, because that is not supported by any of the AddHttpClient overload

The suggested modification is the following:

services.AddHttpClient();

var tokenData = await jwtTokenProvider.GetTokenAsync(appSettingsSectionIdentity);
if (tokenData is null || !tokenData.IsValid)
    throw new TechnicalErrorException("Can't get a token xxx");
var token = await GetToken(tokenData, apiKey); //I assumed that this async as well

services.AddHttpClient("xxx", client =>
{
  client.BaseAddress = new Uri(baseAddress);
  client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", token);
  
}).AddPolicyHandler(PollyPolicys.xxx());
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • 1
    Thank you for your help! This is actually not possible for me, because it's a jwt token I get and it is only valid for a few minutes.. so I will need to check if the old token is still valid and if not request a new token.. so the token provider needs to be called async whenever I request this httpClient. It feels a little weird this, I must be doing something else wrong here, it must be a pretty common thing to do this, need to async get a token to a httpClient in the factory. Of course one solution is to not setup the httpClient here and do it whenever I use it instead. – steb Feb 19 '23 at 12:00
  • @steb To refresh a token I would suggestion one of the following answers: https://stackoverflow.com/questions/59833373/refresh-token-using-polly-with-named-client – Peter Csala Feb 19 '23 at 14:22
  • @steb With the policy and the middleware in your hand the client registration could be kept simple. – Peter Csala Feb 19 '23 at 14:24
  • This is actually an interesting idea.. I've also thought of using the 401 in polly, but I would also check the expiration time for the token so I wont send an unnecessary request I already know will fail. – steb Feb 21 '23 at 09:50
  • @steb Please bear in mind that the token issuer's wall-clock and your application server's wall-clock might not be in sync (even if a NTP is in place). So, there can be a time skew so, be caution whenever you want to perform time-based validation. – Peter Csala Feb 21 '23 at 10:36
  • 1
    Yes sir, that's an excellent point, and that's why I offset the validTime by 30 sec :-) but I totally agree that I also should treat 401 as a retry instead of just throw an exception :-) – steb Feb 21 '23 at 14:26