Search code examples
c#jsonepiserver

Need help getting JSON post working for EpiServer CMS version higher than 8.0.0


We have multiple EpiServer sites where we are adding the ability to post JSON to a site monitoring API. I was able to get the JSON posts successfully working on our EpiServer CMS version 8.0.0 sites, but encountered problems with CMS versions 8.8.1 and higher.

Below is what our successful working code looks like.

private async Task SendMaintenanceEvent(object maintenanceEvent)
{
    string endpoint = "https://OurEndpointURL.com/omitted/";
    string endpointDirectory = "target";

    // Provide basic authorization. Credentials must be base-64 encoded to be recognized.
    string credentials = "AuthCredentialsOmitted";
    string credentialsBase64 = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(credentials));

    // Convert the maintenanceEvent object to consumable JSON.
    string maintenanceEventJson = System.Web.Helpers.Json.Encode(maintenanceEvent);
    StringContent content = new StringContent(maintenanceEventJson, Encoding.UTF8, "application/json");

    using (HttpClient httpClient = new HttpClient())
    {
        httpClient.BaseAddress = new Uri(endpoint);
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentialsBase64);
        HttpResponseMessage response = await httpClient.PostAsJsonAsync(endpointDirectory, content);
        if (!response.IsSuccessStatusCode)
        {
            throw new System.Exception("Error sending maintenance event.");
        }
    }
}

The above method depends on a couple of using statements are also in this class.

using System.Net.Http;
using System.Net.Http.Headers;

The above succeeds in our EpiServer CMS 8.0.0 solutions. But when we port the same code over to one of the higher CMS versions, the posts get stuck at this line:

HttpResponseMessage response = await httpClient.PostAsJsonAsync(access.EndpointDirectory, content);

By "gets stuck" I mean the Visual Studio debugger stops on that line and never proceeds to the following line.

Researching this, I found a suggestion to use PostAsync instead of PostAsJsonAsync. So here is one of my attempts on an EpiServer 9 solution. But this ends up posting as text/plain instead of as application/json.

private async Task SendMaintenanceEvent(object maintenanceEvent)
{               
    string endpointAddress = "https://OurEndpointURL.com/omitted/";
    string endpointDirectory = "target";

    // Provide basic authorization. Credentials must be base-64 encoded to be recognized.
    string credentials = "AuthCredentialsOmitted";
    string credentialsBase64 = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(credentials));

    // Convert the maintenanceEvent object to consumable JSON.
    //string maintenanceEventToPost = System.Web.Helpers.Json.Encode(maintenanceEvent);
    //StringContent stringContent = new StringContent(maintenanceEventToPost, Encoding.UTF8, "application/json");
    string jsonMaintenanceEvent = JsonConvert.SerializeObject(maintenanceEvent);
    StringContent stringContent = new StringContent(jsonMaintenanceEvent, Encoding.UTF8, "application/json");

    using (HttpClient httpClient = new HttpClient())
    {   
        httpClient.BaseAddress = new Uri(endpointAddress);
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentialsBase64);
        HttpResponseMessage httpResponseMessage = await httpClient.PostAsync(endpointDirectory, stringContent);
        if (!httpResponseMessage.IsSuccessStatusCode)
        {
            throw new System.Exception("Error sending maintenance event to Monitor.");
        }   
    }
}

Comparing the posts in Fiddler, the successful code has a Content-Type of application/json. But the unsuccessful block of code has a Content-Type of text/plain. I thought that Content-Type was based on the StringContent object, and I've set the ContentType as follows:

StringContent stringContent = new StringContent(jsonMaintenanceEvent, Encoding.UTF8, "application/json");

I don't understand why PostAsync disregards that setting. The object has a mediaType of application/json.

And if I change the post from PostAsync to PostAsJsonAsync, the post just gets stuck, as mentioned above.

Ultimately I just need to get the JSON post working in EpiServer versions higher than 8.0.0. After working on this for several days, this is just baffling. Thanks for your help.


Solution

  • I don't know what the calling code looks like but what's happening is a deadlock, you can avoid this by using .ConfigureAwait(false) on your PostAsJsonAsync call like this:

    HttpResponseMessage response = await httpClient.PostAsJsonAsync(access.EndpointDirectory, content).ConfigureAwait(false);
    

    You can read more about deadlocks when using async/await here: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

    His example looks a lot like your problem.

    1. The top-level method calls GetJsonAsync (within the UI/ASP.NET context).
    2. GetJsonAsync (within the UI/ASP.NET context). GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context).
    3. GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.
    4. GetJsonAsync awaits the Task returned by GetStringAsync. The context is captured and will be used to continue running the GetJsonAsync method later. GetJsonAsync returns an uncompleted Task, indicating that the GetJsonAsync method is not complete.
    5. The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.
    6. … Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.
    7. The continuation for GetJsonAsync is now ready to run, and it waits for the context to be available so it can execute in the context.
    8. Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync is waiting for the context to be free so it can complete.

    Stephen lists 2 ways to avoid deadlocks

    In your “library” async methods, use ConfigureAwait(false) wherever possible.

    and

    Don’t block on Tasks; use async all the way down.

    Without seeing the code that actually calls your SendMaintenanceEvent method it's difficult to tell what's actually causing the deadlock.