TFS Programming

I’ve been doing a fair amount of Team Foundation Server API programming recently. If you need to create an application that uses the TFS APIs, then these notes might be useful.

References

Here are some handy TFS namespaces, and the DLLs that you’ll need to reference if you wish to use them.

On my pc, the dlls are located here:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ReferenceAssemblies\v2.0\

  • Microsoft.TeamFoundation.Client
    Microsoft.TeamFoundation.Client.dll
  • Microsoft.TeamFoundation.Common
    Microsoft.TeamFoundation.Common.dll
  • Microsoft.TeamFoundation.VersionControl.Client
    Microsoft.TeamFoundation.VersionControl.Client.dll
  • Microsoft.TeamFoundation.VersionControl.Common
    Microsoft.TeamFoundation.VersionControl.Common.dll
  • Microsoft.TeamFoundation.WorkItemTracking.Client
    Microsoft.TeamFoundation.WorkItemTracking.Client.dll

The Team Project Collection

Everything starts with a reference to the TfsTeamProjectCollection. To initiate this, you’ll just need your TFS URL. IE: myname.visualstudio.com

TfsTeamProjectCollection projectCollection = 
   TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(TFS_URL));

Version Control

If you’re wanting to work with version controlled files, you’ll need a reference to the VersionControlServer. It’s done like this:

VersionControlServer vcs=projectCollection.GetService<VersionControlServer>();

You can get a list of files belonging to a folder like this:

string folder = @"$/My TFS Folder";

ItemSet found = VersionControlServer.GetItems(folder, RecursionType.OneLevel);

Each Item in the returned ItemSet will have properties like:

  • ItemType (Is this a file or a subfolder?)
  • ServerItem (the file’s ID)
  • CheckInDate
  • ChangesetID

I use this function to retrieve the item’s filename, based on ServerItem:

private string FileNameFromServerItem(string serverItem)
{
   int slashPos = serverItem.LastIndexOf('/');
   return serverItem.Substring(slashPos+1, serverItem.Length - slashPos-1);
}

The Item also has a useful method, DownloadFile, that can be used to retrieve the file from TFS. Here’s a function that will download a file to a specified folder.

public static string DownloadSpecificFile(string folder, string itemId)
{
    string retVal = string.Empty;

    ItemSet found = VersionControlServer.GetItems(itemId, RecursionType.None);
    foreach (Item oneitem in found.Items)
    {
         retVal = System.IO.Path.Combine(folder, FileNameFromServerItem(oneitem.ServerItem));
         oneitem.DownloadFile(retVal);
         break;
    }

    return retVal;
}

WorkItems

If you’re wanting to work with TFS workitems, you’ll need a reference to the WorkItemStore. It’s done like this:

WorkItemStore wcs=projectCollection.GetService<WorkItemStore>();

Here is a simple function to get basic workitem data as a datatable. The fields available for a workitem can vary, so I use this function to ensure that the datatable has columns for any available fields returned by the query:

private static DataTable TFSQueryToDataTable(WorkItemCollection query)
{
    DataTable results = new DataTable();

   foreach(Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem row in query)
   {
       foreach (Field field in row.Fields)
           if (results.Columns.Contains(field.Name) == false)
               results.Columns.Add(new DataColumn(field.Name));

       DataRow newrow = results.NewRow();

       foreach (Field field in row.Fields)
       {                    
           newrow[field.Name] = field.Value;
       }

       results.Rows.Add(newrow);
   }

   return results;
}

I can then use this routine to execute a query and return a datatable:

public static DataTable TaskData(string project, bool showClosed)
        {
            DataTable results = new DataTable();

            if (projectList.Count > 0)
            {
                StringBuilder sql = new StringBuilder();
                sql.AppendLine("SELECT")
                    .AppendLine("    [System.Id], ")
                    .AppendLine("    [System.WorkItemType],")
                    .AppendLine("    [System.Title], ")
                    .AppendLine("    [System.AssignedTo], ")
                    .AppendLine("    [System.State], ")
                    .AppendLine("    [Changed Date], ")
                    .AppendLine("    [Microsoft.VSTS.TCM.ReproSteps]")
                    .AppendLine(" FROM")
                    .AppendLine("   WorkItems")
                    .AppendLine(" WHERE")
                    .AppendFormat(" [System.Title] CONTAINS  '{0}'", project.Trim());
                }
                    .AppendLine(" ORDER BY")
                    .AppendLine("    [System.WorkItemType], [System.Title]");

                WorkItemStore wcs = WorkItemStore;
                WorkItemCollection query = wcs.Query(sql.ToString());

                results  = TFSQueryToDataTable(query);              
            }

            return results;
        }

Retrieving tasks associated with a workitem

Workitems can have tasks assigned to them. To retrieve these child tasks, I use a query something like this.

private static DataTable GetTFSTaskInfo(string id)
{
    DataTable retVal = null;

    //retrieve list of merge tasks
    StringBuilder allmergetaskssql = new StringBuilder();
    allmergetaskssql.AppendLine("SELECT")
        .AppendLine("    [Source].[System.Id]")
        .AppendLine(" FROM ")
        .AppendLine("    WorkItemLinks ")
        .AppendLine(" WHERE")
        .AppendFormat("    ([Source].[System.Id] = '{0}') ", id)
        .AppendLine("    And([System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward') ")
        .AppendLine("    And ([Target].[System.WorkItemType] = 'Task' ")
        .AppendLine(" ORDER BY ")
        .AppendLine("    [System.Id] mode(MustContain)");

    WorkItemStore wcs = WorkItemStore;
    Query querylinks = new Query(wcs, allmergetaskssql.ToString());
    WorkItemLinkInfo[] links = querylinks.RunLinkQuery();

    StringBuilder onetasksql = new StringBuilder();
    onetasksql.AppendLine("SELECT")
        .AppendLine("    [Assigned To], ")
        .AppendLine("    [System.State],")
        .AppendLine("    [Microsoft.VSTS.Common.ClosedDate],")
        .AppendLine("    [Microsoft.VSTS.Common.ClosedBy]")
        .AppendLine(" FROM ")
        .AppendLine("    WorkItems ")
        .AppendLine(" WHERE");

    string or = string.Empty;

    foreach (WorkItemLinkInfo link in links)
        if (link.SourceId == Convert.ToInt32(id))
        {
            onetasksql.Append(or);
            onetasksql.AppendFormat("    [System.Id] = '{0}') ", link.TargetId);

            or = " or ";
        }

    WorkItemCollection query = wcs.Query(onetasksql.ToString());
    retVal = TFSQueryToDataTable(query);

    return retVal;
}

Attachments to workitems

It’s possible to attach documents such as JPG images or Excel worksheets to tasks. These attachments are stored in an Attachments collection associated with the workitem that they’re associated with, and can be retrieved using code similar to this:

DataTable retVal = new DataTable();

retVal.Columns.Add(new DataColumn("workitemid"));
retVal.Columns.Add(new DataColumn("workitemname"));
retVal.Columns.Add(new DataColumn("fileURI"));
retVal.Columns.Add(new DataColumn("name"));
retVal.Columns.Add(new DataColumn("lastwritetime"));
retVal.Columns.Add(new DataColumn("comment"));

foreach (Attachment attachment in workitem.Attachments)
{
    DataRow newrow = retVal.NewRow();

    newrow["workitemid"] = wi.Id;
    newrow["workitemname"] = wi.Title;
    newrow["fileURI"] = attachment.Uri.AbsoluteUri;
    newrow["name"] = attachment.Name;
    newrow["lastwritetime"] = attachment.LastWriteTime;
    newrow["comment"] = attachment.Comment;

    retVal.Rows.Add(newrow);
}

To retrieve the actual attachment file, it’s necessary to download the file from the TFS web. This routine downloads a specific attachment file.

public static bool DownloadSpecificAttachment(string URI, string name)
{
    bool retVal = true;

    System.Net.WebClient request = new System.Net.WebClient();
    request.Credentials = System.Net.CredentialCache.DefaultCredentials;

    // Save the attachment to a local file
    request.DownloadFile(URI, name);

    return retVal;
}