Click here to Skip to main content
Click here to Skip to main content

TFS Event Handler in .NET 3.5 Part 2 - Handling Team Foundation Server Events

, 6 Sep 2007
Rate this:
Please Sign up or sign in to vote.
I have decided to have a little go at creating a Team Foundation Server Event Handler in .NET 3.5 that is resilient and scalable. I will be using as many of the features of Team Suite as I can, but bear with me as there are a few things that are new to me.

Introduction

I have decided to have a little go at creating a Team Foundation Server Event Handler in .NET 3.5 that is resilient and saleable. I will be using as many of the features of Team Suite as I can, but bear with me as there are a few things that are new to me.

TFS Event Handler in .NET 3.5 articles:

  1. TFS Event Handler in .NET 3.5 Part 1 - The Architecture
  2. TFS Event Handler in .NET 3.5 Part 2 - Handling Team Foundation Server Events
  3. TFS Event Handler in .NET 3.5 Part 3 - Passing the events over a Windows Communication Foundation MSMQ (Coming soon)
  4. TFS Event Handler in .NET 3.5 Part 4 - Workflow (Coming soon)

Handling Team Foundation Server Events

Because of the lack of support for Windows Communication Foundation in the Team Edition for Architects, I will be replacing all of the auto-generated services with Windows Communication Foundation services. But, as it turns out, this messes up everything, so I have started from scratch, and I will post all of the code for doing this without the Architect bit.

The first thing that you need is the Contract for Team Foundation Server event handling. This is very specific, and only works if used accurately:

Imports System.ServiceModel
Imports System.Runtime.Serialization
Imports Microsoft.TeamFoundation.Server

''' <summary>
''' This is the service contract for integrating with the Team 
''' Foundation Server notification events.
''' </summary>
''' <remarks></remarks>
<ServiceContract(Namespace:="http://schemas.microsoft.com/
    TeamFoundation/2005/06/Services/Notification/03")> _
Public Interface INotification

    ''' <summary>
    ''' The Notify method if fired whenever a subscribed event 
    ''' arrives.
    ''' </summary>
    ''' <param name="eventXml">This XML defines the data that was 
    '''    changed on the event.</param>
    ''' <param name="tfsIdentityXml">This XML identifies the 
    '''    Team Foundation Server the event came from.</param>
    ''' <param name="SubscriptionInfo">Information about the 
    '''    subscriber</param>
    ''' <remarks></remarks>
    <OperationContract( _
                    Action:=http://schemas.microsoft.com/ + 
                    "TeamFoundation/2005/06/Services/Notification/" +
                    "03/Notify", _
                    ReplyAction:="*" _
                    )> _
    <XmlSerializerFormat( _
                    Style:=OperationFormatStyle.Document _
                    )> _
    Sub Notify(ByVal eventXml As String, 
        ByVal tfsIdentityXml As String, 
        ByVal SubscriptionInfo As SubscriptionInfo)

End Interface

This code allows you to capture TFS events, and we will be using by reference only. If you have been having trouble handling the events, then look know further than here for Windows Communication Foundation as this has been tried and tested.

The real trick for handling TFS events in Windows Communication Foundation is that all the events come into the same routine. It would be nice if SubscriptionInfo listed what event it was, but alas, it does not. So, you may be thinking that you need to parse the XML and find out what type of event it is. Well, you can if you want, but I find it easer to have more than one endpoint for the same service. You can then parse the URL for the event type, which is way easier than the XML as the events are all different.

The first thing you need to do is create a project to hold the code. In the spirit of expedition, I have created a "Windows Communication Foundation Service Application" to hold all of the Notification service code. But, in the real world, you would keep the Contract and Implementation classes in different assemblies.

image

Now, we have the project, you can add the INotification class and the Notification service. I like to keep all of my services in "v1.0", so that I can add other services in new versions without affecting the current version.

image

Once you have your INotification class looking like the code extract above, we will add a default implementation and test the service. The default implementation should look like:

Imports Microsoft.TeamFoundation.Server

Public Class Notification
    Implements INotification

    Public Sub Notify(ByVal eventXml As String, 
        ByVal tfsIdentityXml As String, 
        ByVal SubscriptionInfo As SubscriptionInfo) 
        Implements INotification.Notify

    End Sub

End Class

This class just implements the INotification contract (interface), and has an empty method for the Notify method that will be called when an event is fired in TFS.

The config file will contain a definition of the service as well as two endpoints for the same interface. The services and behaviours occur within the <system.serviceModel> tags.

<system.serviceModel>
  <services>
   ...
  </services>
  <behaviors>
   ...
  </behaviors>
</system.serviceModel>

This is the really important part of getting the service working, and goes between the <services> tags. The first part of the service configuration contains the service definition. Here, we define the name of the service, which should be the same as the full namespace and the class name of our implementation, the behavior configuration name (we will look at this in a bit), and the endpoints.

<service behaviorConfiguration="NotificationServiceBehavior" 
    name="RDdotNet.TFSEventHandler.NotificationHost.Notification">
    <endpoint address="WorkItemChangedEvent" binding="wsHttpBinding" 
     contract="RDdotNet.TFSEventHandler.NotificationHost.INotification"/>
    <endpoint address=">CheckInEvent" binding="wsHttpBinding" 
     contract="RDdotNet.TFSEventHandler.NotificationHost.INotification"/>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>

In a service hosted within IIS, there is no need to set a base address as the location of the .svc file sets this for us. In this case, it is http://localhost:[port]/v1.0/Notification.svc. The "mex" endpoint allows other applications to discover the capabilities of the service.

The other two endpoints point at the same contract, and are, in fact, implemented by the same method in the service implementation. These two endpoints have different addresses so that we can tell Team Server to send events to different URLs but use the same code to process the events. You could add any of the TFS events in to this list of end points, but it is best to keep the names the same as the event being fired, as we will detect it later. We are using the wsHttpBinding as the most advanced that TFS will support.

You only need to set two options on the service behavior. The httpGetEnabled needs to be set to true for the WSDL and the metadata to work. This allows the discoverability of your services. The includeExceptionDetailInFaults option allows the diagnosis of faults when we test the service.

<serviceBehaviors>
    <behavior name=">NotificationServiceBehavior">
        <serviceMetadata httpGetEnabled=">true"/>
        <serviceDebug includeExceptionDetailInFaults=">true"/>
    </behavior>
</serviceBehaviors>

The service behaviors go between the <behaviors> tags, and are named the same as the service's behaviorConfiguration attribute.

We now have a working service, and can test it by starting a new instance of the web application and going to the URL of the .svc file. You will see a page like:

image

This shows you how you can connect to the service, but as this will only be connected to by TFS, it is not a requirement for just now. What is important is to see the URLs for the two endpoints that we have created. If you click the WSDL URL ("http://localhost:65469/v1.0/Notification.svc?wsdl"), you will see the generated metadata for the service.

I am not going to display the full WSDL here as it is huge, but here is the important section for what we need to look at:

<wsdl:service name="Notification">
  <wsdl:port name="WSHttpBinding_INotification" 
         binding="tns:WSHttpBinding_INotification">
    <soap12:address 
      location="http://localhost:65469/v1.0/Notification.svc/WorkItemChangedEvent"/>
    <wsa10:EndpointReference>
      <wsa10:Address>
        http://localhost:65469/v1.0/Notification.svc/WorkItemChanged
      </wsa10:Address>
      <Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
        <Upn>comuter1\user1</Upn>
      </Identity>
    </wsa10:EndpointReference>
  </wsdl:port>
  <wsdl:port name="WSHttpBinding_INotification1" 
             binding="tns:WSHttpBinding_INotification">
    <soap12:address 
       location="http://localhost:65469/v1.0/Notification.svc/CheckInEvent"/>
    <wsa10:EndpointReference>
      <wsa10:Address>http://localhost:65469/v1.0/Notification.svc/CheckIn</wsa10:Address>
      <Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
        <Upn>computer1\user1</Upn>
      </Identity>
    </wsa10:EndpointReference>
  </wsdl:port>
</wsdl:service>

This section of the WSDL highlights the service definition and the two endpoints that exist. We are now able to get TFS to send events to this service. To do this, you need to add the URLs of the end points to the notification system within TFS. This is done by using a command line utility on the server, or by calling parts of the TFS API. For ease, we will call the command line, but a future version should probably have a user interface to allow the administration of which TFS servers you want to handle events for.

To subscribe to the events, you will need to use the BisSubscribe utility, which you can find out how to use here, or you can use the API and provide an interface to add and remove subscriptions.

If you call:

BisSubscribe.exe /userId TFSEventHandler 
  /eventType WorkItemChangedEvent 
  /deliveryType Soap 
  /address http://localhost:65469/v1.0/Notification.svc/WorkItemChanged

using the utility, you will subscribe to events using SOAP. Or, you call:

SubscribeEvent(tfsServer, "TFSEventHandler", 
    "http://localhost:65469/v1.0/Notification.svc/WorkItemChangedEvent", 
    DeliveryType.Soap, Schedule.Imediate, EventType.WorkItemChangedEvent)

using an API helper class similar to the one below:

''' <summary>
''' Helper methods for subscribing to and from events
''' </summary>
''' <remarks></remarks>
Public Class SubscriptionHelper

    Public Shared Function SubscribeEvent(
        ByRef tfs As TeamFoundationServer, 
        ByVal userName As String, ByVal deliveryAddress As String, 
        ByVal Type As Microsoft.TeamFoundation.Server.DeliveryType, 
   ByVal Schedule As Microsoft.TeamFoundation.Server.DeliverySchedule, 
        ByVal EventType As EventTypes, 
        Optional ByVal Filter As String = "") As Integer
        Dim eventService As IEventService = CType(tfs.GetService(GetType(
            IEventService)), IEventService)
        Dim delivery As DeliveryPreference = New DeliveryPreference()
        delivery.Type = Type
        delivery.Schedule = Schedule
        delivery.Address = deliveryAddress
        Return eventService.SubscribeEvent(userName, 
        EventType.ToString, Filter, delivery)
    End Function

    Public Shared Sub UnSubscribeEvent(
        ByRef tfs As TeamFoundationServer, 
        ByVal subscriptionId As Integer)
        Dim eventService As IEventService = CType(tfs.GetService(
            GetType(IEventService)), IEventService)
        eventService.UnsubscribeEvent(subscriptionId)
    End Sub

End Class

I may write an article in the future on this, but all the code is part of the current TFSEventHandler application.

We now want to determine what sort of event has been raised in the Service implementation. To do this, we need to parse the URL of the endpoint that has been raised and retrieve the event type.

Dim UriString As String = 
   OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri.AbsoluteUri
Dim SlashIndex As Integer = UriString.LastIndexOf("/")
Dim EndieBit As String = UriString.Substring(SlashIndex, 
   (UriString.Length - (UriString.Length - SlashIndex)))
Dim EventType As EventTypes = CType([Enum].Parse(GetType(EventTypes), 
    EndieBit), EventTypes)

As you can see, it is just a case of parsing the URL to get the last bit after the final "/" and then converting it to an enumerator.

Public Enum EventTypes
    Unknown = 0
    AclChangedEvent
    Branchmovedevent
    BuildCompletionEvent
    BuildStatusChangeEvent
    CommonStructureChangedEvent
    DataChangedEvent
    IdentityChangedEvent
    IdentityCreatedEvent
    IdentityDeletedEvent
    MembershipChangedEvent
    WorkItemChangedEvent
    CheckinEvent
End Enum

The enumerator lists all of the events that are possible in TFS, but be warned that not all of the events fire effectively. Once you have the event, it can be converted into an object. I use code from Howard van Rooijen's TFS Project Template for the event objects, and Larry Steinle's CustomXmlSerializer code to convert the XML to Howard's objects, resulting in the following code:

Dim IdentityObject As TFSIdentity = EndpointBase.CreateInstance(
    Of TFSIdentity)(tfsIdentityXml)
Dim EventObject As WorkItemChangedEvent = _
    EndpointBase.CreateInstance(Of WorkItemChangedEvent)(eventXml)

All of the objects are now ready to pass over MSMQ to the TFS Event Processor, which will be the subject of the next article in this series...

Technorati Tags

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Martin Hinshelwood
Instructor / Trainer naked ALM
United States United States

About the Author: Martin has worked with many customers in government, finance, manufacturing, health and technology to help them improve their processes and deliver more. He provides management and technical consulting that intends to expose processes and practices to gain transparency, uncover impediments to value delivery and reduce cycle-time as part of an organisations path to agility. Martin is a Professional Scrum Trainer as well as a Visual Studio ALM MVP and Visual Studio ALM Ranger. He writes regularly on http://nakedalm.com/blog, and speaks often on Scrum, good practices and Visual Studio ALM.

You can get in touch with Martin through naked ALM.

Follow on   Twitter   Google+

Comments and Discussions

 
GeneralCheck In PinmemberJonathan C Dickinson23-Jan-09 2:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 6 Sep 2007
Article Copyright 2007 by Martin Hinshelwood
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid