Visual Studio Team Services

VSTS & TFS Rest API: 04 – Project Collections and Projects

After having successfully authenticated against your VSTS or TFS system, one of the first artifacts you’ll probably work with and access are Project Collections and their Projects. As you need the first to access the later, we’ll start with..

Project Collections

In order to access your Project Collections you’ll only need the base uri of your VSTS account or TFS system, which typically look like this:

  • VSTS: https://myaccount.visualstudio.com (replace ‘myaccount’ with your VSTS’s account name obviously)
  • TFS: http(s)://hostname:port/tfs (by default TFS installs and runs, if chosen, non-https on port 8080, or https on port 443)

 

This base uri is used for the VssConnection and you’ll be using a ProjectCollectionHttpClient in order to interact with your project collections:

using System;
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

namespace ProjectCollectionsAndProjects
{
    public class Program
    {
        const string BaseUri = @"http://win2012:8080/tfs/"; // In my case this is a locally running development TFS (2015 Update 2) system

        static void Main(string[] args)
        {
            // instantiate a vss connection using the BaseUri (not the Project Collection Uri!)
            var visualStudioServicesConnection = new VssConnection(new Uri(BaseUri), new VssCredentials());

            // get ahold of the Project Collection client
            var projectCollectionHttpClient = visualStudioServicesConnection.GetClient<ProjectCollectionHttpClient>();
            
            // iterate over the first 10 Project Collections (I am allowed to see)
            // however, if no parameter(s) were provided to the .GetProjectCollections() method, it would only retrieve one Collection,
            // so basically this allows / provides fine-grained pagination control
            foreach (var projectCollectionReference in projectCollectionHttpClient.GetProjectCollections(10, 0).Result)
            {
                // retrieve a reference to the actual project collection based on its (reference) .Id
                var projectCollection = projectCollectionHttpClient.GetProjectCollection(projectCollectionReference.Id.ToString()).Result;

                // the 'web' Url is the one for the PC itself, the API endpoint one is different, see below
                var webUrlForProjectCollection = projectCollection.Links.Links["web"] as ReferenceLink;

                Console.WriteLine("Project Collection '{0}' (Id: {1}) at Web Url: '{2}' & API Url: '{3}'",
                    projectCollection.Name,
                    projectCollection.Id,
                    webUrlForProjectCollection.Href,
                    projectCollection.Url);
            }
        }
    }
}

The only really interesting things to note here is that

  • the call to projectCollectionHttpClient.GetProjectCollections(10, 0) uses pagination and if you don’t specify any page size and starting page at all, it’ll only retrieve one result by default
  • and what you’ll get back from that call is a TeamProjectCollectionReference, which can / needs to be used to retrieve the actual Project Collection

and the most relevant part here to go on is the Project Collection’s URL, which will, in case of VSTS, looks like this: https://fabrikam.visualstudio.com/DefaultCollection/

Please note, the concept of Collections is currently undergoing a change towards ‘Organizations’ and the corresponding URLs you’ll see in VSTS and later on in TFS will probably no longer include the name of the Project Collection, but working with them via the API does require them, at least for the current API version.

 

Anyway, now that we have the URL for our Project Collection.. or well – if we had known it upfront, we could have just skipped that part in order to work with..

Projects

Retrieving Existing Projects

A Project collection may contain 0 or more Projects and in order to retrieve those, you’ll need to know the Project Collection’s base url and work with a ProjectHttpClient, i.e. like this:

// first we need to create a new connection based on the current Projec Collections 'web' Url
var projectVssConnection = new VssConnection(new Uri(urlForProjectCollection), new VssCredentials());

// and retrieve the corresponding project client 
var projectHttpClient = projectVssConnection.GetClient<ProjectHttpClient>();

// then - same as above.. iterate over the project references (with a hard-coded pagination of the first 10 entries only)
foreach (var projectReference in projectHttpClient.GetProjects(top: 10, skip: 0).Result)
{
    // and then get ahold of the actual project
    var teamProject = projectHttpClient.GetProject(projectReference.Id.ToString()).Result;
    var urlForTeamProject = ((ReferenceLink)teamProject.Links.Links["web"]).Href;

    Console.WriteLine("Team Project '{0}' (Id: {1}) at Web Url: '{2}' & API Url: '{3}'",
        teamProject.Name,
        teamProject.Id,
        urlForTeamProject,
        teamProject.Url);
}

