摘要:本文通过讲解BizTalk输出带默认命名空间或无命名空间Xml消息以及修改消息根元素来阐述可变类型Xml消息的实现,并简单介绍BizTalk的消息处理流程、业务流程设计和自定义管道组件开发。
下载与本文相关的
DefaultNamespace示例代码。
本页内容
BizTalk Server到2006 R2已经历了多个版本,以Xml为基础,MessageBox为数据核心,处理层和存储层分离,BizTalk为企业实现EAI和B2Bi提供了强大支持。 BizTalk Server支持组和群集的部署架构,在实现高安全性和高可靠性的同时支持灵活的扩展模式。
在基于BizTalk平台的开发中, Xml Schema制定和Xml Schema Map 设计是一项基本工作任务。根据业务流程的复杂度,解决方案中可能会包含许多Xml Schema来对应不同的消息数据,同时为转换这些消息数据制定多个Xml Schema Map。一些流程复杂跨单位跨平台的应用集成系统实施时,各家单位的Xml Schema制定标准一般很难统一,Xml转换将变得复杂,如一些单位只处理含默认命名空间或不含命名空间的Xml消息。然而,纵观BizTalk Server 2004以及最新的2006 R2,还没有发现BizTalk对该功能直接的支持,本文接下去来讨论如何处理这些问题。
在BizTalk环境中,对Xml消息结构类型的定义方式是由Xml Schema的目标Namespace + # + 根元素名称组成。比如 http://blog.youkuaiyun.com/zhzuo#Message 就是一个消息类型定义,其中
marvelstack-优快云博客是目标命名空间,而Message则是Xml文档的根元素名称。因此,只要目标命名空间或文档根元素名称改变,那么消息类型也就改变了。
在开始讨论解决方案之前,先来看一下带默认命名空间的Xml消息是什么样的一种格式,为使问题表述简单化,下面制定了两个简单的Xml Schema,分别表示源架构和目标架构。
SourceSchema.xsd
<?xml version="1.0" encoding="utf-16" ?>
<
xs:schema
xmlns:b
="
http://schemas.microsoft.com/BizTalk/2003
"
xmlns
="
http://DefaultNamespace.SourceSchema
"
targetNamespace
="
http://DefaultNamespace.SourceSchema
"
xmlns:xs
="
http://www.w3.org/2001/XMLSchema
">
<
xs:element name
="
SourceRoot
">
<
xs:complexType
>
<
xs:sequence
>
<
xs:element
name
="
SourceElement
"
type
="
xs:string
" />
</
xs:sequence
>
<
xs:attribute
name
="
SourceAttribute
"
type
="
xs:string
" />
</
xs:complexType
>
</
xs:element
>
</
xs:schema
>
|
TargetSchema.xsd
<?xml version="1.0" encoding="utf-16" ?>
<
xs:schema
xmlns:b
="
http://schemas.microsoft.com/BizTalk/2003
"
xmlns
="
http://DefaultNamespace.TargetSchema
"
targetNamespace
="
http://DefaultNamespace.TargetSchema
"
xmlns:xs
="
http://www.w3.org/2001/XMLSchema
">
<
xs:element name
="
TargetRoot
">
<
xs:complexType
>
<
xs:sequence
>
<
xs:attribute
name
="
TargetAttribute
"
type
="
xs:string
" />
</
xs:sequence
>
<
xs:attribute
name
="
TargetAttribute
"
type
="
xs:string
" />
</
xs:complexType
>
</
xs:element
>
</
xs:schema
>
|
源架构和目标架构格式基本相同,从源到目标的转换通过一个Map实现,下图是VS提供的Map设计器,显示了Xml架构元素的具体映射。
Map1.btm
图1:BizTalk Map
源架构测试消息实例,
<ns0:SourceRoot SourceAttribute="
SourceAttribute_0" xmlns:ns0="
http://DefaultNamespace.SourceSchema">
<SourceElement>
SourceElement_0</SourceElement>
</ns0:SourceRoot>
|
从消息实例看到xml命名空间带ns0前缀,
xmlns:ns0="
http://DefaultNamespace.SourceSchema"
通过Map测试映射,得到目标架构测试消息实例,
<?xml version="1.0" encoding="utf-8" ?>
<ns0:TargetRoot TargetAttribute="
SourceAttribute_0" xmlns:ns0="
http://DefaultNamespace.TargetSchema">
<TargetElement>
SourceElement_0</TargetElement>
</ns0:TargetRoot>
|
目标消息实例的命名空间已经修改,但不是默认命名空间。
xmlns:ns0="
http://DefaultNamespace.TargetSchema"
消息实例中除了根节点带命名空间前缀外,属性和子元素并没有包含前缀,可以通过设置目标架构的Attribute FormDefault属性和Element FormDefault属性值为“Qualified”添加前缀。但是输出结果并不符合要求,正确的带默认命名空间实例应该如下,
<?xml version="1.0" encoding="utf-8" ?>
<TargetRoot TargetAttribute="
SourceAttribute_0" xmlns="
http://DefaultNamespace.TargetSchema">
<TargetElement>
SourceElement_0</TargetElement>
</TargetRoot>
|
如果是不带命名空间,那输出实例变成以下格式,
<?xml version="1.0" encoding="utf-8" ?>
<TargetRoot TargetAttribute="
SourceAttribute_0">
<TargetElement>
SourceElement_0</TargetElement>
</TargetRoot>
|
对于修改BizTalk消息根元素情况,输出的格式类似下面描述,
<?xml version="1.0" encoding="utf-8" ?>
<NewRootName TargetAttribute="
SourceAttribute_0" xmlns="
http://DefaultNamespace.NewTargetSchema">
<TargetElement>
SourceElement_0</TargetElement>
</NewRootName>
|
BizTalk中的Map或目标Xml Schema没有提供通过设置相关属性实现带默认命名空间或无命名空间Xml实例输出功能。
通过检查目标实例,第一个解决思路不难想到:Xml本质上是一个平面文件,因此可以把消息内容简单的作为字符串进行操作,消除“ns0:”和“:ns0”子字符串实现带默认命名空间的Xml消息,如果想进一步修改BizTalk消息类型,可以替换目标命名空间字符串和根节点元素名称,如果要使目标实例不带命名空间,可以将其使用空字符串代替。按这个思路编写.NET程序很容易做到,不过想通过BizTalk来处理,需要先来了解一下消息在BizTalk中的处理流程。
图2:BizTalk消息流
接收阶段:Xml、平面文件或二进制消息数据通过接收位置的接收适配器进入接收管道,在接收管道中的各个阶段,管道组件对消息执行解码、拆装、验证以及参与方解析,在消息经过管道后如果需要进行格式转换,接收端口可以对Xml消息执行Xml映射,之后消息会发布到MessageBox。
业务流程处理阶段:如果业务流程订阅了该消息,该业务流程会从MessageBox提取符合要求的消息做进一步处理。业务流程处理可以很复杂,处理过程中可以调用其他接收端口或发送端口,经过业务流程处理的消息再次发布到MessageBox。
发送阶段:发送端口从MessageBox订阅符合筛选条件的消息,在消息进入发送端口后可以对Xml消息执行Xml映射,接下去消息会经过发送管道,在发送管道中的各个阶段,管道组件可以执行预组装、组装和编码,处理后的消息最后通过发送适配器进行数据传输。
通过对BizTalk数据流分析,发现业务流程(Orchestration)和管道(Pipeline)都可以对消息进行处理转换成需要的新消息实例。
业务流程方式实现较为简单,甚至不需要编写额外代码,以下显示了简单的业务流程设计。
DefaultNamespaceOrchestration.odx
图3:BizTalk 业务流程设计
整个业务流程包含一个接收端口和一个发送端口,分别对应接收和发送,在业务流程的中心包含消息构造模块,实际通过消息赋值子模块来构造新的消息。为辅助消息处理,这里定义两个变量:
Variable_String,类型为System.String
Variable_XmlDoc,类型为System.Xml.XmlDocument
Message_In和Message_Out消息分别对应接收的消息和发送的消息,都采用System.Xml.XmlDocument类型,采用通用类型的好处是不需要引用包含xml Schema定义的程序集,便于部署和做到通用化。
消息赋值表达式实现了简单的字符串替换操作。
Variable_String = Message_In.OuterXml;
Variable_String = Variable_String.Replace("<ns0:","<");
Variable_String = Variable_String.Replace("xmlns:ns0=","xmlns=");
Variable_String = Variable_String.Replace("</ns0:","</");
Variable_XmlDoc.LoadXml(Variable_String);
Message_Out = Variable_XmlDoc;
|
以上表达式代码看上去也许不够美观,消息赋值表达式同时支持对外部.NET Assembly的调用,修改后的代码可能会是这样,
Variable_XmlDoc = Message_In;
Message_Out = SomeNamespace.Class.Method(Variable_XmlDoc);
|
在实际应用时,需要处理的目标消息实例通过接收端口进入流程,输出的消息实例将包含默认命名空间,如果需要实现无命名空间消息实例输出,只需删除消息内容中的命名空间属性字符串,对于修改消息类型的实现,可以同时对目标命名空间和根节点元素名称进行替换操作。
相比业务流程实现,通过Pipeline来处理看上去要复杂一些,需要编写自定义管道组件,不过从另一方面也体现出其灵活性。可惜,BizTalk开发环境没有提供管道开发框架生成向导来简化该步骤,而必须通过建立.NET类库自己实现。要编写自定义管道组件,需要实现BizTalk约定的一些接口。接口定义在Microsoft.BizTalk.Pipeline.dll程序集中,默认安装BizTalk 2006时该程序集会在如下位置,
C:/Program Files/Microsoft BizTalk Server 2006
在项目中引用程序集后,需要引用以下命名空间,
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
|
一般编写通用的管道组件,只需实现以下接口就能满足需求,
public class DefaultNamespaceConvert :
//Microsoft.BizTalk.Component.BaseCustomTypeDescriptor,
Microsoft.BizTalk.Component.Interop.IBaseComponent,
Microsoft.BizTalk.Component.Interop.IComponent,
Microsoft.BizTalk.Component.Interop.IPersistPropertyBag,
Microsoft.BizTalk.Component.Interop.IComponentUI
|
对于本文只需关心Microsoft.BizTalk.Component.Interop.IComponent接口,IComponent包含一个方法,
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg);
|
对于消息数据的处理就可以通过该方法实现,下面代码显示了一般的编写方式。
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
{
IBaseMessagePart bodyPart = pInMsg.BodyPart;
if (bodyPart != null)
{
Stream originalStream = bodyPart.GetOriginalDataStream();
if (originalStream != null)
{
try
{
Stream outputStream = Convert(originalStream);
outputStream.Seek(0, SeekOrigin.Begin);
bodyPart.Data = outputStream;
pContext.ResourceTracker.AddResource(outputStream);
}
catch(Exception ex)
{
throw new Exception("Convert Error," + ex.Message);
}
}
}
return pInMsg;
}
|
IComponent.Execute方法调用了自定义的Convert方法来转换数据流,按要求生成新的数据流。关于Convert方法的实现有多种方式,可按具体需求进行代码编写。
在组件编译成功后,Assembly需要复制到BizTalk管道组件子目录中或.NET全局程序集所在目录(GAC)才能被管道设计器使用,默认安装BizTalk 2006时,该子目录在如下位置,
C:/Program Files/Microsoft BizTalk Server 2006/Pipeline Components
程序集部署完成后,可在项目中添加发送管道,从工具箱中拖放编写的自定义管道组件到发送管道的预组装阶段,编译部署BizTalk程序集,在发送端口的发送管道中设置刚编写的自定义发送管道。BizTalk应用程序启动后,只要目标消息通过该管道就会转变成符合要求的新消息。如果在消息进入管道之前不考虑Map映射操作,那对于消息的格式化也可以通过建立自定义接收管道来实现。
通过管道和业务流程实现方式遵循的都是字符串替换的思路,是否应该考虑一下从Xml Schema Map本身入手来寻找解决思路?那么先来分析一下Map的工作原理,Map本质上是对源Xml使用 XSLT样式表转换生成目标Xml,编译的时候应用在Map网格中的Functioid会被转换成XSLT中的函数,内联Functioid在编译后会直接生成函数,如果是非内联Functioid,那XSLT中的函数会引用外部程序集,部署时服务器上必须存在这些程序集。既然是这样,那可以试一下为Map指定自定义XSL来实现Xml转换。
在VS的解决方案资源管理器中,右键选择Map1.btm,在上下文菜单中选择“验证映射”,在输出对话框中提示“输出XSLT 存储在下列文件中: <file:///C:/Documents and Settings/Administrator/Local Settings/Temp/_MapData/Map1.xsl>”,使用记事本打开Map1.xsl文件,查看内容,只需对以下内容进行修改,
xmlns:ns0="
http://DefaultNamespace.TargetSchema"
<ns0:TargetRoot></ns0:TargetRoot>
删除ns0命名空间前缀后的结构如下,
xmlns="
http://DefaultNamespace.TargetSchema"
<TargetRoot></TargetRoot>
以下是修改后的Map1.xsl文件。
<?xml version="1.0" encoding="UTF-16" ?>
<
xsl:stylesheet
xmlns:xsl
="
http://www.w3.org/1999/XSL/Transform
"
xmlns:msxsl
="
urn:schemas-microsoft-com:xslt
"
xmlns:var
="
http://schemas.microsoft.com/BizTalk/2003/var
"
exclude-result-prefixes
="
msxsl var s0
"
version
="
1.0
"
xmlns
="
http://DefaultNamespace.TargetSchema
"
xmlns:s0
="
http://DefaultNamespace.SourceSchema
">
<
xsl:output omit-xml-declaration="
no
" method="
xml
" version="
1.0
"
/>
<
xsl:template match
="
/
">
<
xsl:apply-templates
select
="
/s0:SourceRoot
" />
</
xsl:template
>
<
xsl:template match
="
/s0:SourceRoot
">
<
TargetRoot
>
<
xsl:if test
="
@SourceAttribute
">
<
xsl:attribute name
="
TargetAttribute
">
<
xsl:value-of
select
="
@SourceAttribute
" />
</
xsl:attribute
>
</
xsl:if
>
<
TargetElement
>
<
xsl:value-of
select
="
SourceElement/text()
" />
</
TargetElement
>
</
TargetRoot
>
</
xsl:template
>
</
xsl:stylesheet
>
|
下一步工作是为Map指定自定义XSL,在Map1.btm中,选择“网格”,在网格属性中,设置“自定义 XSL 路径”属性值为修改后的Map1.xsl文件。对Map1进行测试映射操作,查看测试输出实例符合默认命名空间格式要求。如果删除XSL目标命名空间属性,那么输出结果就是无命名空间实例。
根据以上分析有两种思路三种方式来实现可变类型Xml消息输出,那么接下来比较一下三者之间的优缺点。
1.业务流程(Orchestration)
通过业务流程能够简单的实现功能,完全支持VS设计时操作。如果对于包含多个命名空间,结构复杂的Xml消息就显示出不够灵活,无法方便的结合配置信息进行处理,需要调用外部程序集才能满足要求。另外,对于大容量的消息实例处理时性能是一个需要考虑的问题,在业务流程实例运行时需要为其指定主机(Host),这样会额外占用数据库服务器和处理服务器的资源。
2.管道(Pipeline)
管道实现方式很灵活,可控性强,可配置性强,如果对性能有要求,完全可以对大消息以流(Stream)方式进行处理,容易实现重用,并且,管道实现可以包括另外两种实现方式。不过,通过管道方式实现缺点也很明显,自定义管道组件编写需要开发人员对.NET开发语言(比如C#)比较熟悉,还需要了解BizTalk的一些基础类库。另外,管道组件部署更新相对比较麻烦,在多处理服务器环境需要分别部署。
3.自定义XSL
相比业务流程和管道两种实现方式,通过自定义XSL实现工作量最少,不需要额外建立项目或文件,部署也比较简单。但是在Map结构修改的时候,Map需要重新编译生成XSL文件,并修改XSL内容。另外设置自定义XSL方式看上去比较“隐蔽”,默认情况下XSL文件不受VS管理,在调试程序查找问题时不够直观。
本文的实现方案主要讨论如何生成带默认命名空间和不带命名空间的Xml消息,那么反过来如果接收的Xml消息不包含命名空间,而在BizTalk应用程序中部署了带命名空间的Xml Schema,怎么来处理?同样的实现方式,可以通过编写自定义接收管道组件或辅助业务流程为Xml消息添加需要的命名空间,从而生成符合要求的新Xml消息。