美祺 的个人资料Eden of Hexagon照片日志列表更多 工具 帮助

日志


12月29日

biztalk中常用调试方法

Biztalk中以图形方式取代了长串的代码,也不再有源代码级的单步调试,取而代之的是综合的调试手段。下面详细讨论biztlak中的常用调试手段。

一、 在Biztalk administration console中查看挂起的服务

调试biztalk的应用时,如果没有得到预期的结果,比如给了输入的消息,在应该输出消息的位置没有得到预期的消息,肯定是哪个环节出错了,这时首先应该检查的是否有挂起的服务实例,因为biztalk以消息为核心,而消息都是在某个服务实例中被处理的,一旦出错,出错的服务实例连同正在处理的消息就会被biztalk挂起:

l 打开Biztalk Server 2006 Administration

l 在Administration Console左边的树形结构中选Biztalk Group

l Administration Console右边窗口显示Group Overview,按F5刷新状态,在Suspended Items下可以看到目前是否有被挂起的服务,如下图:

clip_image002

Figure 1 Biztalk Administration Console中查看挂起的服务

如果Suspended service instances的计数不为0,就表示有被挂起的服务实例,点击Suspended service instances查看被挂起的具体服务实例。

打开挂起服务实例后,在窗口的右下方显示具体的被挂起的服务实例,双击某个服务实例,即可查看这个挂起服务实例的被挂起的原因:

clip_image004

Figure 2 Biztalk Administration Console中查看挂起服务的挂起原因

在Service Details窗口中,点击Messages标签,查看此服务实例正在处理的那个消息:

clip_image005

Figure 3 Biztalk Administration Console中查看挂起服务的相应消息

二、 查看事件日志

一般biztalk中发生的错误都会在事件日志中有反映,在上面的查看挂起的服务实例中能查到的错误,一般在日志中也都会有反映。但是有时由某些错误导致biztalk应用出错,在biztalk中只反映了跟biztalk直接相关的错误,最原始导致错误的原因可能在事件日志中才会有更详细的反映。

所以,如果在biztalk Administration Console中如果没有找到正真的错误原因时,到事件日志中找找可能会有所发现。

l 开始 – 管理工具 – 时间查看器

l 在事件查看器中右边目录,选“应用程序”,来源为“Biztalk Server 2006”的即为biztalk产生的错误消息。

clip_image006

Figure 4 事件日志中查看biztalk应用相关出错信息

三、 使用Orchestration Debugger断点调试Orchestration

Biztalk中orchestration是实现应用功能的主要手段,业务的流程就是它来实现的,对orchestration是调试一个biztalk应用的最主要的部分。

Orchestration是图形化的设计工具,biztalk也设计了个图形化的调试工具,叫做Orchestration Debugger。这个调试器可以对Orchestration进行断点调试,在断点出同样可以查看各个变量、消息、以及端口的状态。

1、 打开Orchestration Debugger

要想在Orchestration Debugger中打开某个orchestration,这个orchestration必须已经被执行过,或者正在执行中。

clip_image007 注意

如果第一次开始测试一个orchestration,当消息进入到biztalk,在入站的接收管道内就出错了,接收端口这个服务实例就会被挂起,消息也不会被路由到要处理这个消息的orchestration,这时这个orchestration还未被执行,所以这种情况是无法在Orchestration Debugger中打开这个orchestration,必须在排除了接收管道的错误,消息能够进入到Orchestration后才可能进行Orchestration的调试。

有两种方法打开Orchestration Debugger。

1.1. 在HAT中打开Orchestration Debugger

l 开始 – 所有程序 – Microsoft Biztalk Server 2006 -- Health and Activity Tracking

l 在打开的HAT窗口中,选Queries – Most Recent 100 Service Instances

l 在列出的最新的100个服务实例中,找到你要调试的那Orchestration,点击鼠标右键,选Orchestration Debugger。

1.2. 在Biztalk Administration Console中打开Orchestration Debugger

在Biztalk Administration Console中可以查看到被挂起的服务实例和正在运行中停留在断点的服务实例,如果是Orchestration的服务实例,都可以在此打开Orchestration Debugger

1.2.1 挂起的Orchestration服务实例

l 打开Biztalk Server 2006 Administration

l 在Administration Console左边的树形结构中选Biztalk Group

l Administration Console右边窗口显示Group Overview,按F5刷新状态,在Suspended Items下可以看到被挂起的服务

l 点击Suspended service instances查看被挂起的具体服务实例。

l 打开挂起服务实例后,在窗口的右下方显示具体的被挂起的服务实例,如果有你需要调试的Orchestration被挂起,在这个Orchestration上点击右键,选Orchestration Debugger

1.2.2 在断点的Orchestration服务实例

如果一个Orchestration 已经被设置了断点(如何设置断点后面讨论),Orchestration运行到断点处中断,Orchestration服务实例处于运行中状态。查看这样的服务实例同上面步骤一样,只是在第三步是查看Running Service Instances。

2、 设置断点

在Orchestration Debugger中打开了一个Orchestration后,就可以对这个Orchestration设置断点了。

在Orchestration Debugger中,一个Orchestration只显示流程的中间部分,Port Surface部分的端口不被显示,可以在流程的各个形状上设置断点:

clip_image008

Figure 5 Orchestration Debugger中设置断点

设置断点的方法是:选择要需要设置断点的形状,按F9。如果要取消断点,再次按F9键。

2.1. 类级别断点

上面方法设置的断点是类级别的断点,一旦设置了,对以后的这个Orchestration的服务实例都起作用,直到取消这个断点。

2.2. 实例级别断点

当在Orchestration Debugger中附加(Attach)当前在断点的Orchestration服务实例(如何附加下面讨论),即真正在Orchestration Debugger中打开了在断点的那个服务实例后,这时设置的断点只针对当前正在调试的Orchestration服务实例,对其他服务实例不起作用。

3、 断点调试

必须在Orchestration Debugger中打开需要被调试的那个Orchestration服务实例才能对这个Orchestration服务实例进行调试。

只有运行中的Orchestration服务实例才能被断点调试。

运行中的Orchestration服务实例一般有两种情况:出错被挂起、在设置的断点处中断。

出错被挂起的Orchestration服务实例在Biztalk Administration Console挂起的服务中找。在断点处中断的服务在Biztalk Administration Console的Running Service Instances中找。

l 在找到的需要调试的Orchestration上点击右键,选Orchestration Debugger

l 在打开的Orchestration Debugger窗口中,选Debug – Attach,这样就把当前需要调试的服务实例附加进来。这时的Orchestration Debugger看上去是这样:

clip_image010

Figure 6 Orchestration Debugger中附加服务实例后断点

这时,可以看到,服务实例被附加进来,并在设置的断点处中断,窗口下部多了两个窗格。

Variable List 窗格显示了当前消息、变量和端口。

Variable List 窗格显示的变量的类型格式为: Assembly.Namespace.Name

Variable Properties 窗格显示Variable List 窗格选择的变量的值。

Orchestration Debugger不能单步调试,但是可以通过不断的设置断点来实现类似单步调试的效果。

附加服务实例后设置的断点为实例级别的断点,只对当前服务实例有效。

四、 在流程中使用System.Diagnostics.EventLog.WriteEntry方法写入系统日志

设计中在orchestration的一些关键点设置了表达式形状,在orchestration运行到这些点的时候,表达式往日志中写入表示已执行到这一点的信息。使用类似下面这样的语句:

System.Diagnostics.EventLog.WriteEntry("用户信息插入到Sql之前","Ack_Sample");

clip_image011

12月4日

使用BizTalk Server常见问题处理

在开始开发BizTalk项目的时候,一些开发者会碰到许多基础问题,本文对这些常见问题进行罗列,其中有个别问题笔者向微软的BizTalk工程师寻求了问题解决方案,旨在让使用BizTalk的朋友更快的进行开发。文中的内容基于BizTalk Server 2006和Visual Studio 2005。

1.      在多列结果上不支持数据流
错误信息:
适配器“SQL”返回一条错误消息。详细信息为“HRESULT="0x80004005" Description="在多列结果上不支持数据流"”。
解决方法:
可能解决的情况是对SQL查询语句或存储过程加上“FOR XML AUTO--,/*ELEMENTS,*/--XMLDATA”。

2.      缺少根元素
错误信息:
执行发送管道时出错:“Microsoft.BizTalk.DefaultPipelines.XMLTransmit, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”,源:“XML 组装器”,发送端口:“SendPort_DataToXXX”,URI:“C:\Messages\Test\Create\%MessageID%.xml”,原因: 缺少根元素。
解决方法:
出现这种问题最大的可能性是在生成输出的Xml文件时出错导致格式错误。在使用BizTalk过程中使用架构映射文件(.btm)进行xml变换时会遇到这个问题。解决办法就是确认源架构文件(.xsd)和目标架构文件设置的目标命名空间是否正确。通过在vs.net的解决方案资源管理器的btm文件上点击右键菜单选择属性进行输入输出的设置,再通过右键菜单进行“测试映射”或“验证映射”来检查正确性。

3.      重新部署BizTalk应用程序无法刷新服务器运行实例
描述信息:
对BizTalk Server应用程序项目修改后通过vs.net2005进行重新部署到BizTalk服务器,结果BizTalk应用程序无法刷新服务器运行实例,还是运行上一个版本的程序。
解决方法:
a.在重新部署修改后的应用程序前,必须先停止BizTalk Server上的对应用程序实例。
b.通过vs.net部署程序。
c.在BizTalk Server 2006的管理器里的组节点下面的应用程序节点右键点击刷新菜单。
d.打开系统服务管理器找到“BizTalk Server 应用程序服务”,对其进行重新启动。
e.在BizTalk Server 2006的管理器中启动部署的应用程序。

4.      在vs.net 2005中无法自动刷新程序集引用
描述信息:
在vs.net 2005中,对被引用程序集的修改,无法在引用它的项目中进行自动刷新。
解决方法:
http://support.microsoft.com/default.aspx?scid=kb;en-us;313512

5.      验证架构文件(.xsd)出现错误 BEC2004

错误信息:(VS2005输出的错误信息有点读不通)
C:\测试目录\EntryDatasingle.xml: 错误 BEC2004: 元素 命名空间“http://namespace-url”中的“[xml根目录]”。 的子元素 命名空间“http://namespace-url”中的“ENTRY_CONTAINER”。无效。应为可能元素的列表: “某个子元素”。
解决方法:
在VS.net中打开架构文件,选中<Schema>,在属性窗口中。设置Element FormDefault值为Qualified,设置Attribute FormDefault值为Unqualified。详细可以访问http://blog.csdn.net/zhzuo/archive/2006/08/02/1011031.aspx。

6.      通过vs.net部署应用项目提示“无法添加资源”
错误信息:
无法添加资源。 存储区中已存在资源(-Type=“System.BizTalk:BizTalkAssembly” -Luid=“BizTalkTestProject1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3a57bb41dbe2b5dc”),并且该资源与另一个应用程序或另一种类型相关联。
解决方法:
在vs.net中打开BizTalk浏览器,找到前次部署的应用程序对应的程序集,右键点击取消部署,再次通过vs.net进行部署操作。

7.      只能在原子作用域或服务中声明非序列化对象类型
错误信息:
只能在原子作用域或服务中声明非序列化对象类型“System.Xml.XmlNodeList XmlEntryDataNodeList” E:\vs2005projects\BizTalk2006Projects\BiztalkTestSolution\SqlMQTest\SendEntryXXXProcess.odx
解决办法:
一种方式是把非序列化对象类型定义到原子作用域中,另一种方式设置事务类型为无或把整个编排事务类型设置为原子。

8.      消息引擎无法从数据库检索配置。详细信息:“c0002a1f”
错误信息:
消息引擎无法从数据库检索配置。详细信息:“c0002a1f”。
解决办法:
可能的原因,数据库访问出现问题。未仔细确认,最终解决方案是重新安装BizTalk Server。

9.      日志项字符串太长。写入事件日志的字符串不能超过 32766 个字符
错误信息:(错误日志比较长,列出一部分)
未捕获的异常(请参阅下面的“内部异常”)已经挂起服务
内部异常: 日志项字符串太长。写入事件日志的字符串不能超过 32766 个字符。
异常类型: ArgumentException
源: System
目标站点: Void InternalWriteEvent(UInt32, UInt16, System.Diagnostics.EventLogEntryType, System.String[], Byte[], System.String)
解决办法:
出现这种情况可能的原因是接收适配器一次性获取的数据量太大。笔者测试通过SQL适配器接收2500条记录在业务编排中处理时出现该问题。设置数据量在100条运行恢复正常。

10.  BizTalk Server 2006安装问题
安装文档:
http://www.microsoft.com/downloads/details.aspx?FamilyID=b273269c-97e0-411d-8849-5a8070698e4a&DisplayLang=zh-cn

11.  设置MQSeries传输属性的队列定义,队列管理器失败
错误信息:
从计算机192.168.0.10为 CLSID为 {86E96D72-0011-4B28-B1AC-BF52AB47F1B4}的远程组件检索COM类工厂失败,原因出现以下错误:80070005。
解决办法:
http://blog.csdn.net/zhzuo/archive/2006/07/07/890030.aspx

