最近,我决定为TFS创建一个插件,用于根据ISubscriber接口跟踪工作项更改。
因此工作流程如下:
1)工作项状态变化
2)插件捕获WorkItemChangedEvent
3)向请求者
作为我项目的基础,我使用了CodePlex的以下项目 - The Mail Alert
在我根据我的需要采用它并在%TFS-DIR%\Microsoft Team Foundation Server 14.0\Application Tier\Web Services\bin\Plugins
中保存已编译的二进制文件后,TFS重新启动了层,并且......就是这样。在工作项更改时,不会调用ProcessEvent方法,但它应该是。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Server;
using System.Diagnostics;
using System.Xml.Linq;
using System.Xml;
using System.DirectoryServices;
using System.Net.Mail;
using System.Xml.Xsl;
using System.Configuration;
using System.Reflection;
namespace MailAlert
{
public class WorkItemChangedEventHandler : ISubscriber
{
static string serverPath = "";
static string ExternalURL = "";
static string MailAddressFrom = "";
static string SMTPHost = "";
static string Password = "";
static int Port = 25;
static int index = 0;
static string projectCollectionFolder;
static Uri projectCollectionUri;
static WorkItemStore wiStore;
static WorkItem wItem;
static WorkItemChangedEvent workItemChangedEvent;
static string teamProjectPath = "";
static VersionControlServer versionControlServer;
static TfsTeamProjectCollection projectCollection;
static Dictionary<IdentityDescriptor, TeamFoundationIdentity> m_identities = new Dictionary<IdentityDescriptor, TeamFoundationIdentity>(IdentityDescriptorComparer.Instance);
public Type[] SubscribedTypes()
{
return new Type[1] { typeof(WorkItemChangedEvent) };
}
public WorkItemChangedEventHandler()
{
TeamFoundationApplicationCore.Log("WorkItemChangedEvent Started", index++, EventLogEntryType.Information);
}
public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType,
object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
{
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: ProcessEvent entered", index++, EventLogEntryType.Information);
statusCode = 0;
properties = null;
statusMessage = String.Empty;
GetTfsServerName();
projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(TfsTeamProjectCollection.GetFullyQualifiedUriForName(serverPath));
try
{
if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent)
{
workItemChangedEvent = notificationEventArgs as WorkItemChangedEvent;
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: WorkItem " + workItemChangedEvent.WorkItemTitle + " was modified", index++, EventLogEntryType.Information);
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: serverPath - " + serverPath, index++, EventLogEntryType.Information);
projectCollectionFolder = requestContext.ServiceHost.VirtualDirectory.ToString();
projectCollectionUri = new Uri(serverPath + projectCollectionFolder);
projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(projectCollectionUri);
wiStore = projectCollection.GetService<WorkItemStore>();
versionControlServer = projectCollection.GetService<VersionControlServer>();
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: Before process workitem", index++, EventLogEntryType.Information);
ProcessWorkItem();
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: After process workitem", index++, EventLogEntryType.Information);
}
}
catch (Exception ex)
{
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: FUCKING EXCEPTION! =>\n" + ex.Message, index++, EventLogEntryType.Error);
}
return EventNotificationStatus.ActionPermitted;
}
private static void GetTfsServerName()
{
try
{
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
XmlDocument XmlDoc = new XmlDocument();
XmlDoc.Load(assemblyFolder + @"\Settings.xml");
// Declare the xpath for finding objects inside the XML file
XmlNodeList XmlDocNodes = XmlDoc.SelectNodes("/configuration/tfssettings");
XmlNodeList XmlDocExt = XmlDoc.SelectNodes("/configuration/Externaltfssettings");
// Define a new List, to store the objects we pull out of the XML
serverPath = XmlDocNodes[0].InnerText;
ExternalURL = XmlDocExt[0].InnerText;
XmlNodeList XmlDocNodes2 = XmlDoc.SelectNodes("/configuration/appSettings");
foreach (XmlNode mailNode in XmlDocNodes2)
{
foreach (XmlNode varElement in mailNode.ChildNodes)
{
switch (varElement.Attributes["key"].Value)
{
case "MailAddressFrom":
MailAddressFrom = varElement.Attributes["value"].Value;
break;
case "SMTPHost":
SMTPHost = varElement.Attributes["value"].Value;
break;
case "Password":
Password = varElement.Attributes["value"].Value;
break;
case "Port":
Port = Convert.ToInt32(varElement.Attributes["value"].Value);
break;
}
}
}
}
catch (Exception ex)
{
EventLog.WriteEntry("WorkItemChangedEventHandler", ex.Message);
}
}
public string Name
{
get { return "WorkItemChangedEventHandler"; }
}
public SubscriberPriority Priority
{
get { return SubscriberPriority.High; }
}
private static void ProcessWorkItem()
{
var teamProjects = versionControlServer.GetAllTeamProjects(false);
for (int i = 0; i < teamProjects.Length; i++)
{
string teamProjectName = teamProjects[i].Name;
var teamProject = teamProjects[i];
Project teamProjectWI = wiStore.Projects[i];
teamProjectPath = projectCollectionUri + teamProject.Name;
if (workItemChangedEvent.PortfolioProject == teamProjectName)
{
//get the workitem by ID ( CoreFields.IntegerFields[0] == ID ?!)
//check if any of String changed fields
foreach(StringField sf in workItemChangedEvent.ChangedFields.StringFields)
{
//is the State field
if (sf.Name.Equals("State"))
{
//then notify Reuqester
wItem = wiStore.GetWorkItem(workItemChangedEvent.CoreFields.IntegerFields[0].NewValue);
string CollGuid = projectCollection.InstanceId.ToString();
string Requester = wItem.Fields["Requester"].Value.ToString();
string WorkItemId = wItem.Id.ToString();
string mail = GetEmailAddress(Requester);
SendMail(CollGuid, WorkItemId, mail);
}
}
}
}
}
private static string GetEmailAddress(string userDisplayName)
{
DirectorySearcher ds = new DirectorySearcher();
ds.PropertiesToLoad.Add("mail");
ds.Filter = String.Format("(&(displayName={0})(objectCategory=person)((objectClass=user)))", userDisplayName);
SearchResultCollection results = ds.FindAll();
if (results.Count == 0)
{
return string.Empty;
}
ResultPropertyValueCollection values = results[0].Properties["mail"];
if (values.Count == 0)
{
return string.Empty;
}
return values[0].ToString();
}
private static void SendMail(string collID,string workItemId,string tomailAddrees)
{
MailMessage objeto_mail = new MailMessage();
SmtpClient client = new SmtpClient();
client.Port = Port;
client.Host = SMTPHost;
client.Timeout = 200000;
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = true;
client.EnableSsl = true;
client.Credentials = new System.Net.NetworkCredential(MailAddressFrom, Password);
objeto_mail.From = new MailAddress(MailAddressFrom);
objeto_mail.To.Add(new MailAddress(tomailAddrees));
//objeto_mail.CC.Add(new MailAddress("nagarajb@hotmail.com"));
objeto_mail.Subject = "Work Item Changed:"+workItemId;
string mailbody = serverPath+"/tfs/web/wi.aspx?pcguid=" + collID + "&id=" + workItemId;
string mailbody2 = "";
if (ExternalURL.Length > 0)
{
mailbody2 = ExternalURL + "/tfs/web/wi.aspx?pcguid=" + collID + "&id=" + workItemId;
}
string tables = "<table border=1><tr><td>Work Item ID</td><td>" + wItem.Id.ToString() + "</td></tr><tr><td>Title</td><td>" + wItem.Title + "</td></tr><tr><td>State</td><td>" + wItem.State + "</td></tr><tr><td>Assigned To</td><td>" + wItem.Fields["Assigned to"].Value.ToString() + "</td></tr><tr><td>Internal URL</td><td>" + mailbody + "</td></tr><tr><td>External URL</td><td>" + mailbody2 + "</td></tr></table>";
objeto_mail.IsBodyHtml = true;
objeto_mail.Body = "<i>Hi " + wItem.Fields["Requester"].Value.ToString() + ","+"</i></br></br></br>" + tables + " </br></br> Best regards; </br></br>Configuration Management Team</br></br></br>";
client.Send(objeto_mail);
EventLog.WriteEntry("WorkItemChangedEventHandler", "Email Sent");
}
}
}
事件日志中也不会抛出任何错误或异常。跟踪TFS(web.config中的trace = true属性)也无济于事。
也许有人可以帮助或揭示这个神秘的案例?
更新:
感谢Giulio Vian的回复!
这是怎么回事:
1)我还没有看到依赖关系被破坏,加上构造函数WorkItemChangedEventHandler
被成功调用。在Windows事件日志中可以看到 - WorkItemChangedEvent Started
消息已写入
2)我不知道如何注册事件处理程序....我会查看它
3)我不确定这是如何工作的。我认为只需在适当的文件夹中复制粘贴dll即可,而且不需要为该插件创建帐户。介意给出更多信息?
4)是的。 Application Tier\Web Services
中主Web配置中的Web.config
5)是的。使用具有管理权限的帐户。如果我在构造函数中设置了一个断点,则会到达。未到达代码中的任何其他位置。
答案 0 :(得分:0)
很多事情都可能是错的。
我们在插件的文档中收集了类似的建议 https://github.com/tfsaggregator/tfsaggregator/wiki。