or.. if you know the (Gu)Id of a given project, you can retrieve it directly like this:

// or if we already have the project collection url and the project's Id (Guid) upfront, we can access the later directly via:
var knownProjectCollectionUrl = @"http://win2012:8080/tfs/DefaultCollection/";
var projectVssConnectionForKnownProjectCollection = new VssConnection(new Uri(knownProjectCollectionUrl), new VssCredentials());
var projectHttpClientForKnownProjectCollection = projectVssConnectionForKnownProjectCollection.GetClient<ProjectHttpClient>();
var knownTeamProject = projectHttpClientForKnownProjectCollection.GetProject("dc68c474-2ce0-4be6-9617-abe97f66ec1e").Result;

What’s interesting to note here is that the .GetProject(s) methods take an optional (boolean) parameter named ‘includeCapabilities’ which by default is set to false, but if you do retrieve the projects’ capabilities, you can access them via the project’s .Capabilities property (a Dictionary of Dictionaries) like this:

var knownTeamProject = projectHttpClientForKnownProjectCollection.GetProject("dc68c474-2ce0-4be6-9617-abe97f66ec1e", includeCapabilities: true).Result;

// check whether Project uses Git or TFS Version Control
if (knownTeamProject.Capabilities.ContainsKey("versioncontrol") && knownTeamProject.Capabilities["versioncontrol"].ContainsKey("sourceControlType"))
{
    Console.WriteLine("{0} is used!", knownTeamProject.Capabilities["versioncontrol"]["sourceControlType"]);
}

// 'processTemplate' is apparently only an existing capability IF you use the standard templates (Agile, Scrum, CMMI)
// and /or your custom Process Template provides proper Process Configurations (which earlier versions of TFS did not have)
if (knownTeamProject.Capabilities.ContainsKey("processTemplate"))
{
    Console.WriteLine("Process Template '{0}' (Id: '{1}') is used!",
        knownTeamProject.Capabilities["processTemplate"]["templateName"],
        knownTeamProject.Capabilities["processTemplate"]["templateTypeId"]);
}

Finally, you can also get the TeamProject’s default Team via the .DefaultTean property, more precisely like this:

var knownTeamProject = projectHttpClientForKnownProjectCollection.GetProject("dc68c474-2ce0-4be6-9617-abe97f66ec1e", includeCapabilities: true).Result;

Console.WriteLine("Default Team is: '{0}' (Id: {1})", knownTeamProject.DefaultTeam.Name, knownTeamProject.DefaultTeam.Id);

More details about and working with Teams will follow in a later post, too.

Creating new Projects

While retrieving Team Projects might be a common task, you can also create new Projects via the API (assuming you have the necessary permissions, i.e. Project Collection Administrator), particularly via the .QueueCreateProject(…) method. This method takes a newly constructed TeamProject instance and, as the name implies, queues the Team Project creation in the Project Collection and you can track the status of the queued creation work via a OperationsHttpClient:

// We can also create new projects, i.e. like this:
var newTeamProjectToCreate = new TeamProject();
var somewhatRandomValueForProjectName = (int)(DateTime.UtcNow - DateTime.UtcNow.Date).TotalSeconds;

// mandatory information is name,
newTeamProjectToCreate.Name = $"Dummy Project {somewhatRandomValueForProjectName}";

// .. description
newTeamProjectToCreate.Description = $"This is a dummy project";

// and capabilities need to be provided
newTeamProjectToCreate.Capabilities = new Dictionary<string, Dictionary<string, string>>
{
    {
        // particularly which version control the project shall use (as of writing 'TFVC' and 'Git' are available
        "versioncontrol", new Dictionary<string, string>()
        {
            {"sourceControlType", "TFVC"}
        }
    },
    {
        // and which Process Template to use
        "processTemplate", new Dictionary<string, string>()
        {
            {"templateTypeId", "adcc42ab-9882-485e-a3ed-7678f01f66bc"} // This is the Id for the Agile template, on my TFS server at least.
        }
    }
};

// because project creation takes some time on the server, the creation is queued and you'll get back a 
// ticket / reference to the operation which you can use to track the progress and/or completion
var operationReference = projectHttpClientForKnownProjectCollection.QueueCreateProject(newTeamProjectToCreate).Result;

