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");
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;
}