12.  非法尝试更新消息“Message_XXX”的只读属性值
错误信息:
未捕获的异常(请参阅下面的“内部异常”)已经挂起服务“BizTalkServer2006Test.SendNbEportEntryDataProcess(614fa04f-8968-ef95-952b-11c74450b870)”的一个实例。在管理性地恢复或终止该服务实例前,它将保持挂起状态。如果恢复了该服务实例,它将从上次持续的状态继续,这可能再次引发同样的异常。实例 ID: 7f064588-f7a1-4dc0-b7f7-180163b04b61形状名称: Construct_SendData形状 ID: c5e435bc-cbbb-48b5-89df-a97ebe0fbee4引发异常的位置: 段 2,进程 15内部异常: 非法尝试更新消息“Message_SendNbEport”的只读属性值(名称:“http://schemas.microsoft.com/BizTalk/2003/system-properties”、命名空间:“MessageID”)。
异常类型: PropertyUpdateDisallowedException
源: Microsoft.XLANGs.Engine
目标站点: Void ReadonlyPropertySetter(Microsoft.XLANGs.Core.XMessage,Microsoft.XLANGs.BaseTypes.XmlQName, System.Object)
解决办法:
该错误为修改只读的MessageID属性引起,修改设置该属性的代码。比如下面的例子。
Message_XXX(BTS.MessageID) = System.DateTime.Now.ToString("yyyyMMddHHmmss");

13.  使用SQL适配器发送数据提示“新事务不能登记到指定的事务处理器中”
错误信息:
适配器无法传输要发往发送端口“SendPort_InsertList”(URL 为“SQL://192.168.0.2/H_IMS_RECV/”)的消息。在为该发送端口指定的重试时间间隔过后,将会重新传输该消息。详细信息:“新事务不能登记到指定的事务处理器中。 ”。
解决办法:
http://go.microsoft.com/fwlink/?LinkId=61920
http://support.microsoft.com/kb/Q899191

14.  分步启动BizTalk Server 2006应用程序出错
错误信息:
提示部分依赖项没有启动等信息。
解决办法:
先启动发送端口,再启动业务流程,最后启动接收位置。

添加SQL适配器元数据提示“无法执行SQL语句。请确保提供的语法正确。”
错误信息:
在点击BizTalk项目右键菜单中选择->添加->添加生成的项->添加适配器元数据启动添加适配器向导。在SQL传输架构生成向导对话框中选择存储过程,点击生成按钮后,单击下一步按钮出现“无法执行SQL语句。请确保提供的语法正确。新事务不能登记到指定的事务处理器中。”的错误信息。
解决办法:
可能的一种情况是在添加的元数据端口类型为“接收端口”时,选择的存储过程没有返回架构元数据。可以修改该存储过程对SELECT语句加上“FOR XML AUTO /*,LEMENTS*/,XMLDATA”语句使其返回元数据,在生成后再去掉该语句。

15.  添加SQL适配器元数据提示“无法执行SQL语句。请确保提供的语法正确。新事务不能登记到指定的事务处理器中。”
错误信息:
在点击BizTalk项目右键菜单中选择->添加->添加生成的项->添加适配器元数据启动添加适配器向导。在SQL传输架构生成向导对话框中选择存储过程,点击生成按钮后,单击下一步按钮出现“无法执行SQL语句。请确保提供的语法正确。新事务不能登记到指定的事务处理器中。”的错误信息。
解决办法:
http://support.microsoft.com/kb/Q899191

16.  在基于内容的消息路由中提示“无法路由发布的消息,因为找不到订户”

错误信息:
错误详细信息: 无法路由发布的消息,因为找不到订户。如果订阅业务流程或发送端口尚未登记,或者订阅评估必需的某些消息属性尚未升级,就会出现此错误。请使用 Biztalk 管理控制台排除此故障。 解决办法:
可能的解决办法,在没有使用业务流程编排的项目中,在发送端口的筛选器设置,
属性:BTS.ReceivePortName
运算符:==
值:[接收端口名称]
分组依据:与

17.  执行接收管道时出错,没有任何拆装 阶段组件可识别该数据
错误信息:
接收位置“Receive Location1”(URI 为“E:\test\test_Input\*.*”)上的适配器“FILE”接收的消息已挂起。错误详细信息: 执行接收管道时出错:“Microsoft.BizTalk.DefaultPipelines.XMLReceive, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”,源:“XML 拆装器”,接收端口:“ReceivePort1”,URI:“E:\test\test_Input\*.*”,原因: 没有任何 拆装 阶段组件可识别该数据。
解决办法:
在接收文件目录中放入一个非xml文件,在接收端口中设置拆装器为“Microsoft.BizTalk.DefaultPipelines.XMLReceive”所导致,如果是处理平文件,调整接收管道为“Microsoft.BizTalk.DefaultPipelines.PassThruReceive”。如果是xml文件,检查xml格式是否有误。

18.  执行发送管道时出错,原因: 根级别上的数据无效
错误信息:
执行发送管道时出错:“Microsoft.BizTalk.DefaultPipelines.XMLTransmit, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”,源:“XML 组装器”,发送端口:“SendPort1”,URI:“E:\[测试报文]\预定数据到分中心\test_Output\%SourceFileName%”,原因: 根级别上的数据无效。 行 1,位置 1。
解决办法:
同第17个问题相似,在发送端口中发送管道的设置的问题,如果是处理平文件,调整发送管道为“Microsoft.BizTalk.DefaultPipelines.PassThruTransmit”。如果是xml文件,检查xml格式是否有误。

19.  配置MQSeries适配器的队列定义信息选择本地服务器队列管理器出错
错误信息:
服务器 localhost 上未安装或未正确配置适配器。COM异常:从计算机localhost为CLSID为{86E96D72-0011-4B28-B1AC-BF52AB47F1B4}的远程组件检索COM类工厂失败,原因是出现以下错误:80040154。
解决办法:
确认是否运行过BizTalk MQSeries 代理配置向导,如果没有通过开始菜单->程序-> Microsoft BizTalk Server 2006->BizTalk MQSeries 代理配置向导来进行设置。另外需要注意MSDTC的安全配置,以Windows Server 2003为例,在开始菜单的运行中输入dcomcnfg命令打开组件服务控制台,展开左边的组件服务下面的计算机、我的电脑右键菜单属性选择MSDTC选项卡,点击安全配置按钮,调整安全配置,具体的配置可以看http://blog.csdn.net/zhzuo/archive/2006/07/07/890030.aspx。

 20.  通过MQSeries适配器发送消息提示访问被拒绝
错误信息:
适配器无法传输要发往发送端口“SendPort1”(URL 为“MQS://localhost/nbeport_zz_test/zz_test_queue”)的消息。在为该发送端口指定的重试时间间隔过后,将会重新传输该消息。详细信息:“The adapter has encountered an 'Access Denied' error while attempting to contact the COM+ object on the MQSeries server. Ensure the BizTalk account is added to the Role on the MQSAgent COM+ application.”。
解决办法:
以Windows 2003 Server为例,在开始菜单的运行中输入dcomcnfg命令打开组件服务控制台,展开左边的组件服务下面的计算机->我的电脑->COM+ 应用程序->MQSAgent2->角色->CreatorOwner->用户->右键菜单新建->用户->添加BizTalk Server Administrators组用户和BizTalk Application Users组用户。返回到MQSAgent2节点右键->禁用->启用->启动。如果还是出现问题请重新启动计算机。具体的配置可以看http://blog.csdn.net/zhzuo/archive/2006/07/07/890030.aspx。

21.  在应用程序日志中出现“文件系统连接器中发生错误”
错误信息:
文件系统连接器中发生错误。请查看详细信息。Cant make a connection to \\Test-11\EDIDocsHome\Documents\PickupEDI. Errormessage: The operation cannot be performed because a network component is not started or because a specified name cannot be used.Foldername: \\NBEPORT-MQ\EDIDocsHome\Documents\PickupEDI, Errormessage: The operation cannot be performed because a network component is not started or because a specified name cannot be used.
解决办法:
该问题为访问PickupEDI目录权限不够引起,可以设置目录属性的安全来解决。

8月13日

BizTalk Server Tips

提高 BizTalk 编程能力的 8 点技巧和窍门

Marty Wasznicky

 and Scott Zimmerman






more ...


打印


E-mail


添加到收藏夹


评价


RSS (Issues)


Add RSS to Any


相关信息


Live Spaces


Digg This


BlogThis!


Slashdot


del.icio.us


Technorati


本文讨论:
  • 多部分消息
  • 直接绑定端口
  • 创建 Web 服务
  • 调试 XSLT

本文使用了以下技术:
BizTalk Server 2006,Visual Studio


目录

1. 始终使用“多部分消息类型”
2. 始终尽可能使用“直接绑定端口”设计业务流程
3. 始终使用单独的内部和外部架构
4. 永远不要在 WSDL 中直接公开您的内部架构
5. 始终为 Web 服务优化 BizTalk 注册表
6. 始终使用相对路径设置程序集密钥文件
7. 永远不要忽视免费的示例代码
8. 在 Visual Studio 中调试 XSLT
总结


一次,Marty 要与一个大客户进行概念证明,就在要开始前半天,他接到一个需要修复的复杂的 BizTalk® 解决方案。 该解决方案的主要组件是一个业务流程,它集成了几个后端系统,要对每一个后端系统执行多次对外调用。 他看到“业务流程设计器”屏幕上几乎全部由黑色线条构成,将几十个“接收”和“发送”形状连接到 40 多个入站和出站端口 — 这种设计几乎不可能对其进行调试。 他的解决办法: 使用“多部分消息类型”和“直接绑定端口”(下面的技巧 1 和技巧 2)重新开始,沿流程路线执行单元测试。 结果如何? 他成功了!

Marty 的成功部分归功于 BizTalk Server 的设计 — 它的设计可以应对互连系统编程中固有的特殊难题,在某些情况下甚至不必编写代码。 但是,尽管它有简洁的拖放式流程图,而且代码很少,您也不要高兴过了头 — 事情没这么简单。 BizTalk 是一种使用范围广而且功能强大的产品,熟练掌握它需要几年的时间。 如果您打算成为一名 BizTalk 专家,您会遇到很多需要您基于 Microsoft® .NET Framework 编写代码的情形,以与 BizTalk 中复杂的内置消息处理能力形成补充。

直到最近,BizTalk 新手们都一直很少有机会利用大师的心得和技巧来加快掌握 BizTalk。 (请参阅侧栏上的“学习 BizTalk Server 2006 编程”以查看相关资源。) 我第一次深入接触 BizTalk 是在参加 Marty 主办的为期一周的 BizTalk 培训活动中的一次课程期间,在这一周的时间里,他不断告诫我们要正确设计系统,并在使用之前在实际负载条件下用 PerfMon 对其进行测试。 通过这次活动,我们汲取了大量技巧,这些技巧可以帮助您成为更高效的开发人员。 本文的目的旨在与大家分享这些有用的技巧,下面转入正题。

1. 始终使用“多部分消息类型”

BizTalk 的目的旨在处理消息,顺便说一下,所谓消息并非只是电子邮件。 文档、InfoPath 窗体、大型二进制文件、SQL 记录、平面文件以及任何 XML 文件都可以作为消息来处理,同时您还可以获得异步通信带来的好处。 一旦您熟悉了该工具,您会发现面向消息的编程与面向对象的编程一样酷,都是非常有用的编程范例。

BizTalk 中的消息是数据,每个消息都必须属于一种选定的消息类型。 BizTalk 中最常见的消息类型是架构,也就是说,此类消息基于一个 .XSD 文件,此文件指定了消息中的记录和字段结构。 请参阅 msdn2.microsoft.com/en-us/library/ms942182.aspx,查看有关 BizTalk 中的架构和映射的详细讨论。

在用 BizTalk 进行编程的过程中,总有一天您会希望更改某个消息所基于的架构。 但问题是,如果您已经为“发送”或“接收”形状选择了此类消息并将其连接到一个业务流程端口,则在您尝试更改“消息类型”时将收到以下错误:

Property value is not valid: One or more Send or Receive actions are
 connected to Ports and are using this Message. Please disconnect the
 actions before changing the Message Type.

在按错误消息的建议进行操作之前,让我们先想一想它涉及哪些工作。 首先,您必须检查每个“接收”和“发送”形状,以确定它是否使用了与您要更改的架构关联的 Message 变量(已设置了其“消息类型”)。 目前,BizTalk 中没有可提供此类审核或生成依赖关系映射的功能;实际上,若创建的一个业务流程有太多的“接收”/“发送”形状,以至于需要有这样一种功能,只能说明这一流程的创建没有采用良好的做法。 在前面提到的让人发怵的业务流程中,大多数“接收”和“发送”形状都使用不同的 Message 变量,但它们都指向同一架构定义(都是同一消息类型)!

第二,一旦找到所有“接收”/“发送”形状,必须删除它们的端口连接。 第三,必须更改 Message 变量,以便将“消息类型”属性设置为新的架构,然后重新将 Message 变量与每个“接收”/“发送”形状关联起来。

第四(您认为您已经完成了),必须找出与已经从“接收”/“发送”形状断开连接的端口关联的所有“端口类型”,并重新设置其“操作”的“消息类型”属性。 您知道,在您最初将“接收”或“发送”形状与一个端口关联时,BizTalk 会自动将“端口”类型中的“消息类型”属性设置为与此“接收”/“发送”形状的 Message 变量的“消息类型”相符。 在删除“端口”和“接收”/“发送”形状之间的连接时,BizTalk 将丢失该“端口”的“消息类型”。

幸运的是,有一种更好的方法。 计算机科学领域有一条公理指出,添加一个中间环节可以解决许多问题。 本例再次证明了此言不虚;我们需要做的就是使用一个多部分消息类型来包装底层的架构。 最初的工作量可能会稍大一些,但是灵活性会大大提高,并且从长远看可为您节省时间。 下面介绍如何实现这一点。

在您右键单击“业务流程视图”选项卡中的“消息”来创建新消息时,有四种消息类型属性可供选择(参见图 1 中的右下角)。 为消息类型选择一种架构是通常的做法,但是在这里我们所使用的技巧是要尝试一种全然不同的做法。 单击 + 号展开“多部分消息类型”,然后选择“创建新的多部分消息类型”。 为您的多部分消息类型命名,然后单击 + 号展开它,以便您可以查看其 MessagePart_1 成员(这是 BizTalk 为新的多部分消息类型的第一部分建议的名称)。 现在,将“标识符”属性从“MessagePart_1”更改为更好识别的值,如“Body”(但是不要使用小写“body”,因为它是保留字)。 不要忘记将“消息正文部分”属性设置为 True(以便其行为与常规的消息一样)。

图 1 在业务流程视图中设置“消息类型”属性
图 1 在业务流程视图中设置“消息类型”属性 (单击该图像获得较小视图)

图 1 在业务流程视图中设置“消息类型”属性
图 1 在业务流程视图中设置“消息类型”属性 (单击该图像获得较大视图)

需要做的额外工作到此结束。 最后,将新消息部分(如,Body)的“类型”属性设置为常规的基于架构的消息类型所使用的同样的架构。 现在,您的逻辑端口和“接收”/“发送”形状都在使用您的多部分消息类型,将来您可以轻松地更改底层的架构,而无需断开端口与接收形状的连接。

Back to top

2. 始终尽可能使用“直接绑定端口”设计业务流程

请看一下图 2,该图显示了在业务流程设计器中配置端口时可用的选项。 在使用端口配置向导时,您将从此图的顶部移动到底部。 注意此图中的三个“直接”选项。 虽然 BizTalk UI 提供了“直接”作为端口类型,但是我们这里将使用行话,称它们为“直接绑定”。

图 2 业务流程设计器端口配置向导中的“端口”选项
图 2 业务流程设计器端口配置向导中的“端口”选项 (单击该图像获得较小视图)

图 2 业务流程设计器端口配置向导中的“端口”选项
图 2 业务流程设计器端口配置向导中的“端口”选项 (单击该图像获得较大视图)

要理解 BizTalk 中的端口术语,关键要理解逻辑端口(也称为业务流程端口)和物理端口的概念。 一句话说,两者的区别是,在业务流程设计器中创建的是逻辑端口,使用 BizTalk 浏览器或 BizTalk 管理控制台创建的是物理端口。

开发人员在业务流程设计器中创建“以后指定”端口时,他是在配置一个逻辑端口,将相应的物理端口属性留给 BizTalk 管理员在以后进行配置。 “以后指定”是最常使用的选项,关键原因是为了让管理员能够在生产环境中灵活地配置物理端口。

使用“立即指定”选项,则开发人员完成所有配置(包括逻辑和物理配置),得到的解决方案是硬绑定的。 除了要进行快速试验以外,不建议使用此选项。

使用“动态”端口时,物理端口属性的最终配置同样也是由管理员完成的。 但是,在这种情况下,开发人员还可以编写代码,在运行时设置特定出站消息属性。 这对于 SMTP 或 HTTP 非常有用,在这两种情形下,业务流程逻辑包括带有如下语句的“表达式”形状:

DynamicSendPort(Microsoft.XLANGs.BaseTypes.Address)= 
    “mailto:john@contoso.com”;

虽然图 2 中有许多选项,但此图中显示的仅是业务流程设计器中的选项。 “静态”端口适用于哪些环境? 当您在 BizTalk 浏览器或 BizTalk 管理控制台中创建物理端口时,会遇到这一术语。 如果开发人员在业务流程设计器中创建一个“以后指定”或“动态”端口(逻辑端口),那么管理员在 BizTalk 浏览器或 BizTalk 管理控制台中创建相应的物理端口时,将从图 3 中所示的四个选项中选择。

学习 BizTalk Server 2006 编程

如果您刚开始学习 BizTalk 编程,您会发现,如果查看 2005 年 11 月份的《MSDN 杂志》上由 Aaron Skonnard 主持的服务站专栏以及 2005 年 12 月专栏,将对您有很大帮助。 在 BizTalk Server 开发中心,您可以找到数十种免费的网络广播。

如果您处于中级水平,请阅读由 Mark Beckner 编著,Apress 出版的 《BizTalk 2006 Recipes: A Problem-Solution Approach》;并阅读 2006 年 12 月份的《MSDN 杂志》中的 Alex Starykh dives into adapter programming topics(针对 BizTalk Adapters)。

如欲获得中级到高级资料,请参阅由 George Dunphy 与 Marty 合著、Apress 出版的《Pro BizTalk 2006》,以及由 Darren Jefford 编著、Wrox 出版的《Professional BizTalk Server 2006》,在您阅读本文时,这两本书应该已经面世了。 不要因为看到标题中的版本号而感到失望;由 Scott Woodgate 编著、Sams 出版的《Microsoft BizTalk Server 2004》也是非常适用的。 这些书中包括几个使用 C# 代码将 BizTalk 带入更高级别的示例。

为增强您在集成系统理论和设计方面的实力,您需要阅读由 Gregor Hohpe 编著、Addison-Wesley 出版的《企业集成模式》一书。 乍一看,您可能会认为这对您学习 BizTalk 不会有什么帮助,但是,了解一下 BizTalk Server 2006 如何隐式实现这几种模式是很有趣的。 如想了解有关技巧 1 的详细信息,以下由 Kurt Guenther 和 Charles Young 撰写的博客是很好的参考资料:

对于技巧 4,请查看以下由 Brian Loesgen 和 Kevin Lam 撰写的专家博客:

如欲了解更多 BizTalk 技巧,请参阅 Alan Smith 撰写的“七个习惯”一文和 Scott Colestock 撰写的有关 BizTalk 命名约定的短文:

Close [x]

请记住,“动态”物理端口对应于“动态”逻辑端口(相当简单),但“静态”物理端口对应于“以后指定”逻辑端口。 另外,要注意“要求响应”和“请求响应”这两个术语之间的不匹配。 就这些。

完成了这些初步准备之后,我们终于可以讨论“直接绑定”端口的优势了。 “直接绑定”端口在将消息从 BizTalk 发送到 BizTalk 方面是很出色的。 顺便说一下,正是由于这一点,BizTalk 没有必要为 Web 端口提供“直接”选项。 Web 端口用于从 BizTalk 到 Web,而不是从 BizTalk 到 BizTalk。

“直接绑定”端口是自我配置的,不会影响灵活性或性能。 “直接绑定”端口的便捷性在于开发人员和管理员都无需在 BizTalk 浏览器或 BizTalk 管理控制台中创建相应的物理端口。 因此,“直接绑定”端口会形成更独立的业务流程,因而可以更好地重复使用,而且更便于独立地重新部署。

有以下三种“直接绑定”端口类型: 消息框路由、自我关联,以及业务流程到业务流程(也称为合作伙伴端口)。 这三个单选按钮在业务流程设计器中的实际文本要冗长得多,但是,您只要略加观察,就能轻松地把我们的这些短名称与 UI 上的名称相对应。

在这三种端口类型中,“消息框”是最常使用的一类。 它允许您的业务流程完全独立于其他业务流程,尽管您可能只是让它与其自身通信。 没错,这确实意味着有可能出现无限循环。 我们马上就会谈到这一问题。

如果为“消息框路由”配置了一个“激活”属性设置为 True 的“接收”形状,则意味着当消息到达符合给定的订阅筛选器的“消息框”时,将启动业务流程的一个新实例,不管消息来自何处,也不管发送给谁。 这可以帮助您构建一个可重复使用的业务流程,如工具库中的泛型函数。 Marty 使用此技巧构建了一个巧妙的基于消息的异常处理系统,此处暂且不论。

假设您想用以下两种方法启动您的业务流程: 第一,操作员在某个文件夹中对单向静态 FILE(物理)端口发出“启动”消息;或者,第二,业务流程向其自身发送一个启动消息以运行另一个实例。 您首先想到的可能是使用一个“接听”形状(如,用于业务流程的 OR 运算符),并使它下面有两个“接收”形状(“以后指定”用于 FILE 端口,“直接”用于消息框)。 但“直接绑定消息框”端口的最妙之处恰恰是 — 它们不关心消息的来源。 因此,您只需要一个“接收”形状就可以启动此业务流程!

“自我关联直接绑定”端口通常在您想要以异步方式将消息从一个业务流程发送到下一个业务流程时使用。 假定您有两个业务流程:A 和 B。只需进入业务流程 B,在参数部分下创建一个逻辑“发送端口”,并将其设置为使用在业务流程 A 中创建的“端口类型”。现在,您通过业务流程 B 中的该端口发送的任何消息都将立即路由到您在业务流程 A 中创建的逻辑“接收”端口。基本上,我们利用 BizTalk 的发布/订阅特性来转发消息,此外还通过隐藏的独特的关联令牌为这些消息加盖印记,因此称为“自我关联”。 在这种情况下,由于您在各业务流程之间共用一种端口类型,因此各业务流程都对其他业务流程有所了解。 您可以将“端口类型”上“操作”的“消息类型”设置为 System.Xml.XmlDocument,从一定程度上减小依赖性。这是使业务流程异步运行,同时使某种 broker 能够接收来自其他业务流程的主动确认或被动确认的一种很好的方法。

业务流程到业务流程直接绑定(合作伙伴)端口用于以异步方式发送消息。 您将向订阅中添加与使用“消息框直接绑定”端口时相比更宽泛的限制。 在这种情况下,也就是说您将接受来自合作伙伴业务流程的任何消息。 一个业务流程指定“直接绑定”端口,另一个业务流程(合作伙伴)指定自身。 发送方或接收方都可以成为引用“直接绑定”端口的一方。 这里的关键是,您将把两个已知的解决方案连接起来进行通信,而使用“消息框直接绑定”端口时发送方和接收方之间的耦合更松散。

接下来介绍有意思的部分。 使用“直接绑定”端口(特别是使用“消息框”)时常见的缺陷是会造成无限循环。 假设一个简单的业务流程只包括两个形状,“激活”被设置为 True 的“接收”形状(当然是“直接绑定”)和仅将消息转发给 FILE 端口的“发送”形状。 当此业务流程发出消息后,消息将去往何处? 和其他情况一样,首先进入消息框。 一旦消息达到消息框,BizTalk 就会进行搜索,看有没有匹配的订阅。 此消息如何与最先激活业务流程的消息区分开? 无法区分。因此 BizTalk 会不假思索地触发业务流程的另一个实例来处理它,以此类推,直到耗尽内存。 修复此问题的一种方法是将入站消息复制到一个新构造的出站消息,并更改至少一个提升的属性的值,以便“接收”形状上的订阅筛选器无法与新消息匹配。 运行管理控制台,并转到“组中心”|“新建查询”|“打开查询”。 下一步是打开 BizTalk\SDK\Utilities 文件夹中的 BTSSubscriptionViewer.btq。 您将看到,在您为“接收”形状设置消息类型时,BizTalk 将自动创建一个谓词。 在“业务流程设计器”中,将一个谓词添加到“筛选器表达式”属性中,完整订阅将如下所示:

BTS.MessageType == “http://MyInternalSchemas.MyProject#MyRootNode”
AND inbound_message(status) != 1

或者,如果您不愿意像上面所说的那样更改架构以添加自定义字段,还可以向“接收”形状上的订阅筛选器中添加一个子句来测试 BTS.Operation 名称与您的业务流程中的名称不同。

我们前面说过,BizTalk 的目的旨在处理消息,还记得吗? 嗯,回想一下。 实际上也就是订阅。 在 BizTalk 中,在发送端口或业务流程上对消息所做的操作无非就是订阅。

Back to top

3. 始终使用单独的内部和外部架构

您应始终将接收到的消息转换为您自己的规范架构,不管架构的简单程度如何,也不管它的源是谁。

对于少量的处理,一旦(更恰当地说是“当”)发送方更改架构时,此做法将使您获得很大程度的灵活性。 您不希望您的业务流程逻辑依赖于由其他人控制的架构中的任何属性或字段。 在发送方更改其架构时,您只需更改您的映射,而不是业务流程。 这同样运用了中间环节的公理。

通过遵循为映射、业务流程、内部架构和外部架构创建单独的 Visual Studio 项目这一最佳做法,可以更轻松地重新部署您更新的解决方案。 这对于 BizTalk Web 服务发布向导生成的 Web 服务确实至关重要。

如果您想偷懒,那么不创建自己的带有新字段名称的架构也可以。 只要复制为您提供的入站架构,并将所有字段按照原样从源架构中映射出来即可,但至少要将副本保存到您的内部架构项目中,自己给它取个名称。

这样做还可以减少您需要的映射数。 假设您需要将三种类型的入站消息映射到三种类型的出站消息(可能不是现在,而是在将来)。 应用此技巧,您可以为每个入站架构创建一个到您的规范架构的映射,然后创建从您的规范架构到每个出站架构的映射。 这样一来只需构建和维护六个映射,而不是九个。

使用禁忌

BizTalk Server 2006 是一个大型应用程序,幸运的是,它通常会为完成一些事情提供几种方法。 但是由于各种各样的原因,一些方法存在陷阱,多数有经验的使用者现在已经知道远远避开它们:

  1. 避免使用 BizTalk Server 2006 中的 BizTalk 浏览器。此功能在 BizTalk Server 2004 中至关重要,因此,Microsoft 决定不将它从 BizTalk Server 2006 中去除,但新的应用程序打包功能和经过重新设计的 BizTalk 管理控制台已使其成为多余的。 不幸的是,在某些情况下使用它会造成麻烦。
  2. 在 Visual Studio® 2005 解决方案资源管理器中,永远不要在“项目”一级上单击“部署”。 在解决方案中独立构建项目是可以的,但您只应在“解决方案”一级上单击“部署”。 原因是: Visual Studio 会尝试自动跟踪依赖关系,而单独为 BizTalk 部署程序集可能会导致它无法进行跟踪。
  3. 不要指望不仔细编辑命名空间属性(以确保匹配)就能将架构文件 (.XSD) 从一个项目复制到另一个项目。 记住这一点,不然您肯定会遇到让您大为头疼的问题。
  4. BizTalk 专家从不使用 Quick Promote。 等到您采取必要的步骤来纠正它提供给您的类型时(假如您还记得这样做),您会发现其实您自己本来可以快速显式地创建这些类型。
  5. 不要将映射放入业务流程中,除非您需要将多个传入消息映射到一个消息中,或者您需要以现有消息的经过修改(映射后)的内容为基础生成一条新消息。 为简化部署,最好将您的映射放到“接收”和“发送”端口上。 如果您的业务合作伙伴对其架构进行了修改,或者如果您添加了需要新映射的新合作伙伴,您将不得不同时更新您的架构和业务流程,而这不是您所希望的。

Close [x]

Back to top

4. 永远不要在 WSDL 中直接公开您的内部架构

也就是说,您永远不应单击 BizTalk Web 服务发布向导中的第一个单选按钮;而应始终单击第二个按钮(请参见图 4)。 第一个单选按钮用于发布业务流程,第二个单选按钮用于纯消息处理解决方案,然而使用第二个选项发布业务流程也不失为一个好主意。

图 4 发布您的外部构架,而不是业务流程
图 4 发布您的外部构架,而不是业务流程 (单击该图像获得较小视图)

图 4 发布您的外部构架,而不是业务流程
图 4 发布您的外部构架,而不是业务流程 (单击该图像获得较大视图)

是的,这样一来就更复杂了,您肯定希望我们给出合适的理由。 理由是:接口。 如果您想要更详细的解释,那就是松散耦合! 这样做使您在更改业务流程时具有更大的自由性,无需中断调用方。 您可以将这一点看作是技巧 3 中概念的特殊化。

接下来我们讨论一下如何实现这一点。 下面是关于此向导要记住的一点: 进入步骤 2 后,您需要右键单击“Web 服务”对话框(请参见图 5)中的几乎每一项。 您将通过这种方式对您创建的 Web 服务及其方法进行命名。

图 5 在“Web 服务”对话框中编辑您的 WSDL
图 5 在“Web 服务”对话框中编辑您的 WSDL

开始在 Web 服务发布向导中执行此步骤之前,您需要在 BizTalk 中编译您的架构项目,以便您能够浏览到程序集。 实际上,正如您将在下面将看到的一样,在运行此向导之前继续部署您的解决方案是很有必要的。 此外,当您将业务流程发布为 Web 服务时,在构建和部署之前,您必须将业务流程端口类型上的“类型修饰符”属性从“内部”更改为“公用”。 如果您在最初创建此端口时没有更改此属性,那就要下相当大的工夫才能找到它(在“业务流程视图”中的“类型”窗格中突出显示此端口类型 — 而不是在显示不同属性集的“端口图面”上单击此端口)。 将架构发布为 Web 服务的另一个好处是您再也不用执行此操作了!

好了,让我们回到此向导,现在来看看图 5。 您需要右键单击“请求”和“响应”节点以选择 Web 消息所基于的架构。 您发布的业务流程通常会具有用于与 Web 服务的调用方通信的一个逻辑“接收”形状和一个“发送”形状。 这些形状将具有关联的消息,而这些消息将有一个架构类型(不需要多部分消息类型,只是建议采用)。 因此,您需要告知此向导使用您的面向外部的架构(记住技巧 3)。 然后,您将使用“接收”端口中的映射(入站和出站映射)将 Web 服务请求和响应架构转换为您的业务流程所期望的内部架构。 当客户端添加对您的 Web 服务的引用时,他将得到一个代理类,其中包括您的面向外部的架构的类型。 客户端将使用该类型创建并填充一个要发送到您的 Web 服务的请求消息。 此外,他将把来自您的 Web 服务的响应添加到一个对象中,此对象基于您在图 5 中指定的外部响应架构。

您还要记住在“接收”位置选择“XML 管道”,否则,BizTalk 将不会创建“BTS 消息上下文”属性 — 这些属性中的其中一个是传入 Web 服务消息的“消息类型”。 BizTalk 了解此“消息类型”(因为它曾通过管道中的“XML 反汇编程序”解析此消息类型并提升该属性),并使用该类型执行映射,以将请求转换为业务流程期望的类型。

真正重要的是,您在此向导中选择的操作名称必须与您在业务流程的逻辑端口中指定的操作的名称完全匹配。 完全接受每个名称的默认值是不可行的,因为业务流程设计器中默认的操作名称为“Operation_1”,而向导中默认的名称为“WebMethod1”(请参见图 5)。 用动词 — 名词格式(如 UpdateInventory)将其设置为具有特定意义的名称是一种不错的做法。

请记住,各种消息(甚至包括传入 Web 服务请求消息)是基于订阅发送到业务流程的。 当您将一个逻辑端口绑定到一个物理端口时(这是在将“请求响应”端口发布为 Web 服务时的一项要求(即使是通过架构发布),BizTalk 将为您创建一个自动订阅。 您可以从 BizTalk 管理控制台中通过以下方法查看订阅:在查询窗口中选择“Subscriptions”作为值。 订阅与通常为其他逻辑到物理端口创建的非“直接绑定”的 SOAP 略有不同。 BizTalk 为业务流程创建的常规订阅将如下所示:

http://schemas.microsoft.com/BizTalk/2003/
system-properties.ReceivePortID == {some Guid}
AND
http://schemas.microsoft.com/BizTalk/2003/system-properties.MessageType == 
http://schemas.someschema#Root
AND
http://schemas.microsoft.com/BizTalk/2003/
system-properties.InboundTransportType != SOAP
OR
http://schemas.microsoft.com/BizTalk/2003/
system-properties.ReceivePortID == {some Guid}
AND
http://schemas.microsoft.com/BizTalk/2003/soap-properties.MethodName ==
 {Inbound Operation Name on Logical Port}

您会发现,您键入到向导中用作 MethodName 的名称必须与业务流程中该逻辑端口的“操作”名称相匹配,否则,映射的 Web 请求消息将无法发送到业务流程。

当您进入向导中的“Web 服务项目”对话框时,请输入您的 Web 服务的 URL,例如: http://localhost/MyBizTalkWebService。 另外,您还可以让 BizTalk 在应用程序中为您创建接收位置。 因为您已部署了应用程序,所以在此您可以从下拉列表中选择它。 要了解有关以上所有内容的更多详细信息,请务必查看 BizTalk 帮助文件中名为“如何使用 BizTalk Web 服务发布向导将架构发布为 Web 服务”一文。

我记得我们说过 BizTalk 的核心就是订阅,但这并不是我们的最终答案。 通过后两个技巧提示,您会发现它的核心实际上是架构。 每个消息和每个映射都依赖于一个(或两个)架构,所以掌握架构对于熟练使用 BizTalk 至关重要。

Back to top

5. 始终为 Web 服务优化 BizTalk 注册表

如果您的 BizTalk 业务流程被发布为 Web 服务,那么您一定想要调整 BizTalk 使用的一些默认的 ASP.NET 参数。 实际上,这些参数会影响任何使用 CLR 的 BizTalk 项目,包括 XLANG。 BizTalk 会自动将这些建议的值乘以您拥有的 CPU 数。 具体步骤如下:

1. 在修改注册表前,首先对其进行备份。

2. 在记事本中创建一个包含以下代码的文件。

Windows Registry Editor version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\BTSSvc$BTSHOST\CLR Hosting]
“MaxIOThreads”=dword:00000064
“MaxWorkerThreads”=dword:00000064
“MinIOThreads”=dword:00000019
“MinWorkerThreads”=dword:00000019

在以此方式创建了文件之后,您可以在其他运行 BizTalk 的计算机上轻松地重复使用它。 DWORD 值是以十六进制形式表示的(注册表脚本不使用标准的 0x 前缀表示十六进制值)。

3. 用您的计算机的 BizTalk 主机名称替代字符串“BTSHOST”。 您可以首先运行 RegEdit 来检查主机上的实际密钥名称。

4. 如果您将文件保存为 .REG 格式,那么双击此文件就能使其在您的 BizTalk 安装上运行。

5. 运行 RegEdit 并导航至 CLR Hosting 项以验证是否已正确添加了这些值。

请参阅 msdn2.microsoft.com/en-us/library/aa561380.aspx,以了解有关此优化过程及其他优化过程的更多详细信息;并参阅 msdn2.microsoft.com/en-us/library/aa475435.aspx,以查看有关针对低延迟消息处理的性能优化方面的优秀文章。

Back to top

6. 始终使用相对路径设置程序集密钥文件

此技巧将帮助您从版本控制中找到解决方案并在另一个主机上构建它。 您的团队中的一些开发人员可能会用不同的驱动器号来设置其环境,将来您也可以更改您的驱动器号。 这确实非常简单,但是您可能还想了解这些点以后的情况。

建议让解决方案的第一个开发人员在 Visual Studio 解决方案文件 (.sln) 所在的文件夹中创建一个强名称密钥文件。 然后,创建该解决方案文件夹的子文件夹,用于按项目类型(如映射、管道、业务流程、内部架构、外部架构,以及您正在编译的任何 .NET 组件)存放每个 Visual Studio 项目。 顺便说一下,不管您曾经多少次听到此建议,都不必将其视为严格的规则。 如果您了解某些项目将来可能要一起修改(如映射及其架构),那么您就应将它们放在同一项目中,以减少必须重新部署的组件的数量。 在开发过程中,我们可以在解决方案中的各项目之间以及在各开发人员之间共享同一密钥文件。 (为运行环境创建安全的密钥文件是另一回事。) 只需将密钥文件与解决方案中的其他项目一起签入到版本控制中即可。 现在指明解决方案中每个程序集的密钥文件的路径。 对于 BizTalk 项目来说指明路径的方法是“..\..\..\Key.snk”,如图 6 中所示。 这一方法之所以可行,是因为 BizTalk 在名为类似 [YourSolution]\[YourProject]\bin\Development 的子文件夹中生成您的二进制文件。 因此,三个父目录跳转使 BizTalk 返回到您的包含密钥文件的解决方案文件夹。

图 6 为 BizTalk 项目配置程序集密钥文件
图 6 为 BizTalk 项目配置程序集密钥文件 (单击该图像获得较小视图)

图 6 为 BizTalk 项目配置程序集密钥文件
图 6 为 BizTalk 项目配置程序集密钥文件 (单击该图像获得较大视图)

注意,如果您要向您的解决方案中添加多个 .NET Framework 类库项目,而使用与 BizTalk 项目略有不同的目录结构对这些项目进行编译,那么用于设置密钥文件路径的 Visual Studio UI 一定不允许您输入相对路径。 一种解决方案是将 .csproj(针对 C#)或 .vbproj(针对 Visual Basic®)作为文本文件进行编辑。 搜索您在 Visual Studio 项目属性 UI 中浏览到的密钥文件的名称,然后用一个只包含一级父目录的相对路径(如,..\Key.snk)替换它。 在每次生成后,不要忘记将您的 .NET Framework 程序集添加到 GAC 中。 利用 Visual Studio 中的生成后步骤可以自动实现这一点。 它只需要完整路径和用引号引起来的字符串以及空格,格式如下所示(全部在同一行中):

“C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe” /i “$(TargetPath)” /F

Back to top

7. 永远不要忽视免费的示例代码

下面是一个非常有意义的小技巧。 BizTalk 帮助文件收录了 50 多个示例应用程序和脚本(安装在 SDK\Samples 文件夹中),您可以从中学习,对其进行修改和重新使用。 在以下页上您还可以找到 30 多个有用的 BizTalk 应用程序: msdn2.microsoft.com/en-us/biztalk/aa937647.aspx。 最后,BizTalk 博客作者指南(位于 GotDotNet.com)中涵盖了 400 多篇技术文章,其中包括许多有用的代码段和示例。 我将此博客作者指南的快捷方式保存到我的桌面上,这是在进入新的 BizTalk 领域时我首先会去看的地方之一。

我们承认,如果您已经知道了这一点,您可能会认为这个技巧提示没什么稀奇。 但考虑到除此之外很难找到专业水准的 BizTalk 示例代码,我们认为需要提醒您还是应该将它放在手边。 问题是,这些示例不光在您开始了解 BizTalk 时非常有用,它们还是可供反复查阅的宝贵参考资料。

顺便说一下,不要忘记经常回到 MSDN 查看 BizTalk 帮助文件,它是定期更新的。 您可以以 PDF 或压缩的 .chm 格式下载它,也可以在线浏览。

Back to top

8. 在 Visual Studio 中调试 XSLT

这可能是 Visual Studio 中最鲜为人知的功能了,它不需要安装 BizTalk。 XSLT 调试器使您能够在其运行时查看转换的结果、设置断点、检查局部变量和调用堆栈。 您甚至可以从 C# 或 Visual Basic 单步执行您的 XSLT 脚本。

图 7 显示了一个正在运行的转换示例。 我刚刚创建了一个空白的解决方案,并加载了以下这个将转换为 HTML 格式的很小的 XML 数据文件:

图 7 Visual Studio 中的 XSLT 调试器
图 7 Visual Studio 中的 XSLT 调试器 (单击该图像获得较小视图)

图 7 Visual Studio 中的 XSLT 调试器
图 7 Visual Studio 中的 XSLT 调试器 (单击该图像获得较大视图)

<?xml version=”1.0” encoding=”utf-8”?>
<node attr=”debug” />
然后,我进入 XSLT 文件(您可在图 7 的左窗格中看到)。它将创建一个 Hello World 文档,而如果节点元素有一个 attr 属性等于“debug”,它也会启动调试。

现在,将光标放在 XSLT 文件中(这一点很重要),将“输入”属性设置为您将要转换的 XML 数据文件的路径。 注意,在“解决方案资源管理器”窗口中右键单击 XSLT 文件名将显示一组不同的属性,这是“文件”属性,不是“文档”属性。 我希望上述过程更加直观,但是现在只要一学就能掌握。 如果说专业级人员与水平一般的人员有何区别的话,那就是探索精神。 您还可以设置“输出”属性,或体验中间窗格中所示的转换过程。

当 Visual Studio 中的活动文档为 XSLT 文件时,下拉 XML 菜单(位于主菜单栏上)包括 Debug XSLT 命令。 只需单击此命令即可查看转换代码,甚至深入查看子例程。 图 7 显示了程序执行完 xsl:if 测试后“监视”窗口中 @attr 的值。 正如您可从中间窗口的输出看到的那样,测试结果为 true,并且 <h1> 节点为输出。

Back to top

总结

考虑到当前公司正在开发的应用程序数量如此之大,使用较好的工具和技术尽快开发出可行的解决方案至关重要。 BizTalk 将帮助您高效地完成从概念到工作原型的过程,同时我们希望这些技巧提示将在您的工作中为您节省一些时间。

Back to top


Marty Wasznicky(MCSE、MCSD、MCDBA、MCTS)是在 Microsoft Connected System 部门中负责 BizTalk Server 的地区项目经理。 他帮助客户和 Microsoft 合作伙伴采用和实施这一领域的 Microsoft 解决方案。 目前,他致力于研究面向软件的体系结构和企业服务总线技术。 他和妻子一起生活在洛杉矶。
Scott Zimmerman(MCSD、MCTS)是 Microsoft 的一名资深应用程序开发顾问,专门提供 BizTalk 和 .NET 方面的咨询。 他曾两次荣获 Web 服务设计奖,八次在世界飞碟锦标赛中夺冠。 他和妻子 Vera 一起生活在华盛顿特区。

8月7日

biztalk中使用.net class类型的消息(一)

一. 概述... 1

二. 消息的结构... 1

三. 消息的类型... 2

1. 平面文件... 2

2. Xml文档... 2

3. .net class. 2

4. 二进制数据... 2

四. .net class类型的序列化... 2

五. .net class类型的消息... 3

1. 类要设置为可序列化... 3

2. 类中只包含公共属性和字段... 3

3. 设置名称空间... 3

4. 设置可分辨字段和升级属性... 3

4.1. 设置可分辨字段... 3

4.2. 设置升级属性... 3

5. 一个.net class消息类型的完整代码... 4

六. Biztalk如何处理.net class类型的消息... 5

七. Orchestration中从零开始新建一个xml类型的消息... 5

1. 在消息构造形状中构造新xml消息... 5

2. 在消息构造形状中构造新.net class类型消息... 5

3. 从零开始构造新xml类型消息... 6

3.1. 设置xml文件属性... 6

3.2. 写一个从嵌入资源中读取xml文件内容的方法... 6

3.3. 在消息赋值形状中调用读取xml文件的方法... 7

一. 概述

一般来讲,在biztalk项目中使用的消息都是基于xsd的xml消息,因为这是biztalk最擅长处理的消息。Biztalk整个体系架构都是建构在xml基础之上,在biztalk内部用xml来处理消息,在message box的存储形式也是xml。

但是这并不表示biztalk只能处理xml消息,事实上,biztalk可以处理消息类型可以是平面文件、xml类型、可序列化的.net class或者其他任意的二进制数据流。

这里要讨论的是在biztalk中,尤其是在orchestration中如何使用.net class类型消息。

二. 消息的结构

BizTalk中每条消息都可视为多部分消息,此消息可以由零个或多个部分组成。具有一个或多个部分的消息都具有一个标识为正文部分的部分(但是只能有个一部分标识为正文)。每个部分均由可表示 XML 文档、平面文件、序列化的 .NET 类或其他二进制数据流的二进制数据块组成。

一般的开发biztalk项目体验中,大多是新建一个消息,然后给这个消息指定一个xsd作为这个消息的类型。感觉上一个消息就只能指定一个xsd类型(也可以指定为一个序列化的.net class类型),跟一个xsd相关。其实这样通过这样方式建立的消息也是多部分消息,只是这个消息只有一个正文部分而已。

如果要建立真正有多个部分的消息类型,可以在orchestration view面板中的Types窗格中,展开Types,在Multi-part Message Types下新建一个多部分消息类型,在新建的多部分消息类型中可以建立多个部分,每个部分分别指定类型,还要指定其中的一个部分为正文部分。以后新建消息把消息类型指定为这个多部分消息类型,这个新建消息就是一个具有多个部分的消息了。

注意

任何消息在biztalk的表现都是一个多部分的消息,不过一般的消息只有一个正文部分。

三. 消息的类型

从上面的叙述可以得知,每个消息其实都是多部分消息,一般来讲每个消息至少有一个部分,也可能有多个部分,不管是只有一个部分还是有多个部分,其中必须有一个部分被指定为正文部分,整个消息的类型就由正文部分的消息类型决定。如果正文部分分别为以下情况时,如何确定消息的类型:

1. 平面文件

平面文件进入到biztalk都会在接收管道中都会被平面文件拆装器拆解为xml的格式,有一个经过平面文件扩展后的xsd架构跟其对应,所以平面文件可以被认为也是xml的消息类型。参看下面的xml文档部分。

2. Xml文档

Xml消息的消息类型由xml消息的命名空间(后接 # 符号)和xml消息根节点的名称组成。比如,一个xml消息的根结点的名称空间是“http://tempuri.org/samples/MessageType”,xml消息的根结点的名称是Message,那么这个消息的类型就是:http://tempuri.org/samples/MessageType#Message

3. .net class

.net class类型的消息同样是由名称空间加上根元素名称组成。

.net class类型的消息的名称空间由class类的System.Xml.Serialization.XmlRootAttribute属性指定,在定义.net class类时可以在类名前加上相关attribute来指定这个类的一些类级别的attribute(请参考第五部分“.net class类型消息”中的详细定义一个.net class的完整代码),System.Xml.Serialization.XmlRootAttribute中用Namespace参数指定此类的名称空间。

定义.net class的类名就相当于xml类型的根元素。

“名称空间#类名”就是.net class消息的消息类型,跟一般xml消息一样,.net class消息类型也可以作为订阅消息的条件。

4. 二进制数据

二进制数据流的消息类型需要由定制的接收管道把消息类型升级到消息的上下文,一般的二进制数据流的消息类型使用全限定的类型名。

这种情况这里不加讨论,如有必要,以后用专文来探讨二进制数据流消息的情况。

四. .net class类型的序列化

Biztalk中的消息必须是可以序列化的,原因很简单,biztalk中消息不管是从外部进来,还是由orchestration产生的消息,统统都要被保存在Message Box数据库中,要能被保存在数据库中持久保存,消息就必须能被序列化。

还有一点,在长时间运行的orchestration中,运行的周期可能是一天、几天、十几天,甚至可能几个月。在这么长的时间中,一个orchestration实例由始至终都运行在内存中是及其不明智的做法,将耗费大量的资源。所以biztalk设计了一个dehydrat(分解)和rehydrat(重组)的机制,在orchestration实例空闲的时候,把orchestration实例和相关的状态和消息序列化保存到数据库,一旦有消息激活这个orchestration实例时,再把这个orchestration实例和相关状态消息反序列化到内存继续运行。

对.net class序列化Dotnet框架支持两种序列化方式,可以把类序列化为xml格式,也可以把类序列化为二进制格式。

二进制序列化是把对象转换成一串二进制的数据流,二进制序列化保持类型保真度,可以最真实的保存一个对象的全部状态。

Xml序列化是把对象转换成一个xml文档,XML 序列化仅序列化公共属性和字段,且不保持类型保真度。但对于biztalk应用中使用的.net class一般就是使用了类的公共属性和字段。

Biztalk内建直接支持.net class序列化,就是说在biztalk中使用.net class的消息不用开发者自己考虑.net class消息如何如何序列化和反序列化。Biztalk内建.net class消息序列化使用的是xml序列化方式。

二进制序列化方式不是biztalk内建支持的序列化方式,可以选择对.net class消息进行二进制的序列化,但是这需要开发者做更多的工作,需要写代码对消息进行二进制的序列化和反序列化,并定制自己的拆装器和组装器对消息进行序列化和反序列化的工作。

本文只讨论xml序列化的方式,对于二进制序列化的方式,如有必要以后再用专文讨论。

五. .net class类型的消息

Biztalk中使用.net class需要考虑作为消息的特点,跟一般的类有些不同,一般要注意以下几点:

1. 类要设置为可序列化

在定义类前加上表示可以序列化的attribute:[Serializable()]

2. 类中只包含公共属性和字段

一般类中的公共属性和字段会被转换为xml文档中的元素,私有属性没什么意义,方法对转成xml也没什么意义。

3. 设置名称空间

名称空间是构成消息类型的一部分,.net class转成xml也可以带有名称空间,看下面的代码,名称空间由定义类前的“[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://myURL")]”设置,表示这个类转成xml的Target Namespace是"http://myURL"。

另外类的名称就相当于xml的根元素名称,跟上面的名称空间一起组合成消息的类型。

如果没有设置名称空间,则此.net class消息的类型就是类名称。

4. 设置可分辨字段和升级属性

在.net class类型中同样可以设置可分辨字段和升级字段。

4.1. 设置可分辨字段

设置可分辨字段很简单,在需要设置的公共属性或字段前加上“[DistinguishedField]” attribute即可,表示这个公共属性或字段已被设置为可分辨字段。

4.2. 设置升级属性

设置升级属性同xsd中设置升级属性类似,首先也先有一个属性架构,然后在需要升级为属性的公共属性或字段前用“Property”attribute指定关联到哪个属性。如下面的代码中的:

[Property(typeof(netClassMessage_PromoteXSD.PropertyName))]

public string PropertyName

这表示要把公共属性PropertyName升级,并关联到属性架构netClassMessage_PromoteXSD中的PropertyName属性。

5. 一个.net class消息类型的完整代码

 1using System;
 2using Microsoft.XLANGs.BaseTypes;
 3
 4namespace netClassMessage_netClass
 5{
 6 //表示此类是可以序列化的
 7    [Serializable()]
 8 //此属性设置目标名称空间Target Namespace
 9    [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://myURL")]
10 //类名称相当于XSD中的根元素,上面设定的名称空间加上类名称就组成了消息类型
11 public class netClass
12 {
13 private string _ID;
14 private string _name;
15 private string _address;
16 private string _propertyName;
17 public netClass()
18 {
19            _ID = System.Guid.NewGuid().ToString();
20        }
21 //DistinguishedField属性表示这个字段是可分辨字段
22        [DistinguishedField]
23 public string ID
24 {
25 get
26 {
27 return _ID;
28            }
29 set
30 {
31                _ID = value;
32            }
33        }
34        [DistinguishedField]
35 public string Name
36 {
37 get
38 {
39 return _name;
40            }
41 set
42 {
43                _name = value;
44            }
45        }
46        [DistinguishedField]
47 public string Address
48 {
49 get
50 {
51 return _address;
52            }
53 set
54 {
55                _address = value;
56            }
57        }
58        [DistinguishedField]
59 //netClassMessage_PromoteXSD.PropertyName是在属性schema中定义的属性,此Property属性
60 //指示这个字段为promote字段,对应netClassMessage_PromoteXSD.PropertyName
61        [Property(typeof(netClassMessage_PromoteXSD.PropertyName))]
62 public string PropertyName
63 {
64 get
65 {
66 return _propertyName;
67            }
68 set
69 {
70                _propertyName = value;
71            }
72        }
73    }
74}

六. Biztalk如何处理.net class类型的消息

.net class类型的消息从外部进入biztalk,.net class类型消息在进入到biztalk时的存在形式必定是被序列化后的xml形式(这里只讨论xml序列化的情况)。

所以,对入站的.net class类型消息需要使用xml拆装器,.net class类型消息序列化后同样有名称空间和根元素,并且也跟一般的xml消息类型一样,名称空间和根元素也决定了这个.net class消息的消息类型。

同样,.net class消息根一般xml消息一样根据消息类型被路由到订阅此消息的orchestration。

.net class消息在orchestration中的存在形式还是xml形式,只有在消息构造形状中对.net class消息进行构造或赋值的时候,biztalk引擎会对消息做相关的序列化和反序列化的工作,以便对.net class消息进行相关的处理。在消息构造形状以外,.net class消息的存在形式就是xml。

七. Orchestration中从零开始新建一个xml类型的消息

在orchestration,一个消息一旦被构造完成,这个消息就不再被允许有任何改动,就是不能被修改了。如果要修改这个消息,必须重新在消息构造形状中构造一个新消息,然后修改新的消息,新消息一旦出了构造形状就算构造完成。

构造xml消息和构造.net class类型消息有所不同,下面分别列出

1. 在消息构造形状中构造新xml消息

l 通过map映射,map映射把源架构的消息映射到目标架构的消息,在转换的同时把消息值赋给了新的目标架构的消息,构造了一个新的消息。

l 通过消息赋值语句,赋值语句把同类型的消息赋给一个新的消息,构造了新消息。

l 通过把XmlDocument赋值给消息,xml消息的本质就是一个xml,所以可以把一个XmlDocument类型的变量直接赋给xml消息,当然,这个XmlDocument要是跟这个消息兼容的,XmlDocument要符合这个xml消息的架构。

2. 在消息构造形状中构造新.net class类型消息

只能通过new操作符构造新.net class类型消息,.net class类型消息首先它是一个.net的类,可以通过new操作符来新建一个此类的对象。

3. 从零开始构造新xml类型消息

如果一个.net class类型消息从外部进入到orchestration,在orchestration中对这个.net class类型消息进行处理转换,无外乎会有两种情况:一种是把这个消息赋值给同类的或者另一个.net class的消息,然后对新消息进行处理。更多的情况是,要把这个.net class消息转换成一个xml的消息,然后处理这个xml消息。

对于第一种情况简单,只要用new操作符新建一个.net class类型的消息,然后进行赋值或者修改新消息的属性等等即可进行相关处理。

但是对于第二种情况就比较麻烦,用map吧,map只支持从xml消息到xml消息的转换,不支持.net class消息的转换。用赋值语句吧,xml类型和.net class类型不是同类型,不能直接赋值。如果xml消息类型可以用new操作符新建一个新消息多好,可是,目前还不行,不能跟一般的.net类一样通过new操作符来新建一个对象。

但是这里又必须要先新建一个xml类型的消息,有了这个xml消息,才能把.net class类型的消息的相关属性转移到这个xml类型的消息。

从零新建一个xml类型消息(from scratch)在biztalk中是个比较麻烦的事,上面提到的通过把XmlDocument赋值给xml消息好像是目前的唯一方法。

这种情况大致需要做以下的事情:

在orchestration中新建一个XmlDocument类型的变量,比如变量名为xmlDocVar,要构造的xml消息为xmlMsg,然后在消息赋值形状表达式中

xmlDocVar.LoadXml(“xml字符串”);

xmlMsg = xmlDocVar;

其中的“xml字符串”是一个跟xmlMsg消息的架构相容的一个xml样例的文本。赋值以后,就可以对xmlMsg进行各种操作了,把可分辨字段或升级属性修改为需要的值。

把xml样例文本直接放在硬代码里面不太方便,也不灵活,比较好的做法是把xml文本放在一个xml文件中,从文件中读取xml文本。

上面的例子可以变成这样:

xmlDocVar.LoadXml(“xml文件名”);

xmlMsg = xmlDocVar;

其中的“xml文件名”是xml样例文件的完整路径,这样如果要修改xml样例文本直接修改这个文件就行了,不用去改硬代码。

但是这样会给部署带来问题,项目部署以后这个xml文件往哪放,biztalk项目其他的资源都是dll,被部署到GAC中,就这么个xml文件孤零零的要找个地方放,虽说也没什么问题,但总觉得不很舒服,至少这个办法不够优雅。

其实还有一个更舒服的办法,就是把这个xml文件作为一个嵌入资源,把它嵌入到项目生成的dll中,一起被部署到GAC中,读取的时候也到这个dll中读取。这需要做些额外的编码工作,步骤如下:

3.1. 设置xml文件属性

在解决方案资源管理其中,选中这个xml文件,查看它的属性,把“生成操作”这一项,改成“嵌入的资源”,这样这个xml文件就会被嵌入到编后的dll文件中。

3.2. 写一个从嵌入资源中读取xml文件内容的方法

在跟这个xml文件同一个项目中,新建一个类,这个类只有一个静态方法,这个方法根据提供的xml文件名,在assembly中找到嵌入的xml文件,返回这个xml文件对应的XmlDocument的对象。

这个方法的完整代码如下:

 1using System;
 2using System.Collections;
 3using System.IO;
 4using System.Reflection;
 5using System.Xml;
 6
 7namespace netClassMessage_netClass
 8{
 9 public class EmbeddedResourseProcessor
10 {
11 public static XmlDocument GetXmlDocResource(string fileName)
12 {
13            XmlDocument doc = null;
14            Type type = MethodBase.GetCurrentMethod().DeclaringType;
15            Assembly _assembly = Assembly.GetExecutingAssembly();
16 string _namespace = type.Namespace;
17
18 string resourceName =  _namespace + "." + fileName;
19
20            Stream stream = _assembly.GetManifestResourceStream(resourceName);
21            doc = new XmlDocument();
22            doc.Load(stream);
23 return doc;
24        }
25    }
26}

3.3. 在消息赋值形状中调用读取xml文件的方法

在orchestration的消息赋值形状中,用类似下面的语句给新xml消息赋值:

XmlMsg = netClassMessage_netClass.EmbeddedResourseProcessor.GetXmlDocResource("xml文件名");

参考资料:

l Using .NET Classes for Orchestration Message Types

l BizTalk messages based on .NET types instead of Xsd schemas

l Constructing BizTalk 2004 XML Messages (In an Orchestration)

l Messages from Scratch with Embedded Resources

l Using a Custom .NET Type for a Message in Orchestrations

biztalk中使用信封(Envelope)消息

一. 信封(envelope)的作用

一般的传送消息就传送一个单个的消息,这个消息架构由一个消息schema定义。但是有时候可能需要同时传送多个消息到同一个接收点,如果一个个的传送就显得效率比较低,biztalk支持信封消息功能。所谓信封消息是将多个消息打包成一个交换(interchange),就是用信封(envelope)将多个消息封装起来,形成一个貌似单个xml消息的功能。

Biztalk收到信封消息,将信封拆开,分别取出其中的消息正文,然后单独的处理每个消息正文。

信封消息还可以在信封部分定义升级属性,信封部分的升级属性可以被带入到所有的消息正文的上下文中。

二. 信封(envelope)的设置

要使用信封,首先要定义信封的架构schema,并指示哪些部分是属于信封部分,哪些部分是要被装入的消息正文的位置。

定义信封架构形式上跟定义一般的schema没什么区别,只是需要设定一些特殊的属性。

要把一个schema设置为信封架构,可以在这个schema的<schema>标签的“Envelope”属性设为“Yes”,表示这个架构是个信封架构。

<schema>标签下可以包含多个根元素,表示可以有多个信封的架构,但是使用时只能使用其中一种信封架构。一般就定义一个根元素。

根元素有个“Boby Xpath”属性,用来指示这个信封架构中的哪个位置是消息正文的位置。

看下面这个信封架构,根元素Envelope的“Boby Xpath”属性设置为“Envelope/Boby”,表示消息正文在Boby元素里面的位置,除此之外,别的部分都属于信封部分。


  “Boby Xpath”属性指定的那个元素里面就是消息正文所在的位置,这个元素其下只能包含一个<any>元素(不能包含其他任何元素),这是个特殊的元素,含义就是消息正文的占位符,可以被任意的或者<any>元素的属性所限定的消息正文所替换。

<any>元素相关属性说明:

1. Process Contents

此属性指示消息正文验证的级别,有四个可能的的值:

Lax ―― 宽松的验证级别,这种设置,只有在收到的消息正文对应的schema在信封schema中已经被import的情况下才会验证此消息正文,否则不验证。

Skip ―― 不验证收到的任何消息正文。

Strict ―― 严格的验证级别,这种设置,所有可能收到的消息正文对应的schema都必须在信封schema中预先导入,对每个消息都要进行验证。

Default ―― 缺省设置等同于“Strict”

如果<any>元素的Process Contents设置为“Strict”或者“Default”(缺省设置为Strict),按文档的描述,在消息实例进入管道后,XML拆装器会寻找每个跟对应<any>位置的元素的名称空间相对应的schema验证这个元素,如果在信封schema中不到相应的schema被import,则这个元素会验证失败。但是实际测试中发现<any>元素Process Contents的“Strict”设置并没有起作用,在信封schema中没有import输入的消息实例名称空间对应的schema,消息依然传送成功。原因不明。

2. Namespace

表示可以接受的消息正文的名称空间,如果指定了名称空间,则只有属于这个名称空间的消息正文可以被接受,其他的不被接受。如果不设置名称空间,表示接受任何名称空间的消息正文。

3. Max Occurs、Min Occurs

表示消息正文可以出现的最小次数和最大次数,正文可以再次重复出现。但是这两个属性好像对<any>元素不起作用。

在实际测试中,设置<any>元素的这两个属性都为1,这个信封消息应该只能包含一条正文消息,但是测试信封消息中包含两条消息正文,依然被接受,被正常拆封,正常的把两条消息正文路由到订阅的服务。

三. 信封(envelope)的拆装

信封的拆装是在pipeline的第二阶段拆装阶段(Disassemble)。

接收位置的接收pipeline要选XmlReceive(其中包含XML Disassembler),或者自建pipeline并使用XML Disassembler,因为只有XML Disassembler 才具有拆装信封消息的功能。

看一下XML Disassembler是如何处理信封消息的,消息进入到消息管道,经过第一阶段的解码后到达拆装阶段,XML Disassembler对消息的处理步骤如下:

1. 删除信封

首先根据消息类型在已部署的schema查找,看这个消息是否能跟哪个信封架构匹配,如果匹配不到信封架构,则继续匹配一般架构。如果匹配到信封架构,则根据信封架构的定义把所有的消息正文提取出来,去掉信封。

2. 拆装消息正文和升级属性

分别处理每一个消息正文,根据每个消息正文的消息类型(名称空间#根元素名)在已部署的schema中匹配,匹配到后,给这个消息建立上下文,先把信封部分升级的属性加入到消息上下文,然后根据消息架构升级这个消息本身的属性。

每条消息都如此处理,直到都处理完毕。如果一切正常,每个消息都匹配到合适的schema,则biztalk把所有的消息正文作为独立的消息发送到MessageBox等待路由。

如果匹配消息中出现问题,比如某一条消息没有匹配到schema,那么会有两种情况出现,跟接XML收管道的设置有关。

XmlReceive管道有个RecoverableInterchangeProcessing属性(在biztalk管理器中,查看相应的接收位置的属性,在接收管道的右边有个省略号按钮,点击可以配置一些接收管道的属性,RecoverableInterchangeProcessing属性就在这里),这个属性缺省为false,这时如果消息匹配中有某一条消息出现没有匹配到schema的情况,则所有的消息正文都不被处理,整个消息被挂起。如果这个属性设置为true,则只有没有匹配到schema的那个消息正文被挂起,其他的消息正文被发送到MessageBox等到路由。

biztalk的例子

一. 随机文档的tutorial

在文档中的位置:Help > Getting Started > BizTalk Server 2006 Tutorials

这套教程一共分五个教程,从简单到复杂,循序渐进,逐步的把biztalk涉及的技术呈现出来。步骤十分详细,很容易上手,十分适合刚开始学习biztalk的人跟着做,作完后就会对biztalk有个比较完整的认识。建议初学者从这套教程开始。
• Tutorial 1: Enterprise Application Integration.
• Tutorial 2: Purchase Order Process.
• Tutorial 3: Invoice and Payment Process.
• Tutorial 4: Trading Partner Management.
• Tutorial 5: Business Activity Monitoring.

二. 随机文档的sample in the SDK

在文档中的位置:Help > Development > Samples in the SDK

SDK中提供了超过30个例子,包含在下面这些类别中,每个类别中可能有几个例子。

Adapter Samples - Development

Adapter Samples - Usage

Admin (BizTalk Server Samples Folder)

Application Deployment (BizTalk Server Samples Folder)

Business Activity Monitoring (BizTalk Server Samples Folder)

Business Rules (BizTalk Server Samples Folder)

HWS (BizTalk Server Samples Folder)

Messaging (BizTalk Server Samples Folder)

Orchestrations (BizTalk Server Samples Folder)

Pipelines (BizTalk Server Samples Folder)

SSO (BizTalk Server Samples Folder)

XML Tools (BizTalk Server Samples Folder)

三. 随机文档的walkthrough

文章中就某些特殊的特性给出的一些step by step的例子,叫做演练。

在文档中的位置:分散在文档的各处,可以用搜索功能搜索“Walkthrough”来找出这些例子。这些例子包括:

Adapters

Walkthrough: Creating a BizTalk Application That Uses the MQSeries Adapter

Walkthrough: Creating a BizTalk Application That Uses the POP3 Adapter

Walkthrough: Module 1 - Sending and Receiving Messages with the Windows SharePoint Services Adapter

Walkthrough: Module 2 - Integrating Office with the Windows SharePoint Services Adapter

Walkthrough: Module 3 - Accessing SharePoint Properties from an Orchestration

Walkthrough: Using Multiple SQL Adapters in an Orchestration

Walkthrough: Disassembling Result Sets Using the SQL Adapter

Walkthrough: Using the SQL Adapter with a Stored Procedure in an Orchestration

Walkthrough: Using the SQL Adapter with an Updategram in an Orchestration

Application Deployment

Walkthrough: Deploying a Basic BizTalk Application

Business Activity Monitoring (BAM)

Walkthrough: Consuming BAM Data

Walkthrough: Creating a BAM Activity with ODBA

Walkthrough: Deploying the BAM Observation Model

Walkthrough: Creating a BAM Observation Model with the BAM Add-In for Excel

Walkthrough: Creating a Tracking Profile

Pipelines

Walkthrough: Using XML Envelopes (Basic)

Walkthrough: Flat File Disassembly Using a Header and Trailer

Schemas

Walkthrough: Creating a Flat File Schema From a Document Instance

四. 随机文档中的Scenarios for Business Solutions

Biztalk SDK中除了提供上面那些简单例子外,还提供了三个重量级的例子,应该说是三个完整的商业应用解决方案。

在文档中的位置:Help > Planning and Architecture > Scenarios for Business Solutions

这三个方案分别是:

1. Service Oriented Solution面向服务的解决方案

2. Business Process Management Solution业务流程管理 (BPM)解决方案

3. Business-to-Business Solution企业对企业(B2B)解决方案

五. MSDN提供的例子

微软biztalk产品开发团队应biztalk开发者的要求开发了一些例子,并根据开发者的反馈和要求不断推出新的例子,你也可以写信给他们告诉他们你想要看到什么例子。

BizTalk Server Code Samples

网址:(http://msdn2.microsoft.com/en-us/biztalk/aa937647.aspx

目前已有的例子如下(不断更新中):

Publishing and Consuming Web Services with SOAP Headers

BAM and HAT Correlation

Consuming Web Services with Array Parameters

Extending the BizTalk Server Administration Console

Viewing Failed Tracking Data

Inserting XML Nodes from Business Rules

Using the Mass Copy Functoid

Using Role Links

Split File Pipeline

Using Enterprise Library 2.0 with BizTalk Server

Consuming Web Services

Console Adapter

Delivery Notification

Using Long-Running Transactions in Orchestrations

Using the Looping Functoid

Mapping to a Repeating Structure

Parallel Convoy

Policy Chaining

Recoverable Interchange Processing Using Pipelines

Using the Table Looping Functoid

Using the Value Mapping and Value Mapping (Flattening) Functoids

Direct Binding to an Orchestration

Direct Binding to the MessageBox Database in Orchestrations

Using a Custom .NET Type for a Message in Orchestrations

Writing Orchestration Information as XML Using the ExplorerOM API

Correlating Messages with Orchestration Instances

SSO as Configuration Store

Atomic Transactions with COM+ Serviced Components in Orchestrations

Exception Handling in Orchestrations

Implementing Scatter and Gather Pattern

Using the SQL Adapter with Atomic Transactions in Orchestrations

六. CodeProject网站

CodeProject是一个Visual Studio .NET开发者社区,在这里你可以得到别人优秀的源代码,也可以把你自己的得意文章或源代码跟大家分享。

在CodeProject有biztalk的目录,在里面有很多优秀的文章和例子。

网址:http://www.codeproject.com/biztalk/

目前有的文章和例子有:

Using Dynamic Maps in BizTalk

How to invoke a BizTalk Orchestration using Office InfoPath 2003

How to create a self-routing message using Dynamic Ports in BizTalk Server 2004

Asynchronous call to web services in Biztalk orchestrations

Using a SQL Adapter in BizTalk Server 2004

UnzipDisassembler - A custom pipeline component for BizTalk Server 2004

How to split an XML message in BizTalk 2004 using Document and Envelope Schemas

Debatching large messages and extending Flatfile pipeline disassembler component in Biztalk 2006

A Quick guide to handling exceptions in BizTalk Orchestrations

HOW TO Auto-generate schemas using Well-formed XML documents and DTD documents in Biztalk

BizUnit Context Tutorial

How To Troubleshoot Schemas in BizTalk Server 2006

Building BizTalk Server 2006 Currency Converter Custom Functoid

HOW To Compensate a Transaction in a BizTalk Orchestration

Creating an Automated Purchase Order Workflow using BizTalk Server 2004

Using the MSI installer wizard for deploying applications created in BizTalk Server 2006

Keep The Orchestration Simple (KTOS) - A BizTalk Pattern

Creating Flat File schemas using the BizTalk Server 2006 Flat File Schema Wizard

Calling a Web Service with Custom Parameters from an Orchestration in BizTalk Server 2004

Using the %SourceFileName% macro to create a custom send file name in BizTalk 2004

biztalk项目设置注意事项

适用版本:biztalk 2006

适用环境:开发测试环境

在开发过程中,在开发环境中,一定会是一个对项目不断的修改、编译、部署、测试,查看测试结果,发现有问题,然后回到开发环境再修改、编译、部署、测试的反复过程。所以开发环境的项目设置一定要适合这种反复的重新部署的特点,可以方便的修改,编译,重新部署,修改的内容立即能够生效,然后马上测试。

下面的一些设置注意事项可以帮助biztalk项目在开发环境中达到以上目的

一、 设置强名称程序集的密钥文件

在项目属性页中,在左边的树行结构中选择 “通用属性”-“Assembly”,然后在右面的内容面板中找到“Strong name”-“Assembly Key File”项,在这里点击后面的省略号按钮找到密钥文件(密钥文件怎么生成见biztalk文档)。因为biztalk项目生成的程序集必须安装到GAC中,所以程序集必须是强名称的。

二、 设置项目的部署属性

在项目属性页中,在左边的树行结构中选择 “配置属性”-“Deployment”,然后看右面的内容面板中,各个属性的含义:

Server ―― 把项目部署到哪个服务器,开发环境中,选择本地机器(local)

Configuration database ―― biztalk配置数据库,选BizTalkMgmtDb

Application name ―― 这个项目部署到biztalk中所属的应用程序名,所有属于同一应用的项目(一般就是一个解决方案中的项目)都应该部署到一个应用程序中。

Redeploy ―― 本项目是否可以重新部署。指示如果在GAC中已经有了本项目的程序时是否先删掉已经存在的程序集,然后部署新的程序集。开发环境中选“true”,以便可以反复的重新部署程序集。

Install to Global Assembly Cache ―― 是否将本项目生成的程序集安装到GAC中,当然要,选true

Restart Host Instance ―― 是否重启主机实例。这一个选项特别重要,下面详细解释这个选项。

在dotnet托管环境中,应用程序域是代码隔离的边界,就是说每个应用程序域之间是相互隔离,不能直接通讯,也不会相互干扰的。一般一个应用程序域需要由一个运行库宿主创建,通常是一个exe的文件,这个运行库宿主创建应用程序域,在运行中使用到其他程序集会被加载到这个应用程序域内,用到多少程序集就会加载多少,但是这些被加载的程序集在使用完后不会马上从内存中卸载,必须等整个应用程序域运行完毕后,一起从内存中卸载。

通常Biztalk有两个主机实例:进程内主机实例BizTalkServerApplication,独立进程主机实例BizTalkServerIsolatedHost。BizTalkServerIsolatedHost主机实例是给web services等进程外的服务使用的。一般biztalk项目生成的程序集都会被部署到BizTalkServerApplication实例中。

BizTalkServerApplication主机实例就是一个BTSNTSvc.exe运行库宿主,所以一旦有biztalk项目部署运行后,相关的程序集都会运行在BTSNTSvc.exe创建的一个应用程序域内,并且如果BTSNTSvc.exe服务不停这些程序集就不会被卸载。

Restart Host Instance属性设置为true后,每次重新部署biztalk项目后,biztalk会重新启动BizTalkServerApplication主机实例,原先已经运行的那些程序集也会被卸载。

刚开始学习biztalk的朋友,大多都会碰到一些问题,就是第一次部署biztalk项目后比较顺利,测试运行,发现问题修改后重新部署就会出现几种问题:

1、 部署不成功。因为上面提到的那个“Redeploy”属性没有设置为true。

2、 重新部署后结果还是老样子,好像修改没有起作用。一般都是因为修改前的程序集已经在内存中运行,没被卸载,实际运行的还是老版本的程序集,当然结果也就是老样子。这时重启一下BizTalkServerApplication主机实例即可。或者把上面谈到的Restart Host Instance属性设置为true,让每次重新部署都自动重启一下BizTalkServerApplication主机实例,把原来的程序集从内存中卸载。

三、 配置管理

一般biztalk的解决方案都是有几个项目组成,比如biztalk文档中tutoial 1“Enterprise Application Integration”这个例子,就是由两个项目组成:EAIOrchestrations和EAISchemas。大些的项目还可能包括更多的项目,这时可能需要配置一下,哪些项目是需要生成的,哪些是不用生成的;哪些项目是要部署的,哪些项目是不用部署的。

在解决方案上点击右键,选“配置管理器”,弹出如下窗口:


   在需要生成的项目在“生成”那一列打上勾,在需要部署的项目在“部署”那一列打上勾。这样在点击解决方案右键选“生成解决方案”时,就只有设为生成的项目被编译,在点击解决方案右键选“部署解决方案”时,就只有设置了部署的项目被部署。

对于非biztalk的项目,上图中的“ClassLibray1”是一般的类库项目,可以被biztalk项目引用。非biztalk的项目不能被设置为可部署。

四、 biztalk项目的部署

biztalk项目有的地方可以写代码,代码中可以引用一般托管类的代码,所以biztalk解决方案中一般会包含非biztalk项目的类库项目,这些类库项目中的类被biztalk项目中的代码调用。因为biztalk项目的程序集是强名称的,被部署在GAC中,所以被引用的类库的程序集也必须是强名称的并部署到GAC中。

非biztalk项目不能跟biztalk项目一样通过简单设置就能被部署到GAC中,但是可以通过一定的方法做到。以下是Snega (水滴石穿)提供的方案

1、 在类库项目中->属性->生成事件->预生成命令

PATH = C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin

GacUtil /u $(TargetName)

这一设置在生成类库项目前先把原来已经在GAC的本项目的程序集卸载,以便新的程序集装载进GAC。

2、 在类库项目中->属性->生成事件->生成后命令

PATH = C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin

GacUtil /i $(TargetPath)

这一个设置把生成好的类库程序集装载到GAC中。

或者:
在类库项目中->属性->生成事件->生成后命令
REM 先设置适当的环境变量以启用各种命令,其中就有GACUTIL命令所在的“C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin”路径。
CALL "%VS80COMNTOOLS%\vsvars32.bat" > NUL
REM “if”参数表示将程序集安装到全局程序集缓存中。如果全局程序集缓存中已经存在同名的程序集,全局程序集缓存工具将改写该程序集。
GACUTIL /if "$(TargetPath)" 

五、 总结

安装以上所述设置biztalk的项目,修改biztalk项目,重新部署会很方便。

但是有一点要注意,再重新部署一个解决方案时,这个解决方案在biztalk所在的那个应用不要有挂起的实例,有的话把它终止掉然后再进行重新部署。

一般在开发环境中进行修改,然后编译解决方案,重新部署解决方案,就可以测试新的修改的效果了。不用手工去做停止应用、删除应用,重启服务等等工作,这些上面的设置都已经给解决了。

biztalk消息引用和引用计数

一. 消息引用概述

Biztalk完全基于消息和消息订阅的机制,由发布服务器发布消息,消息代理根据订阅服务器的订阅条件判断发布的消息是否跟某个或某几个订阅匹配到,如果有匹配到消息代理机会把消息路由到所有的订阅了此消息的服务实例。如果没有匹配到就会抛出一个异常,告知这个消息没有订阅者订阅,无法路由。

可以看出消息跟订阅消息的服务实例是成对出现的,姑且称之为服务实例消息对。一条消息如果只有一个订阅者,那这个消息跟这个订阅的服务实例就是一对。如果一条消息有多个订阅者订阅,那么这个消息要同时发送给所有的订阅者,就会出现多对服务实例消息对。

同一个消息在biztalk的messagebox数据库中只保存一个副本,多个服务实例订阅同一消息时,各个实例跟这个消息之间是引用关系,不会各自创立一个消息副本。

消息保存在messagebox数据库的spool表和parts表中。SPOOL 表的内容是消息的总体性描述和消息的上下文属性,一条消息在此表中为一条记录。PARTS 表存放多部分消息的各个部分,一个部分在此表中占一条记录。哪一个是正文部分由spool表中的uidBodyPartID标识。

消息进入到biztalk系统后,路由到某个服务实例,就跟这个服务实例有了引用关系,在这个服务实例运行期间消息不能从数据库中删除。一旦这个服务实例运行完毕,这个消息也就不再使用,可以删除。如果有多个实例同时引用这个消息,则必须引用此消息的实例都完成后才能删除消息。

为了反应消息跟实例之间的引用关系,判断一个消息是否还在被某个实例引用,biztalk有着一套消息引用计数功能。

Biztalk采用消息引用计数表来记录消息的引用数,分为两种情况:如果消息只被一个订阅服务订阅,采用本地引用计数机制,如果消息被多个订阅服务订阅,采用全局引用计数机制,下面分别分析这两种情况下的消息引用计数功能的实现。

二. 本地引用计数

如果消息只有一个服务订阅,消息代理调用bts_FindSubscriptions存储过程时发现一个订阅,这个存储过程返回一条记录。

这种情况下,消息代理会在本地引用计数表BizTalkServerApplication _MessageRefCountLog中记入实例对消息的引用计数。

BizTalkServerApplication_MessageRefCountLog表有三个字段:

uidInstanceID ―― 服务实例的guid

uidMessageID ―― 消息的guid

nRefCount ―― 引用计数(值为1,表示引用了1次)

一旦服务实例运行完成,执行如下两个动作:

l 把BizTalkServerApplication_MessageRefCountLog相关记录删除

l 将此消息的guid写入MessageZeroSum表中,MessageZeroSum表中保存不再被引用的消息的guid,表示这些消息没有服务实例引用了,可以删除。MessageBox_Message_Cleanup_BizTalkMsgBoxDb作业最终就是根据这个表来清理无用的消息。

三. 全局引用计数

如果消息有多个服务订阅,消息代理调用bts_FindSubscriptions存储过程时发现多个订阅,这个存储过程返回N条记录。

这种情况下,消息代理会把消息引用计数记入到全局消息引用计数表MessageRefCountLog1或者MessageRefCountLog2中,这两个表结构一样,只是一个表是活动的表,另一个作为备用表。至于当时哪个表处于活动状态有ActiveRefCountLog活动引用计数表的相关字段指示。

MessageRefCountLog表的结构:

uidMessageID ―― 消息guid

uidInstanceID ―― 服务实例guid

snRefCount ―― 引用计数。此字段可以为正负,正表示被引用,负表示被放弃引用。

   ActiveRefCountLog表的结构:
   fType ―― =1 表示消息引用表
   tnActiveTable ―― 哪个引用计数表为活动,可能的值为1和2。=1表示MessageRefCountLog1当前为活动表;=2 表示MessageRefCountLog2当前为活动表。

当N个服务订阅了同一个消息,消息代理记入一条引用记录:uidMessageID为这条消息的guid;uidInstanceID为空,因为有多个服务订阅;snRefCount设为N,表示这个消息被引用N次。

每个服务各自运行,一个服务完成后就会在此表中增加一条记录:uidMessageID为这条消息的guid;uidInstanceID为空;snRefCount设为-1,表示减少一次引用。如果所有服务都完成后,将会增加N条引用计数为-1的记录,正好跟正的N计数对消。

四. 总结

Biztalk中消息如果不特别的进行清理的话,将会一直堆积下去,不管这个消息是否已经不再有用。所以一般需要定期对messagebox数据库中的消息进行清理,把一些过期的无用的消息清理掉,以防大量无用的消息占用很多硬盘空间,也给系统的性能带来负面影响。

Biztalk对消息的引用有完整的记录,可以从这些记录中获知哪些消息已经不再使用,系统可以根据这些信息来清理消息。

关于清理消息biztalk本身提供了几个相关的作业。MessageBox_Message_ManageRefCountLog_BizTalkMsgBoxDb和MessageBox_Message_Cleanup_BizTalkMsgBoxD,它们就是根据这些引用计数的统计来完成清理消息工作的。关于这两个作业另外写文章介绍。

biztalk消息以及消息订阅发布路由机制(四)-消息的轮询和执行

一. 消息的轮询和执行

1. 轮询机制

消息路由到MessageBox数据库中,只是在数据库中写入了相关记录,表示哪个消息需要由哪个服务实例去执行,并没有付诸实施,还需要在进程中实实在在的去实例化这个服务对象,运行服务实例对象,并把消息交给这个实例对象处理。

轮询主机队列是由订阅服务器的那些类先实例化为对象后,由服务实例去查询主机队列中的消息队列,找到是自己订阅的消息就拿过来处理,否则继续轮询下去。

服务实例轮询的时间间隔和同时运行的线程数由在BizTalkMgmtDb管理数据库中的adm_ServiceClass表中设置,此表中有两个字段:

MaxReceiveInterval ―― 设置此类服务连续轮询数据库的时间间隔

MaxDequeueThread ―― 最大出列线程的数目

adm_ServiceClass表中目前有四条记录,表示有四类可用的订阅服务类别。前面章节已经有介绍,分别是:

XLANG/s – 业务流程(orchestration)

Messaging InProcess – 表示一般的发送端口、Solicit- Response发送端口

MSMQT – MS消息队列

Messaging Isolated –表示请求/响应(Request-Response)接收端口,目前基本上就是指HTTP和SOAP的Request-Response双向接收端口。

每个服务相应的MaxReceiveInterval字段表示此类服务轮询主机队列的时间间隔(单位毫秒,默认轮询间隔都是500毫秒),就是说每隔这么多时间就要新开一个线程实例化一个这个服务类,这个实例去查询主机队列。

MaxDequeueThread字段表示同时处理本类服务的线程数,就是可以同时生成多少个本类服务的实例,去查询主机队列(默认都是5个)。这些实例去查询主机队列时有锁定机制进行保障,保证具体某一个时刻只能有一个实例对主机队列就行操作。

2. 轮询过程

2.1. 产生轮询的服务实例

一类服务的轮询包括两种情况,一种是激活订阅的情况,一种是实例订阅的情况。

激活订阅的情况,Biztalk先按照adm_ServiceClass表中规定的某一服务类的轮询间隔时间一次性生成MaxDequeueThread字段规定的多少个本类服务实例,就是新建多个线程,每个线程把某个服务类实例化为对象,由这些对象去查询主机队列。 这类实例是用来查询激活订阅的,因为激活订阅需要有新的实例来运行接收消息。

实例订阅的情况,原先“请求/响应接收端口”服务实例在接收消息发并发布出去后,本身会产生一个对返回消息的订阅,这就是实例订阅的情况。这类服务实例也会对主机队列进行轮询,查看是返回消息是否已经路由过来了。

2.2. 查询主机队列中的消息
2.2.1. 激活订阅的实例

每个新生成的服务实例都是由一个线程实例化并在其中运行,每个线程都有一个线程的guid来标识这个线程,就是说每个服务实例都可以对应到一个由线程guid标识的线程。运行中的服务实例到主机队列中进行查询,查看是否有可以处理的消息,主机队列要符合一个条件:

l 主机队列中订阅消息的服务类型跟本服务实例类型一致

运行的服务实例找到符合条件的,转入下一步,处理消息。

2.2.2. 实例订阅的实例

实例订阅的时候,同样在线程中运行的服务实例去查询主机队列,同样查看是否有路由到自己这的消息,主机队列要符合两个条件:

l 主机队列中订阅消息的服务类型跟本服务实例类型一致

l 主机队列中服务实例uidInstanceID字段值跟本服务实例的uidInstanceID一致。

如果有符合条件的消息,转入下一步,处理消息。

2.3. 处理消息

轮询的服务实例一旦在主机队列中匹配到待处理的消息,开始处理消息,大致过程如下:

l 把线程的guid写入到实例表的uidProcessID字段,表示这个实例由本线程进行处理。

l 将实例表的nState字段置为2,表示实例在运行中

l 根据主机队列表指示的消息guid,到spool表和parts表中获得消息具体内容进行处理

l 删除主机队列中的本条记录,表示此消息已经开始处理。

l 在服务实例运行过程中,原先在主机队列表中服务实例跟消息实例是有关联的,在删除主机队列中的本条记录后,服务实例跟正在处理的消息就失去了关联,故此,biztalk在删除主机队列记录的同时,把跟本实例的关联的消息guid记入到InstanceStateMessageReferences_BizTalkServerApplication实例与消息引用表,这个表主要字段:
uidInstanceID ―― 服务实例的guid
uidInstanceStateID ―― 含义不明
uidWorkID ―― 含义不明
uidMessageID ―― 消息的guid
uidServiceID ―― 服务guid
这样能保证服务实例在运行中能够知道跟本身相关联的消息是哪一个。

2.4. 消息处理完毕后

服务实例处理一个消息,处理完毕后要做的工作:

l 如果是“请求/响应接收端口”服务请求部分订阅产生的激活订阅服务实例完成后,服务实例在实例表中不删除,依然保留,以便这个实例继续处理返回的消息。

l 如果是一般的激活实例或“请求/响应接收端口”服务响应部分订阅产生的实例订阅服务实例完成后,会把这个服务实例的记录从实例表中删除。

l 删除InstanceStateMessageReferences_BizTalkServerApplication实例与消息引用表中跟此实例相关记录。

biztalk消息以及消息订阅发布路由机制(三)-消息发布和路由

一. 消息发布和路由

消息的发布有几种情况,上面讲述发布服务器时说过的三种发布服务都可以发布消息,他们的发布原理基本一致,这里以适配器发布消息为主进行描述消息发布和路由的过程。

上面的消息流程章节中描述了消息从接收端口接收进入biztalk后的处理过程,但是消息如何进入到MessageBox,如何路由到订阅此消息的服务的过程还没详细说明,这里将详细描述消息的发布和路由过程。

消息代理处理整个消息发布和路由的过程。

消息在经过了接收管道处理后,相关的属性已经升级到消息上下文(使用了默认接收管道XMLReceive或自建的管道中使用了xml拆装器的情况下biztalk会根据schema升级属性,使用默认接收管道PassThruTransmit不会升级属性)。

消息代理获得消息后,先把消息的属性写入到MessageBox数据库的消息属性表MessageProps表,包括了此消息的系统属性和从消息本身升级的属性。

MessageProps表的结构如下:

uidBatchID -- 消息批次id。此字段还不是很清楚,可能是一次可能处理多个消息,同一批进行处理的作为一个批次。

uidMessageID -- 消息的uid

uidPropID -- 这个消息的某一个属性,属性都用guid来表示。

vtPropValue -- 这个属性的值

1. 调用bts_FindSubscriptions过程

消息代理调用bts_FindSubscriptions存储过程来查找订阅了消息的相关订阅。这个存储过程把订阅表和各个订阅谓词表定义的订阅跟消息的属性表中的属性比较,找出所有所有符合此消息条件的订阅,返回到一个结果集中。这个结果集中最主要包含字段有:订阅guid(uidSubID)、此订阅所属主机实例名(nvcApplicationName)、消息guid(uidMessageID),服务的guid(uidServiceID)、服务实例的guid(uidInstanceID),结果集表示订阅表中的哪些订阅匹配到了这个消息,每个订阅了此消息的订阅为一条记录。如果一个消息被两个服务订阅了,这个结果集中将会有两条记录。

消息代理根据这个结果集,对每条记录调用存储过程bts_InsertMessage。以下的步骤,结果集中的每条记录都要执行一遍。

2. 第一次调用bts_InsertMessage过程

这次调用bts_InsertMessage过程主要任务是调用int_EvaluateSubscription过程。int_EvaluateSubscription过程根据当前处理的订阅所属的应用主机实例名,再调用对应此主机实例的存储过程进一步处理,调用的存储过程名称是“int_InsertIntoQueue_主机实例名”这样的命名形式,比如服务属于BizTalkServerApplication主机,则调用int_InsertIntoQueue_BizTalkServerApplication。

存储过程“int_InsertIntoQueue_主机实例名”的作用:

2.1. 激活订阅时,新建实例guid

如果是订阅类型是激活订阅,即uidInstanceID为空,说明需要新建一个处理订阅消息的服务实例,这一步骤新建一个guid,作为新建实例的实例guid。

之后要新建实例记录,即在实例表中插入一条实例记录表示要处理这条消息的实例。

Instances实例表主要字段:

主要字段

含义

uidAppOwnerID

所属主机实例的guid

uidInstanceID

此服务实例的guid

uidServiceID

此服务的guid

uidClassID

此服务的类型guid

uidProcessID

运行这个实例的线程guid

nState

实例状态:
1-准备运行,已激活但尚未开始运行的服务实例,通常由于资源暂时不可用,例如服务器的处理负载过大
2-已启动,正在运行的服务实例。
4-可恢复的挂起,实例已挂起,但可恢复该实例。
8-已冻结,实例状态保持在 MessageBox 数据库中,并且没有 Windows 服务运行该实例。
32-不可恢复的挂起,实例已挂起,且不可恢复该实例。可以保存该实例所引用的消息,然后终止该实例。
64 -- 在断点处
256-已计划要运行

2.1.1. 订阅状态为活动时(状态码为1)

如果订阅的状态是处于正常的活动状态,则在Instances实例表中插入一条记录,只记入四个字段:uidAppOwnerID, uidInstanceID, uidServiceID, uidClassID, nState。uidAppOwnerID为此主机实例的guid,uidInstanceID为新建的服务实例的guid,uidServiceID为订阅消息的服务guid,uidClassID为订阅服务的类型guid,nState实例状态为256,表示已计划要运行。

2.1.2. 订阅状态为停用时(状态码为2)

如果订阅的状态是处于停用状态,在插入Instances实例表(插入的状态字段nState为4)的同时还会在InstancesSuspended挂起实例表中都插入一条记录,挂起实例表中除了上面上面提到的四个字段,还包括一些错误代码和错误描述字段,可能是描述挂起原因的一些内容。此时nState实例状态为4,表示这个实例是可恢复的挂起。

2.1.3. 订阅状态码为8时

我没看出来这个状态码的含义,有知道的朋友请告知

如果是实例订阅,由于订阅的主体服务实例已经存在(在实例表中有这个服务的一条记录),不必再新建一个订阅服务的实例。

2.2. 将消息队列插入到相应的主机队列表

如果订阅是处于活动状态,这一步骤要将这个订阅内容插入到相应的主机队列表中,主机队列表为“<主机实例名>Q”形式,表示路由到此主机的还未被处理的消息队列。如果相应的主机实例是BizTalkServerApplication,那就将此消息订阅内容插入到BizTalkServerApplicationQ表中。

主机队列表BizTalkServerApplicationQ主要字段:

主要字段

含义

nID

自动标识

uidMessageID

消息guid

uidSubscriptionID

订阅guid

uidClassID

订阅此消息的服务类别guid

uidServiceID

订阅此消息的服务guid

uidInstanceID

订阅此消息的服务实例guid

uidAppInstanceID

主机实例guid

如果订阅处于停用(订阅状态码为2),或者订阅状态码为8(此码含义不清楚)时,订阅内容不被插入到主机队列表中,而是插入到主机队列挂起表中,表示订阅此消息的订阅不处于活动状态,此消息被挂起。挂起表为“<主机实例名>Q_Suspended”形式,此表大部分内容跟主机队列表相同,增加了一些表示为何挂起的字段,比如挂起原因nvcAdditionalInfo字段,挂起是否可恢复nIsResumable字段等。

3. 其后调用bts_InsertMessage过程

接下来的过程将把消息的所有内容写入到MessageBox数据库中,一条消息本身内容在数据只有一个副本,所有订阅了此消息的服务都参考引用这一个消息的副本。消息本身是不能修改的,如果哪个服务需要修改消息,必然是需要生成另外一条消息。

3.1. 写入消息上下文和多部分消息的正文部分

其后的第一次调用bts_InsertMessage过程将传递消息上下文,然后将该消息上下文以及有关消息的元数据(例如,部分数、正文部分名称和 ID)插入到 SPOOL 表中。SPOOL 表是对一条消息的总体性描述和消息的上下文属性,一条消息在此表中有一条记录。另外,消息正文部分也在这一步使用 int_InsertPart 存储过程插入到 PARTS 表中,一条消息的多部分中只能有一个正文部分。

SPOOL 表主要字段:

主要字段

含义

uidMessageID

消息的guid

UserName

用户名,运行主机实例的用户

PublishingServer

biztalk所在服务器的名称

OriginatorSID

创建者的安全id,即启动biztalk服务的帐户

OriginatorPID

创建者参与方id,一般是“s-1-5-7”

nvcMessageType

消息的类型,由正文部分决定的

nNumParts

消息包含的部分数

uidBodyPartID

正文部分的guid

nvcBodyPartName

正文部分名

nCounter

部分计数

imgContext

上下文的内容

MessageParts表是SPOOL消息队列表跟PARTS消息部分表之间的中间关系表,MessageParts. uidMessageID字段跟SPOOL.uidMessageID字段关联,PARTS. uidPartID字段跟MessagePart. uidPartID段关联。

主要字段

含义

uidMessageID

消息guid

uidPartID

消息部分的guid

nvcPartName

消息部分名

nBodyPart

是否正文
1 -- 正文
0 -- 不是正文

   PARTS 表存放多部分消息的各个部分,一个部分在此表中占一条记录。哪一个是正文部分由spool表中的uidBodyPartID标识。

uidPartID

消息部分guid

nPartSize

部分的尺寸

imgPart

部分的数据

实例表Instances表跟消息队列SPOOL表之间关系,就是哪个服务实例订阅了哪个消息是靠主机队列BizTalkServerApplicationQ表的关联起来,Instances. uidInstanceID字段跟BizTalkServerApplicationQ. uidInstanceID字段相关,BizTalkServerApplicationQ. uidMessageID字段跟SPOOL.uidMessageID字段相关。

这一步的最后还有做一件事就是把消息代理写入到MessageBox属性表MessageProps中这个批次的所有属性记录都删除,因为它们已经不再有用。

3.2. 依次写入多部分消息的其他部分

随后,除了消息正文部分为每个剩余的消息部分调用 bts_InsertMessage 存储过程,把这些消息部分一一写入到PARTS 表中。

4. 消息发布和路由总结

一个消息有从适配器进来,到消息代理把消息路由到MessageBox数据口中相应的表中,这个过程就是消息的发布和路由过程。

路由完毕后,在主机队列表中存在待处理的消息队列,这个表可以简单的理解为哪个消息将要由哪个服务实例处理。

实例表Instances表存放要处理这些消息的服务实例记录,一个待处理的消息对应一个实例,实例表每条记录可以简单的理解为这个实例是哪个服务类型的服务,服务目前是什么状态。其实Instances表中的实例还需要由具体的服务线程来运行它,这时才是真正的服务实例化,这点下一章“消息的轮询和执行”中会有详细讲解。

消息的具体内容存放在SPOOL 表和PARTS 表中,Spool表存放消息的总体性描述和消息的上下文属性,PARTS 表存放消息的各个部分。

8月4日

biztalk消息以及消息订阅发布路由机制(一)-消息概述——转载

一. 消息概述

BizTalk Server 基于消息发布/订阅的基本机制,在BizTalk Server内部分为发布服务器和订阅服务器,所谓发布服务器就是产生消息并发布到MessageBox中,所谓订阅服务器就是可以消费消息的服务器,根据订阅的消息的条件,只对自己需要的信息进行处理。

一般的情况是从外部收到的信息经过接收端口后都会被处理转换包装成biztalk的消息,消息中包含了相关属性(消息上下文),比如接收的端口、接收的适配器信息,还有预设的从消息内部提取出来的可以用来路由消息的特征值(从schema升级的属性)。把然后存放到消息数据库MessageBox中。

消息代理根据订阅表查找订阅了此消息的服务,并将订阅了此消息的所有订阅分别存到的相应的服务器实例对应的消息队列表中,系统不断的轮询此队列,发现有未处理的消息,就会把消息发送到订阅此消息的服务,同时把此条订阅消息从消息队列表去除。

订阅消息的服务(这个服务可能是业务流程,也可能是输出端口等等)收到消息后对消息就行处理,处理完后,如果服务是输出端口,此消息就经过适配器发送到系统外部。如果服务是业务流程,可能会产生新的消息,这种情况的处理同从接收端口进来的消息做类似的处理,发送消息到MessageBox,并查找订阅此消息的服务,把消息发送到订阅消息队列中继续流转。

1. 发布和订阅服务器

发布服务器指的是可以送出消息发布到MessageBox的服务,包括:

接收端口 ―― 从外部接收信息,处理后发布到MessageBox

业务流程 ―― 业务流程启动后可能生成消息发送到发送端口或启动别的业务流程,生成的消息也是发布到MessageBox

要求/响应(Solicit-Response)发送端口 ―― 即双向的要求/响应发送端口。

订阅服务器是可以订阅并消费消息的服务,可以作为订阅服务器的服务类型目前有四类:

XLANG/s – 业务流程(orchestration)

Messaging InProcess – 表示发送端口,Solicit- Response发送端口

MSMQT – MS消息队列

Messaging Isolated – 表示请求/响应(Request-Response)接收端口,目前基本上就是指HTTP和SOAP的Request-Response双向接收端口。

Biztalk中的发布和订阅都是相对MeassagBox而言的,导致消息进入到MeassagBox的就叫发布,根据条件从MeassagBox中取得消息的叫订阅。

2. 消息构成

BizTalk Server 2006 本质上是一个消息处理引擎。每条消息都可视为多部分消息,此消息可以由零个或多个部分组成。具有一个或多个部分的消息都具有一个标识为正文部分的部分(只能有个一部分标识为正文)。每个部分均由可表示 XML 文档、平面文件、序列化的 .NET 类或其他二进制数据流的二进制数据块组成。消息的正文部分的类型决定了整个消息的类型,这个消息类型可用为路由消息的条件。


2.1. 上下文

每条消息都附带有一个属性集,称为消息上下文,这些属性的值是从消息本身提取或不来自消息本身但与消息本身相关的值。属性分为两种类型,升级属性和写入属性。这两种属性的区别是升级的属性可以用来作为路由消息的条件,写入的属性不能。

2.1.1. 升级属性

要把一个消息架构中的某个元素升级到上下文属性,首先要有一个属性架构来对应各个被升级的属性。这个属性架构是简化的架构,只能包含简单数据类型的元素,每个元素对应一个属性,并分配给这个属性一个GUID,用来标识这个属性。看一下升级属性架构中的一个属性:

<xs:element name=”Property1” type=”xs:string”>

<xs:annotation>

<xs:appinfo>

<b:fieldInfo propertyGuid=”4bfe2aee-9c25-436f-979d-862ed812a523” />

</xs:appinfo>

</xs:annotation>

</xs:element>

属性名是Property1,属性GUID是4bfe2aee-9c25-436f-979d-862ed812a523。升级消息架构中的属性,先要在消息架构中指定升级架构,然后把消息架构中的某个元素对应到升级架构中的某个同数据类型的属性。消息架构中的元素被升级后,消息架构会保存此升级属性在架构中的位置信息的xpath,以便可以通过xpath提取元素的值。

如果接收管道中使用了xml拆装器,则消息经过接收端口的管道时,xml拆装器会自动把入站的消息跟已部署的消息架构匹配,匹配到合适的消息架构后,管道根据消息架构中升级属性的xpath提取相应的值保存到上下文属性,然后消息代理把这些属性的GUID和值保存到BizTalkMsgBoxDb数据库MessageProps 表中,就可以根据订阅的条件对照这些属性来路由消息。

比如某个服务订阅了消息类型为订单、收货单位为xxxx的消息,这里消息类型和收货单位都是升级到上下文属性的属性,消息代理发现这个条件跟收到的消息一致就会把消息发送到这个订阅的服务。

升级的属性除了从消息本身升级来的外,还有不是消息本身的信息但跟此消息相关的系统属性,比如接到此消息的端口适配器类型,接收此消息的端口id等等,这些系统属性也都具有自己的guid,会同其他的升级属性一同写入到MessageProps 表中作为路由消息的依据。

关于详细的消息订阅和消息发布路由的机制下面有专门章节论述。

2.1.2. 写入属性

写入属性就是把消息架构中的某个元素设置为可分辨字段(Distinguished),消息架构会保存此写入属性在架构中位置信息的xpath,以便可以通过xpath提取元素的值。

写入的属性不必有属性架构的支持,也就没有相应的GUID,属性会被提取出来保存到上下文属性中,但是写入的属性不能用来作为路由消息的条件。

写入属性一般是为了在业务流程中方便的访问消息中的某个元素,可以直接从消息上下文属性获得此属性值,省去开发人员手工解析提取这个元素的值麻烦。

2.2. 多部分

biztalk中的消息又叫做多部分消息,意为一个消息可以包含多个部分,每个部分都可能为XML 文档、平面文件、序列化的 .NET 类或其他二进制数据流的二进制数据块。一个消息可以是简单的只包含一个部分的消息,也可以有多个部分,但是其中必须并且只能有一个部分为正文本分,正文部分的类型就是整个消息的消息类型。

3. 消息流程

先看一下消息在biztalk中的流转过程的图,下面详细描述一个从接收端口进入到biztalk后一直到消息发送出biztalk的整个过程。


3.1. 接收端口

一个接收端口可以包含多个接收位置,每个接收位置就是一个对外接收信息的实际物理接口。接收位置接收到的信息经过处理,如果设计有映射,经过消息映射转化后再由消息代理根据消息订阅情况分发到各个订阅消息的服务。

接收位置:

接收位置主要由接收适配器和接收管道组成,这两个组件再终结点管理器的管理下协调工作。

接收适配器:

Biztalk作为EAI和B2B的平台软件,需要跟各种系统通讯联系,所以信息接口应该覆盖面尽可能的广,能够接收各种协议的各种类型的信息,biztalk使用适配器来解决这个问题,通过适配器来把多种多样的信息接收进来并转换成biztalk内部使用的xml的消息。

Biztalk本身包含了许多一般常用的适配器SOAP、http、ftp、Smtp、平面文件、msmq、sql等等,基本能满足大多数需求。如果biztalk本身带的适配器不能满足需求,还可以自己开发需要的适配器,另外有很多第三方的公司也提供各种自己开发的适配器给需要者使用。

接收适配器读取数据流创建消息(Microsoft.BizTalk.Message.Interop.IbaseMessage 接口的实现)、向该消息添加部分(Microsoft.BizTalk.Message.Interop.IbasePart 接口的实现)、然后将数据流作为该部分内容进行提供。

接收消息后,适配器将接收位置、适配器类型以及其他内容(与适配器相关)升级到消息的上下文属性中, 以便根据接收位置或适配器类型订阅消息的服务能接收到订阅的消息。

接收管道:

从适配器出来的消息在终结点管理器的控制下送到接收管道中进行处理。


接收管道如上图包含四个阶段的处理,这里我们重点关心的是拆装部分,biztalk提供了两个拆装器:xml拆装器和平面文件拆装器。

Xml拆装器可以指定消息的架构,xml拆装器就会按照这个消息的架构对消息就行处理,并可以校验消息是否符合架构。同时如果消息架构中有升级属性,这一步会根据升级属性的xpath把属性值从消息中提取出来放到消息的上下文属性中。指定了消息架构的xml拆装器还会把消息的类型升级到消息的上下文属性中,消息的类型为命名空间(后接 # 符号)和根节点的名称组成。比如:http://tempuri.org/samples/MessageType#Message

平面文件拆装器需要指定架构,并且架构是做过平面文件扩展的,就是架构中包含如果分解平面文件数据到xml的信息,平面文件拆装器根据这些信息解析平面文件,并生成xml。

Biztalk的默认接收管道PassThruTransmit是个直通的管道,它不处理xml文档,所以如果要接收xml消息并升级属性此管道不适合。

Biztalk的默认接收管道XMLReceive,这个管道专门用来拆装xml消息,管道中的xml拆装器根据架构的命名空间和根节点名称在已经部署的所有架构逐个进行匹配,一旦匹配成功,就使用这个架构,然后根据schema中升级属性的xpath提取属性值放入上下文。如果一个都没匹配成功,则根据是否路由不可识别消息的设置确定是否继续路由,还是生成错误信息。

消息代理:

接收管道出来的消息又回到终结点管理器,如果有映射器就把消息送到映射器就行消息的架构转换,如果没有把消息直接送到消息代理,由消息代理把消息发布到MessageBox中,并处理消息的路由。

消息的发布和路由在下面章节详述。

3.2. MessageBox

Microsoft BizTalk Server 中发布/订阅引擎的核心是 MessageBox 数据库。接收到的消息存放在MessageBox中,各个服务消息订阅也是存放在MessageBox中,发送到订阅服务的消息队列也是在MessageBox中。MessageBox是biztalk的信息核心。

3.3. 业务流程

业务流程可以参与消息的处理过程,但业务流程不是处理消息过程的必须过程,一个消息流程完全可以直接从接收端口到MessageBox,然后直接路由到一个输出端口,不需要业务流程的参与。

业务流程中也有端口概念,是业务流程内部跟外部进行消息交换的接口,业务流程的端口称作逻辑端口,用户从biztalk外部接收数据、发送数据的端口称作物理端口,是不同的概念。

业务流程可以作为发布服务器也可以作为订阅服务器,作为订阅服务器时可以订阅从物理接收端口进入biztalk的消息,作为发布服务器,业务流程从逻辑输出端口输出的消息也可以被别的订阅服务器订阅。

业务流程内部的各种功能不在本文的讨论范围,可以自行参考biztalk的随即文档的业务流程相关的部分。

3.4. 发送端口

在消息准备从 BizTalk Server 发送时,它将在发送端口中经历一个互补的过程。映射将在发送管道执行前应用于消息,从而,消息在由管道处理并通过适配器发送前转换为特定于客户或应用程序的格式。在发送管道中,属性将从上下文降级到消息中,而非升级到消息上下文中。

biztalk消息以及消息订阅发布路由机制(二)-消息订阅 (转载)

一. 消息订阅

订阅消息的主体叫订阅服务器,订阅服务器是可以订阅并消费消息的服务,可以作为订阅服务器的服务类型目前有四类,在BizTalkMgmtDb管理数据库中的adm_ServiceClass的Name字段列出了所有可以作为订阅服务器的服务类型,包括:

XLANG/s – 业务流程(orchestration)

Messaging InProcess – 表示一般的发送端口、Solicit- Response发送端口

MSMQT – MS消息队列

Messaging Isolated –表示请求/响应(Request-Response)接收端口,目前基本上就是指HTTP和SOAP的Request-Response双向接收端口。

每种服务都有自己的id,为16字节GUID的形式(biztalk中对象的标识id都采用GUID的形式,biztalk数据表中大量的使用到这种16字节的统一标识数据,sql server中的uniqueidentifier 数据类型,就是用来存放GUID类型的数据)

1. 消息订阅主体

消息订阅最后体现在BizTalkMsgBoxDb数据库的反应订阅主体的Subscription表(表示是哪个服务产生的这个订阅)和反应订阅条件的一组谓词表中(表示这个订阅的具体条件,用来判断哪些消息是符合这个订阅的)。

先来看一下Subscription表的主要字段含义,这个表指示了订阅消息的具体是哪一个服务:

nvcName ―― 此订阅的名称

uidSubID ―― Guid类型,此订阅的uid

nvcApplicationName ―― 订阅服务器所属主机实例名,路由消息时不同的主机实例将调用不同的存储过程处理。

uidClassID ―― 产生订阅的服务类型,是adm_ServiceClass 服务表UniqueId字段的外键

uidServiceID ―― 产生此订阅的具体服务,根据uidClassID服务类型的不同,对应到不同类别的服务.如果uidClassID指示的是XLANG/s,则对应的是业务流程,此字段对应到bts_Orchestration表中的uidGUID字段。为Messaging In-Proc时,表示由发送端口订阅消息,对应到bts_SendPort表中的uidGUID 。MSMQt时有点混乱,但大多数情况下也在bts_SendPort table表中体现。实例订阅时,服务实例是请求/响应端口时的这个字段并不是接收端口的guid也不是接收位置的guid,不清楚这个guid跟什么对应。哪位知道的朋友能不能告知一下。

uidInstanceID ―― 实例订阅时的订阅消息的服务实例的guid。激活订阅时此字节为空。

Biztalk中存在两类订阅:激活订阅和实例订阅。激活订阅收到执行订阅的消息后,该消息创建新的订阅服务器实例来处理这个消息,一般的订阅都属于这类订阅。实例订阅将指示执行订阅的消息应路由到已在运行的订阅服务器实例。有一种服务会产生实例订阅的情况即“Messaging Isolated ”,这类服务的典型就是“请求/响应接收端口”,这类服务其实可以产生两个订阅。先是“请求/响应接收端口”的接收部分,这部分相当于消息分布服务器,可以由其他订阅服务器来订阅这个消息,比如一般是业务流程的一个接收形状绑定这个端口,实际上就是订阅了这个端口接收到的消息。这个接收端口服务实例建立后,把收到的消息发送到MessageBox。然后“请求/响应接收端口”的响应部分,这部分相当于订阅服务器,如果这部分绑定到业务流程的一个发送形状,这个接收端口服务实例会生成一个实例订阅,这个字段就用来存放这个接收端口服务实例的guid,表示还是由这个服务实例来处理返回的消息。这样,就完成了一个从接收到响应的一个往返过程。实例订阅是由服务实例生成的订阅,所以在产生实例订阅收到消息被放入到消息队列后,会在订阅表中把这个实例订阅删除。

还有一种情况会形成实例订阅,orchestration中使用相关集时,一个orchestration实例的一个端口发送消息出去,返回的消息仍然由这个orchestration实例的一个接收形状接收。这里也有一个orchestration实例的订阅发出去的消息的实例订阅。

uidPortID ―― 订阅服务类型为XLANG/s时,此字段表示业务流程中接收消息的端口形状,对应bts_orchestration_port表中的uidGUID字段。对于 Messaging或MSMQt,这个字段指示使用发送端口的主通道还是辅助通道。先根据uidServiceID在bts_SendPort表中找到相应的端口,然后用bts_SendPort表的nID 跟bts_sendport_transport表的nSendPortID 关联。uidPortID跟bts_sendport_transport表的uidGUID 关联,就能确定使用哪个发送端口的通道。

fEnabled ―― 此订阅的状态,0 ―― 禁用;1 ―― 活动;2 ―― 停用

uidPredicateGroupID ―― 订阅谓词组id。

2. 消息订阅条件(谓词)

一个订阅的主体存在Subscription表中,相应的订阅条件则分别存在一组谓词表中,不同的类型的条件放在不同的表中,比如等于的条件放在EqualsPredicates中,小于的条件放在LessThenPredicates中,一共有以下这么多的谓词表:

l BitwiseANDPredicates

l EqualsPredicates

l EqualsPredicates2ndPass

l ExistsPredicates

l FirstPassPredicates

l GreaterThanOrEqualsPredicates

l GreaterThanPredicates

l LessThenOrEqualsPredicates

l LessThenPredicates

l NotEqualsPredicates

这些表的基本结构都是一样的,都是按照什么属性(属性的GUID)+谓词(等于、大于、小于。。。)+属性的值的形式出现。看一下这些表的数据结构:

uidPropID ―― 属性的guid。所有属性都有自己的guid,包括类似接收端口、接收适配器类型等系统属性,或者从schema升级的属性。

vtValue ―― 属性的值

uidPredicateGroupID ―― 一组谓词的id,一个订阅可以有多个条件谓词,这个字段表示同一组的条件,条件也可分为或条件和与条件,条件的组合通过PredicateGroup表在Subscription表和各个谓词表之间进行组合。

看一下PredicateGroup表的结构:

uidPredicateORGroupID -- 订阅谓词,对应到Subscription的uidPredicateGroupID字段表示是与这个订阅相关的条件。此字段相同的记录表示相应的订阅有多组条件用或运算组成。

uidPredicateANDGroupID -- 订阅谓词与运算一组的谓词id,每个或运算条件都能由多条与运算的条件组成,最终由这个字段跟各个谓词表去关联。

nNumFirstPassPredicates -- 这个或条件组中包含几个与运算条件。


这个图中显示了一个发送端口设置筛选条件(就是设置订阅条件)的示例。从上可以看出这个订阅实际上设置了两组或运算的条件,其中第一组内又包含了两个与运算的条件。在PredicateGroup表中的表现就是对应这个订阅的有两条uidPredicateORGroupID相同的记录,表示两组或运算条件,相应的uidPredicateANDGroupID字段对应到EqualsPredicates谓词表中,其中有一个对应两条记录,一个对应一条记录。

3. 消息订阅过程

下面的图是涉及到订阅部分数据库表的主要结构和相互关系。各字段的含义上面两节基本都有描述了。


下面看一下biztalk中两种订阅方式的各自如何产生订阅的:

3.1. 激活订阅

一般情况下,业务流程、发送端口和MSMQ这三类服务产生的订阅是激活订阅。这种订阅是被订阅的消息根据订阅条件新建订阅消息的服务主体,即新建一个服务实例,然后由这个服务实例处理订阅的消息,服务实例将消息处理完毕后,这个实例就算完成任务。

业务流程订阅一般表现形式是业务流程的接收端口跟物理端口绑定。其实它的订阅主体是这个业务流程的这个接收端口。条件是绑定的发送端口发送来的消息。

发送端口订阅一般可以是发送端口绑定到业务流程的一个发送端口。也可以是发送端口直接订阅由接收位置接收的消息,在发送端口的筛选器中可以根据消息的各种属性(包括系统属性和从schema升级的属性)来定义订阅的条件,比如某个消息的哪个升级属性大于某个值时。

以业务流程绑定一个物理接收端口为例说明:

业务流程跟一个物理接收端口绑定后,实际进行的操作是产生一个订阅。就是在Subscription表中生成一条记录表示订阅的主体,然后在一组谓词表中生成相应的订阅条件。

订阅主体就是这个业务流程的绑定的端口。在Subscription表中生成记录,字段uidClassID是XLANG/s业务流程服务的guid,表示订阅主体的服务类型是业务流程。uidServiceID字段是订阅的具体业务流程的guid(对应bts_Orchestration表)。uidPortID字段是这个业务流程中接收端口的guid(对应bts_orchestration_port表),uidPredicateGroupID字段是订阅谓词组id,表示跟这个订阅相关的谓词表中的记录。

订阅主体在Subscription表生成记录后,然后把订阅的条件要记入到相应的谓词表中。本例中端口绑定的情况,会产生两个条件,一个是接收端口id等于绑定的那个接收端口,形式为:http://schemas.microsoft.com/BizTalk/2003/system-properties. ReceivePortID == {9DB16A5F-ADD7-4CF2-9B9A-D7924426DD18};另一个是消息类型是业务流程接收端口指定的消息类型,形式为:http://schemas.microsoft.com/BizTalk/2003/system-properties.MessageType == http://EAISchemas.Request#Request

这两个条件中都含有“system-properties”,表示是系统属性,这两个又都是等于条件,所以都记入EqualsPredicates谓词表中。所有的属性都有guid,系统属性有预设的guid,升级属性前面章节已经讲到,每个升级的属性都会生成一个guid(这就是为什么升级的字段可以用来路由消息,而可分辨字段不可以用来路由消息的原因,可分辨字段没有guid)。所以这两个条件在谓词表中的表现是,两属性guid代表的属性分别等于什么值。

3.2. 实例订阅

这种订阅由服务实例产生,并放入到订阅表中。当消息路由到消息队列后,会将订阅表中的实例订阅记录删除。

一般来讲Messaging Isolated(表示物理请求/响应类型的接收端口)这类服务会用到实例订阅,下面就这个为例说明:

请求/响应类型的接收端口,一般是由业务流程订阅这个端口的请求部分的消息,经过处理后返回消息,这个端口的的响应部分再订阅这个返回的消息,处理后返回给请求端。

请求部分的订阅是激活订阅,不再赘述。

响应部分是实例订阅,是由请求部分收到消息后新建Messaging Isolated服务实例后,这个服务实例在订阅表中建立的一个订阅,这个订阅其他部分跟激活订阅一致,就是在Subscription表中的uidInstanceID字段填入这个服务实例自身的guid,表示这是实例订阅,订阅主体就是这个服务实例。

当消息路由到这个实例订阅的消息队列后,这个实例订阅的使命就完成了,被从订阅表中删除,这个过程在下面消息的发布和路由中还有叙述。

4. 端口绑定的本质

端口绑定本质就是生成订阅,订阅服务器订阅发布服务器的消息,但是不表示这个发布服务器的消息只能给一个订阅服务器消费,一个消息可以同时被多个服务订阅,只要这个消息符合订阅条件,消息代理会把订阅把消息发送到所有订阅这个消息的服务实例。

7月29日

Hibernate 对视图的操作及配置

   也是这几天,在弄Hibernate的时候,发现对于视图的mapping往往做的很不够,如果这个视图里没有主键的话,那是无法搜出要的数据类的。呵呵,这个也就是平时说得给hibernate配视图:)不过,后面的myeclipse3.2平台上已经统一解决了这类问题,通过id类。这个针对的是过去eclipse3.1的平台而言,同时也让自己明白了hibernate的一些小脾气:)

hibernate对视图的操作
      由于视图没有主键,所以在用hibernate对视图操作就需要做点处理了,网上搜了一通也没能找到相关文章,后来突然想到了myeclipse可以帮助生成hibernate的配置文件和对应的pojo代码。
      打开myeclipse,选择相关的视图,生成了配置文件和pojo类,发现pojo类生成了两个,而配置文件却一个,参看了配置文件和类,明白了是怎么回事。生成的配置文件通过了组合的方式生成,所以会对应两个类,一个类主要存放id信息,以个类存放对应的字段信息。
      下面是生成的类和配置文件:
类:
public class AllTablePb implements Serializable {

 // Fields
 private AllTablePbId id;

 // Property accessors
 public AllTablePbId getId() {
  return this.id;
 }

 public void setId(AllTablePbId id) {
  this.id = id;
 }
}

public class AllTablePbId implements java.io.Serializable {

 // Fields

 private String owner;

 private String tableName;

 private String columnName;

 private String dataType;

 private String pbcCnam;

 private String pbcCmnt;

 // Property accessors

 public String getOwner() {
  return this.owner;
 }

 public void setOwner(String owner) {
  this.owner = owner;
 }

 public String getTableName() {
  return this.tableName;
 }

 public void setTableName(String tableName) {
  this.tableName = tableName;
 }

 public String getColumnName() {
  return this.columnName;
 }

 public void setColumnName(String columnName) {
  this.columnName = columnName;
 }

 public String getDataType() {
  return this.dataType;
 }

 public void setDataType(String dataType) {
  this.dataType = dataType;
 }

 public String getPbcCnam() {
  return this.pbcCnam;
 }

 public void setPbcCnam(String pbcCnam) {
  this.pbcCnam = pbcCnam;
 }

 public String getPbcCmnt() {
  return this.pbcCmnt;
 }

 public void setPbcCmnt(String pbcCmnt) {
  this.pbcCmnt = pbcCmnt;
 }

}

配置文件:
<hibernate-mapping>
 <class name="com.hhkj.workflow.bean.AllTablePb" table="V_ALLTAB_PB" schema="CANP">
  <composite-id name="id" class="com.hhkj.workflow.bean.AllTablePbId">
   <key-property name="owner" type="string">
    <column name="OWNER" length="30" />
   </key-property>
   <key-property name="tableName" type="string">
    <column name="TABLE_NAME" length="30" />
   </key-property>
   <key-property name="columnName" type="string">
    <column name="COLUMN_NAME" length="30" />
   </key-property>
   <key-property name="dataType" type="string">
    <column name="DATA_TYPE" length="106" />
   </key-property>
   <key-property name="pbcCnam" type="string">
    <column name="PBC_CNAM" length="30" />
   </key-property>
   <key-property name="pbcCmnt" type="string">
    <column name="PBC_CMNT" length="254" />
   </key-property>
  </composite-id>
 </class>
</hibernate-mapping>
这样就可以通过AllTablePb.getId()取得相关的信息。

通过这样对视图的操作,同样也可以用到对于那些没有定义主键的表,操作方法是一样的。

 

Java中Double的高精度问题及bigdecimal解决方式

    最近有空写了点老的J2EE的代码,发现有一个十分有意思的问题,当用Hibernate从数据库里把浮点数读取出来的时候做一些比如累加的工作,例如 summary 或者递减之类的,就会发现在最后的结果中会出现些许问题。
如:3.41+5.2+56.2+23.3+... (这类两位小数的价钱),结果会出现103.00000000000001这种结果,但是人算的话反而会得出正常的数据。看样子double,float这类数据精度上来了还会有这类问题。
    于是,翻了点资料,在有的编程语言中提供了专门的货币类型来处理这种情况,但是Java没有。
    四舍五入
我们的第一个反应是做四舍五入。Math类中的round方法不能设置保留几位小数,我们只能
象这样(保留两位):
public double round(double value){
    return Math.round(value*100)/100.0;
}
非常不幸,上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02,
如我们在上面看到的
4.015*100=401.49999999999994
因此如果我们要做到精确的四舍五入,不能利用简单类型做任何运算
java.text.DecimalFormat也不能解决这个问题:
System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
输出是4.02

BigDecimal
在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者
是工程计算,在商业计算中我们要用 java.math.BigDecimal。BigDecimal一共有4个够造方
法,我们不关心用BigInteger来够造的那两个,那么还有两个,它们是:
BigDecimal(double val)
          Translates a double into a BigDecimal.
BigDecimal(String val)
          Translates the String repre sentation of a BigDecimal into a
BigDecimal.
上面的API简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。我
们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现上面哪个够造方
法的详细说明中有这么一段:
Note: the results of this constructor can be somewhat unpredictable. One might
assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal
to .1000000000000000055511151231257827021181583404541015625. This is so because
.1 cannot be represented exactly as a double (or, for that matter, as a binary
fraction of any finite length). Thus, the long value that is being passed in to
the constructor is not exactly equal to .1, appearances nonwithstanding.
The (String) constructor, on the other hand, is perfectly predictable: new
BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is
generally recommended that the (String) constructor be used in preference to
this one.
 
原来我们如果需要精确计算,非要用String来够造BigDecimal不可!在《Effective Java》
一书中的例子是用String来够造BigDecimal的,但是书上却没有强调这一点,这也许是一个
小小的失误吧。
 
解决方案
现在我们已经可以解决这个问题了,原则是使用BigDecimal并且一定要用String来够造。
但是想像一下吧,如果我们要做一个加法运算,需要先将两个浮点数转为String,然后够造
成BigDecimal,在其中一个上调用add方法,传入另一个作为参数,然后把运算的结果(
BigDecimal)再转换为浮点数。你能够忍受这么烦琐的过程吗?下面我们提供一个工具类
Arith来简化操作。它提供以下静态方法,包括加减乘除和四舍五入:
public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)
 
附录
 
源文件Arith.java:
package com.common.util;
import java.math.BigDecimal;
/**
 * 由于Java的简单类型不能够精确的对浮点数进行运算,
 * 这个工具类提供精确的浮点数运算,包括加减乘除和四舍五入。
 */
public final class Arith {
 // 默认除法运算精度
 private static final int DEF_DIV_SCALE = 2;
 // 这个类不能实例化
 private Arith() {
 }
 /**
  * 提供精确的加法运算。
  *
  * @param v1 被加数
  * @param v2 加数
  * @return 两个参数的和
  */
 public static double add(double v1, double v2) {
  BigDecimal b1 = new BigDecimal(Double.toString(v1));
  BigDecimal b2 = new BigDecimal(Double.toString(v2));
  return b1.add(b2).doubleValue();
 }
 /**
  * 提供精确的减法运算。
  *
  * @param v1 被减数
  * @param v2 减数
  * @return 两个参数的差
  */
 public static double sub(double v1, double v2) {
  BigDecimal b1 = new BigDecimal(Double.toString(v1));
  BigDecimal b2 = new BigDecimal(Double.toString(v2));
  return b1.subtract(b2).doubleValue();
 }
 /**
  * 提供精确的乘法运算。
  *
  * @param v1 被乘数
  * @param v2 乘数
  * @return 两个参数的积
  */
 public static double mul(double v1, double v2) {
  BigDecimal b1 = new BigDecimal(Double.toString(v1));
  BigDecimal b2 = new BigDecimal(Double.toString(v2));
  return b1.multiply(b2).doubleValue();
 }
 /**
  * 提供(相对)精确的除法运算,当发生除不尽的情况时,
  * 精确到小数点以后10位,以后的数字四舍五入。
  *
  * @param v1 被除数
  * @param v2 除数
  * @return 两个参数的商
  */
 public static double div(double v1, double v2) {
  return div(v1, v2, DEF_DIV_SCALE);
 }
 /**
  * 提供(相对)精确的除法运算。
  * 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
  *
  * @param v1 被除数
  * @param v2 除数
  * @param scale 表示表示需要精确到小数点以后几位。
  * @return 两个参数的商
  */
 public static double div(double v1, double v2, int scale) {
  if (scale < 0) {
   throw new IllegalArgumentException(
   "The scale must be a positive integer or zero");
  }
  BigDecimal b1 = new BigDecimal(Double.toString(v1));
  BigDecimal b2 = new BigDecimal(Double.toString(v2));
  return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
 }
 /**
  * 提供精确的小数位四舍五入处理。
  *
  * @param v 需要四舍五入的数字
  * @param scale 小数点后保留几位
  * @return 四舍五入后的结果
  */
 public static double round(double v, int scale) {
  if (scale < 0) {
   throw new IllegalArgumentException(
   "The scale must be a positive integer or zero");
  }
  BigDecimal b = new BigDecimal(Double.toString(v));
  BigDecimal one = new BigDecimal("1");
  return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
 }
}
呵呵,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。这个才是解决的方式,我也试过了发现确实不错。不过话又说回来了,在.net里似乎没有发现有这类问题呀,能说他好么?我看未必,有时候封装的太好,功能太多,往往让人忘记了这些背后本来的面目:)
7月21日

FTP协议——相关参数

最近弄了一点FTP的client端操作例程,由于在老的平台.net1.1上进行开发,只好使用最基本的socket通信去和Ftp服务通信,因此不得不调用这些FTP的命令,同时参照返回code码。可喜的是,.net2.0后的FLC里给FTP的操作专门提供了一系列方便的类和接口,呵呵,ftpclient好像是这个,省去了好多麻烦呢:)

不过socket的协议接触下也不是什么坏事,至少还知道了通信这个层面的操作规范,算是有所小小收获吧。


FTP ,尽管可以直接被终端用户使用,但其应用主要还是通过程序实现。FTP协议通过FTP控制帧使用TCP协议进行通信。FTP 控制帧即指 TELNET 交换信息,包含 TELNET 命令和选项。然而,大多数 FTP 控制帧是简单的 ASCII 文本,可以分为 FTP 命令或 FTP 消息。 FTP 消息是对 FTP 命令的响应,它由带有解释文本的应答代码构成。

FTP命令命令  描述 
ABOR 中断数据连接程序
ACCT <account> 系统特权帐号
ALLO <bytes>  为服务器上的文件存储器分配字节
APPE <filename> 添加文件到服务器同名文件
CDUP <dir path> 改变服务器上的父目录
CWD <dir path> 改变服务器上的工作目录
DELE <filename> 删除服务器上的指定文件
HELP <command> 返回指定命令信息
LIST <name> 如果是文件名列出文件信息,如果是目录则列出文件列表
MODE <mode> 传输模式(S=流模式,B=块模式,C=压缩模式)
MKD <directory> 在服务器上建立指定目录
NLST <directory> 列出指定目录内容
NOOP 无动作,除了来自服务器上的承认
PASS <password> 系统登录密码
PASV 请求服务器等待数据连接
PORT <address> IP 地址和两字节的端口 ID
PWD 显示当前工作目录
QUIT 从 FTP 服务器上退出登录
REIN 重新初始化登录状态连接
REST <offset> 由特定偏移量重启文件传递
RETR <filename> 从服务器上找回(复制)文件
RMD <directory> 在服务器上删除指定目录
RNFR <old path> 对旧路径重命名
RNTO <new path> 对新路径重命名
SITE <params> 由服务器提供的站点特殊参数
SMNT <pathname> 挂载指定文件结构
STAT <directory> 在当前程序或目录上返回信息
STOR <filename> 储存(复制)文件到服务器上
STOU <filename> 储存文件到服务器名称上
STRU <type> 数据结构(F=文件,R=记录,P=页面)
SYST 返回服务器使用的操作系统
TYPE <data type> 数据类型(A=ASCII,E=EBCDIC,I=binary)
USER <username>> 系统登录的用户名


FTP响应码
响应代码  解释说明 
110 新文件指示器上的重启标记
120 服务器准备就绪的时间(分钟数)
125 打开数据连接,开始传输
150 打开连接
200 成功
202 命令没有执行
211 系统状态回复
212 目录状态回复
213 文件状态回复
214 帮助信息回复
215 系统类型回复
220 服务就绪
221 退出网络
225 打开数据连接
226 结束数据连接
227 进入被动模式(IP 地址、ID 端口)
230 登录因特网
250 文件行为完成
257 路径名建立
331 要求密码
332 要求帐号
350 文件行为暂停
421 服务关闭
425 无法打开数据连接
426 结束连接
450 文件不可用
451 遇到本地错误
452 磁盘空间不足
500 无效命令
501 错误参数
502 命令没有执行
503 错误指令序列
504 无效命令参数
530 未登录网络
532 存储文件需要帐号
550 文件不可用
551 不知道的页类型
552 超过存储分配
553 文件名不允许

7月7日

有趣的两道汇编题——Don't U Break Me down Today

    最近有位朋友正好要考到一些和汇编有关的题,所以打来这里要个解决方案。呵呵,我也是一下子来了兴趣,毕竟过去对汇编还是沉迷了一段时间,而且也在上机调ASM的时候成功的把一台PC搞歇了——乱调系统地址造成的(事后知道他们给拿去重装了)。Ok,题目也很简单,就两道,我写的时候加了比较多的注释,大概也是职业使然吧:)
 
   1。从strin单元开始有一串字符,,以* 为结束标志,要求字符串的长度,存入count单元,要求段说明和必要的伪指令。
解答如下:
name length_of_string
data segment ;数据段定义
string db 'dontubreakmedowntoday',2ah ;读取的字符串,2ah是*的ascii码且跟在string后
count db ? ;定义count
star equ 2ah ;定义star为*,方便后面比较
data ends
stack segment para stack'stack' ;堆栈段的定义(不是很重要,死记)
 db 100 dup(?)
stack ends
code segment ;代码段定义
 assume cs:code,ds:data,es:data,ss:stack ;定义各个段的用处
 
start: proc far
begin: push ds
 mov ax,0
 push ax
 mov ax,data
 mov ds,ax
 mov es,ax ;以上的是一个固定的初始化模式,照背就可以了
 
 lea di,string ;设置string串的地址指针
 mov dl,0  ;设置串的长度初始长度为0
 mov al,star  ;设置串的结束标记到al中
again: scasb   ;搜索串(逐字节地)
 je done  ;找到结束标记,然后跳转到停止段
 inc dl  ;计数器加1
 jmp again  ;循环
done: lea bx,count ;把count的地址入bx寄存器
 mov [bx],dl  ;把dl的赋给count
 ret
start endp
code ends
 end begin  ;game1 over
 
    2。从键盘上输入一行字符,要求第一个为space,否则end,如果是space,则开始接受键入字符,并保存在首地址为buffer的缓冲区,直到接受第二个space为止。(调用中断)
 
解答如下:
 
 name input_string_store
data segment ;数据段定义
buffer db 100 dup(?) ;预存的缓冲区(100个大小,超过机器肯定崩)
space equ 20h  ;定义空格,方便比较
data ends
stack segment para stack'stack' ;堆栈段的定义(不是很重要,死记)
 db 100 dup(?)
stack ends
code segment ;代码段定义
 assume cs:code,ds:data,es:data,ss:stack ;定义各个段的用处
  
start: proc far
begin: push ds
 mov ax,0
 push ax
 mov ax,data
 mov ds,ax
 mov es,ax ;以上的是一个固定的初始化模式,照背就可以了
  
 mov ah,1 ;将ah置1
   ;8086汇编中断21H系统功能调用全部子功能
   ;AH-01 键盘输入并回显 AL=输入字符
 int 21h ;调用中断,这两步是读取输入的步骤
 
 mov bl,al ;al中读取的字符放入bl中
 and bl,space ;比较bl和space字符
    ;如果相同,则进位标志C清0
 jc done  ;如果不同C=1,则跳转到结束,
    ;即第一个输入不是space,结束 
    ;第一个输入是space,则继续
    ;特别注意jc是根据c=1跳转的
 lea cx,buffer ;将buffer的首地址入cx寄存器
again: mov ah,1  
 int 21h  ;调用中断,获得输入的字符
 mov bl,al  ;al中读取的字符放入bl中
 and bl,space ;比较bl和space字符
    ;如果相同,则进位标志C清0
    ;如果不同C=1
 jnc done  ;当第二次输入space时C=0跳转到结束
    ;否则继续
    ;特别注意jnc是根据c=0跳转的
 mov [cx],bl  ;将bl中的字符入cx当前指针
    ;指向的地址(即缓冲区)
 inc cx  ;cx的地址自增,指向buffer的下一个存放位置
 jmp again  ;循环
done: ret
start endp
code ends
 end begin  ;game2 over
 
5月8日

强名称工具(SN.exe)

.NET Framework 工具 
强名称工具 (Sn.exe) 

强名称工具有助于使用强名称对程序集进行签名。Sn.exe 提供用于密钥管理、签名生成和签名验证的选项。

sn [-quiet][option [parameter(s)]]

参数

 

选项 说明

-c [csp]

将默认加密服务提供程序 (CSP) 设置为用于强名称签名。此设置应用于整台计算机。如果不指定 CSP 名称,则 Sn.exe 将清除当前设置。

-d container

从强名称 CSP 中删除指定的密钥容器。

-D assembly1 assembly2

验证两个程序集是否只是签名不同。这经常用作使用不同的密钥对重新为程序集创建签名后的检查。

-e assembly outfile

assembly 中提取公钥并将其存储在 outfile 中。

-h

显示该工具的命令语法和选项。

-i infile container

从指定密钥容器中的 infile 安装密钥对。密钥容器位于强名称 CSP 中。

-k [keysize] outfile

生成一个指定大小的新 RSACryptoServiceProvider 密钥并将其写入指定的文件。公钥和私钥都写入该文件。

如果不指定密钥大小,并且已安装了 Microsoft Enhanced Cryptographic Provider,则默认情况下生成 1,024 位的密钥;否则,生成 512 位的密钥。

如果安装了 Microsoft Enhanced Cryptographic Provider,则 keysize 参数支持 384 位至 16,384 位(增量为 8 位)的密钥长度。如果安装了 Microsoft Base Cryptographic Provider,则支持 384 位至 512 位(增量为 8 位)的密钥长度。

-m [y|n]

指定密钥容器是计算机特定的还是用户特定的。如果指定 y,则密钥容器是计算机特定的。如果指定 n,则密钥容器是用户特定的。

如果既没有指定 y 也没有指定 n,则此选项显示当前设置。

-o infile [outfile]

infile 中提取公钥并将其存储在 .csv 文件中。公钥的每一字节都由逗号分隔。这种格式对于通过硬编码在源代码中将公钥作为初始化数组引用很有用。如果不指定 outfile,则此选项将输出放到剪贴板上。

-p infile outfile

infile 中的密钥对提取公钥并将其存储在 outfile 中。此公钥可用于通过程序集链接器 (Al.exe)/delaysign+ /keyfile 选项对程序集进行延迟签名。如果延迟创建程序集的签名,则在编译时只设置公钥,并在文件中为以后知道公钥时添加的签名保留空间。

-pc container outfile

container 中的密钥对中提取公钥并将其存储在 outfile 中。

-q[uiet]

指定安静模式;取消显示成功消息。

-R[aassembly infile

使用 infile 中的密钥对,重新签名先前已签名的程序集或延迟已签名的程序集。

如果使用 -Ra,则重新计算程序集中所有文件的哈希。

-Rc[aassembly container

使用容器中的密钥对,重新签名先前已签名的程序集或延迟已签名的程序集。 

如果使用 -Rca,则重新计算程序集中所有文件的哈希。

-Rh assembly

重新计算程序集中所有文件的哈希。

-t[p] infile

显示存储在 infile 中的公钥的标记。infile 的内容必须是以前使用 -p 从密钥对文件生成的公钥。不要使用 -t[p] 选项直接从密钥对文件提取该标记。

Sn.exe 使用公钥中的哈希函数计算该标记。为节省空间,公共语言运行库在记录对具有强名称的程序集的依赖性时,将公钥标记存储在清单中,作为对另一个程序集的引用的一部分。-tp 选项除显示标记外还显示公钥。

请注意,此选项不验证程序集签名,而且不应用于做出信任决策。此选项仅显示原始公钥标记数据。

-T[p] assembly

显示 assembly 的公钥标记。assembly 必须是包含程序集清单的文件的名称。

Sn.exe 使用公钥中的哈希函数计算该标记。为节省空间,公共语言运行库在记录对具有强名称的程序集的依赖性时,将公钥标记存储在清单中,作为对另一个程序集的引用的一部分。-Tp 选项除显示标记外还显示公钥。

请注意,此选项不验证程序集签名,而且不应用于做出信任决策。此选项仅显示原始公钥标记数据。

-v assembly

验证 assembly 中的强名称,其中 assembly 是包含程序集清单的文件名。

-vf assembly

验证 assembly 中的强名称。与 -v 选项不同,-vf 强制实施验证,即使已使用 -Vr 选项禁用了验证。

-Vl

列出此计算机上的强名称验证的当前设置。

-Vr assembly [userlist] [infile]

注册 assembly 以跳过验证。或者,可以指定用逗号分隔的用户名列表。如果指定 infile,则验证保持启用,但 infile 中的公钥将用于验证操作。可以 *, strongname 的形式指定程序集,以注册所有具有指定强名称的程序集。 Strongname 应指定为十六进制数字的字符串以表示标记形式的公钥。参见 -t-T 选项以显示公钥标记。

Caution note警告

仅在开发期间使用此选项。将程序集添加到跳过验证列表会产生安全漏洞。如果将某程序集添加到跳过验证列表中,则恶意的程序集可以通过使用该程序集的完全限定程序集名称来隐藏身份,完全限定程序集名称由程序集名称、版本、区域性和公钥标记组成。这使恶意程序集也可以跳过验证。

-Vu assembly

注销 assembly,不跳过验证。应用于 -Vr 的同一程序集命名规则也应用于 -Vu

-Vx

移除所有验证跳过项。

-?

显示该工具的命令语法和选项。

Note注意

所有 Sn.exe 选项都区分大小写,并且必须完全按上面显示的样子键入才可以被此工具识别。

备注备注

-R–Rc 选项对被延迟签名的程序集很有用。在此方案中,编译时只设置公钥,并且以后知道私钥时执行签名。

示例示例

下面的命令创建一个新的随机密钥对并将其存储在 keyPair.snk 中。

sn -k keyPair.snk

下面的命令将 keyPair.snk 中的密钥存储在强名称 CSP 中的容器 MyContainer 中。

sn -i keyPair.snk MyContainer

下面的命令从 keyPair.snk 中提取公钥并将其存储在 publicKey.snk 中。

sn -p keyPair.snk publicKey.snk

下面的命令显示公钥和 publicKey.snk 包含的公钥的标记。

sn -tp publicKey.snk

下面的命令验证程序集 MyAsm.dll

sn -v MyAsm.dll

下面的命令从默认 CSP 中删除 MyContainer

sn -d MyContainer
5月3日

程序集链接器(AL.exe)

.NET Framework 工具 
程序集链接器 (Al.exe) 

“程序集链接器”从一个或多个文件(可以是模块,也可以是资源文件)生成一个带有程序集清单的文件。模块是不含程序集清单的“Microsoft 中间语言”(MSIL) 文件。

al sources options

参数

您可以指定以下一个或多个 sources

 
说明

file[,target]

file(模块)的内容复制到 target 指定的文件名。复制后,Al.exe 将 target 编译为程序集。

/embed[resource]:file[,name[,private]]

file 指定的资源嵌入到包含程序集清单的映像中;Al.exe 将 file 的内容复制到可移植可执行 (PE) 映像中。

name 参数是资源的内部标识符。默认情况下,资源在程序集中是公共的(对于其他程序集可见)。指定 private 会使该资源对于其他程序集不可见。

例如,如果 file 是由资源文件生成器 (Resgen.exe) 创建或在开发环境中创建的 .NET Framework 资源文件,则可使用 System.Resources 命名空间的成员来访问它。有关更多信息,请参见 System.Resources.ResourceManager 类。对于所有其他资源,请使用 System.Reflection.Assembly 类中的 GetManifestResource* 方法在运行时访问资源。

如果只将资源文件传递给 Al.exe,则输出文件会是附属资源程序集。

/link[resource]:file[,name[,target[,private]]]

将资源文件链接到程序集。file 指定的资源成为程序集的组成部分;不复制该文件。file 参数可以是任何文件格式。例如,可以指定本机 DLL 作为 file 参数。这将使本机 DLL 成为程序集的组成部分,从而可将它安装到全局程序集缓存中,并且可以通过该程序集中的托管代码访问它。也可以通过使用 /linkresource 编译器选项实现该目的。有关更多信息,请参见 /linkresource(链接到 .NET Framework 资源)(C# 编译器选项)

name 参数是资源的内部标识符。target  参数指定 Al.exe 将 file  复制到其中的路径和文件名。复制后,Al.exe 将 target 编译为程序集。默认情况下,资源在程序集中是公共的(对于其他程序集可见)。指定 private 会使该资源对于其他程序集不可见。

例如,如果 file 是由资源文件生成器 (Resgen.exe) 创建或在开发环境中创建的 .NET Framework 资源文件,则可使用 System.Resources 命名空间中的成员来访问它。有关更多信息,请参见 System.Resources.ResourceManager。对于所有其他资源,请使用 System.Reflection.Assembly 类中的 GetManifestResource* 方法在运行时访问资源。

如果只将资源文件传递给 Al.exe,则输出文件会是附属资源程序集。

您可以指定以下“选项”;请注意,必须指定 /out 

 

选项 说明

/algid:id

指定一种算法以散列多文件程序集中的所有文件,包含程序集清单的文件除外。默认算法是 CALG_SHA1。有关其他算法,请参见 Platform SDK 文档中的 ALG_ID。对于 .NET Framework 的第一版,只有 CALG_SHA1 和 CALG_MD5 是有效的。

哈希值存储在程序集清单的文件表中。在安装和加载时,会对照相应的哈希值检查程序集文件。

还可以将此选项指定为任何模块的源代码中的自定义属性 (System.Reflection.AssemblyAlgorithmIdAttribute)。

/base[address]:addr

指定一个地址,运行时在用户计算机上在该地址加载 DLL。如果指定 DLL 的基址,而不是让操作系统在进程空间重新定位 DLL,应用程序的加载就会快一些。

/bugreport:filename

创建包含有关报告 bug 的信息的文件 (filename)。

/comp[any]:text

为程序集中的 Company 字段指定字符串。如果 text 包含空格,则将字符串放置在双引号 (" ") 中。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果不指定 /win32restext 就会在 Microsoft Windows 资源管理器中显示为文件的 Company 属性。如果指定 /win32res,所指定资源文件中的公司信息就会在 Windows 资源管理器中显示为 Company 属性。

如果文本是空字符串 (""),Win32 Company 资源就会显示为一个空格。

如果指定 /win32res/company 就不会影响 Win32 资源信息。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (System.Reflection.AssemblyCompanyAttribute)。

/config[uration]:text

为程序集中的 Configuration 字段指定字符串。如果 text 包含空格,则将字符串放置在双引号 (" ") 中。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果文本是空字符串,Win32 Configuration 资源就会显示为一个空格。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (System.Reflection.AssemblyConfigurationAttribute)。

/copy[right]:text

为程序集中的 Copyright 字段指定字符串。如果 text 包含空格,则将字符串放置在双引号 (" ") 中。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果不指定 /win32res/copyright 在 Windows 资源管理器中将显示为 Win32 Copyright 资源。

如果文本是空字符串,Win32 Copyright 资源就会显示为一个空格。

如果指定 /win32res/copyright 就不会影响 Win32 资源信息。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (System.Reflection.AssemblyCopyrightAttribute)。

/c[ulture]:text

指定要与程序集相关联的区域性字符串。区域性的有效值是名为“Tags for the Identification of Languages”(语言标识的标记)的 Internet Requests for Comments (RFC)(Internet 注释请求)文档 1766 定义的那些值。

如果 text 包含空格,则将字符串放置在双引号 (" ") 中。没有默认的区域性字符串。使用反射可以查看此字符串。

有关有效的 text 字符串的信息,请参见 CultureInfo

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyCultureAttribute)。

/delay[sign][+|-]

指定程序集是完全签名的还是部分签名的。如果需要完全签名的程序集,则使用 /delaysign-。如果只想将公钥放在程序集中,则使用 /delaysign+

在请求完全签名的程序集时,Al.exe 散列包含清单(程序集元数据)的文件,并使用私钥对该散列签名。产生的数字签名存储在包含清单的文件中。在对程序集延迟签名时,Al.exe 并不计算和存储签名,而只是在文件中保留空间以便以后可以添加签名。

默认值为 /delaysign-

如果不与 /keyfile/keyname 一起使用,/delaysign 选项将无效。

例如,使用 /delaysign+ 将允许测试人员把程序集放入全局缓存中。测试完成后,可以通过将私钥放入程序集中对程序集进行完全签名。

Note注意

使用全局程序集缓存工具 (Gacutil.exe) 将延迟签名的程序集放入全局缓存中之前,请使用强名称工具 (Sn.exe) 注册该程序集,以跳过验证。例如 Sn.exe –Vr delaySignedAssembly。仅将它用于开发。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyDelaySignAttribute)。

/descr[iption]:text

为程序集中的 Description 字段指定一个字符串。如果 text 包含空格,则将字符串放置在双引号 (" ") 中。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果不指定 /win32res/description 在 Windows 资源管理器中就会显示为 Win32 Comments 资源。

如果文本是空字符串,Win32 Comments 资源就会显示为一个空格。

如果指定 /win32res/description 就不会影响 Win32 资源信息。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (Description)。

/e[vidence]: file

使用资源名称 Security.Evidence file 嵌入到程序集中。

对常规资源不能使用 Security.Evidence

/fileversion: version

为程序集中的 File Version 字段指定字符串。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果不指定 /win32res/fileversion 就会作为 Win32 File Version 资源使用。如果不指定 /fileversion,Win32 File Version 资源就会被 Win32 Assembly Version 资源填充。

如果指定 /win32res/fileversion 就不会影响 Win32 资源。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyFileVersionAttribute)。

/flags: flags

指定程序集中 Flags 字段的值。flags 的可能值有:

0x0000

程序集是相邻兼容的。

0x0010

程序集无法与其他版本在同一应用程序域中一起执行。

0x0020

程序集无法与其他版本在同一进程中一起执行。

0x0030

程序集无法与其他版本在同一计算机上一起执行。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyFlagsAttribute)。

/fullpaths

使 Al.exe 对在错误信息中报告的任何文件使用绝对路径。

/help

显示该工具的命令语法和选项。

/keyf[ile]: filename

指定一个文件 (filename),该文件包含密钥对,或只包含用于对程序集进行签名的公钥。编译器在程序集清单中插入公钥,然后使用私钥对最终的程序集签名。有关生成密钥文件和将密钥对安装到密钥容器的信息,请参见强名称工具 (Sn.exe)

如果使用延迟签名,此文件通常会具有公钥而不是私钥。

(密钥对的)公钥信息显示在程序集的 .publickey 字段中。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyKeyFileAttribute)。

如果在同一编译中同时指定了 /keyfile/keyname(通过命令行选项或者通过自定义属性),Al.exe 将首先尝试由 /keyname 指定的容器。如果成功,则使用密钥容器中的信息对程序集进行签名。如果 Al.exe 没有找到密钥容器,它将尝试由 /keyfile 指定的文件。如果成功,则使用密钥文件中的信息对程序集签名,并且将密钥信息安装到密钥容器中(类似于 Sn.exe 中的 -i 选项),以便在下一次编译中,/keyname 选项可以生效。

/keyn[ame]: text

指定保存密钥对的容器。这样将会通过将公钥插入程序集清单来对程序集签名(为它指定一个强名称)。然后 Al.exe 使用私钥对最终程序集签名。

使用 Sn.exe 生成密钥对。

密钥信息在程序集的 .publickey 字段中显示。

如果有嵌入的空格,请用双引号 (" ") 将 text 引起来。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyKeyNameAttribute)。

/main: method

指定方法的完全限定名称 (class.method),以用作将模块转换为可执行文件时的入口点。

/nologo

调用 Al.exe 时,在命令行取消显示版权标志或徽标。

/out: filename

指定 Al.exe 产生的文件的名称,这是必选项。

/platform:text

限制可以运行该代码的平台;必须为 x86、Itanium、x64 或 anycpu(默认值)之一。

/prod[uct]: text

为程序集中的 Product 字段指定字符串。如果 text 包含空格,则将字符串放置在双引号 (" ") 中。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果不指定 /win32res/product 将在 Windows 资源管理器中显示为 Win32 Product Name 资源。

如果文本是空字符串,Win32 Product Name 资源就会显示为一个空格。

如果指定 /win32res/product 就不会影响 Win32 资源信息。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyProductAttribute)。

/productv[ersion]: text

为程序集中的 Product Version 字段指定字符串。如果 text 包含空格,则将字符串放置在双引号 (" ") 中。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果不指定 /win32res/productversion 将作为 Win32 Product Version 资源使用。如果不指定 /productversion,Win32 Product Version 资源就会被 Win32 File Version 资源填充。

如果指定 /win32res/productversion 就不会影响 Win32 资源信息。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyInformationalVersionAttribute)。

/t[arget]:lib[rary] | exe | win[exe]

指定输出文件的文件格式:lib[rary](代码库)、exe(控制台应用程序)或 win[exe](基于 Windows 的应用程序)。默认值为 lib[rary]

/template: filename

指定程序集 filename,除区域性字段之外的所有程序集元数据都从该程序集继承。指定的 filename 必须有强名称。

/template 创建的程序集将是附属程序集。

/title: text

为程序集中的 Title 字段指定字符串。如果 text 包含空格,则将字符串放置在双引号 (" ") 中。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果不指定 /win32res/title 就会在 Windows 资源管理器中显示为 Win32 Description 资源,外壳程序将其用作应用程序的友好名称。如果某个文件类型有多个支持应用程序,则该字符串也会出现在此文件类型的快捷菜单的“打开方式”子菜单中。

如果文本是空字符串,Win32 Description 资源就会显示为一个空格。

如果指定 /win32res/title 就不会影响 Win32 资源信息。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyTitleAttribute)。

/trade[mark]: text

为程序集中的 Trademark 字段指定字符串。如果 text 包含空格,则将字符串放置在双引号 (" ") 中。此字符串是程序集上的自定义属性,可以使用反射进行查看。

如果不指定 /win32res/trademark 在 Windows 资源管理器中就会显示为 Win32 Trademark 资源。

如果文本是空字符串,Win32 Trademark 资源就会显示为一个空格。

如果指定 /win32res/trademark 就不会影响 Win32 资源信息。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyTrademarkAttribute)。

/v[ersion]: version

指定此程序集的版本信息。版本字符串的格式为 major.minor.build.revision。默认值为 0。

如果指定 /version,则必须指定 major。如果指定 majorminor,则可以指定一个星号 (*) 作为 build。这样会使 build 等于从当地时间 2000 年 1 月 1 日算起的天数,使 revision 等于从当地时间 2000 年 1 月 1 日午夜算起的秒数的一半。

如果指定 majorminorbuild,则可以指定一个星号作为 revision。这会使 revision 等于从当地时间 2000 年 1 月 1 日午夜算起的秒数的一半。

概括而言,有效的版本字符串有:

X

X.X

X.X.*

X.X.X

X.X.X.*

X.X.X.X

其中 X 是 0 至 65534 之间(不含 65535)的任何一个无符号短常数。

如果不指定 /win32res/version 将作为 Win32 Assembly Version 资源使用。

如果不指定 /win32res/productversion/fileversion/version 将用于 Assembly Version、File Version 和 Product Version Win32 资源。

如果指定 /win32res/version 就不会影响 Win32 资源信息。

还可以将此选项指定为任何 MSIL 模块的源代码中的自定义属性 (AssemblyVersionAttribute)。

/win32icon: filename

在程序集中插入 .ico 文件。.ico 文件在 Windows 资源管理器中赋予输出文件所需的外观。

/win32res: filename

在输出文件中插入 Win32 资源(.res 文件)。Win32 资源文件可以用资源编译器创建。编译 Visual C++ 程序时将调用资源编译器;.res 文件是从 .rc 文件创建的。

@filename

指定包含 Al.exe 命令的响应文件。

响应文件中的命令既可以每行显示一个,也可以显示在同一行中,用一个或多个空格分隔。

/?

显示该工具的命令语法和选项。

备注备注

所有 Visual Studio 编译器都产生程序集。但是,如果您有一个或多个模块(没有清单的元数据),则可使用 Al.exe 在单独的文件中创建带清单的程序集。

要在缓存中安装程序集,从缓存中删除程序集,或列出缓存内容,请使用全局程序集缓存工具 (Gacutil.exe)

示例示例

以下命令使用 t2.netmodule 模块中的程序集创建可执行文件 t2a.exe。入口点是 MyClass 中的 Main 方法。

al t2.netmodule /target:exe /out:t2a.exe /main:MyClass.Main

4月28日

本机映像生成器(Ngen.exe)

.NET Framework 工具 
本机映像生成器 (Ngen.exe) 

本机映像生成器 (Ngen.exe) 是一个提高托管应用程序性能的工具。Ngen.exe 创建本机映像(包含经编译的特定于处理器的机器代码的文件),并将它们安装到本地计算机上的本机映像缓存中。运行库可从缓存中使用本机映像,而不是使用实时 (JIT) 编译器编译原始程序集。

在 .NET Framework 2.0 版中,Ngen.exe 有了很大变化:

  • 安装程序集时还将安装其依赖项,从而简化了 Ngen.exe 的语法。

  • 现在可以在应用程序域之间共享本机映像。

  • 可利用新增操作 update 重新创建已经失效的映像。

  • 操作可由计算机上使用空闲时间生成和安装映像的服务推迟执行。

  • 消除了一些导致映像无效的因素。

有关如何使用 Ngen.exe 和本机映像服务的其他信息,请参见本机映像服务

Note注意

本机映像生成器 (Ngen.exe) 旧式语法中可以找到 .NET Framework 1.0 和 1.1 版的 Ngen.exe 语法。

ngen <action> [options]
ngen /? | /help
操作操作

下表说明了每个操作的语法。有关 actionArguments 各部分的说明,请参见参数方案配置表。选项表描述了 options 和帮助开关。

 

操作 说明

install [assemblyName | assemblyPath] [scenarios] [config] [/queue[:{1|2|3}]]

生成程序集及其依赖项的本机映像,并在本机映像缓存中安装这些映像。

如果指定了 /queue,则操作将排队等待本机映像服务。默认优先级是 3。

uninstall [assemblyName | assemblyPath | *] [scenarios] [config]

将程序集及其依赖项的本机映像从本机映像缓存中删除。

若要卸载单个映像及其依赖项,可使用与安装此映像时相同的命令行参数。

update [/queue]

更新已无效的本机映像。

如果指定了 /queue,则更新将排队以等待本机映像服务。更新的优先级总是预先设定为 3,因此它们在计算机空闲时运行。

display [assemblyName | assemblyPath]

显示程序集及其依赖项的本机映像的状态。

如果未提供参数,则显示本机映像缓存中的所有内容。

executeQueuedItems [1|2|3]

执行排队的编译作业。

如果指定了优先级,则执行具有较高或同等优先级的编译作业。如果未指定优先级,则执行所有排队的编译作业。

queue {pause | continue | status}

暂停本机映像服务,允许暂停的服务继续,或查询服务状态。

参数参数
 

参数 说明

assemblyName

程序集的名称。可提供程序集的部分名称(如 myAssembly),也可提供完整的显示名称(如 myAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0038abc9deabfle5)。

每个 Ngen.exe 命令行只能指定一个程序集。

assemblyPath

程序集的显式路径。可指定完整路径或相对路径。

如果指定文件名而不指定路径,则程序集必须位于当前目录中。

每个 Ngen.exe 命令行只能指定一个程序集。

方案方案
 

方案 说明

/Debug

生成可在调试器下使用的本机映像。

/Profile

生成可在探查器下使用的本机映像。

/NoDependencies

生成指定方案选项所需的最小数目的本机映像。

配置配置
 

配置 说明

/ExeConfig: exePath

使用指定的可执行程序集的配置。

绑定到依赖项时,Ngen.exe 需要作出与加载程序相同的决策。如果在运行时使用 Load 方法加载共享组件,应用程序的配置文件决定了为该共享组件加载的依赖项,例如,所加载依赖项的版本。/ExeConfig 开关就运行时将加载哪些依赖项向 Ngen.exe 给出指导。

/AppBase: directoryPath

查找依赖项时,使用指定目录作为应用程序基础。

选项选项
 

选项 说明

/nologo

禁止显示 Microsoft 启动版权标志。

/silent

禁止显示成功消息。

/verbose

显示详细的调试信息。

Note注意

由于操作系统限制,此选项显示的附加信息比在 Windows 98 和 Windows Millennium Edition 上显示的少。

/help, /?

显示当前版本的命令语法和选项。

备注备注

若要运行 Ngen.exe,您必须具有管理特权。

Ngen.exe 为指定程序集及其所有依赖项生成本机映像。依赖项是根据程序集清单中的引用来确定的。仅当应用程序使用反射(例如通过调用 System.Reflection.Assembly.Load 方法)来加载依赖项的情况下才需要单独安装依赖项。

Note要点

不要将 System.Reflection.Assembly.LoadFrom 方法用于本机映像。使用此方法加载的映像不能由执行上下文中的其他程序集使用。

Ngen.exe 维护着一个与依赖项有关的计数。例如,假设本机映像缓存中同时安装了 MyAssembly.exeYourAssembly.exe,而且它们都具有对 OurDependency.dll 的引用。如果卸载了 MyAssembly.exe,则不会卸载 OurDependency.dll。只有当 YourAssembly.exe 也被卸载时才会将其移除。

如果为全局程序集缓存中的程序集生成本机映像,请指定其显示名称。请参见 System.Reflection.Assembly.FullName

Ngen.exe 生成的本机映像可以在应用程序域之间共享。这意味着,在要求在应用程序域之间共享程序集的应用程序方案中可以使用 Ngen.exe。若要指定域非特定性:

将同一个程序集加载到多个应用程序域中时,总是使用非特定于域的代码。如果本机映像在已加载到共享域之后又被加载到非共享的应用程序域中,则该映像将无法使用。

Note注意

非特定于域的代码无法卸载,并且性能可能会稍微降低,尤其是在访问静态成员时。

为不同的方案生成映像为不同的方案生成映像

在您生成一个程序集的本机映像后,每当运行库运行该程序集时,都会自动尝试找到并使用该本机映像。根据使用方案的不同,可生成多个映像。

例如,如果您在调试或分析方案中运行程序集,则运行库将查找利用 /Debug/Profile 选项生成的本机映像。如果运行库无法找到匹配的本机映像,它将恢复为标准的 JIT 编译。调试本机映像的唯一方式是使用 /Debug 选项创建本机映像。

uninstall 操作也能识别方案,因此您可以卸载所有方案或只卸载选择的方案。

确定何时使用本机映像确定何时使用本机映像

本机映像可从两方面提高性能:改善内存使用情况和减少启动时间。

Note注意

本机映像的性能取决于很多因素,这些因素使得分析难以进行,如代码和数据访问模式,有多少调用跨模块边界进行,以及多少依赖项已由其他应用程序加载。确定本机映像是否对应用程序有利的唯一方式是在关键部署方案中仔细进行性能测量。

改善内存使用情况

当代码在进程间共享时,本机映像可显著改善内存使用情况。本机映像为 Windows PE 文件,因此一个 .dll 文件的单个副本可由多个进程共享;而 JIT 编译器生成的本机代码存储在私有内存中,并且不可共享。

运行于终端服务下的应用程序也可从共享代码页中获益。

此外,不加载 JIT 编译器会为每个应用程序实例节省固定量的内存。

更快的应用程序启动速度

使用 Ngen.exe 预编译程序集可减少某些应用程序的启动时间。通常,如果应用程序共享组件程序集,则可从中获益,因为在第一个应用程序启动之后,共享组件即已加载,可供后续应用程序使用。而冷启动(应用程序中的所有程序集必须从硬盘上加载)则不会从本机映像中获得相同的益处,因为硬盘访问时间占了很大比重。

硬绑定可影响启动时间,因为硬绑定至主应用程序程序集的所有映像必须同时加载。

Note注意

如果您有强命名的共享组件,则请将它们放置在全局程序集缓存中。加载程序对未处于全局程序集缓存中的强命名程序集执行额外验证,实际抵消了使用本机映像在启动时间方面获得的任何改善。

程序集基址的重要性

因为本机映像为 Windows PE 文件,所以它们和其他可执行文件一样有着相同的重定基址问题。如果采用硬绑定,则重定位的性能开销甚至更显著。

若要设置本机映像的基址,请使用编译器的相应选项设置程序集的基址。Ngen.exe 对本机映像使用此基址。

Note注意

本机映像大于创建它时所基于的托管程序集。基址必须进行计算以允许使用这些更大的大小。

可使用 dumpbin.exe 之类的工具查看本机映像的首选基址。

使用注意事项摘要

下面的常规注意事项和应用程序注意事项可能有助于您决定是否对应用程序本机映像进行评估:

  • 本机映像的加载速度比 MSIL 更快,因为本机映像不必执行很多启动操作,如 JIT 编译和类型安全验证。

  • 本机映像需要较小的初始工作集,因为它不需要 JIT 编译器。

  • 本机映像允许在进程间共享代码。

  • 本机映像需要占用比 MSIL 程序集更大的硬盘空间,并且可能需要相当长的时间才能生成。

  • 必须维护本机映像。

    • 在提供原始程序集或原始程序集的某个依赖项后,需要重新生成映像。

    • 一个程序集可能需要多个本机映像,分别用在不同应用程序或不同方案中。例如,两个应用程序中的配置信息可能为同一个依赖程序集生成不同的绑定方案。

    • 必须由管理员(也就是从 Administrators 组中的某个 Windows 帐户)来生成本机映像。

除了这些常规注意事项之外,在确定本机映像是否可提供性能益处时,必须考虑应用程序的性质:

  • 如果应用程序运行于使用很多共享组件的环境中,本机映像允许多个进程共享组件。

  • 如果应用程序使用多个应用程序域,本机映像允许跨域共享代码页。

    Note注意

    在 .NET Framework 1.0 和 1.1 版中,本机映像不能跨应用程序域共享。而在 2.0 版中则可以。

  • 如果应用程序将在终端服务器下运行,本机映像允许共享代码页。

  • 编译为本机映像通常有利于大型应用程序。小型应用程序通常不会获益。

  • 对于长时间运行的应用程序,运行时 JIT 编译的性能略高于本机映像。(硬绑定某种程度上可减少这一性能差别。)

硬绑定硬绑定

硬绑定增加吞吐量并减少本机映像的工作集大小。硬绑定的缺点是硬绑定到程序集的所有映像在加载程序集时必须都加载。对于大型应用程序,这会大大增加启动时间。

硬绑定适合于在所有应用程序性能关键的方案中加载的依赖项。与本机映像使用情况的任何方面一样,仔细测量性能是确定硬绑定是否可改善应用程序性能的唯一方式。

DependencyAttributeDefaultDependencyAttribute 属性可使您向 Ngen.exe 提供硬绑定提示。

Note注意

这些属性是对 Ngen.exe 的提示,而不是命令。使用这些提示不保证进行硬绑定。这些属性的含义在将来的版本中可能会更改。

为依赖项指定绑定提示

DependencyAttribute 应用于程序集可指示加载指定依赖项的可能性。System.Runtime.CompilerServices.LoadHint.Always 指示适合进行硬绑定,Default 指示应使用依赖项的默认提示,而 Sometimes 则指示不适合使用硬绑定。

下面的代码显示有两个依赖项的程序集的属性。第一个依赖项 (Assembly1) 适合于进行硬绑定,而第二个 (Assembly2) 不适合进行硬绑定。

Visual Basic
Imports System.Runtime.CompilerServices
<Assembly:DependencyAttribute("Assembly1", LoadHint.Always)>
<Assembly:DependencyAttribute("Assembly2", LoadHint.Sometimes)>
using System.Runtime.CompilerServices;
[assembly:DependencyAttribute("Assembly1", LoadHint.Always)]
[assembly:DependencyAttribute("Assembly2", LoadHint.Sometimes)]
using namespace System::Runtime::CompilerServices;
[assembly:DependencyAttribute("Assembly1", LoadHint.Always)];
[assembly:DependencyAttribute("Assembly2", LoadHint.Sometimes)];

程序集名称不包括文件扩展名。可使用显示名称。

为程序集指定默认绑定提示

只有某些程序集需要默认绑定提示:这些程序集将由依赖于它们的任何应用程序直接并经常使用。以 System.Runtime.CompilerServices.LoadHint.AlwaysDefaultDependencyAttribute 应用于这样的程序集可指定应使用硬绑定。

Note注意

不应将 DefaultDependencyAttribute 应用于不属于此类别的 .dll 程序集,因为以 System.Runtime.CompilerServices.LoadHint.Always 之外的其他值应用该属性的效果与根本不应用该属性相同。

Microsoft 使用 DefaultDependencyAttribute 指定硬绑定为 .NET Framework 中极少数程序集(如 mscorlib.dll)的默认绑定。

疑难解答疑难解答

若要确认应用程序正在使用本机映像,可使用程序集绑定日志查看器 (Fuslogvw.exe)。在绑定日志查看器窗口上,选择“日志类别”框中的“本机映像”。Fuslogvw.exe 提供了有关为什么拒绝本机映像的信息。

可使用 JitCompilationStart 托管调试助手 (MDA) 确定 JIT 编译器何时开始编译函数。

推迟处理推迟处理

超大型应用程序的本机映像生成过程可能需要相当长的时间。同样,更改共享组件或更改计算机设置可能需要更新很多本机映像。installupdate 操作有一个 /queue 选项,该选项将该操作排入队列,以由本机映像服务推迟执行。此外,Ngen.exe 具有 queueexecuteQueuedItems 操作,这些操作提供了对本机映像服务的某些控制。有关更多信息,请参见本机映像服务

本机映像和 JIT 编译本机映像和 JIT 编译

如果 Ngen.exe 在程序集中遇到它无法生成的任何方法,则它会将这些方法从本机映像中排除。当运行库执行此程序集时,对于那些不包括在本机映像中的方法,它将恢复为 JIT 编译。

此外,如果程序集已更新,或者本机映像出于任何原因已失效,则不会使用本机映像。

无效映像

当您使用 Ngen.exe 创建程序集的本机映像时,输出取决于您指定的命令行选项以及计算机上的某些设置。这些设置包括:

  • .NET Framework 的版本。

  • 操作系统的版本(在从 Windows 9x 系列更改为 Windows NT 系列的情况下)。

  • 程序集的确切标识(重新编译将更改标识)。

  • 程序集引用的所有程序集的确切标识(重新编译将更改标识)。

  • 安全因素。

Ngen.exe 在生成本机映像时记录这些信息。当您执行程序集时,运行库将查找用匹配计算机的当前环境的选项和设置生成的本机映像。如果运行库没有找到匹配的本机映像,它将恢复为程序集的 JIT 编译。对计算机的设置和环境进行以下更改会导致本机映像失效:

  • .NET Framework 的版本。

    如果将更新应用于 .NET Framework,则使用 Ngen.exe 创建的所有本机映像都将失效。因此,.NET Framework 的所有更新都执行 Ngen Update 命令,以确保重新生成所有的本机映像。.NET Framework 为它安装的 .NET Framework 库自动创建新的本机映像。

  • 操作系统的版本(在从 Windows 9x 系列更改为 Windows NT 系列的情况下)。

    例如,如果计算机上运行的操作系统的版本从 Windows 98 更改为 Windows XP,则存储在本机映像缓存中的所有本机映像都将失效。但是,如果将操作系统从 Windows 2000 更改为 Windows XP,则这些映像将不会失效。

  • 程序集的确切标识。

    如果重新编译程序集,则程序集的相应本机映像将失效。

  • 程序集引用的任何程序集的确切标识。

    如果更新一个托管程序集,则所有直接或间接依赖该程序集的本机映像都将失效,并需要重新生成。这既包括一般引用,也包括硬绑定依赖项。每当应用软件更新,安装程序就应执行 Ngen Update 命令,以确保重新生成所有依赖的本机映像。

  • 安全因素。

    更改计算机安全策略以限制先前授予某个程序集的权限,这样会导致该程序集的先前编译的本机映像失效。

    有关公共语言运行库如何管理代码访问安全以及如何使用权限的详细信息,请参见代码访问安全性

示例示例

下面的命令为当前目录中的 ClientApp.exe 生成本机映像,并在本机映像缓存中安装该映像。如果该程序集存在配置文件,Ngen.exe 将使用它。此外,还会为 ClientApp.exe 所引用的任何 .dll 文件生成本机映像。

ngen install ClientApp.exe

使用 Ngen.exe 安装的映像也称为根。根可以为应用程序或共享组件。

下面的命令生成具有指定路径的 MyAssembly.exe 的本机映像。

ngen install c:\myfiles\MyAssembly.exe

当查找程序集及其依赖项时,Ngen.exe 使用与公共语言运行库所使用的相同的探查逻辑。默认情况下,包含 ClientApp.exe 的目录用作应用程序基目录,所有程序集的探查均从此目录开始。使用 /AppBase 选项可重写此行为。

Note注意

这是对 .NET Framework 1.0 和 1.1 版中的 Ngen.exe 行为的更改,在这些版本中,应用程序基目录设置为当前目录。

程序集可以具有不带引用的依赖项(例如,它使用 System.Reflection.Assembly.Load 方法加载 .dll 文件)。您可以使用 /ExeConfig,使用应用程序程序集的配置信息来为这样的 .dll 文件创建本机映像。下面的命令使用 MyApp.exe 的配置信息为 MyLib.dll, 生成一个本机映像。

ngen install c:\myfiles\MyLib.dll /ExeConfig:c:\myapps\MyApp.exe

在移除应用程序时将不移除以此方式安装的程序集。

若要卸载依赖项,请使用与安装时相同的命令行选项。下面的命令卸载上面示例中的 MyLib.dll

ngen uninstall c:\myfiles\MyLib.dll /ExeConfig:c:\myapps\MyApp.exe

若要在全局程序集缓存中为程序集创建本机映像,请使用程序集的显示名称。例如:

ngen install "ClientApp, Version=1.0.0.0, Culture=neutral, 
  PublicKeyToken=3c7ba247adcd2081, processorArchitecture=MSIL"

NGen.exe 会为您安装的每个方案生成一个单独的映像集。例如,下面的命令为正常操作生成一个完整的本机映像集,为调试生成另一个完整的映像集,并为探测生成第三个映像集:

ngen install MyApp.exe
ngen install MyApp.exe /debug
ngen install MyApp.exe /profile

显示本机映像缓存

在缓存中安装本机映像后,就可使用 Ngen.exe 显示这些映像。下面的命令显示本机映像缓存中的所有本机映像。

ngen display

display 操作首先列出所有的根程序集,然后列出计算机上的所有本机映像。

使用程序集的简单名称仅显示该程序集的信息。下面的命令显示本机映像缓存中与部分名称 MyAssembly 匹配的所有本机映像、其依赖项以及所有依赖 MyAssembly 的根:

ngen display MyAssembly

了解哪些根依赖于共享组件程序集对于在共享组件升级后确定 update 操作的影响非常有用。

如果指定了程序集的文件扩展名,则必须指定路径,或从包含该程序集的目录执行 Ngen.exe:

ngen display c:\myApps\MyAssembly.exe

下面的命令显示本机映像缓存中名为 MyAssembly 、版本为 1.0.0.0 的所有本机映像。

ngen display "myAssembly, version=1.0.0.0"

更新映像

映像通常是在共享组件更新之后进行更新的。若要更新本身发生更改或者其依赖项发生了更改的所有本机映像,请不带任何参数使用 update 操作。

ngen update

更新所有映像可能会耗费很长时间。使用 /queue 选项可对更新操作进行排队以等候本机映像服务执行。有关 /queue 选项和安装优先级的更多信息,请参见本机映像服务

ngen update /queue

卸载映像

Ngen.exe 维护依赖项的列表,所以,只有当依赖于这些共享组件的所有程序集都被移除后,才会移除这些共享组件。此外,已安装为根的共享组件不会被移除。

下面的命令卸载根 ClientApp.exe 的所有方案:

ngen uninstall ClientApp

uninstall 操作可用于移除特定方案。下面的命令卸载 ClientApp.exe 的所有调试方案:

ngen uninstall ClientApp /debug
Note注意

卸载 /debug 方案不会卸载同时包含 /profile/debug. 的方案

下面的命令卸载特定版本的 ClientApp.exe 的所有方案:

ngen uninstall "ClientApp, Version=1.0.0.0"

下面的命令卸载 "ClientApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3c7ba247adcd2081, processorArchitecture=MSIL", 的所有方案,或者只卸载该程序集的调试方案:

ngen uninstall "ClientApp, Version=1.0.0.0, Culture=neutral, 
  PublicKeyToken=3c7ba247adcd2081, processorArchitecture=MSIL"
ngen uninstall "ClientApp, Version=1.0.0.0, Culture=neutral, 
  PublicKeyToken=3c7ba247adcd2081, processorArchitecture=MSIL" /debug

install 操作一样,如果提供了扩展名,则需要从包含该程序集的目录执行 Ngen.exe,或者指定完整路径。

有关本机映像服务的示例,请参见本机映像服务

3月21日

.net 中通用的formatstring格式符整理——转载

格式说明符 名称 说明
d 短日期模式 显示由与当前线程关联的 DateTimeFormatInfo.ShortDatePattern 属性定义的模式或者由指定格式提供程序定义的模式。
D 长日期模式 显示由与当前线程关联的 DateTimeFormatInfo.LongDatePattern 属性定义的模式或者由指定格式提供程序定义的模式。
t 短时间模式 显示由与当前线程关联的 DateTimeFormatInfo.ShortTimePattern 属性定义的模式或者由指定格式提供程序定义的模式。
T 长时间模式 显示由与当前线程关联的 DateTimeFormatInfo.LongTimePattern 属性定义的模式或者由指定格式提供程序定义的模式。
f 完整日期/时间模式(短时间) 显示长日期和短时间模式的组合,由空格分隔。
F 完整日期/时间模式(长时间) 显示由与当前线程关联的 DateTimeFormatInfo.FullDateTimePattern 属性定义的模式或者由指定格式提供程序定义的模式。
g 常规日期/时间模式(短时间) 显示短日期和短时间模式的组合,由空格分隔。
G 常规日期/时间模式(长时间) 显示短日期和长时间模式的组合,由空格分隔。
M 或 m 月日模式 显示由与当前线程关联的 DateTimeFormatInfo.MonthDayPattern 属性定义的模式或者由指定格式提供程序定义的模式。
R 或 r RFC1123 模式 显示由与当前线程关联的 DateTimeFormatInfo.RFC1123Pattern 属性定义的模式或者由指定格式提供程序定义的模式。这是定义的标准,并且属性是只读的;因此,无论所使用的区域性或所提供的格式提供程序是什么,它总是相同的。属性引用 CultureInfo.InvariantCulture 属性并遵照自定义模式“ddd, dd MMM yyyy HH:mm:ss G\MT”。请注意,“GMT”中的“M”需要转义符,因此它不被解释。格式化并不修改 DateTime 的值,所以您必须在格式化之前将值调整为 GMT。
s 可排序的日期/时间模式;符合 ISO 8601 显示由与当前线程关联的 DateTimeFormatInfo.SortableDateTimePattern 属性定义的模式或者由指定格式提供程序定义的模式。属性引用 CultureInfo.InvariantCulture 属性,格式遵照自定义模式“yyyy-MM-ddTHH:mm:ss”。
u 通用的可排序日期/时间模式 显示由与当前线程关联的 DateTimeFormatInfo.UniversalSortableDateTimePattern 属性定义的模式或者由指定格式提供程序定义的模式。因为它是定义的标准,并且属性是只读的,因此无论区域性或格式提供程序是什么,模式总是相同的。格式化遵照自定义模式“yyyy-MM-dd HH:mm:ssZ”。格式化日期和时间时不进行时区转换;所以,请在使用格式说明符之前将本地日期和时间转换为通用时间。
U 通用的可排序日期/时间模式 显示由与当前线程关联的 DateTimeFormatInfo.FullDateTimePattern 属性定义的模式或者由指定格式提供程序定义的模式。请注意,显示的时间是通用时间,而不是本地时间。
Y 或 y 年月模式 显示由与当前线程关联的 DateTimeFormatInfo.YearMonthPattern 属性定义的模式或者由指定格式提供程序定义的模式。
任何其他单个字符 未知说明符    

下表描述了标准数字格式字符串。请注意,这些格式说明符产生的输出字符串受“区域选项”控制面板中的设置的影响。使用不同设置的计算机会生成不同的输出字符串。

格式说明符 名称 说明
C 或 c 货币 数字转换为表示货币金额的字符串。转换由用于格式化数字的 NumberFormatInfo 对象的货币格式信息控制。精度说明符指示所需的小数位数。如果省略精度说明符,则使用 NumberFormatInfo 给定的默认货币精度。
D 或 d 十进制 只有整型才支持此格式。数字转换为十进制数字 (0-9) 的字符串,如果数字为负,则前面加负号。精度说明符指示结果字符串中所需的最少数字个数。如果需要的话,则用零填充该数字的左侧,以产生精度说明符给定的数字个数。
E 或 e 科学计数法(指数) 数字转换为“-d.ddd...E+ddd”或“-d.ddd...e+ddd”形式的字符串,其中每个“d”表示一个数字 (0-9)。如果该数字为负,则该字符串以减号开头。小数点前总有一个数字。精度说明符指示小数点后所需的位数。如果省略精度说明符,则使用默认值,即小数点后六位数字。格式说明符的大小写指示在指数前加前缀“E”还是“e”。指数总是由正号或负号以及最少三位数字组成。如果需要,用零填充指数以满足最少三位数字的要求。
F 或 f 固定点 数字转换为“-ddd.ddd...”形式的字符串,其中每个“d”表示一个数字 (0-9)。如果该数字为负,则该字符串以减号开头。精度说明符指示所需的小数位数。如果忽略精度说明符,则使用 NumberFormatInfo 给定的默认数值精度。
G 或 g 常规 根据数字类型以及是否存在精度说明符,数字会转换为固定点或科学记数法的最紧凑形式。如果精度说明符被省略或为零,则数字的类型决定默认精度,如下表所示。
  • ByteSByte:3
  • Int16UInt16:5
  • Int32UInt32:10
  • Int64UInt64:19
  • Single:7
  • Double:15
  • Decimal:29

如果用科学记数法表示数字时指数大于 -5 而且小于精度说明符,则使用固定点表示法;否则使用科学记数法。如果要求有小数点,并且忽略尾部零,则结果包含小数点。如果精度说明符存在,并且结果的有效数字位数超过指定精度,则通过舍入删除多余的尾部数字。使用科学记数法时,如果格式说明符是“G”,结果的指数带前缀“E”;如果格式说明符是“g”,结果的指数带前缀“e”。

上述规则有一个例外:如果数字是 Decimal 而且省略精度说明符时。在这种情况下总使用固定点表示法并保留尾部零。

N 或 n 数字 数字转换为“-d,ddd,ddd.ddd...”格式的字符串,其中每个“d”表示一个数字 (0-9)。如果该数字为负,则该字符串以减号开头。小数点左边每三个数字之间插入一个千位分隔符。精度说明符指示所需的小数位数。如果忽略精度说明符,则使用 NumberFormatInfo 给定的默认数值精度。
P 或 p 百分比 数字转换为由 NumberFormatInfo.PercentNegativePattern 属性或 NumberFormatInfo.PercentPositivePattern 属性定义的、表示百分比的字符串。如果数字为负,则产生的字符串由 PercentNegativePattern 定义并以负号开头。已转换的数字乘以 100 以表示为百分比。精度说明符指示所需的小数位数。如果省略精度说明符,则使用 NumberFormatInfo 给定的默认数值精度。
R 或 r 往返过程 往返过程说明符保证转换为字符串的数值再次被分析为相同的数值。使用此说明符格式化数值时,首先用常规格式测试:Double 使用 15 位精度,Single 使用 7 位精度。如果此值被成功地分析回相同的数值,则使用常规格式说明符对其进行格式化。但是,如果此值未被成功地分析为相同的数值,则它这样格式化:Double 使用 17 位精度,Single 使用 9 位精度。虽然精度说明符可以追加到往返过程格式说明符,但它将被忽略。使用此说明符时,往返过程优先于精度。此格式仅受浮点型支持。
X 或 x 十六进制 数字转换为十六进制数字的字符串。格式说明符的大小写指示对大于 9 的十六进制数字使用大写字符还是小写字符。例如,使用“X”产生“ABCDEF”,使用“x”产生“abcdef”。精度说明符指示结果字符串中所需的最少数字个数。如果需要的话,则用零填充该数字的左侧,以产生精度说明符给定的数字个数。只有整型才支持此格式。

如果标准数字格式说明符未提供所需的格式化类型,可以使用自定义格式字符串进一步增强字符串输出。标准格式字符串包含一个字母字符,后面可能会跟有数字序列(形成一个 0 到 99 的值);而所有其他格式字符串都是自定义格式字符串。
下表显示可以用于创建自定义数字格式字符串及其定义的字符。请注意,与当前线程关联的 NumberFormatInfo 对象的“区域选项”控制面板的设置会影响这些字符中的某些所产生的输出字符串。使用不同区域性的计算机将生成不同的输出字符串。
格式字符值说明
0零占位符如果格式化的值在格式字符串中出现“0”的位置有一个数字,则此数字被复制到输出字符串中。小数点前最左边的“0”的位置和小数点后最右边的“0”的位置确定总在输出字符串中出现的数字范围。“00”说明符使得值被舍入到小数点前最近的数字,其中零位总被舍去。例如,用“00”格式化 34.5 将得到值 35。
#数字占位符如果格式化的值在格式字符串中出现“#”的位置有一个数字,则此数字被复制到输出字符串中。否则,输出字符串中的此位置不存储任何值。请注意,如果“0”不是有效数字,此说明符永不显示“0”字符,即使“0”是字符串中唯一的数字。如果“0”是所显示的数字中的有效数字,则显示“0”字符。“##”格式字符串使得值被舍入到小数点前最近的数字,其中零总被舍去。例如,用“##”格式化 34.5 将得到值 35。
.小数点格式字符串中的第一个“.”字符确定格式化的值中的小数点分隔符的位置;任何其他“.”字符被忽略。用作小数点分隔符的实际字符由控制格式化的 NumberFormatInfo 的 NumberDecimalSeparator 属性确定。
,千位分隔符和数字比例换算“,”字符有两种用途。首先,如果格式字符串在小数点(如果有)左边的两个数字占位符(0 或 #)之间包含“,”字符,则输出将在小数点分隔符左边的每三个数字之间插入千位分隔符。输出字符串中用作小数点分隔符的实际字符由控制格式化的当前 NumberFormatInfo 的 NumberGroupSeparator 属性确定。
其次,如果格式字符串在紧邻小数点的左侧包含一个或多个“,”字符,则数字在格式化之前将被“,”字符数除然后乘以 1000。例如,格式字符串“0,,”将 100,000,000 简单表示为 100。使用“,”字符指示比例换算在格式化数字中不包括千位分隔符。因此,若要将数字缩小 1,000,000 倍并插入千位分隔符,应使用格式字符串“#,##0,,”。
%百分比占位符在格式字符串中出现“%”字符将导致数字在格式化之前乘以 100。适当的符号插入到数字本身在格式字符串中出现“%”的位置。使用的百分比字符由当前的 NumberFormatInfo 类确定。
E0
E+0
E-0
e0
e+0
e-0科学计数法如果“E”、“E+”、“E-”、“e”、“e+”或“e-”中的任何一个字符串出现在格式字符串中,而且后面紧跟至少一个“0”字符,则数字用科学计数法来格式化,在数字和指数之间插入“E”或“e”。跟在科学计数法指示符后面的“0”字符数确定指数输出的最小位数。“E+”和“e+”格式指示符号字符(正号或负号)应总是置于指数前面。“E”、“E-”、“e”或“e-”格式指示符号字符仅置于负指数前面。
\转义符在 C# 和 C++ 的托管扩展中,反斜杠字符使格式字符串中的下一个字符被解释为转义序列。它与传统的格式化序列一起使用,如“\n”(换行)。
在某些语言中,转义符本身用作文本时必须跟在转义符之后。否则,编译器将该字符理解为转义符。使用字符串“\\”显示“\”。
请注意,Visual Basic 中不支持此转义符,但是 ControlChars 提供相同的功能。
'ABC'
"ABC"字符串引在单引号或双引号中的字符被原样复制到输出字符串中,而且不影响格式化。
;部分分隔符“;”字符用于分隔格式字符串中的正数、负数和零各部分。
其他所有其他字符所有其他字符以文本形式复制到输出字符串中它们出现的位置。

请注意,对于固定点格式字符串(不包含“E0”、“E+0”、“E-0”、“e0”、“e+0”或“e-0”的字符串),数字被舍入为与小数点右边的数字占位符数目相同的小数位数。如果格式字符串不包含小数点,数字被舍入为最接近的整数。如果数字位数多于小数点左边数字占位符的个数,多余的数字被复制到输出字符串中紧挨着第一个数字占位符的前面。
可以根据值为正、为负还是为零来为字符串应用不同的格式化。为产生这种行为,自定义格式字符串可以包含最多三个用分号分隔的部分:
一个部分:格式字符串应用于所有值。
两个部分:第一部分应用于正值和零,第二部分应用于负值。如果要格式化的数字为负,但根据第二部分中的格式舍入后为零,则最终的零根据第一部分进行格式化。
三个部分:第一部分应用于正值,第二部分应用于负值,第三部分应用于零。第二部分可能为空(分号间没有任何内容),在这种情况下,第一部分应用于所有非零值。如果要格式化的数字为非零值,但根据第一部分或第二部分中的格式舍入后为零,则最终的零根据第三部分进行格式化。
格式化最终值时,此类型的格式化忽略所有先前存在的与数字关联的格式化。例如,使用部分分隔符时,显示的负值永远不带负号。如果您希望格式化后的最终值带有负号,则应明确包含负号,让它作为自定义格式说明符的组成部分。