Console.WriteLine("Project '{0}' creation is '{1}'", newTeamProjectToCreate.Name, operationReference.Status);

// tracking the status via a OperationsHttpClient (for the Project collection
var operationsHttpClientForKnownProjectCollection = projectVssConnectionForKnownProjectCollection.GetClient<OperationsHttpClient>();

var projectCreationOperation = operationsHttpClientForKnownProjectCollection.GetOperation(operationReference.Id).Result;
while (projectCreationOperation.Status != OperationStatus.Succeeded
    && projectCreationOperation.Status != OperationStatus.Failed
    && projectCreationOperation.Status != OperationStatus.Cancelled)
{
    Console.Write(".");
    Thread.Sleep(1000); // yuck

    projectCreationOperation = operationsHttpClientForKnownProjectCollection.GetOperation(operationReference.Id).Result;
}

// alright - creation is finished, successfully or not
Console.WriteLine("Project '{0}' creation finished with State '{1}' & Message: '{2}'",
    newTeamProjectToCreate.Name,
    projectCreationOperation.Status,
    projectCreationOperation.ResultMessage ?? "n.a.");

Updating Projects

You can also update existing Projects, particularly their Description but also Name using the .UpdateProject() method like this:

newTeamProjectJustCreated.Description = "Some updated Description";

var projectUpdateOperationReference = projectHttpClientForKnownProjectCollection.UpdateProject(newTeamProjectJustCreated.Id, newTeamProjectJustCreated).Result; // .Id stays the same, even after updates

Console.WriteLine("Project '{0}' Update is '{1}'", newTeamProjectToCreate.Name, projectUpdateOperationReference.Status);

// and again, we track the queued deletion work / operation like before
var projectUpdateOperation = operationsHttpClientForKnownProjectCollection.GetOperation(projectUpdateOperationReference.Id).Result;
while (projectUpdateOperation.Status != OperationStatus.Succeeded
    && projectUpdateOperation.Status != OperationStatus.Failed
    && projectUpdateOperation.Status != OperationStatus.Cancelled)
{
    Console.Write(".");
    Thread.Sleep(1000); // again, yuck

    projectUpdateOperation = operationsHttpClientForKnownProjectCollection.GetOperation(projectUpdateOperationReference.Id).Result;
}

Console.WriteLine("Project '{0}' Update finished with State '{1}' & Message: '{2}'",
    newTeamProjectJustCreated.Name,
    projectUpdateOperation.Status,
    projectUpdateOperation.ResultMessage ?? "n.a.");

Deleting Projects

Similarly you can also delete previously created Team Projects via the API, more precisely via the .QueueDeleteProject() method:

var projectDeletionOperationReference = projectHttpClientForKnownProjectCollection.QueueDeleteProject(projectGuid).Result;

Console.WriteLine("Project '{0}' Deletion is '{1}'", newTeamProjectToCreate.Name, projectCreationOperationReference.Status);

// and again, we track the queued deletion work / operation like before
var projectDeletionOperation = operationsHttpClientForKnownProjectCollection.GetOperation(projectDeletionOperationReference.Id).Result;
while (projectDeletionOperation.Status != OperationStatus.Succeeded
    && projectDeletionOperation.Status != OperationStatus.Failed
    && projectDeletionOperation.Status != OperationStatus.Cancelled)
{
    Console.Write(".");
    Thread.Sleep(1000); // again, yuck

    projectDeletionOperation = operationsHttpClientForKnownProjectCollection.GetOperation(projectDeletionOperationReference.Id).Result;
}

// alright - creation is finished, successfully or not
Console.WriteLine("Project '{0}' Deletion finished with State '{1}' & Message: '{2}'",
    newTeamProjectToCreate.Name,
    projectCreationOperation.Status,
    projectCreationOperation.ResultMessage ?? "n.a.");

Conclusion

That’s it for the Project Collections and Projects currently – there’s one method for Projects left not covered here (.GetProjectHistory()) which does exactly what the name states – gets all historic revision of a Team Project, i.e. to see what changed over time (Description, Name etc). However, that’s a rather rare use-case and follows the rest of the methods and APIs shown above, so its for you to find out yourself.

 

Index for all Posts | Full code is available at GitHub

Discussion

Comments disabled.