Paul Selles

Computers and cats

Team Foundation Server API: Programmatically Downloading Files From Source Control

For several of my custom check-in polices, I need to compare the developer’s local copy to a server copy of the same file. The challenge then becomes how to I find and download a file from source control programmatically using the Team Foundation Server API.

Finding the Server Items in Source Control

The first thing that you need to do is find file on the source control server that we want to download, all our searches will return an Item object or an ItemSet object (I will call this object our Server Item) [1][2]. This Server Item can be found multiple ways for example, the Item ID, the Server Item path, the Local Item Path, the Changeset Number, the Changeset Owner, or a Date Range.

Server Item Path Within Custom Check-in Policies

Within my custom check-in policy the path and ID of our Server Item can be easily found from the IPendingCheckin Interface, within the PendingChange array IPendingCheckinPendingChanges.CheckedPendingChanges Property (IPendingCheckin.PendingChanges.CheckedPendingChanges) [3][4][5]. Each CheckedPendingChanges will reference a PendingChange object that is associated with each checked out, included item. The PendingChange.ServerItem Property gives us the path to our Server Item and the PendingChange.ItemId Property gives us the Server Item ID [6][7].

Creating an Instance of VersionControlServer

All our Server Item Queries are done using VersionControlServer object and all methods mentioned below are fromthe VersionControlServer class [8]. First we need an instance of VersionControlServer:

string teamProjectCollectionUrl = "https://YourTfsUrl.com/tfs/YourTfsProjectCollection";

TfsTeamProjectCollection teamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(teamProjectCollectionUrl));
VersionControlServer versionControlServer = teamProjectCollection.GetService<VersionControlServer>();

GetItem Method

The VersionControlServer.GetItem Method have several options, but the first three are the ones that I most commonly use [9]. They are:

Examples of usage:

// Get the latest Item for local path "C:\projects\myfiles.cs"
Item item1 = versionControlServer.GetItem("C:\projects\myfiles.cs");

// Get ItemId = 12345 for changesetId = 54321
Item item2 = versionControlServer.GetItem(12345,54321);

// Get the latest Item for server path "$/ProjectName/myfile.cs"
Item item1 = versionControlServer.GetItem("$/ProjectName/myfile.cs", VersionSpec.Latest);

GetItems Method

The VersionControlServer.GetItems methods are very similar to the GetItem methods with the exception that it will return a collection of Server Items [11]. The most useful GetItems methods I have found, are those that provide a RecursionType option to get a collection Server Items within a directory structure [12].

QueryHistory Method

The added benefit of using VersionControlServer.QueryHistory Method is to use the QueryHistoryParameters [13][14]. QueryHistoryParameters allows for substantial customization of our search parameters, for example, if we wanted to download the the content of a project file a the time of a given Changeset that is not associated with the project file, we could. Since the project file in this example is not referenced to that particular Changeset, we will have to perform our query based on the DateTime of the Changeset.

// Example ChangsetNumber and ServerItemPath
int changesetNumber = 12345;
string serverItemPath = "$/ProjectName/myproject.csproj";

// Get the changeset for a given Changeset Number
Changeset changeset = versionControlServer.GetChangeset(changesetNumber);

// Create a queryHistoryParameters class based on our desired project file server path
QueryHistoryParameters queryHistoryParameters = mew QueryHistoryParameters(serverItemPath,RecursionType.None);

// We want to sort descending
queryHistoryParameters.SortAscending = false;

// Interested only one result
queryHistoryParameters.MaxResults = 1;

// Set the version end date to that of the changeset date
queryHistoryParameters.VersionEnd = new DateVersionSpec(changeset.CreationDate);

// Perform Query
ItemSet items = versionControlServer.QueryHistory(queryHistoryParameters);

Downloading the Sever Item Content

Once we have the specific Item or ItemSet of interest it’s just a matter of downloading the file contents locally. This process is quite simple and my methods make uses of the Stream and MemoryStream [15][16]. After some difficulties with the downloaded file’s encoding I use StreamReader to download the Server Item to a string [17]. Here is the method I use to retrieve the Server Item as a byte array:

public byte[] GetFileByteArray(Item item)
{  
	// create a container
	byte[] content;

	// Download file into stream
	using (Stream stream = item.DownloadFile())
	{
		// Use MemoryStream to copy steam into a byte array
		using (MemoryStream memoryStream = new MemoryStream())
		{
			stream.CopyTo(memoryStream);
			content = memoryStream.ToArray();
		}
	}

	// return byte array
	return content;       
}

Here is the method I use to retrieve the Server Item as a string:

public string GetFileString(Item item)
{
	// Setup string container
	string content  = string.Empty;

	// Download file into stream
	using (Stream stream = item.DownloadFile())
	{
		// Use MemoryStream to copy downloaded Stream
		using (MemoryStream memoryStream = new MemoryStream())
		{
			stream.CopyTo(memoryStream);
                   
			// Use StreamReader to read MemoryStream created from byte array
			using (StreamReader streamReader = new StreamReader(new MemoryStream(memoryStream.ToArray())))
			{
				content = streamReader.ReadToEnd();
			}
		}
	}

	// return string
	return content ;
}

Putting it all Together

With our new found knowledge we can create a simple console application that will retrieve the latest version for a given file.

using System;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string teamProjectCollectionUrl = "https://YourTfsUrl/tfs/YourTeamProjectCollection";
            string filePath = @"C:\project\myfile.cs";

            // Get the version control server
            TfsTeamProjectCollection teamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(teamProjectCollectionUrl));
            VersionControlServer versionControlServer = teamProjectCollection.GetService<VersionControlServer>();

            // Get the latest Item for filePath
            Item item = versionControlServer.GetItem(filePath, VersionSpec.Latest);

            // Download and display content to console
            string fileString = string.Empty;

            using (Stream stream = item.DownloadFile())
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    stream.CopyTo(memoryStream);
                   
                    // Use StreamReader to read MemoryStream created from byte array
                    using (StreamReader streamReader = new StreamReader(new MemoryStream(memoryStream.ToArray())))
                    {
                        fileString = streamReader.ReadToEnd();
                    }
                }
            }

            Console.WriteLine(fileString);
            Console.ReadLine();
        }
    }
}

Paul

Reference

[1] Item Class. MSDN Library

[2] ItemSet Class. MSDN Library

[3] IPendingCheckin Interface. MSDN Library

[4] PendingChange Class. MSDN Libary

[5] IPendingCheckinPendingChanges.CheckedPendingChanges Property. MSDN Library

[6] PendingChange.ServerItem Property. MSDN Library

[7] PendingChange.ItemId Property. MSDN Library

[8] VersionControlServer Class. MSDN Library

[9] VersionControlServer.GetItem Method. MSDN Library

[10] VersionSpec Class. MSDN Library

[11] VersionControlServer.GetItems Method. MSDN Library

[12] RecursionType Enumeration. MSDN Library

[13] VersionControlServer.QueryHistory Method. MSDN Library

[14] QueryHistoryParameters Class. MSDN Library

[15] Stream Class. MSDN Library

[16] MemoryStream Class. MSDN Library

[17] StreamReader Class. MSDN Library

2 responses to “Team Foundation Server API: Programmatically Downloading Files From Source Control

  1. Anonymous April 23, 2014 at 9:34 pm

    Good article.

    You can simplify your GetFileString to this:

    private static string GetFileString(Item item)
    {
    string content;
    using (var stream = item.DownloadFile())
    {
    var reader = new StreamReader(stream);
    content = reader.ReadToEnd();
    }
    return content;
    }

  2. Bernard Vander Beken (@jawndotnet) September 8, 2015 at 8:11 am

    Thanks!

    Minor note:
    To make it compile, change
    string filePath = “C:\project\myfile.cs”;

    to

    string filePath = @”C:\project\myfile.cs”;

Leave a comment