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

在自定义管道组件中更改消息的命名空间

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2011 年 1 月 14 日

CPOL

5分钟阅读

viewsIcon

41277

downloadIcon

470

如何在自定义管道组件中更改消息的命名空间

引言

本文详细介绍如何在 BizTalk 2006 R2 中开发自定义管道组件以更改消息的命名空间。在 BizTalk 2009 中,已有组件用于添加和删除命名空间。

此组件可在接收管道的解码阶段使用。一个 `public` 属性允许用户键入将添加到根节点的新命名空间,或替换 XML 消息中现有的命名空间。

代码

此项目在 Visual Studio 中创建为新的类库,并使用强名称密钥文件进行签名。

添加了一个引用,Microsoft.BizTalk.Pipeline.dll。可以在 BTS 安装位置的“Microsoft BizTalk Server 2006”文件夹中找到它。

此项目中的类重命名为 `ChangeNsDecoder`,代码首先添加以下命名空间

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Component.Interop;

在下面的代码中,我向类添加了两个属性。第一个属性表示这是一个管道组件,第二个属性将组件的使用限制在管道的解码阶段。

管道组件实现的特定接口使其与其他组件区分开来。一旦组件实现了接口,就可以被 BizTalk 运行时调用。所有组件和组件类别都在 `Microsoft.BizTalk.Component.Interop` 命名空间中。解码管道组件需要实现 `IBaseComponent`、`IComponentUI` 和 `IComponent` 接口。此外,下面的类实现了 `IPersistPropertyBag` 接口,但这并非必需。

namespace Stm.ChangeNsDecoder
{
    [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
    [ComponentCategory(CategoryTypes.CATID_Decoder)]
    [System.Runtime.InteropServices.Guid("5136B07D-DC44-4481-B3A8-35DA712CC538")]
    public class ChangeNsDecoder : IBaseComponent, IComponentUI, 
				IComponent, IPersistPropertyBag
    {

在类内部,我实现了所有接口所需的属性和方法。

我从 IBaseComponent 接口开始。该接口提供了有关组件的基本信息的属性。

#region IBaseComponent
private const string _description = 
	"Pipeline component used to change the namespace of a message";
private const string _name = "ChangeNsDecoder";
private const string _version = "1.0.0.0";

// Component description
public string Description
{
    get { return _description; }
}
// Component name
public string Name
{
    get { return _name; }
}
// Component version
public string Version
{
    get { return _version; }
}
#endregion

IComponentUI 接口定义了在管道设计器环境中使用的方法和属性。为保持简单,我在此处未提供任何代码。

#region IComponentUI
private IntPtr _icon = new IntPtr();
// Icon associated with this component   
public IntPtr Icon
{
    get { return _icon; }
}
// Verifies that all the configuration properties are set correctly        
public System.Collections.IEnumerator Validate(object projectSystem)
{
    return null;
}
#endregion

接下来,我实现了 `IPersistPropertyBag` 接口。这样做是为了存储管道组件的属性信息(在本例中是新命名空间)。当在接收管道中使用此组件时,它允许用户在 BizTalk 管理控制台的接收位置管道配置中的属性中键入新命名空间。

#region IPersistPropertyBag
private string _newNamespace;
public string NewNamespace
{
    get { return _newNamespace; }
    set { _newNamespace = value; }
}
public void GetClassID(out Guid classID)
{
    classID = new Guid("3A9B8F47-60A0-43F0-AFE2-73D08511668D");
}
public void InitNew()
{
}
public void Load(IPropertyBag propertyBag, int errorLog)
{
    object val = null;
    try
    {
        propertyBag.Read("NewNamespace", out val, 0);
    }
    catch(ArgumentException)
    {
    }
    catch(Exception ex)
    {
        throw new ApplicationException("Error reading PropertyBag: " + ex.Message);
    }
    if (val != null)
        _newNamespace = (string)val;
    else
        _newNamespace = "http://AnonymousURL";
}
public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
{
    object val = (object)_newNamespace;
    propertyBag.Write("NewNamespace", ref val);
}
#endregion

`NewNamespace` 属性用于在设计时配置新命名空间。

`Load` 方法将 `PropertyBag` 中的值读取到 `NewNamespace` 属性中。在管道设计器中将组件添加到解码阶段时,我遇到了一个错误消息。然而,该组件工作正常,但为了避免此错误消息,我添加了一个 `catch` 块来捕获 `ArgumentException`。

`Save` 方法将 `NewNamespace` 属性中的值保存到 `PropertyBag` 中。

`GetClassID` 返回组件的唯一标识值。

InitNew 用于初始化要持久化的对象到组件属性中。此项目不需要。

核心接口是 `IComponent`。在此项目中,它包含一个方法,用于执行管道组件来处理输入消息并获取结果消息。

#region IComponent
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
{
    IBaseMessagePart bodyPart = pInMsg.BodyPart;            

    if(bodyPart != null)
    {
        Stream originalStream = bodyPart.GetOriginalDataStream();
        if(originalStream!=null)
        {
            using (XmlReader reader = XmlReader.Create(originalStream))
	   {
	       XmlWriterSettings ws = new XmlWriterSettings();
	       ws.Indent = true;
	       ws.ConformanceLevel = ConformanceLevel.Auto;

	       MemoryStream outputStream = new MemoryStream();

	       using (XmlWriter writer = XmlWriter.Create(outputStream, ws))
	       {
	           reader.Read();
		  if (reader.NodeType == XmlNodeType.XmlDeclaration)
		  {
		      writer.WriteStartDocument();
		      reader.Read();
		   }

		   // Root Node
		   if (reader.NodeType == XmlNodeType.Element)
		       writer.WriteStartElement(reader.Name, _newNamespace);

		   reader.Read();
	            while (reader.NodeType == XmlNodeType.Element)
		   {
		       writer.WriteNode(reader, true);
		   }
	        }
	        outputStream.Position = 0;
	        bodyPart.Data = outputStream;
	    }
        }
    }
    return pInMsg;
}
#endregion
}// Ends the class
}// Ends the namespace

在上面的代码中,我首先检查输入流是否包含数据,然后我创建一个 `XmlReader` 从原始流读取,创建一个 `XmlWriter` 写入输出流。

创建编写器后,`reader.Read()` 行读取 XML 消息中的第一个节点。如果第一个节点是 XML 声明,那么编写器会将 XML 声明写入输出流,然后读取器读取下一个节点。

此时读取器指向根节点,这里是更改命名空间的位置。编写器通过将新元素写入输出流来实现这一点。这将是输出消息中的根节点。 `writer.WriteStartElement(reader.Name, _newNamespace)` 行执行此操作。第一个参数是元素的名称(根节点),第二个参数是新命名空间。请记住,`_newNamespace` 是在 `IPersistPropertyBag` 部分定义的。

接下来,`reader.read()` 行读取根节点内的第一个节点。在 `while` 循环内部,编写器将根节点内的所有节点及其所有内容写入输出流。输出流现在已完成,并包含与输入流相同的数据。

为了完成代码,我将输出流的位置设置为流的开头,并将输出流分配给消息的 `bodyPart` 的 `Data` 属性。然后我返回消息。

安装和测试组件

要使用此组件,需要构建项目,然后可以将 DLL 复制到 BTS 安装位置的“Microsoft BizTalk Server 2006\Pipeline Components”文件夹中。(不过,也可以将其复制到任何位置。)

在 BTS 接收管道项目中,必须将此组件 DLL 添加到工具栏并在解码阶段使用。 `NewNamespace` 属性的值可以在管道设计时设置。部署管道后,也可以在 BTS 管理控制台的接收位置中配置管道时设置此属性。

提示:如果在管道中使用组件后需要更改和重新构建该组件,则在构建组件时可能会遇到问题,因为 DLL 文件可能被另一个进程使用。要解决此问题,我使用 `WhoLockedMe` 应用程序来查看哪个进程锁定了文件,然后可以终止这些进程。 `WhoLockedMe` 是免费的,可以在网上轻松搜索并下载。

历史

  • 2011 年 1 月 14 日:初次发布
© . All rights reserved.