-1

I have a hangfire job build in C# .netcore. This job is making postasync call inside a forloop. After first call to api, it is throwing below error.

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

How to make post call synchronously in a loop effectively? Call needs to be synchronous.

class myclass
{

  public void process(){
  
   _httpClient = new HttpClient();
   
   foreach(var item in list){
   _httpClient.BaseAddress = baseUri;
            _httpClient.DefaultRequestHeaders.Clear();
            _httpClient.DefaultRequestHeaders.ConnectionClose = true
    var res=  _httpClient.PostAsync(url,body);
    var result= res.content.readasstringasync().Result;
    saveResultInDB(result);
   }
  }
James Z
  • 12,209
  • 10
  • 24
  • 44
Anil
  • 1,669
  • 5
  • 19
  • 44
  • 2
    Post the *actual, full exception text* and *actual, executing code*. Post the full result of `Exception.ToString()` not just the message. `res.content.readasstringasync().Result;` won't even compile and if it did, blocking an async operation is a bad idea. Use `await` instead – Panagiotis Kanavos Sep 16 '21 at 15:53
  • Are you modifying `BaseAddress` or `DefaltRequestHeaders` in the loop? – Panagiotis Kanavos Sep 16 '21 at 15:57
  • @PanagiotisKanavos Yes, I have modified code snippet. – Anil Sep 16 '21 at 15:59
  • 2
    You need to `await` the `PostAsync` call (and the `ReadAsStringAsync` one too) – Jcl Sep 16 '21 at 15:59
  • `Call need to be synchronous.` it can't. `PostAsync` is asynchronous. `ReadAsStringAsync` is asynchronous. Create a separate async method, use `async/await` properly and if you really need to (why??) wait for it to finish – Panagiotis Kanavos Sep 16 '21 at 15:59
  • 1
    The code won't compile, it has concurrency bugs, but the immediate problem is that yo modify the default address and headers. Don't do that. Those are meant to be set just once. HttpClient is meant to be reused so those properties *really* are the defaults used by all calls. ` _httpClient = new HttpClient();` is a bug as well that will lead to socket exhaustion as more and more HttpClient instances are created – Panagiotis Kanavos Sep 16 '21 at 16:00
  • @PanagiotisKanavos my bad forgot to mention one thing. baseURL is dependent on value of each item in for loop. based on the item need to call different url. – Anil Sep 16 '21 at 16:40
  • In that case create an absolute URL instead of modifying the case address. Why are you setting `ConnectionClose` ? – Panagiotis Kanavos Sep 16 '21 at 16:59

1 Answers1

1

HttpClient is thread-safe and meant to be reused. Modifying BaseAddress and DefaultRequestHeaders modifies the defaults used by all requests, including the current ones. Modifying these inside a loop only causes problems.

HttpClient methods are asynchronous. Instead of blocking each one, it's far easier and cleaner to write a proper asynchronous method and then, if and only if there's a real need, call it in a blocking way.

Assuming the base URL and headers don't change, the code could change to :


HttpClient _httpClient;

public MyClass()
{
    _httpClient=new HttpClient();
    _httpClient.DefaultHeaders......;
}

public void Process()=>ProcessAsync().Wait();

public async Task ProcessAsync(List<Item> items)
{ 
   foreach(var item in items)
   {    
        var absoluteUrl=CalculateUrl(item);
        var res=  await _httpClient.PostAsync(absoluteUrl,item.Body);
        var result= await res.Content.ReadAsStringAsync();
        saveResultInDB(result);
   }
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Thanks for the detail explanation. if I need to set basic authorization and credential also changes based on url, how to handle that? – Anil Sep 16 '21 at 19:14