Search code examples
c#winformsazure-devopsazure-api-apps

How to manage a series of PATCH calls to Azure DevOps REST API to avoid HTTP Response Conflict error 409


In a custom C# Winforms app, I'm using the Azure DevOps REST API Update Comments call to update work item comments using async/await

My call to UpdateComment_Async is in a tight loop designed to submit all update comment requests for a single work item and then process each comment update as it completes.

Following is a mix of p-code and C# for (almost) working code. In the case where there are 2 (or more) comments to update, the first update returns HttpResponseMessage.Status 200 (success), but the second update submitted returns HttpResponseMessage.Status 409 "Conflict". I assume that ADS has the work item locked for the first update, and so the 2nd update fails with the 409. I think I proved this by sleeping the thread for 5 seconds after the call to UpdateComment_Async. With the sleep in place, both updates work.

Is there a way to manage the series of calls to UpdateComment_Async so that subsequent calls aren't done until the previous one is complete?

CODE


// get comment(s) for one work item .......

foreach (comment in workItem)
{
    newCommentText = "new comment text blah-blah-blah";

    Task<Tuple<string, string, HttpResponseMessage>> updateCommentTask = UpdateComment_Async(projectUrl, workItem.Id.ToString(), comment.Id.ToString(), newCommentText);
    
    // I put a thread.sleep(5000) right here

    updateCommentTaskList.Add(updateCommentTask);
}

// Process update comments tasks as they complete
while (updateCommentTaskList.Count > 0)
{
    Task<Tuple<string, string, HttpResponseMessage>> finishedUpdateCommentTask = await Task.WhenAny(updateCommentTaskList);

    // Get Results
    Tuple<string, string, HttpResponseMessage> updateCommentTaskResult = finishedUpdateCommentTask.Result;

    // process updateCommentTaskResult
    // etc
    // etc
    // etc
    
    updateCommentTaskList.Remove(finishedUpdateCommentTask);
}

//*******************************

public async Task<Tuple<string, string, HttpResponseMessage>> UpdateComment_Async(string projectUrl, string workItemId, string commentId, string commentNumber, string newCommentText)
{
    HttpResponseMessage responseResult = null;

    #region MAKE JSON REQUEST BODY

    IList<ClsUpdateComment> updateFieldJsonList = new List<ClsUpdateComment>();
    updateFieldJsonList.Clear();

    // Note: This code works but is not in compliance with MS docs on 2 counts.
    //
    // 1) The MS docs on comment update say that the body should look like fig 1.
    // The only I could do this was to create a new ClsUpdateComment and then add it to  
    // List<ClsUpdateComment>. Adding the ClsUpdateComment object to a list causes the [ ] 
    // to be created when the list is serialized. To make this work, I had to serialize
    // just the ClsUpdateComment object so that when serialzed, it ends up looking like Fig 2 (no brackets)
    //
    // 2) application/json-patch+json causes error 415 - unsupported media type to occur. application/json
    // works.

    /*
    Fig 1

    [
        {
            "text": "Moving to the right area path - Fabrikam-Git"
        }
    ]

    Fig 2

    {
        "text": "Moving to the right area path - Fabrikam-Git"
    }

    */

    ClsUpdateComment updateFieldJson = new ClsUpdateComment
    {
        Text = $"{newCommentText}"
    };

    updateFieldJsonList.Add(updateFieldJson);

    #endregion MAKE JSON REQUEST BODY

    #region SUBMIT UPDATE REQUEST

    string request = $"{projectUrl}/_apis/wit/workitems/{workItemId}/comments/{commentId}?api-version=5.1-preview.3";

    JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
    {
        StringEscapeHandling = StringEscapeHandling.EscapeHtml,
    };

    string updateFieldJsonSerialized = JsonConvert.SerializeObject(updateFieldJson, Formatting.None, jsonSerializerSettings);

    using (HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true, ClientCertificateOptions = ClientCertificateOption.Manual }))
    {
        client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

        HttpRequestMessage httpRequestMessage = new HttpRequestMessage(new HttpMethod("PATCH"), request)
        {
            Content = new StringContent(updateFieldJsonSerialized, Encoding.UTF8, "application/json")
        };

        using (responseResult = await client.SendAsync(httpRequestMessage))
        {
            // don't need Content, just the HttpResponseMessage
            // //string content = await responseResult.Content.ReadAsStringAsync();
        }
    }

    #endregion SUBMIT UPDATE REQUEST

    return Tuple.Create(workItemId, commentNumber, responseResult);
}


Solution

  • It's not an answer, but after trying to do mass updates of comments using the REST API in various ways, I've come to the conclusion that it simply doesn't support mass updates of comments