Search code examples
c#jsonasynchronousdotnet-httpclient

Re-use of serialized StringContent for http PostAsync


I'm making 2 httpclient PostAsync calls to different URIs with the same content. If I do not serialize the StringContent a 2nd time, it seems my async Task never executes to completion. Please ignore the fact that i'm not using the 'await' operator on the async calls - unless that somehow affects the result. My understanding is that it shouldn't, but obviously if that's wrong, please help with my misconception?

private static List<string> validPackageType = new List<string>(new string[] { "A", "B", "C"});
private static shipmentValidationURL = "https://shipment.com.au/v1/shipmentvalidation";
private static shipmentURL = "https://shipment.com.au/v1/shipment";

    public void Controller (){
        PostContent();
        logger.Trace("Sleep");
    }

    private async PostContent(){
        var json = JsonConvert.SerializeObject(validPackageType );
        var data = new StringContent(json, Encoding.UTF8, "application/json");
        var datastring = data.ReadAsStringAsync().Result;
        logger.Trace("data:{data}", datastring);
        logger.Trace("Shipment validation Posting to:{shipmentValidationURL}", shipmentValidationURL);
        var response = client.PostAsync(shipmentValidationURL, data);
        var respcontent = response.Result.Content.ReadAsStringAsync().Result;

        if (response.Result.IsSuccessStatusCode != true)
        {
            logger.Error("Validation Error:{errorxml}", respcontent);
            return;
        }
        else 
        {
            logger.Trace("Validation success");
        }

        logger.Trace("Calling Shipment Creation");
        data = new StringContent(json, Encoding.UTF8, "application/json"); //2nd StringContent()
        logger.Trace("data:{data}", datastring);
        var result = client.PostAsync(shipmentURL, data);
        var resultcontent = result.Result.Content.ReadAsStringAsync().Result;
        logger.Trace("Calling Shipment done");
        
        if (result.Result.IsSuccessStatusCode == true)
        {
            logger.Trace("Shipment success");

        }else{
            logger.Trace("Shipment failed");
        }
    }

Outputs:

The output WITHOUT the 2nd StringContent() call looks like:
11:01:07.1060 TRACE data:"{ A,B,C....}"
11:01:07.1060 TRACE Shipment validation Posting to:"https://shipment.com.au/v1/shipmentvalidation"
11:01:07.4680 TRACE Validation success
11:01:07.4680 TRACE Shipment update Posting to:"https://shipment.com.au/v1/shipment"
11:01:07.4680 TRACE Calling Shipment Creation
11:01:17.4865 TRACE Sleep

However if the 2nd StringContent call is made, it all executes as expected.
11:01:07.1060 TRACE data:"{ A,B,C....}"
11:01:07.1060 TRACE Shipment validation Posting to:"https://shipment.com.au/v1/shipmentvalidation"
11:01:07.4680 TRACE Validation success
11:01:07.4680 TRACE Shipment update Posting to:"https://shipment.com.au/v1/shipment"
11:01:07.4680 TRACE Calling Shipment Creation
11:01:07.1060 TRACE data:"{ A,B,C....}"
11:01:07.4680 TRACE Calling Shipment done
11:01:07.4680 TRACE Shipment success
11:01:17.4865 TRACE Sleep

Solution

  • Apparently PostAsync disposes the Content so it no longer can be used after the first PostAsync call. This is briefly discussed in multiple threads such as:

    I didn't dig deeper into why this is best behaviour or best practice (other than recreating the Content).

    Strange how Visual Studio debugger did not post an exception in the example provided above. I fiddled around with another version sprinkled with 'awaits' and by forcing the Task.Wait() and that version then warned me of the ObjectDisposedException(). I guess that's another question...