3

I'm trying to create a web app (hosted on Azure) for clients to be able to submit work items to our team services page. Basically a support ticket page so they don't have to call to explain their backlog all the time.

Below is the class and method i've made to create work items, following Microsoft's sample code, with some obvious changes for privacy reasons. This method is triggered by a button click, and so far I cannot get it to create any work items.

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;

 namespace customapp
 {
  public class CreateWorkItem
  {


    public void CreateWorkItemMethod()
    {

        string personalAccessToken = "xxxxxxxxx";
        string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "xxx", personalAccessToken)));

        Object[] patchDocument = new Object[1];

        patchDocument[0] = new { op = "add", path = "/fields/System.Title", value = "Test" };


        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);


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

            var method = new HttpMethod("PATCH");
            var request = new HttpRequestMessage(method, "https://example.visualstudio.com/exampleproject/_apis/wit/workitems/$Support&20Ticket?api-version=1.0") { Content = patchValue };
            var response = client.SendAsync(request).Result;


            if (response.IsSuccessStatusCode)
            {
               var result = response.Content.ReadAsStringAsync().Result;

            }

 }}}} 

In the url for the PATCH I am using the team project's ID (in place of /exampleproject you see below). Our site is set up to have an overall project, let's call it ""Master", and inside is a team project for each client, for example "ClientProject". So basically I want to create a "Support Ticket" work item in Master->ClientProject->Backlog/Board.

David Makogon
  • 69,407
  • 21
  • 141
  • 189
  • what is the error that you are getting? can you please provide info on that? – Bhadhri Vishal Kannan Jun 23 '17 at 01:52
  • Appears to be an error in where it's going. In the HttpRequestMessage I added %20 for the space between Support and Ticket, but the error was saying it wasn't a work type. So I tried "Bug" instead, which works, and the stats page on vsts says a work item was created, but I cannot locate it anywhere. –  Jun 23 '17 at 02:44
  • For the "Bug" Work Item if you try to de-serialize the response you will get an Id, which you can use to search in the VSO. for the "Support Ticket" Work Item, navigate to the Team Project "Client Project" and are you able to see the Work Item under "New" dropdown? – Bhadhri Vishal Kannan Jun 23 '17 at 02:47
  • Is the work item type named *Support Ticket* or *$Support Ticket*? Your code is trying to create a `$Support&20Ticket`, which should probably be `Support%20Ticket`. Note the lack of a leading dollar sign and the space properly escaped as `%20` instead of `&20`. – Daniel Mann Jun 23 '17 at 02:50
  • @BhadhriVishalKannan Cannot find the "Bug" Work Items but I believe it might be the admin filtering out Bug work items from that projects backlog. Not sure though. –  Jun 23 '17 at 02:59
  • @DanielMann Good catch on the & instead of %. I can now create work items, but they are sent to the Master project folder's backlog, not the team project inside of it. Adding to the payload "path = "/fields/System.AreaPath", value = "Master\teamproject" does not work and gives the error "Invalid tree name given for work item" –  Jun 23 '17 at 03:01
  • @M.W. Your explanation of your team project structure is confusing. VSTS only supports a single team project collection per account, and you're already specifying the team project in the REST URL. So you should only need to specify an area path relative to the team project root. The term "team project" has very specific meaning, and I suspect you're misusing it. – Daniel Mann Jun 23 '17 at 03:06
  • @DanielMann To explain better, the overall collection is what I refer to as the Master. Inside this collection should be an area path (I believe?) for each client, so that they each have their own backlogs and team members assigned. I thought that the "exampleproject" in the PATCH request url should be the name of the area path, but it appears it should actually be the team project (Master). So now I attempt to specify the area path with "path = "/fields/System.AreaPath", value = "areapathname" but still get "invalid tree name" error. –  Jun 23 '17 at 04:03
  • @DanielMann For the value of System.AreaPath i've tried "Master\areapath" since on the vsts page for an already created work item it displays that area in this manner. I've also just tried "areapath" but both give that same error. –  Jun 23 '17 at 04:06

2 Answers2

5

Using Master\\areapath instead (not Master\areapath).

Sample body:

[
  {
    "op": "add",
    "path": "/fields/System.Title",
    "value": "PBIAPI2"
  },
  {
    "op": "add",
    "path": "/fields/System.AreaPath",
    "value": "Scrum2015\\SharedArea"
  }
]

On the other hand, it’s better to create work item by using VSTS/TFS API with Microsoft Team Foundation Server Extended Client package.

Simple sample code:

var u = new Uri("https://[account].visualstudio.com");
            VssCredentials c = new VssCredentials(new Microsoft.VisualStudio.Services.Common.VssBasicCredential(string.Empty, "[personal access token]"));
           var connection = new VssConnection(u, c);

var workitemClient = connection.GetClient<WorkItemTrackingHttpClient>();
var workitemtype = "Product Backlog Item";
            string teamProjectName = "Scrum2015";
            var document = new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchDocument();
            document.Add(
    new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
    {
        Path = "/fields/Microsoft.VSTS.Common.Discipline",
        Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
        Value = "development"
    });
            document.Add(
                new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
                {
                    Path = "/fields/System.Title",
                    Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                    Value = string.Format("{0} {1}", "RESTAPI", 6)
                });
            document.Add(new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
            {
                Path = "/fields/System.AreaPath",
                Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                Value =string.Format("{0}\\{1}",teamProjectName, "SharedArea")
            });
            document.Add(
                new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
                {
                    Path = "/fields/System.AssignedTo",
                    Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                    Value = "[user account]"
                });
            document.Add(
                new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
                {
                    Path = "/fields/System.Description",
                    Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                    Value = "destest"
                });
var workitem= workitemClient.CreateWorkItemAsync(document, teamProjectName, workitemtype).Result;
starian chen-MSFT
  • 33,174
  • 2
  • 29
  • 53
3

The method must be POST and Uri correct for consume the Api tfs is :

https://dev.azure.com/{organization}/{proyect}/_apis/wit/workitems/${type}?api-version=5.0

Check in :

https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/work%20items/create?view=azure-devops-rest-5.0

The next code function for me.

 static void Main(string[] args)
    {
        CreateWorkItem();
    }


    public static void CreateWorkItem()
    {
        string _tokenAccess = "************"; //Click in security and get Token and give full access https://azure.microsoft.com/en-us/services/devops/
        string type = "Bug";
        string organization = "type your organization";
        string proyect = "type your proyect";
        string _UrlServiceCreate = $"https://dev.azure.com/{organization}/{proyect}/_apis/wit/workitems/${type}?api-version=5.0";
        dynamic WorkItem = new List<dynamic>() {
                new
                {
                    op = "add",
                    path = "/fields/System.Title",
                    value = "Sample Bug test"
                }
            };

        var WorkItemValue = new StringContent(JsonConvert.SerializeObject(WorkItem), Encoding.UTF8, "application/json-patch+json");
        var JsonResultWorkItemCreated = HttpPost(_UrlServiceCreate, _tokenAccess, WorkItemValue);
    }


    public static string HttpPost(string urlService, string token, StringContent postValue)
    {
        try
        {
            string request = string.Empty;
            using (HttpClient httpClient = new HttpClient())
            {
                httpClient.DefaultRequestHeaders.Accept.Clear();
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", token))));
                using (HttpRequestMessage httpRequestMessage = new HttpRequestMessage(new HttpMethod("POST"), urlService) { Content = postValue })
                {
                    var httpResponseMessage = httpClient.SendAsync(httpRequestMessage).Result;
                    if (httpResponseMessage.IsSuccessStatusCode)
                        request = httpResponseMessage.Content.ReadAsStringAsync().Result;
                }
            }
            return request;
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }
    }