在TFS插件

时间:2016-05-02 15:22:23

标签: c# visual-studio plugins tfs

最近,我决定为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)是的。使用具有管理权限的帐户。如果我在构造函数中设置了一个断点,则会到达。未到达代码中的任何其他位置。

1 个答案:

答案 0 :(得分:0)

很多事情都可能是错的。

  1. 如果任何依赖项被破坏,您应该会在事件日志中看到错误
  2. 如果您未注册“WorkItemChangedEventHandler”事件源,则不会写入任何消息
  3. 运行插件的用户帐户可以访问TFS吗?
  4. 您是如何启用跟踪的(您粘贴了一堆代码,但没有配置)?
  5. 您是否已将调试器附加到TFS流程并设置断点(在生产TFS上不这样做)?
  6. 我们在插件的文档中收集了类似的建议 https://github.com/tfsaggregator/tfsaggregator/wiki