After working with Visual Studio Team System at Southworks for some months, I noticed a common practice that some of us repeated frecuently: after creating a Work Item we use to send an email to the assigned user just with the subject: "Assigned WI #xxx". This was not only a boring task, but also innecessary and prone to errors. VSTS was born to be extended, so I started researching on how to extend it with my own artifacts to provide better user experience for the whole team at Southworks.

The result of this effort is a simple and effective solution: when a user A assigns a work item to a user B, B recieves automatically an email with the notice and all the information he needs about the work item assigned:

Email automatically recieved by the assigned user

Now let's see how this was achieved.

Note: In this article I explain a way of extending VSTS. Similar results of the implemented solution can be achieved using Team Foundation Alerts, anyway, my goal is to provide you with an extensible open-source solution which you can take and adapt to your needs easily. I found Team Foundation Alerts somehow difficult -if not impossible- to extend or customize, so I hope my solution will help you when dealing with notifications in Team Foundation Server.

Extending Visual Studio Team System

VSTS has many extension points, for example:

  • Reporting. Eg.: extend TFS Warehouse.
  • Project Portal. Eg.: create new web parts.
  • Core Services. Eg.: link work items to other tool's artifacts, subscrive to events.
  • Work Item Tracking. Eg.: write against object model.
  • Source Control. Eg.: create new police.
  • Build. Eg.: create new build tasks.
  • Others...

Note that creating a custom process template, defining new work item types, creating reports, etc. are not listed here because they are customization examples. In this article I will discuss about extending VSTS (which involves development activities) rather than customizing it (which generally can be achieved by using provided tools).

From the list shown above, we will focus in the Core Services and build our solution using the Eventing Service.

Eventing Service

The Eventing Service is an extensible asynchronous publish/subscribe event notification system included in VSTS. It is used in two different scenarios:

  1. Integration of loosely coupled services/systems.
  2. User notification of events.

In both scenarios, there exists one or more publishers and cero or more subscribers for events. Subscribers may define filter expressions, so that notifications to subscribers are delivered only if the filter condition is true.

In scenario N-1, the mechanism used for notification is SOAP. This simply implies that when an event is fired, subscribed web services will be invoked (if they match the event filter). Note that these web services must have a specific (and simple) signature, but we will talk about this later. As regards scenario N-2, the notification mechanism is e-mail: every user subscribed to an event will recieve an e-mail with event-specific information when it is delivered.

 VSTS includes built-in events but, if you need more functionality, you can register events raised from your own applications. Some of the built-in events included in RC are:

Event Name Fired when...
BuildCompletionEvent a build has completed.
CheckInEvent files are being checked in to source control.
WorkItemChangedEvent a work item has been changed.

To find a list of all events that your version of VSTS can fire, have a look at this post [1].

At a first glance, the scenario N-2 seems to be the one that fits our situation, but actually it doesn't. As I said before, user notification of events means that each subscribed user will recieve an e-mail per each subscription when the event is fired. But this also means that when subscribing to an event, we have to know which user will have a work item assinged to - which is impossible at that moment. That's why our situation fits scenario N-1: we need to process event data before sending an e-mail to the assigned user.

Having said this, we are ready to move to next level: solution design.

Solution Design

Our solution will take advantage of the Eventing Service described recently by subscribing a custom web service to the WorkItemChangedEvent. Every time a work item is changed, the web service will be invoked with the event information. This information includes the old and new values of the work item's common and changed fields. Then, the web service will check some bussiness rules, look in the Active Directory for the email address of the assigned user and finally, send the notification email.

It is said that a picture tells more than a thousands words... so here is solution diagram:

Solution Design

Implementation

The Web Service

First we need to create the web service that will listen to the WorkItemChangedEvent. Every web service subscribed to the Eventing Service must have the following signature:

[SoapDocumentMethod(Action = 
    "http://schemas.microsoft.com/TeamFoundation/
     2005/06/Services/Notification/03/Notify",
RequestNamespace="http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03"
)]
[WebMethod]
public void Notify(string eventXml, string tfsIdentityXml) { //... }

When TFS calls the web service, the eventXml string will hold all the event information in a XML format that matches the schema defined for the event. If you want to see the schema for the WorkItemChangedEvent, click here. To see other events' schemas, see this post [1]. As regards the tfsIdentityXml string, it will only hold the caller TFS address.

Here is my implementation of the web method:

[SoapDocumentMethod(Action = 
    "http://schemas.microsoft.com/TeamFoundation/
    2005/06/Services/Notification/03/Notify",
RequestNamespace="http://schemas.microsoft.com/TeamFoundation/2005/
06/Services/Notification/03"
)]
[WebMethod]
public void Notify(string eventXml, string tfsIdentityXml)
{
// Load the recieved XML into a XMLDocument XmlDocument eventXmlDoc = new XmlDocument();
eventXmlDoc.LoadXml(eventXml);
XmlElement eventData = eventXmlDoc.DocumentElement;

// Validate event data if (eventData != null && ValidateEventData(eventData))
{
// Get assigned user's name from event data string toName = GetDisplayName(eventData);

// Retrieve assigned user's e-mail address from Active Directory
string toAddress = GetAddress(toName);

// If the e-mail addres was found in AD, send the e-mail if (toAddress != String.Empty)
SendMail(new MailAddress(toAddress, toName),
eventData, GetBody(eventXml));
}
}

As you can see, the logic is quite simple. I suggest you to watch the source code for more details. Anyway, some methods deserve more detailed explanation:

  • ValidateEventData(XmlElement eventData): As the web method will be invoked whenever a work item is changed (see section Subscribing to WorkItemChangedEvent below), we need to check if the Assigned To field has changed and that the user that changed the work item is not the same as the assigned user. You don't need to recieve an email notification if you assigned the work item to yourself, do you? Here is the code:

    private bool ValidateEventData(XmlElement eventData)
    {
    // Check if the Assigned To field changed XmlNode assignedTo = eventData
    .SelectSingleNode("ChangedFields/StringFields/Field[ReferenceName='System.AssignedTo']");

    if (assignedTo != null)
    {
    // Check that the assigned user is different to the user that changed the work item XmlNode changedBy = eventData
    .SelectSingleNode("CoreFields/StringFields/Field[ReferenceName='System.ChangedBy']");

    return (assignedTo.SelectSingleNode("NewValue").InnerText !=
    changedBy.SelectSingleNode("NewValue").InnerText);
    }
    else return false;
    }



  • GetAddress(string userDisplayName): This method is in charge of accessing the Active Directory [2] in current domain to retrieve user's e-mail address given its Display Name. Note that since the RC version of Team Foundation Server the Display Name is what VSTS uses to identify a user. Besides, take into account that you will need read access to the AD.

    private string GetAddress(string userDisplayName)
    {
    // Bind DirectorySearcher to current domain DirectorySearcher searcher = new DirectorySearcher();

    // We only need user email searcher.PropertiesToLoad.Add("mail");

    // Set the filters. We only want Users whose Display Name is userDisplayName searcher.Filter = String.Format("(&(displayName={0})(objectCategory=person)((objectClass=user)))"
    , userDisplayName);

    // Retrieve results SearchResultCollection results = searcher.FindAll(); // Check if the user was found if (results.Count > 0)
    {
    // Check if the user has the email set in AD ResultPropertyValueCollection values = results[0].Properties["mail"];
    if (values.Count > 0)
    return values[0].ToString();
    else return String.Empty;
    }
    else return String.Empty;
    }


  • GetBody(string eventXml): To create the e-mail body, I used an adapted version of the XSL transformation that TFS uses when sending user notifications by e-mail. Supposing you installed TFS in the default directory, the xsl file can be found in C:\Program Files\Microsoft Visual Studio 2005 Team Foundation Server\Web Services\Services\v1.0\Transforms\WorkItemChangedEvent.xsl. I ommited method implementation here because it's just an XSL transformation. Anyway, the full source code is available in the download package.

    A useful feature to keep in mind is that TFS exposes a nice and complete web page that allows you to visualize work items from your web browser. The URL of this page is stored in the DisplayUrl node of the eventXml string, so you should use it! If you have never seen this web page, just point your browser to http://[TFS]:8080/WorkItemTracking/WorkItem.aspx?artifactMoniker=[WorkItem#].

    TFS provides a web page for visualizing work items

