11 March 2010

Adding Properties to Artifacts within TFS 2010

As you know, you can store a lot of information within Team Foundation Server – in the form of Work Items and Versioned Items (e.g. source code).  While these are great features within TFS there are times when you want to store other types of information within TFS and/or “tag” existing items with custom information.  For example, if you create a Visual Studio add-in for TFS, you might want to store the add-in’s properties within TFS such that they “follow” the developer from machine to machine – or - maybe you want to store some custom information with a specific changeset?

To help with the above scenarios (and many others) Team Foundation Server 2010 introduces the concept of Properties.  There are several types (Artifact Kinds) of properties defined within TFS (this set of properties can be expanded as well) and, depending upon the type, they are accessed in different ways.  The default set of Artifact Kinds include:

  • Version Control
    • Changeset
    • Versioned Item
    • Pending Change
    • Annotation (no longer used)
  • Framework
    • Generic (at Project Collection or Configuration Server)

Of the above Artifact Kinds, those listed under Version Control are considered to be “Internal”.  Internal properties can only be accessed by the service-specific APIs they are associated with (e.g. VersionControlServer).  As such, access to these properties are secured by the artifact they are associated with (e.g. a versioned file within TFS).

The Generic Artifact Kind is not considered to be internal.  Generic properties are accessed via the IPropertyService interface.  Unlike internal properties, there is no access control mechanism for generic properties.

Identifying Properties

Properties are identified by an ArtifactSpec. There are three constructor overloads including:

  • ArtifactSpec(Guid kind, int artifactId, int version);
  • ArtifactSpec(Guid kind, byte[] artifactId, int version);
  • ArtifactSpec(Guid kind, string moniker, int version);

In the case of internal properties, you don’t care much about the ArtifactSpec because it is hidden by the service-specific APIs.  For Generic (non-internal) properties, you will need to define an ArtifactSpec.  You can use any of the above constructors when creating an ArtifactSpec for a Generic property but the 3rd one (with the moniker parameter) seems like it would be the most common choice.

Generic Property Example:

Here is a simple example for setting a Generic property:

public void SetGenericProperty(bool isInstanceProperty, string moniker, int version, string propertyName, string propertyValue)
{
IPropertyService propertyService;

if (isInstanceProperty)
{
propertyService = _tpc.ConfigurationServer.GetService<IPropertyService>();
}
else
{
propertyService = _tpc.GetService<IPropertyService>();
}

ArtifactSpec artifactSpec = new ArtifactSpec(ArtifactKinds.Generic, moniker, version);

propertyService.SetProperty(artifactSpec, propertyName, propertyValue);
}

Notice the “if” check on line 5.  Here we’re checking to see if the Generic property should be stored at the Configuration Server (i.e. “instance”) or stored at the Project Collection.  If you want your property accessible across multiple project collections, store it at the Configuration Server.

Using the above method, here’s an example call to store this blog’s URL:

SetGenericProperty(false, "A Developer's Life", 1, "blog", "devmatter.blogspot.com");

In this example, the second parameter (“A Developer’s Life”) defines the moniker for this property.  In this case, the moniker groups a set of properties.  Grouping properties under a common moniker allows you to retrieve all (or a subgroup of) properties for a given moniker (e.g. using wildcards).  For example, here is a simple method for retrieving a list of Generic properties:

public IEnumerable<ArtifactInfo> GetGenericProperties(bool isInstanceProperty, string moniker, int version)
{
IPropertyService propertyService;

if (isInstanceProperty)
{
propertyService = _tpc.ConfigurationServer.GetService<IPropertyService>();
}
else
{
propertyService = _tpc.GetService<IPropertyService>();
}

ArtifactSpec artifactSpec = new ArtifactSpec(ArtifactKinds.Generic, moniker, version);
ArtifactPropertyValue[] values = propertyService.GetProperties(artifactSpec, null);

var props = new List<ArtifactInfo>();

foreach (var value in values)
{
foreach (var keyValue in value.PropertyValues)
{
props.Add(new ArtifactInfo()
{
Moniker = value.Spec.Moniker,
Version = value.Spec.Version,
Kind = value.Spec.Kind,
Id = value.Spec.Id,
PropertyName = keyValue.PropertyName,
PropertyValue = keyValue.Value.ToString(),
});
}
}

return props;
}

Just like in the “SetGenericProperty” example we’re checking whether to look for the properties at the Configuration Server or at the Project Collection.  Calling the method is also very simple:

var results = GetGenericProperties(false, "A Developer's Life", 1);

This call would return a list of all properties defined under the moniker “A Developer’s Life”.  Note that you can also make use of the wildcard characters ‘*’ and ‘?’ in the moniker.  For example:

This call would return the properties defined under the same moniker as above as well as any other moniker that starts with the text “A Dev”.

As with monikers, you can also apply filters the the property names themselves by using one of the overloaded versions of IPropertyService.GetProperties (line 15 above is using an overload that does not specify property filters).

A Property Browser

Unfortunately, there is no property “browser” built into Visual Studio/Team Explorer.  Therefore, I decided to create one to ease testing and querying of properties.  Although this utility is not 100% functional/complete, yet, I have uploaded the application source and binaries in their current state (you only need the EXE if you don’t care to look at the source code).  You can download the project from hereNOTE: you must have the Team Foundation 2010 Client installed for this utility to run.  The source code is part of a Visual Studio 2010 solution/project.  Once I’ve completed the project (i.e. fully implemented each of the property features) I will most likely upload the project to CodePlex or the MSDN Code Gallery.

When you first run the application, known as the “Artifact Property Browser”, you will be prompted to select a Project Collection:

Select Team Project Collection

Once you select a Project Collection, you will see something similar to this:

Artifact Property Browser

Notice the Artifact Type drop-down list – it currently lists Changeset, Generic, and Versioned Item (Pending Change is not yet implemented).  As you change the Artifact Type the various options will change depending upon the type selected.

To query properties:


  1. Select the desired Artifact Type

  2. Specify the appropriate values

  3. Click Search

For example, to see all Generic properties at the Project Collection level, enter the following values and click Search:


  • Artifact Type: Generic

  • Option: Moniker (Artifact ID (int) and Artifact ID (byte[]) is not yet implemented)

  • Moniker: * (wildcard character for all monikers)

  • Version: 1

  • Instance Property: Unchecked (check to see properties at the Configuration Server level)

Artifact Property Browser

Notice that we have one property defined, “blog”, with a value of “devmatter.blogspot.com”.

To add another Generic property, simply:


  1. Select the desired Artifact Type

  2. Specify the appropriate values in the Artifact Filter section (e.g. for Generic properties, set the Moniker and Version)

  3. Specify a property Name and Value in the Property section

  4. Click Set Value

Querying and setting properties for the other artifact types follow the same approach only the Artifact Filter settings change.  For example, querying Changeset properties looks something like this:

Artifact Property Browser

In this example, were querying all properties for Changeset number 3.

Querying Versioned Item properties looks like this:

Artifact Property Browser

In this example, we’re querying properties for all items that match the specified ItemSpec (in this case, a versioned folder) and VersionSpec (in this case “T” for “Latest” – click here for other examples).  We’ve also checked the “Recursive” option to retrieve properties for all items under the ItemSpec. (note: the properties shown above are set by TFS).

There are still quite a few examples that can be provided for the various property APIs (e.g. Versioned Item) that are not covered in this post.  I plan on creating a follow-up post in the near future.  In the meantime, download the provided source code for further examples.

Download the Artifact Property Browser and source code here.