65.9K
CodeProject 正在变化。 阅读更多。
Home

将 HTML 字段数据导入 TFS 数据仓库

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.75/5 (5投票s)

2013年4月23日

CPOL

3分钟阅读

viewsIcon

34882

downloadIcon

132

关于如何将 HTML 数据导入数据仓库的一个小技巧。

问题

之前,我面临这样一种情况:我想在流程模板工作项中使用 HTML 控件,但需要将该控件中的数据传递到数据仓库以进行报告。对于那些了解流程 WIT 如何工作的人来说,您会知道您不能将 HTML 数据类型字段标记为可报告。今天,我将向您展示我是如何做到这一点的。

开始之前

在您阅读本文之前,我只想告诉您,这仅允许少量 HTML,它旨在用于小的标记更改,例如粗体、斜体和下划线。我还没有完全分析所有兼容性问题,但我知道当您将图像添加到 HTML 时,此方法有时不起作用。这很可能是因为字符串字段在 TFS 数据仓库中是 `[nvarchar]`(256)。

成功的步骤

安装正确的工具

您不需要这些工具,但它会使开发、管理和实施流程模板更改变得更容易。

添加 HTML 字段

我使用 Card.xml,它位于由 ALM Rangers 提供的 Microsoft Kanban 1.0 流程模板中,这篇 博文解释了如何安装它。因此,使用此流程模板,您可以浏览到 `~\Microsoft Kanban 1.0\WorkItem Tracking\TypeDefinitions` 并在 Visual Studio 2012 中打开 *Card.xml*。您应该看到如下所示的窗口

Image0

单击“新建”并填写如下所示的表格

Image1

单击“规则”选项卡,然后单击“新建”并选择“AllowExistingValue”,如下所示

Image2

现在添加第二个字段,该字段与第一个字段相同,只是这次将 _Copy 添加到名称和引用名称字段中,如下所示。在此之后,添加与之前添加的相同的规则

Image3

将新的 HTML 字段添加到布局

单击上面的“布局”,然后通过右键单击任何不是控件的节点并选择 *新建控件* 来添加新控件。填写“字段名称”和“类型”属性,如下所示,其余由您决定。

Image4

为团队更新 WIT

接下来,我们需要更新 TFS 中的 Card WIT。为此,请单击“工具”>“流程编辑器”>“工作项类型”>“导入 WIT”。浏览到您正在修改的 Card.xml 文件,然后选择要导入此更改的团队,然后单击“确定”。

创建 TFS 的订阅服务器插件

这现在已经通过 TFS 更新了所选团队的 Card 窗口的窗口。转到 TFS WebAccess 门户并添加一张新卡,您会注意到该字段在那里。如果您填写表格并单击“保存”,您会注意到一切都保存得很顺利,没有任何问题。接下来,我们需要创建一个新的类库并添加一个 *TFSFunctions.cs* 类(此类中的源代码取自 ALM Planning zip 文件中的 GlobalList Updater 项目并进行了一些修改)。将下面的代码添加到 *TFSFunction.cs* 中。

namespace HtmlFieldsInReports 
{
    #region 
    using System; 
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.Framework.Server;
    using Microsoft.TeamFoundation.WorkItemTracking.Client;
 
    #endregion 
    public static class TFSFunctions
    { 
        #region Public Methods and Operators

        public static WorkItemCollection ExecuteQuery(WorkItemStore store, 
                string query, string teamProjectName, string processStepWorkItemType)
        {
            query = query.ToLower().Replace("@project", teamProjectName);
            query = query.ToLower().Replace("@processstepworkitemtype", 
                                            processStepWorkItemType);
            return store.Query(query);
        } 
        public static Uri GetTFSUri(TeamFoundationRequestContext requestContext)
        {
            return new Uri(
              requestContext.GetService<TeamFoundationLocationService>().GetServerAccessMapping(
              requestContext).AccessPoint.Replace("localhost", 
              Environment.MachineName) + "/" + requestContext.ServiceHost.Name);
        } 
        public static TfsTeamProjectCollection GetTeamProjectCollection(
           string requestContextVirtualDirectory, string workItemChangedEventDisplayUrl)
        {
            string tpcUrl = GetTeamProjectCollectionUrl(
                  requestContextVirtualDirectory, workItemChangedEventDisplayUrl); 
            var collection = new TfsTeamProjectCollection(new Uri(tpcUrl)); 
            collection.EnsureAuthenticated();
            return collection;
        }

        public static string GetTeamProjectCollectionUrl(
           string requestContextVirtualDirectory, string workItemChangedEventDisplayUrl) 
        { 
            string[] strArray = workItemChangedEventDisplayUrl.Split('/');
            return string.Format("{0}//{1}{2}", 
              strArray[0], strArray[2], requestContextVirtualDirectory);
        }

        public static WorkItemStore GetWorkItemStore(TfsTeamProjectCollection collection)
        {
            return (WorkItemStore)collection.GetService(typeof(WorkItemStore));
        } 
        #endregion
    }
}

并且还添加一个 *HtmlFieldSyncSubscriber.cs* 文件,并在其中包含以下代码。

namespace HtmlFieldsInReports
{
    #region

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Reflection;  
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.Common;
    using Microsoft.TeamFoundation.Framework.Server;
    using Microsoft.TeamFoundation.WorkItemTracking.Client;
    using Microsoft.TeamFoundation.WorkItemTracking.Server;

    #endregion

    public class HtmlFieldSyncSubscriber : ISubscriber
    {
        // ISubscriber - requested event types

        // ISubscriber - name of the plugin
        #region Public Properties 
        public string Name
        {
            get
            {
                return "BinaryDigit.Subscribers.HtmlFieldsInReports";
            }
        }