Subscribing to the WorkItemChangedEvent

We have the web service complete, but it will never get called if we don't subscribe it to the Eventing Service!
The process of subscribing to events can be done in two different ways: calling a TFS web service (http://[TFS]:8080/services/v1.0/Eventservice.asmx) or using the TFS object model. Independently from the method you choose, you need to call the SubscriveEvent method and supply the following parameters (from VS SDK 2005):

  • userId is an arbitrary string for identifying the user. It is used for looking up subscriptions by owner. In Beta 3, the Project Alerts set in Team Explorer use e-mail addresses for the userId. For the final release, the system will use SIDs.
  • eventType is the fully qualified name of the event type to which the user is subscribing.
  • filterExpressions is string that describes how to filter through many instances of this event type. Only events that match the filter are delivered.
  • DeliveryPreference is a structure that has the following format. It describes how an event that matches the eventType and filterExpression is to be delivered. The filtering language is discussed in greater detail in a later section.

    class DeliveryPreference
    {
    	enum Type;   	// EmailHtml, EmailPlaintext Soap.
    	enum Schedule;	// Immediate, Daily or Weekly.
    	string Address;	// Email address or SOAP URL depending 
    //on the Type or other identifying string.
    }

This time I used the TFS Object Model, so here is a code snippet for subscribing to the WorkItemChangedEvent (not included in the download package):

using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;

// ... // Connect to TFS TeamFoundationServer tfsServer = TeamFoundationServerFactory.GetServer
(ConfigurationManager.AppSettings["TFSServer"], new UICredentialsProvider());
tfsServer.Authenticate();

// Get Eventing Service IEventService eventService = (IEventService)tfsServer.GetService(typeof(IEventService));

// Set delivery preferences DeliveryPreference dPref = new DeliveryPreference();
dPref.Schedule = DeliverySchedule.Immediate;
dPref.Address = ConfigurationManager.AppSettings["WSAdress"];
dPref.Type = DeliveryType.Soap;

// Subscribe to event string userId = ConfigurationManager.AppSettings["Subscriber"];
string filter = String.Empty;
eventService.SubscribeEvent(userId, "WorkItemChangedEvent", filter, dPref).ToString();

Note: It's important to notice that we didn't use any subscription filter in this solution, altough it would be desirable and would avoid using unnecessary network bandwidth. For instance, I thought of a filter expression like this: "notify me only if the old value of the Assigned To field is different to the new value of the Assigned To field."  The big obstacle for doing this is that the language used to write event filter expressions in Visual Studio (VSEFL) is a bit limited. You can't make comparissons between two fields in a work item. You are only allowed to make comparisons between a field and a constant. Check out this forum thread for more information.

Concluding...

In this article I tried to show you, in simple words, one means of extending VSTS. With current lack of official documentation about this topic, I hope this article will help you save time understanding a bit more about Visual Studio Team System. Take into account that there exist many other ways of customizing and extending VSTS. Keep an eye on this blog as I will be posting about them as I go on with my research.

Next Steps

If you enjoyed extending VSTS, don't miss these useful links. You will find a lot of information about Customizing & Extending VSTS!

Sources

Download them from here.


Footnotes

  1. Marcel Vries, Team System MVP. Blog: http://blogs.infosupport.com/marcelv/.
  2. If you want more information about using Active Directory in your applications, I recommend you to visit: