0

I have read through the API, online help articles, etc. and am getting 400 errors creating work items.

I have a proper token, etc. Not sure if I am doing too many things at once or not either. I need to create a parent Issue item and n number of sub child tasks linked to it. I am creating a Azure DevOps helper application for my manager to more quickly create tasks from our ticketing system. For user I am passing the email address (of a user in DevOps) and for sprint and projectID I am passing name of them.

The error I get from just creating the first parent item is :

{
   "$id":"1",
   "innerException":null,
   "message":"You must pass a valid patch document in the body of the request.",
   "typeName":"Microsoft.VisualStudio.Services.Common.VssPropertyValidationException, Microsoft.VisualStudio.Services.Common",
   "typeKey":"VssPropertyValidationException",
   "errorCode":0,
   "eventId":3000
}

I don't know what I am passing wrong in this patch call.

From appSetttings.json

 "AzureDevOpsConfig": {
    "ApiURL": "https://dev.azure.com",
    "Token": "........" }

Here is my code: ...

    [HttpPost("create")]
    public async Task<string> CreateStory(AzureStoryCreationInfo model)
    {
        var workItemData = new List<dynamic>()
        {
            new
            {
                op = "add",
                path = "/fields/System.Title",
                value = model.Title
            },
            new
            {
                op = "add",
                path = "/fields/System.IterationPath",
                value = $"{model.ProjectID}\\{model.Sprint}"
            },
            new
            {
                op = "add",
                path = "/fields/System.Description",
                value = model.Description ?? string.Empty
            }
        };

        if (!string.IsNullOrWhiteSpace(model.User))
        {
            workItemData.Add(new
            {
                op = "add",
                path = "/fields/System.AssignedTo",
                value = model.User
            });
        }

        var parentResponse = await this.CreateWorkItem(model.Organization, model.ProjectID, model.WorkItemType, workItemData);
        var parentResponseData = JsonConvert.DeserializeObject<AzureWorkItemCreateResponse>(parentResponse);

        if (parentResponseData != null && parentResponseData.Id > 0 && model.Tasks != null && model.Tasks.Any())
        {
            foreach (var task in model.Tasks)
            {
                var workItemTaskData = new List<dynamic>()
                {
                    new
                    {
                        op = "add",
                        path = "/fields/System.Parent",
                        value = parentResponseData.Id
                    },
                    new
                    {
                        op = "add",
                        path = "/fields/System.Title",
                        value = task.Title
                    },
                    new
                    {
                        op = "add",
                        path = "/fields/System.IterationPath",
                        value = $"{model.ProjectID}\\{model.Sprint}"
                    }//,
                    //new
                    //{
                    //    op = "add",
                    //    path = "/fields/System.Description",
                    //    value = string.Empty
                    //}
                };

                if (!string.IsNullOrWhiteSpace(task.User))
                {
                    workItemTaskData.Add(new
                    {
                        op = "add",
                        path = "/fields/System.AssignedTo",
                        value = task.User
                    });
                }

                var taskResponse = await CreateWorkItem(model.Organization, model.ProjectID, "Task", workItemTaskData);

                //Maybe Check if tasks fail
                var taskResponseData = JsonConvert.DeserializeObject<AzureWorkItemCreateResponse>(taskResponse);
            }

        }

        return await Task.FromResult(parentResponse);
    }

private async Task<string> CreateWorkItem(
            string organization,
            string projectIDorName,
            string workItemType,
            List<dynamic> values)
        {
            var workItemValue = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json-patch+json");

            return await PostData($"{organization}/{projectIDorName}/_apis/wit/workitems/${workItemType}?api-version=5.1&validateOnly=true", workItemValue);

            //var result = await PostData($"{organization}/{projectIDorName}/_apis/wit/workitems/${workItemType}?api-version=5.1", workItemValue);
        }

private async Task<string> PostData(string subUrl, StringContent data, string baseApi = null)
{
    if (client == null)
        client = new HttpClient();

    if (baseApi == null)
        baseApi = this.config.Value.ApiURL;

    client.DefaultRequestHeaders.Clear();

    client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json-patch+json"));

    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
        Convert.ToBase64String(
            Encoding.ASCII.GetBytes(
                string.Format("{0}:{1}", "", this.config.Value.Token))));

    using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Patch, $"{baseApi}/{subUrl}"))
    using (HttpResponseMessage response = await client.SendAsync(request))
    {
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

...

I even tried this as well :

JsonPatchDocument test = new JsonPatchDocument(
                values.Select(val => new Microsoft.AspNetCore.JsonPatch.Operations.Operation(val.op, val.path, null, val.value)).ToList(),
                new Newtonsoft.Json.Serialization.DefaultContractResolver());
            var workItemValue = new StringContent(JsonConvert.SerializeObject(test), Encoding.UTF8, "application/json-patch+json");
James Z
  • 12,209
  • 10
  • 24
  • 44
kyleb
  • 1,968
  • 7
  • 31
  • 53

1 Answers1

1

In your sample you use PATCH method. However, you have to use POST: Work Items - Create. Additionally, you do not pass any content, the PostData method does not use StringContent data. Here is the sample to create Task based on your code:

        string pat = "<your_pat>";

        var workItemTaskData = new List<dynamic>()
        {
             new
                {
                    op = "add",
                    path = "/fields/System.Title",
                    value = "MyTitle"
                }
        };

        var workItemValue = new StringContent(JsonConvert.SerializeObject(workItemTaskData), Encoding.UTF8, "application/json-patch+json");

        var client = new HttpClient();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json-patch+json"));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
            Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "", pat))));


        using (HttpResponseMessage response = client.PostAsync("https://dev.azure.com/<org_name>/<team_project_name>/_apis/wit/workitems/$Task?api-version=5.1", workItemValue).Result)
        {
            response.EnsureSuccessStatusCode();
            var result = response.Content.ReadAsStringAsync().Result;
        }
Shamrai Aleksander
  • 13,096
  • 3
  • 24
  • 31
  • Thanks changing to Post versus Send worked. But from what I read Post uses Send method internally and just constructs a HttpRequestMessage behind the scenes. Mine should of worked using HttpMethod.Post, which I did change but forgot to update code here. Also you mispoke, because you do pass StringContent data to your Post, but you just did it in one line instead of two like me. Also I was creating an "Issue" work item type, but it still works. – kyleb Feb 25 '20 at 13:50