        public SubscriberPriority Priority
        {
            get
            {
                return SubscriberPriority.Low;
            }
        }

        #endregion

        // ISubscriber - event handler
        #region Public Methods and Operators 
        public EventNotificationStatus ProcessEvent(
            TeamFoundationRequestContext requestContext, NotificationType notificationType, 
            object notificationEventArgs, out int statusCode, 
            out string statusMessage, out ExceptionPropertyCollection properties)
        {
            this.WriteInfo("In the EventNotificationStatus ProcessEvent method now", 
                           EventLogEntryType.Information);
            string strDump = string.Empty;
            statusCode = 0;
            properties = null;
            statusMessage = string.Empty; 
            try
            {
                if (notificationType == NotificationType.Notification && 
                       notificationEventArgs is WorkItemChangedEvent)
                {
                    // change this object to be a type we can easily get into
                    var workItemEvent = notificationEventArgs as WorkItemChangedEvent;
                    string currentField = string.Empty;
                    IntegerField id = null;
                    foreach (IntegerField item in workItemEvent.CoreFields.IntegerFields)
                    {
                        if (string.Compare(item.Name, "ID", true) == 0)
                        {
                            id = item;
                            break;
                        }
                    } 
                    if (id != null)
                    {
                        try
                        {
                            Uri uri = TFSFunctions.GetTFSUri(requestContext);

                            if (workItemEvent.TextFields != null)
                            {
                                var affectedWorkItems = new List<WorkItem>();
                                using (TeamFoundationServer tfs = 
                                    TeamFoundationServerFactory.GetServer(uri))
                                {
                                    var wit = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
                                    foreach (TextField item in workItemEvent.TextFields)
                                    {
                                        if (item.Name.ToLower().EndsWith("_copy") && 
                                             item.ReferenceName.ToLower().EndsWith("_copy"))
                                        {
                                            currentField = item.Name;

                                            WorkItem wi = this.SetFieldValue(wit, id, item, uri);
                                            if (wi != null)
                                            {
                                                affectedWorkItems.Add(wi);
                                            }
                                        }
                                    }

                                    if (affectedWorkItems.Count > 0)
                                    {
                                        wit.BatchSave(affectedWorkItems.ToArray());
                                    }
                                }
                            }
                            else
                            {
                                return EventNotificationStatus.ActionPermitted;
                            }
                        }
                        catch (Exception ex)
                        {
                            this.WriteInfo("Failed to update field '" + 
                                   currentField + "'.\n\n" + ex, EventLogEntryType.Error);
                            if (!string.IsNullOrEmpty(currentField))
                            { 
                                statusMessage = 
                                  "Failed to update field '" + currentField + "'.";
                                return EventNotificationStatus.ActionDenied;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                // diagnostics
                strDump = string.Format(
                  "There was a unhandled exception: {0} \n {1}", ex, ex.StackTrace);
                this.WriteInfo(strDump, EventLogEntryType.Error);
            }

            return EventNotificationStatus.ActionPermitted;
        } 
        public Type[] SubscribedTypes()
        {
            return new[] { typeof(WorkItemChangedEvent) };
        } 
        // helper - dumping diagnostics
        public void WriteInfo(string strDump, EventLogEntryType entryType)
        {
            var log = new EventLog();
            log.Source = "TFS Services";
            if (!EventLog.SourceExists(log.Source))
            {
                EventLog.CreateEventSource(log.Source, "Application");
            }

            string strMessage = string.Format("The TFS server plugin {0} provides " + 
             "the following logging information:\n\n{1}", 
             Assembly.GetCallingAssembly().GetName().Name, strDump);
            log.WriteEntry(strMessage, entryType);
        }

        #endregion

        #region Methods

        private WorkItem SetFieldValue(WorkItemStore wit, 
          IntegerField id, TextField currentHtmlField, Uri uri)
        {
            WorkItem affectedWorkItem = null;

            WorkItemCollection result = wit.Query(
              "SELECT [System.Id] FROM WorkItems WHERE [System.Id] = " + 
              id.NewValue);
            foreach (WorkItem wi in result)
            {
                string fieldLookingFor = currentHtmlField.Name.Remove(
                   currentHtmlField.Name.ToLower().IndexOf("_copy")).Trim();
                foreach (Field item in wi.Fields)
                {
                    if (string.Compare(item.Name, fieldLookingFor, true) == 0)
                    {
                        if (item.Value.ToString() != currentHtmlField.Value)
                        {
                            wi.Open();
                            item.Value = currentHtmlField.Value;
                            affectedWorkItem = wi;
                        } 
                        break;
                    }
                }
            } 
            return affectedWorkItem;
        } 
        #endregion
    }
}

您需要将以下引用添加到您的项目中,然后它将构建

Image5

将插件添加到 TFS

编译完成后,获取输出的程序集并将它们复制到 TFS 服务器上的 *C:\Program Files\Microsoft Team Foundation Server 11.0\Application Tier\Web Services\bin\Plugins*。现在,当您在任何 Card 工作项窗口中更改 Card 时,此插件会将 HTML 从您的 _Copy 字段复制到另一个字段中,并且当数据移动到数据仓库时,HTML(字符串字段)也将被移动。

验证数据

数据同步后,您可以使用下面的 sql 查询来验证插件是否正常工作。

SELECT *
FROM [dbo].[DimWorkItem] with (nolock)
WHERE [Fields_MyHtmlField] IS NOT NULL 
order by [WorkItemSK] desc

在报告中使用 HTML 字段数据

现在您可以编写报告,并在显示该字段时将其标记为 HTML 字段,如下所示。

Image6

希望这能帮助您,如果您需要,请随时要求提供有关此主题的更多信息。

© . All rights reserved.