今天我们一起来看下Pipeline,但是在学习Pipeline之前,我们需要先看下Message类的结构

 

image

上图是Message的框架结构。

其中对于每个Biztalk框架来说,adapter和pipeline处理的相应对象为IBaseMessage,orchestration处理的相应对象为XLANGMessage。这2种类型非常相似。

 

在IBaseMessage中,不存放任何数据实体,相应的数据实体都会以stream的类型存放在IBaseMessagePart中。一个message可以有0到多个messagePart。每个Part都对应一个stream。但是在多个messagepart中只有一个messagebody用以存放消息主体信息。

 

接下来是IBaseMessageContext。这个类是从IBasePropertyBag中扩展而来的。在该对象中存放消息属性,如被提升的关键字等。可作为全局变量使用。

 

(一)Pipeline结构:

大家都知道Pipeline的作用是将adapter提供的数据进行转换,验证。Messagebox通过拉的形式从adapter处得到信息。Pipeline是位于adapter与messagebox中间的一个组件。由于跟水管的用途非常类似,这个也是Pipeline名称的来源。

 

在receive pipeling中,我们可以把整个过程分为4大阶段:

1. Decode: 对adapter过来的数据进行一个拆分前的操作,例如解密。

2. Disassemble:拆分的过程。

3. Validate:对拆分以后的数据进行数据验证

4. ResolveParty:将拆分后的消息与用户的授权或者安全身份做对应的阶段

 

在send pipeline中,我们可以把整个过程分为3大阶段:

1. Pre-Assemble: 对将要的封装的消息进行最后的处理。例如我们将要发送一个平面文件。在这里,我们可以对响应的数据进行查询,修正。因为在此阶段消息还是xml形式。远比平面文件形式来得方便,快捷。

2. Assemble:消息的封装

3. Encode:消息的加密

 

(二)Pipeline开发

很遗憾的告诉大家,在biztalk2006中我们还不能对上述的几大阶段进行添加或删除,只能进行简单的修改。

 

当我们安装biztalk2006以后,在program files\microsoft biztalk server 2006\developer tools\pipeline policy files\文件夹下,我们可以看到2个xml。分别名为btsreceivepolicy.xml和btstransmitpolicy.xml。在这2个文件夹下分别对应着我们的receive pipeling和send pipeline的几大阶段。我们可以通过修改Name的方式并保存的方式修改相应的阶段名称。保存后新建在vs2005中新建一个项目就会看到相应的阶段名称会发生变化了。

 

另外,execMethod是一个很重要的参数。我们可以设置3种不同的值给这个参数,他们分别是:

1. all: 表示在当前的这个阶段中,所有的组件都会被执行。

2. first recognized:表示在当前的这个阶段中,只会执行第一个被辨识的组件。(这个在自定义pipeline开发中会进行介绍)

3. all recognized:表示在当前的这个阶段中,会执行所有被辨识的组件。(这个在自定义pipeline开发中会进行介绍)

 

(三)自定义Pipeline组件开发

在自定义pipeline组件开发中,我们可以使用3种模式

1. 通用组件模式:这类模式适合开发Decode, Encode, Pre-assemble, Resolve Party或者Validate阶段使用的组件

2. 拆解组件模式:顾名思义

3. 封装组件模式:同上

 

接下来我们将介绍一些组件需要实现的通用接口:

 

1. IBaseComponent Interface需要实现以下方法

1 public interface IBaseComponent 
2 {
3 string Name { get; }
4 string Version { get; }
5 string Description { get; }
6 }

这些方法都是些非常显而易见的属性,就不浪费大家时间了

 

2. IPersistPropertyBag 需要实现以下方法

 1 public interface IPersistPropertyBag 
2 {
3 void GetClassID(out System.Guid classID); // 返回一个特有的ID。所以我们使用Guid
4 void InitNew();
5 void Load(IPropertyBag propertyBag, int errorLog);
6 void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties);
7 }
8 public interface IPropertyBag
9 {
10 void Read(string propName, out object ptrVar, int errorLog);
11 void Write(string propName, ref object ptrVar);
12 }


在IPersistPropertyBag中,我们需要看一下Load,Save方法。这2个方法会在运行时和设计时被调用。Load方法会将组件的配置作为IPropertyBag的对象传给组件。组件得到相应对象后,会根据对象生成相应的变量。也就是通常在右侧属性框中出现的自定义属性。在运行时,Load方法也会做同样的事情,这样每个属性的值都会被对应到组件中。Save方法类似,在设计时将属性写入IPropertyBag的对象中,并被持久化。

3. IComponentUI需要实现以下方法

1 public interface IComponentUI 
2 {
3 IntPtr Icon { get; }
4 IEnumerator Validate(object projectSystem);
5 }

Icon是用来设置设计时工具图标的。Validate是在设计时用来验证属性的正确与否,所有的错误会以IEnumerator 的形式返回给Vs2005.

以上的三个接口为通用接口,无论我们使用哪种开发模式我们都需要使用这3个接口。

 

接下来我们将按照不同的模式介绍几个特有的接口:

 

1. 通用组件模式: 该模式必须在实现以上3个接口的前提下实现IComponent接口。

以下为接口代码

1 public interface IComponent 
2 {
3 IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg);
4 }

其中我们可以使用的2个参数为IPipelineContext pContext, IBaseMessage pInMsg。

pContext为我们前面介绍的IBaseMessageContext接口的实现。而pInMsg为message主体。我们可以通过inmsg.BodyPart.GetOriginalDataStream()方法取得消息主体的stream流对象,进行处理。例如加解密等。

注意:请大家使用GetOriginalDataStream()而不是IBaseMessagePart.Data属性。因为IBaseMessagePart.Data属性可能会根据adapter或者pipeline的上层数据流的结构克隆stream对象。这样会破坏stream的模型,所以请大家一定要注意这点。

 

以下是一段示例代码:

 1 public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg) 
2 {
3 Stream oldStream = inmsg.BodyPart.GetOriginalDataStream();
4 Stream newStream;
5
6 // ---------- 对stream对象进行操作----------
7
8 // ---------- 对stream对象进行操作----------
9 inmsg.BodyPart.Data = newStream;
10 return inmsg;
11 }

 

当我们把操作完成的stream对象赋回inmsg.BodyPart.Data属性,并返回该inmsg对象时,对本组件而言inmsg对象会成为我们的下流消息。而对下一个接受我们消息的组件而言就是一个上流消息。

 

2. 拆解组件模式:该模式必须在实现以上3个接口的前提下实现IDisassemblerComponent接口。

1 public interface IDisassemblerComponent 
2 {
3 void Disassemble(IPipelineContext pContext, IBaseMessage pInMsg);
4 IBaseMessage GetNext(IPipelineContext pContext);
5 }

通常我们会这样实现这个接口

 1 ///<summary> 
2 /// this variable will contain any message generated by the Disassemble method
3 ///</summary>
4 private System.Collections.Queue _msgs = new System.Collections.Queue();
5
6 public void Disassemble(IPipelineContext pContext, Microsoft.BizTalk.Message.Interop.IBaseMessage pInMsg)
7 {
8
9 // ----------消息拆分----------
10
11 // ----------消息拆分----------
12 _msgs.Enqueue(pInMsg);
13 }
14
15 public Microsoft.BizTalk.Message.Interop.IBaseMessage GetNext(IPipelineContext pContext)
16 {
17 // get the next message from the Queue and return it
18 Microsoft.BizTalk.Message.Interop.IBaseMessage msg = null;
19 if ((_msgs.Count > 0))
20 {
21 msg = ((Microsoft.BizTalk.Message.Interop.IBaseMessage)(_msgs.Dequeue()));
22 }
23 return msg;
24 }

添加一个队列对象,用于存放我们需要发送给下游组件的消息。我们在Disassemble方法中对相应消息进行处理后,直接将拆分完成的消息推入队列,且有biztalk调用GetNext方法,从队列中将消息取出,并发送出去。

 

3. 封装组件模式:该模式必须在实现以上3个接口的前提下实现IAssemblerComponent接口。正好跟前面提到的IDisassemblerComponent接口作用相反,所以我也不一一介绍了。

1 public interface IAssemblerComponent 
2 {
3 void AddDocument(IPipelineContext pContext, IBaseMessage pInMsg);
4 IBaseMessage Assemble(IPipelineContext pContext);
5 }

4. 在前文中我们提到一个被辨识的组件的概念。现在我们来看下这个是一样什么样的东西。

首先,大家都应该知道在pipeline的每个阶段中我们可以放置0到255个组件。例如在disassmble阶段中,我们可以放置很多个disassmbler。每个disassmbler都可以对应一种特殊的schema。所有的schema都可以完全不一样。这样我们就会碰到一个问题,如何去匹配消息的schema的问题。这个就是我们前文中所说的被辨识的组件的概念。那如何实现呢,接下来我们就来看一看一个接口。

1 public interface IProbeMessage 
2 {
3 bool Probe(IPipelineContext pContext, IBaseMessage pInMsg);
4 }

 

这个接口返回一个bool值变量。也就是说如果在这个接口中对消息的判断结果是true的话,则当前这个组将会被认为是匹配项。否则则是不匹配项。

 

我这里有2个比较好的自定义pipeline的例子。如果大家感兴趣的话,留下邮箱。我发给大家。

下次我们介绍另一个核心部分orchestration。