[Sample of Feb 21st] Add CheckOut Event to TFS

 

Homepage image
Sample of the Day RSS Feed

Sample Download: https://code.msdn.microsoft.com/CSTFSAddCheckOutEventType-673d0536

imageToday’s code sample demonstrates how to enable checkout notification in TFS2010.  In TFS2010, when a user sends a Check out (PendChanges) request to server, the server will send a PendChangesNotification before the items are checked out. If we subscribe this notification, we can deny the request or fire some custom check out events.  If you are customizing your TFS workspace for your team projects or if you are learning TFS, we hope that the Microsoft All-In-One Code Framework sample would reduce your effort in this typical TFS programming scenario.

The sample was written by our star Sample Writer Ruiz Yi.

imageYou can find more code samples that demonstrate the most typical programming scenarios by using Microsoft All-In-One Code Framework Sample Browser or Sample Browser Visual Studio extension. They give you the flexibility to search samples, download samples on demand, manage the downloaded samples in a centralized place, and automatically be notified about sample updates. If it is the first time that you hear about Microsoft All-In-One Code Framework, please watch the introduction video on Microsoft Showcase, or read the introduction on our homepage https://1code.codeplex.com/.

 

Introduction

The sample demonstrates how to enable checkout notification in TFS2010. 

In TFS2010, when a user sends a Check out (PendChanges) request to server, the server will send a PendChangesNotification before the items are checked out. If we subscribe this notification, we can do following things:

  1. Deny the request.
  2. Fire the custom CheckOut event.

NOTE: As this notification is sent before the items are checked out, we can only know someone is trying to checkout some items.

Lots of TFS developers ask about the programming scenario in MSDN forums, so we decided to create the code sample.

https://social.msdn.microsoft.com/Forums/en-US/tfsgeneral/thread/24d81d27-c992-41eb-8aa6-4b734565de2c
https://social.msdn.microsoft.com/Forums/en-US/tfsversioncontrol/thread/d95ad82d-b788-4181-9b73-708801692294
https://social.msdn.microsoft.com/Forums/en-US/tfsgeneral/thread/96eed6a2-c7b8-4c8f-9976-baa6c2b3d275

 

Building the Sample

This sample should be complied on the Application Tier of TFS, as it uses TFS Server Object Model.

 

Running the Sample

1. Copy CSCheckOutNotification.dll to X:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins.
X: means the driver in which TFS is installed.
 
2. Copy CheckOutEvent.plaintextXsl and CheckOutEvent.xsl to X:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\TFSJobAgent\Transforms.
 
3. Copy CSAddEventType.exe and CSAddEventType.exe.config to X:\Program Files\Microsoft Team Foundation Server 2010\Tools.  Open CSAddEventType.exe.config, and modify <server> and driver label in following keys:

 <add key="applicationDatabase" value="Data Source=&lt;server>;Initial Catalog=Tfs_Configuration;Integrated Security=True;" /> 
<add key="ToolsPath" value="X:\Program Files\Microsoft Team Foundation Server 2010\Tools\"/>

4. Launch CSAddEventType.exe and type following command.

X:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins\CSCheckOutNotification.dll" CSCheckOutNotification.CheckOutEvent TFSLab CheckOutEvent VersionControl

image

5. Use BisSubscribe.exe to subscribe the CheckOut event.
 
Run the following command:
 
X:\Program Files\Microsoft Team Foundation Server 2010\Tools\BisSubscribe.exe /eventType CheckOutEvent /address <your mail address> /collection https://<server>:8080/tfs/<collection> /deliveryType EmailHtml

image

6. In TFS Admin Console, enable TFS Alert, and make sure that your mail settings are correct.
 
7. Check out items in source control explorer, and then you will get the notification mails.

image

 

Using the Code

In TFS2010, when a user sends a Check out (PendChanges) request to server, the server will send a PendChangesNotification before the items are checked out.  If there is any subscriber (a class implements the Microsoft.TeamFoundation.Framework.Server.ISubscriber interface) subscribes this notification, the ProcessEvent method will be called. In this method, we can fire the custom CheckOutEvent, and then TFSJobAgent will send the alert.

1. Create CheckOut Event Type.

 public class CheckOutEvent 
{ 
    public string OwnerName { get; set; } 
    public string UserName { get; set; } 
    public string WorkspaceName { get; set; } 
    public string[] Items { get; set; } 
}

2. Create xslt files for the event.

Before the alert the send, TFSJobAgent will serialize the CheckOutEvent to XML, and then use these files are to transform the  XML to Plaintext or HTML.
 
The XML is like

 <CheckOutEvent xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"> 
    <OwnerName>domain\user</OwnerName> 
    <UserName>domain\user</UserName> 
    <WorkspaceName>workspaename</WorkspaceName> 
    <Items> 
        <string>Local Path:*** EditType:Edit</string> 
        <string>Local Path:*** EditType:Add</string> 
    </Items> 
</CheckOutEvent> 

The Xslt file for HTML is

 <?xml version="1.0" encoding="utf-8"?> 
<xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" version="1.0"> 
    <xsl:import href="TeamFoundation.xsl"/> 
    <!-- Common TeamSystem elements --> 
    <xsl:template match="CheckOutEvent"> 
        <head> 
            <!-- Pull in the command style settings --> 
            <xsl:call-template name="style"> 
            </xsl:call-template> 
        </head> 
        <body> 
            <div class="Title"> 
                CheckOut Event 
            </div> 
            <div> 
                <xsl:value-of select="UserName"/> 
                <xsl:text> is trying to check out following items:</xsl:text> 
            </div> 
            <div> 
                <xsl:apply-templates select="Items"/> 
            </div> 
        </body> 
    </xsl:template> 
    <xsl:template match="CheckOutEvent/Items"> 
        <xsl:apply-templates select="string"/> 
    </xsl:template> 
    <xsl:template match="CheckOutEvent/Items/string"> 
        <xsl:value-of select="node()" /> 
        <br/> 
    </xsl:template> 
</xsl:stylesheet>

And the output is

 <head> 
</head> 
<body> 
    <div class="Title"> 
        CheckOut Event 
    </div> 
    <div>domain\user is trying to check out following items:</div> 
    <div> 
        Local Path:***  EditType:Edit<br/> 
        Local Path:***  EditType:Add<br/> 
    </div> 
</body> 

3. Subscribe PendChangesNotification and fire CheckOut Event.

 public class CheckOutNotificationSubscriber : ISubscriber 
{ 
  
    public string Name 
    { 
        get 
        { 
            return "Team Foundation Source Control: CheckOut Alert Provider"; 
        } 
    } 
  
    public SubscriberPriority Priority 
    { 
        get 
        { 
            return SubscriberPriority.Normal; 
        } 
    } 
  
    /// <summary> 
    /// Return the types that it subscribes. 
    /// </summary> 
    /// <returns></returns> 
    public Type[] SubscribedTypes() 
    { 
        return new Type[] { typeof(PendChangesNotification) }; 
    } 
  
    /// <summary> 
    /// The ProcessEvent method will be called when the server receives the pend 
    /// change request, and in this method, we can fire the custom CheckOutEvent, 
    /// then TFSJobAgent will send out the alert. 
    /// </summary> 
    /// <param name="requestContext"> 
    /// From the request, we can get the parameters that contains the detailed 
    /// information. 
    /// </param> 
    /// <param name="notificationType"> 
    /// DecisionPoint: before the request is processed. 
    /// Notification: after the request is processed. 
    /// 
    /// In this sample, this parameter is DecisionPoint. 
    /// </param> 
    /// <param name="notificationEventArgs"> 
    /// An instance of PendChangesNotification. 
    /// </param> 
    /// <param name="statusCode"></param> 
    /// <param name="statusMessage"></param> 
    /// <param name="properties"></param> 
    /// <returns> 
    /// Return EventNotificationStatus.ActionPermitted. 
    /// </returns> 
    public EventNotificationStatus ProcessEvent( 
        TeamFoundationRequestContext requestContext, 
        NotificationType notificationType, 
        object notificationEventArgs, 
        out int statusCode, 
        out string statusMessage, 
        out ExceptionPropertyCollection properties) 
    { 
        statusCode = 0; 
        properties = null; 
        statusMessage = string.Empty; 
  
        try 
        { 
            if (notificationEventArgs is PendChangesNotification) 
            { 
  
                // Initialize a CheckOutEvent instance. 
                PendChangesNotification notification = 
                    notificationEventArgs as PendChangesNotification; 
                CheckOutEvent checkoutEvent = new CheckOutEvent(); 
                checkoutEvent.WorkspaceName = notification.WorkspaceName; 
                checkoutEvent.UserName = notification.UserName; 
                checkoutEvent.OwnerName = notification.OwnerName; 
  
                // Get the detailed information from the request context. 
  
                // Get the count of pend changes. 
                string changes = requestContext.Method.Parameters["changes"]; 
                int count = 0; 
                if (int.TryParse(changes.Replace("Count = ", string.Empty), out count)) 
                { 
                    if (count > 0) 
                    { 
                        checkoutEvent.Items = new string[count]; 
  
                        for (int i = 0; i < count; i++) 
                        { 
                            checkoutEvent.Items[i] = string.Format("Path:{0} EditType:{1}", 
                                requestContext.Method.Parameters[string.Format("changes[{0}].ItemSpec", i)], 
                                requestContext.Method.Parameters[string.Format("changes[{0}].RequestType", i)]); 
                        } 
                    } 
                } 
  
                // Fire the CheckOutEvent. 
                this.FireSystemEvent(requestContext, checkoutEvent); 
            } 
        } 
        catch (Exception exception) 
        { 
            TeamFoundationApplication.LogException("Sending Event Failed", exception); 
        } 
        return EventNotificationStatus.ActionPermitted; 
    } 
  
    /// <summary> 
    /// Fire the CheckOutEvent. 
    /// </summary> 
    private void FireSystemEvent(TeamFoundationRequestContext requestContext, object eventObject) 
    { 
        try 
        { 
            requestContext.GetService<TeamFoundationNotificationService>().FireSystemEvent(requestContext, eventObject); 
        } 
        catch (Exception exception) 
        { 
            TeamFoundationApplication.LogException("Sending Event Failed", exception); 
        } 
    } 
} 

4. Add event type to TFS.

We can use TFS Server Object Model to add the event type to TFS.
 
a. Initialize the ApplicationServiceHost

 /// <summary> 
/// Initialize AppHost using its constructor. 
/// </summary> 
private void InitializeAppHost(string applicationDatabaseConnectionString, 
    string toolPath) 
{ 
    if (string.IsNullOrEmpty(applicationDatabaseConnectionString) 
        || string.IsNullOrEmpty(toolPath)) 
    { 
        throw new ArgumentException("applicationDatabase or toolPah should not be null."); 
    } 
  
    this.applicationHost = new ApplicationServiceHost( 
        Guid.Empty, 
        applicationDatabaseConnectionString, 
        "unused", 
        Path.Combine(toolPath, "Plugins"), 
        "/tfs", 
        true); 
} 

b. Initialize the CollectionServiceHost

 /// <summary> 
/// Get CollectionHost using TeamProjectCollectionService. 
/// </summary> 
private void InitializeCollectionHost(string collectionName) 
{ 
    if (this.applicationHost == null) 
    { 
        throw new ApplicationException("AppHost is null."); 
    } 
  
    // Generate a SystemContext. 
    TeamFoundationRequestContext systemRequest = applicationHost.CreateSystemContext(); 
  
    Guid collectionId = Guid.Empty; 
  
    // Get TeamProjectCollectionService using the SystemContext. 
    TeamProjectCollectionService tpcService = systemRequest.GetService<TeamProjectCollectionService>(); 
     
    // Query the properties to find the GUID of the specified collection. 
    List<TeamProjectCollectionProperties> collectionProperties = 
        tpcService.GetCollectionProperties(systemRequest, ServiceHostFilterFlags.None); 
  
    foreach (TeamProjectCollectionProperties properties in collectionProperties) 
    { 
        if (string.Equals(properties.Name, collectionName, StringComparison.OrdinalIgnoreCase) 
            && properties.State == TeamFoundationServiceHostStatus.Started) 
        { 
            collectionId = properties.Id; 
        } 
    } 
  
    if (collectionId == Guid.Empty) 
    { 
        throw new ApplicationException("Cannot find the collection :" + collectionName); 
    } 
  
    this.collectionHost = applicationHost.GetServiceHost(collectionId) as CollectionServiceHost; 
     
} 

c. Add event type to TFS.

 /// <summary> 
/// Add an EventTYpe using TeamFoundationNotificationService. 
/// </summary> 
/// <param name="schema"></param> 
public void AddEventType(string name, string toolType, string schema) 
{ 
    // Generate a SystemContext. 
    TeamFoundationRequestContext systemRequest =  this.collectionHost.CreateSystemContext(); 
  
    // Get TeamFoundationNotificationService using the SystemContext. 
    TeamFoundationNotificationService notificationService =  
        systemRequest.GetService<TeamFoundationNotificationService>(); 
  
    RegistrationEventType eventType = new RegistrationEventType(name, schema); 
  
    notificationService.AddEventTypes(systemRequest, toolType, 
        new RegistrationEventType[] { eventType }); 
} 

More Information

PendChangesNotification

ApplicationServiceHost Class

CollectionServiceHost Class

TeamFoundationRequestContext Class

TeamProjectCollectionService Class

TeamFoundationNotificationService Class

ISubscriber Interface

XML Schema Definition Tool (Xsd.exe)