Home > C#, Team Foundation Server, WinRT > Using the Team Foundation Service OData API from a Windows Store app – Part one

Using the Team Foundation Service OData API from a Windows Store app – Part one

05/03/2013

With the recent introduction of Team Foundation Service OData API, we can make OData queries against Team Foundation Service to obtain the list of our projects, changesets, builds, work items, and so on. In this series of posts, I’ll show how to build a client for Windows Store that is able to consume this data.

The first thing to do is is to enable and configure basic authentication credentials, as you can read in the section Team Foundation Service authentication at https://tfsodata.visualstudio.com:

Setting TFS alternate Credentials

Setting TFS alternate Credentials

Now, we can move on the Windows Store app. In this first post, we’ll see how to connect to Team Foundation Service and get the list of team projects.

Create a new project using the Blank App (XAML). Then, delete the MainPage.xaml file, right click on the project in the Solution Explorer and select the Add | New Item… command. Now, choose Basic Page as template and name it MainPage. In this way, we have a basic structure on which we’ll add all the functionalities of our client. Add the following XAML objects to the page:

<Grid Grid.Row="1" Margin="116,0,40,46">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <ListView x:Name="itemsListView"
              SelectionMode="None">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" 
                        Foreground=
                        "{StaticResource ListViewItemOverlayForegroundThemeBrush}" 
                        Style="{StaticResource TitleTextStyle}" Height="60" 
                        TextWrapping="Wrap"
                        Margin="15,5,15,0"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    <Border Grid.Column="1" BorderThickness="1" BorderBrush="LightGray" Margin="10,0,0,0">

    </Border>
</Grid>

We have defined a Grid with two columns: on the left, there is ListView that will show all our projects, while we’ll use the right column to show information about the selected project (changesets, builds, and so on). For this first article, it will have no contents.

As default, the OData API returns XML feeds, so we can use the SyndicationClient object to retrieve them (alternatively, we have the possibility to get JSON). Create a Project class:

public class Project
{
    public string Name { get; set; }
}

At this moment, it contains only the name of the project, but we’ll add new properties in the next articles. Now, define a TfsConnector class, that handles all the communications with Team Foundation Service:

public class TfsConnector
{
    public string Domain { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public string ServiceEndpoint { get; set; }

    private string authorizationHeader;

    private const string PROJECTS_PATH = "/Projects";

    public async Task<IEnumerable<Project>> GetProjectsAsync()
    {
        var projects = new List<Project>();
        var feed = await this.GetAsync(PROJECTS_PATH);

        foreach (SyndicationItem item in feed.Items)
        {
            var project = new Project { Name = item.Title.Text };
            projects.Add(project);
        }

        return projects.OrderBy(p => p.Name);
    }

    private async Task<SyndicationFeed> GetAsync(string path)
    {
        var client = this.GetSyndicationClient();
        var uri = new Uri(ServiceEndpoint + path);
        var feed = await client.RetrieveFeedAsync(uri);

        return feed;
    }

    private string GetAuthorizationHeader()
    {
        if (string.IsNullOrEmpty(authorizationHeader))
        {
            var credentials = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}:{2}",
                                     Domain, UserName, Password);
            authorizationHeader = "Basic " +
                Convert.ToBase64String(
                Encoding.GetEncoding("us-ascii").GetBytes(credentials));
        }

        return authorizationHeader;
    }

    private SyndicationClient GetSyndicationClient()
    {
        var syndicationClient = new SyndicationClient();
        syndicationClient.Timeout = UInt32.MaxValue;
        syndicationClient.BypassCacheOnRetrieve = true;
        syndicationClient.SetRequestHeader("Authorization", 
            this.GetAuthorizationHeader());

        return syndicationClient;
    }
}

In order to connect to Team Foundation Service and use OData, we need four parameters:

  1. Domain is the domain of our account (for example, in http://myaccount.visualstudio.com, the Domain is myaccount);
  2. UserName is the user name you use to log in to Team Foundation Service. If you haven’t set a secondary user name in the Credentials tab of the User Profile window, it is your Microsoft Account user name;
  3. Password is the alternate password you have set in the Credentials tab;
  4. ServiceEndpoint is the endpoint of the OData service. For Team Foundation Service, it is https://tfsodata.visualstudio.com/DefaultCollection.

The first three properties are used to create the basic authorization header, as you can see in the GetAuthorizationHeader method. The latter is invoked in the GetSyndicationClient method, that instantiates the object used to retrieve OData response. This is, in turn, called by the GetAsync method, that takes a path as parameter, constructs the resource Uri using the ServiceEndpoint and then retrieves the feed with the requested information.

All these methods are needed by the GetProjectsAsync method, that makes a request on the /Projects path and returns a list with all the team projects. It extract information from an XML element like the following:

<entry>
  <id>https://tfsodata.visualstudio.com/DefaultCollection/Projects('MyProject')</id> 
  <title type="text">MyProject</title> 
  <updated>2013-03-09T16:07:04Z</updated> 
  <author>
    <name /> 
  </author>
  <link rel="edit" title="Project" href="Projects('MyProject')" /> 
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Changesets" 
        type="application/atom+xml;type=feed"
        title="Changesets" href="Projects('MyProject')/Changesets" /> 
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Builds" 
        type="application/atom+xml;type=feed" 
        title="Builds" href="Projects('MyProject')/Builds" /> 
  <link 
      rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/BuildDefinitions" 
      type="application/atom+xml;type=feed" 
      title="BuildDefinitions" href="Projects('MyProject')/BuildDefinitions" /> 
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/WorkItems" 
      type="application/atom+xml;type=feed" 
      title="WorkItems" href="Projects('MyProject')/WorkItems" /> 
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Queries" 
      type="application/atom+xml;type=feed" 
      title="Queries" href="Projects('MyProject')/Queries" /> 
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Branches" 
      type="application/atom+xml;type=feed" 
      title="Branches" href="Projects('MyProject')/Branches" /> 
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/AreaPaths" 
      type="application/atom+xml;type=feed" 
      title="AreaPaths" href="Projects('MyProject')/AreaPaths" /> 
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/IterationPaths" 
      type="application/atom+xml;type=feed" 
      title="IterationPaths" href="Projects('MyProject')/IterationPaths" /> 
  <category term="Microsoft.Samples.DPE.ODataTFS.Model.Entities.Project" 
      scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
  <content type="application/xml">
    <m:properties>
      <d:Name>MyProject</d:Name> 
      <d:Collection>https://mydomain.visualstudio.com/DefaultCollection</d:Collection> 
    </m:properties>
  </content>
</entry>

Now, we can call the TfsConnector class in the LoadState method of MainPage.xaml.cs:

protected override async void LoadState(Object navigationParameter, 
    Dictionary<String, Object> pageState)
{
    var tfsConnector = new TfsConnector
    {
        Domain = "my_tfs_domain",
        UserName = "my_tfs_username",
        Password = "my_tfs_password",
        ServiceEndpoint = "https://tfsodata.visualstudio.com/DefaultCollection"
    };

    var projects = await tfsConnector.GetProjectsAsync();
    itemsListView.ItemsSource = projects;
}

Our demo is ready. We can execute the project and, after some seconds, we’ll see all the team projects in the list:

TFS Projects List

TFS Projects List

The app we have described in this post is avaible for download:

TfsOData_PartOne

Note that the service is in beta, so the API is subject to change in any moment and there is room for performance improvement.

In the next article, we’ll talk about how to retrieve changesets.

About these ads
Follow

Get every new post delivered to your Inbox.

Join 27 other followers

%d bloggers like this: