TFS 事件处理程序在 .NET 3.5 第 2 部分 - 处理 Team Foundation Server 事件






4.67/5 (2投票s)
我决定尝试用 .NET 3.5 创建一个具有弹性且可扩展的 Team Foundation Server 事件处理程序。我将尽可能多地使用 Team Suite 的功能,但请耐心等待,因为有些东西对我来说是新的。
引言
我决定尝试用 .NET 3.5 创建一个具有弹性且可销售的 Team Foundation Server 事件处理程序。我将尽可能多地使用 Team Suite 的功能,但请耐心等待,因为有些东西对我来说是新的。
TFS 事件处理程序在 .NET 3.5 文章:
- TFS 事件处理程序在 .NET 3.5 第 1 部分 - 架构
- TFS 事件处理程序在 .NET 3.5 第 2 部分 - 处理 Team Foundation Server 事件
- TFS 事件处理程序在 .NET 3.5 第 3 部分 - 通过 Windows Communication Foundation MSMQ 传递事件 (即将推出)
- TFS 事件处理程序在 .NET 3.5 第 4 部分 - 工作流 (即将推出)
处理 Team Foundation Server 事件
由于 Team Edition for Architects 对 Windows Communication Foundation 的支持不足,我将用 Windows Communication Foundation 服务替换所有自动生成的服务。但事实证明,这会搞乱一切,所以我从头开始,并将发布所有关于此操作的代码,而无需 Architect 部分。
您首先需要的是 Team Foundation Server 事件处理的契约。这非常具体,并且仅在正确使用时才有效。
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
此代码允许您捕获 TFS 事件,我们将仅通过引用使用它。如果您在处理事件时遇到麻烦,那么请不要再寻找 Windows Communication Foundation 了,因为这已经过尝试和测试。
在 Windows Communication Foundation 中处理 TFS 事件的真正诀窍是,所有事件都进入同一个例程。如果 SubscriptionInfo
列出了事件的类型,那将是很好的,但遗憾的是,它没有。所以,您可能认为您需要解析 XML 来找出事件的类型。当然,如果您愿意,可以这样做,但我发现为同一个服务拥有多个终结点更简单。然后,您可以解析 URL 以获取事件类型,这比 XML 容易得多,因为事件都不同。
您需要做的第一件事是创建一个项目来保存代码。本着快速的精神,我创建了一个“Windows Communication Foundation 服务应用程序”来保存所有通知服务代码。但在实际世界中,您会将契约和实现类保存在不同的程序集中。
现在,我们有了项目,您可以添加 INotification
类和通知服务。我喜欢将所有服务保留在“v1.0”中,以便在新版本中添加其他服务而不会影响当前版本。
一旦您的 INotification
类看起来像上面的代码片段,我们将添加一个默认实现并测试服务。默认实现应如下所示:
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
此类仅实现 INotification
契约(接口),并为将在 TFS 中触发事件时调用的 Notify
方法提供一个空方法。
配置文件将包含服务的定义以及同一接口的两个终结点。服务和行为发生在 <system.serviceModel>
标签内。
<system.serviceModel>
<services>
...
</services>
<behaviors>
...
</behaviors>
</system.serviceModel>
这是让服务正常工作真正重要的部分,它位于 <services>
标签之间。服务配置的第一部分包含服务定义。在这里,我们定义了服务的名称,它应该与我们的实现的完全命名空间和类名相同,行为配置名称(我们稍后会介绍),以及终结点。
<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>
在 IIS 中托管的服务中,无需设置基址,因为 .svc 文件的位置已经为我们设置好了。在这种情况下,它是 https://:[port]/v1.0/Notification.svc。“mex
”终结点允许其他应用程序发现服务的各项功能。
另外两个终结点指向相同的契约,实际上由服务实现中的同一个方法实现。这两个终结点具有不同的地址,以便我们可以告诉 Team Server 将事件发送到不同的 URL,但使用相同的代码来处理事件。您可以将任何 TFS 事件添加到此终结点列表中,但最好将名称与触发的事件保持一致,因为我们稍后会检测到它。我们正在使用 wsHttpBinding
,因为它是 TFS 支持的最先进的绑定。
您只需要在服务行为上设置两个选项。httpGetEnabled
需要设置为 true
才能使 WSDL 和元数据正常工作。这允许您的服务可被发现。includeExceptionDetailInFaults
选项允许在测试服务时诊断故障。
<serviceBehaviors>
<behavior name=">NotificationServiceBehavior">
<serviceMetadata httpGetEnabled=">true"/>
<serviceDebug includeExceptionDetailInFaults=">true"/>
</behavior>
</serviceBehaviors>
服务行为位于 <behaviors>
标签之间,并且名称与服务的 behaviorConfiguration
属性相同。
现在我们有了一个正常工作的服务,可以通过启动新的 Web 应用程序实例并转到 .svc 文件的 URL 来测试它。您将看到一个页面,如下所示:
这向您展示了如何连接到服务,但由于它仅由 TFS 连接,因此目前并不是必需的。重要的是看到我们创建的两个终结点的 URL。如果您单击 WSDL URL(“https://:65469/v1.0/Notification.svc?wsdl”),您将看到服务的生成元数据。
我不会在此显示完整的 WSDL,因为它非常庞大,但这里是我们需要的重点部分:
<wsdl:service name="Notification">
<wsdl:port name="WSHttpBinding_INotification"
binding="tns:WSHttpBinding_INotification">
<soap12:address
location="https://:65469/v1.0/Notification.svc/WorkItemChangedEvent"/>
<wsa10:EndpointReference>
<wsa10:Address>
https://: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="https://:65469/v1.0/Notification.svc/CheckInEvent"/>
<wsa10:EndpointReference>
<wsa10:Address>https://: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>
WSDL 的这一部分重点介绍了服务定义和存在的两个终结点。我们现在能够让 TFS 将事件发送到此服务。为此,您需要将终结点的 URL 添加到 TFS 中的通知系统中。这可以通过使用服务器上的命令行实用程序或调用 TFS API 的部分来完成。为了方便起见,我们将调用命令行,但未来版本可能需要一个用户界面来允许管理您要处理事件的 TFS 服务器。
要订阅事件,您需要使用 BisSubscribe 实用程序,您可以在 此处了解如何使用它,或者您可以使用 API 并提供一个添加和删除订阅的接口。
如果您调用
BisSubscribe.exe /userId TFSEventHandler
/eventType WorkItemChangedEvent
/deliveryType Soap
/address https://:65469/v1.0/Notification.svc/WorkItemChanged
使用该实用程序,您将使用 SOAP 订阅事件。或者,您可以调用
SubscribeEvent(tfsServer, "TFSEventHandler",
"https://:65469/v1.0/Notification.svc/WorkItemChangedEvent",
DeliveryType.Soap, Schedule.Imediate, EventType.WorkItemChangedEvent)
使用类似于以下内容的 API 帮助类
''' <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
我可能会在未来撰写一篇关于此的文章,但所有代码都是当前 TFSEventHandler 应用程序的一部分。
现在,我们想确定在服务实现中引发了什么类型的事件。为此,我们需要解析引发的终结点的 URL 并检索事件类型。
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)
如您所见,只需解析 URL 以获取最后一个“/”后面的部分,然后将其转换为枚举即可。
Public Enum EventTypes
Unknown = 0
AclChangedEvent
Branchmovedevent
BuildCompletionEvent
BuildStatusChangeEvent
CommonStructureChangedEvent
DataChangedEvent
IdentityChangedEvent
IdentityCreatedEvent
IdentityDeletedEvent
MembershipChangedEvent
WorkItemChangedEvent
CheckinEvent
End Enum
该枚举列出了 TFS 中所有可能的事件,但请注意,并非所有事件都能有效触发。一旦获得事件,就可以将其转换为对象。我使用 Howard van Rooijen 的 TFS 项目模板中的代码来处理事件对象,并使用 Larry Steinle 的 CustomXmlSerializer 代码将 XML 转换为 Howard 的对象,从而得到以下代码:
Dim IdentityObject As TFSIdentity = EndpointBase.CreateInstance(
Of TFSIdentity)(tfsIdentityXml)
Dim EventObject As WorkItemChangedEvent = _
EndpointBase.CreateInstance(Of WorkItemChangedEvent)(eventXml)
所有对象现在都准备好通过 MSMQ 传递给 TFS 事件处理器,这将是本系列下一篇文章的主题……