接下来我们还是以上次的HelloBamActivity模型为例,配置一个WCF程序。通过添加一个Behavior,定义一个IC(interceptor Configuration)拦截模型。把WCf运行过程中的一些关键业务数据喂给BAM。
我们首先写一个简单的WCF 程序。客户端发送一个订单到WCF 服务。订单包括State,和 Amount以及ID,这个订单的两个属性我们认为是个关键业务数据,需要提取出来反映在BAM中。
写一个WCF 程序。 由于WCF是基于消息的应用,不像Remoting,会有对象的跨Appdomain调用,写一个remoting应用需要两个appdomain,一般来说是两个应用程序。所以很简单,我就把WCF的Client和Service 都放在一个Console程序中。 为了调试方便,采用最简单的basicHttpBinding,没有安全。这样我们可以很轻松用TCPTrace,看到消息的传递过程。编译IC中的Xpath配置。
New一个Console Application叫做SingleWCF,添加对System.ServiceModel 和 System.Runtime.Serialization 的引用。
然后新建一个Class,定义Contract,Service实现。代码如下:
using System;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Threading;
namespace SingleWCF
{
[DataContract()]
public class PO
{
[DataMember]
public float Amount;
[DataMember]
public string State;
[DataMember]
public string Id;
}
[ServiceContract()]
public interface IPOService
{
[OperationContract()]
string SubmitPo(PO po);
}
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
public class POServiceImpl : IPOService
{
#region IPOService Members
public string SubmitPo(PO po)
{
Console.WriteLine("Service get the PO");
Thread.Sleep(3000);
Console.WriteLine("Service Processed the PO");
return po.Id;
}
#endregion
}
}
然后添加一个 APP.Config 文件。来Host这个Service。文件如下,
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="enableWSDL">
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="enableWSDL" name="SingleWCF.POServiceImpl">
<endpoint address="ws" binding="basicHttpBinding" bindingConfiguration="basicBindingNoSecurity"
contract="SingleWCF.IPOService" behaviorConfiguration="bamBehavior" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8888/service" />
</baseAddresses>
</host>
</service>
</services>
<client>
<endpoint address=http://localhost:8888/service/ws behaviorConfiguration="bamBehavior" binding="basicHttpBinding" bindingConfiguration="basicBindingNoSecurity"
contract="SingleWCF.IPOService" name="clientendpoint" />
</client>
<bindings>
<basicHttpBinding>
<binding name="basicBindingNoSecurity">
<security mode="None"/>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
然后来Host Service并且调用。
using System;
using System.ServiceModel;
using System.Threading;
namespace SingleWCF
{
class Program
{
static System.Threading.ManualResetEvent mre;
static void Main(string[] args)
{
mre = new ManualResetEvent(false);
System.Threading.ThreadPool.QueueUserWorkItem(StartHost);
mre.WaitOne();
System.Threading.ThreadPool.QueueUserWorkItem(StartClient);
Console.ReadLine();
}
public static void StartHost(object o)
{
ServiceHost host = new ServiceHost(typeof(POServiceImpl));
host.Open();
Console.WriteLine("HostStarted");
mre.Set();
Console.ReadLine();
}
public static void StartClient(object o)
{
ChannelFactory<IPOService> factory = new ChannelFactory<IPOService>("clientendpoint");
IPOService client = factory.CreateChannel();
PO p=new PO();
p.Amount=120;
p.State="WA";
p.Id = Guid.NewGuid().ToString();
client.SubmitPo(p);
Console.WriteLine("PO Sent Done");
}
}
}
然后运行一下,就可以看到Service正常调用。
这时候把程序App.Config少许该一下,让客户端发送的目的地址由http://localhost:8888/service/ws 端口8888改为9999,然后TCPTrace负责forward9999到8888.这样客户和服务端之间的http通讯都可以被监视到。类似Soaptoolkit的monitor工具。
首先启动TCPTrace,可以从 http://www.pocketsoap.com/tcptrace/ 下载。
在运行程序,就可以在TCPTrace看到消息的调用。包括发送和接受的消息。
到这里我们的WCF service 就写好了。接下来需要配置我们的拦截模型IC,并且部署到BAM的配置库。
关于拦截模型。
首先需要定义一个事件源(EventSource),比如WCF程序是一个事件源。WF程序也是一个事件源。Biztalk也是事件源。
对于事件源,如果是.net 程序集,需要制定Assembly的fullname,如果是Biztalk,要制定是messagebox还是orchastration。
有了事件源,需要定义一个或者多个事件模型。
事件的定义,是通过Filter来筛选,事件发生后有要更改Activity的那些属性,通过Update来配置。另外还可以指定Reference,类似引用那个文档或者Activity。以及如何把多个事件源生成的Activity关联到一个的Continous模型。这个后面会讲。
对于事件源,一下是一个例子。
对于WCF,manifest是Contract的fullname,WF,则是workflow的fullname。
然后是一个事件的tree
接下来我们配置一个我们的IC.
我们需要配置的IC的事件源来自WCF。
当客户端发出一个请求的时候,并且是调用SubmitPo 的时候,我们称这个事件叫做BeginOrder。这时候拦截到State,Amount 属性。并且把当前的时间更新到Activity的BeginOrder。
当客户端收到submitPO的时候,我们成为事件EndOrder,更新Activity的EndOrder属性。
为了方便生成这个XML。Biztalk R2提供了三个Schema,分别面向WCF、WF,Common IC
WcfInterceptorConfiguration.xsd
CommonInterceptorConfiguration.xsd
这两个文件可以在SDK目录下面找到,如果没有的话,在安装盘Msi\Program Files\SDK\Samples\BIX 下面。可以把这两个文件copy到visual studio的schema 目录下面。方便有智能提示。
我们新建一个XML到项目中,成为wcfInterceptor.xml
<?xml version="1.0" encoding="utf-8" ?>
<bam:InterceptorConfiguration xmlns:bam="http://schemas.microsoft.com/BizTalkServer/2004/10/BAM/InterceptorConfiguration"
xmlns:bamwcf="http://schemas.microsoft.com/BizTalkServer/2004/10/BAM/WcfInterceptorConfiguration">
</bam:InterceptorConfiguration>
这时候按照智能提示,大多数配置可以轻松搞定。
定义一个事件源
<bam:EventSource Manifest="SingleWCF.IPOService, SingleWCF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Name="wcfSource" Technology="WCF">
</bam:EventSource>
注意这里的manifest是fullname,wcf程序在运行的时候会根据当前endpoint所绑定的contract的全名去数据库中选招match的ic。如果manifest写错了的话,你就得不到任何BAM数据。
然后定义第一个事件。条件是:
ClientRequest 一个Operation是SubmitPo
对应成语法是GetServiceContractCallPoint()=ClientRequest And GetOperationName()="SubmitPO"
IC 采用一个特殊的语法,叫做反向表达式。就是操作数在前,操作符在后。比如我们计算2+3×4 写作 234×+
WCF 有一些特殊的Operation,比如:
AutoGenerateCorrelationToken
GetContextProperty
GetEndpointName
GetOperationName //返回当前的调用的Contract的Operation
GetServiceContractCallPoint//调用拦截点。是ClientRequest还是ServiceReply
XPath//从message中抓数据。
这些可以从MSDN看到返回值和使用方式。
对应成ic的xml就是
<bam:OnEvent Name="BeginOrder" IsBegin="true" IsEnd="false" Source="wcfSource">
<bam:Filter>
<bam:Expression>
<bamwcf:Operation Name="GetServiceContractCallPoint">
</bamwcf:Operation>
<bam:Operation Name="Constant">
<bam:Argument>ClientRequest</bam:Argument>
</bam:Operation>
<bam:Operation Name="Equals">
</bam:Operation>
<bamwcf:Operation Name="GetOperationName">
</bamwcf:Operation>
<bam:Operation Name="Constant">
<bam:Argument>SubmitPo</bam:Argument>
</bam:Operation>
<bam:Operation Name="Equals">
</bam:Operation>
<bam:Operation Name="And"></bam:Operation>
</bam:Expression>
</bam:Filter>
<bam:CorrelationID>
<bam:Expression>
<bamwcf:Operation Name="XPath">
<bamwcf:Argument>
//*[local-name(.)='Id']
</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:CorrelationID>
<bam:Update DataItemName="BeginOrder" Type="DATETIME">
<bam:Expression>
<bamwcf:Operation Name="GetContextProperty">
<bamwcf:Argument>EventTime</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:Update>
<bam:Update DataItemName="State" Type="NVARCHAR">
<bam:Expression>
<bamwcf:Operation Name="XPath">
<bamwcf:Argument>//*[local-name(.)='State']</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:Update>
<bam:Update DataItemName="Amount" Type="FLOAT">
<bam:Expression>
<bamwcf:Operation Name="XPath">
<bamwcf:Argument>//*[local-name(.)='Amount']</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:Update>
</bam:OnEvent>
其中有一个element是CorrelationID,相当于Activity的PK值。不同的事件触发如何关联到一个Activity
接下来定义另外一个事件,服务返回。更新EndOrder时间。
<bam:OnEvent Name="EndOrder" IsBegin="false" IsEnd="true" Source="wcfSource">
<bam:Filter>
<bam:Expression>
<bamwcf:Operation Name="GetServiceContractCallPoint">
</bamwcf:Operation>
<bam:Operation Name="Constant">
<bam:Argument>ServiceReply</bam:Argument>
</bam:Operation>
<bam:Operation Name="Equals">
</bam:Operation>
</bam:Expression>
</bam:Filter>
<bam:CorrelationID>
<bam:Expression>
<bamwcf:Operation Name="XPath">
<bamwcf:Argument>
//*[local-name(.)='SubmitPoResult']
</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:CorrelationID>
<bam:Update DataItemName="EndOrder" Type="DATETIME">
<bam:Expression>
<bamwcf:Operation Name="GetContextProperty">
<bamwcf:Argument>EventTime</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:Update>
</bam:OnEvent>
请注意CorrelationID 的两个写法。从TCPTrace中可以看到这两个值对应一个请求。
写好这个IC之后,通过BM.exe部署
bm deploy-interceptor -filename:"xxx\SingleWCF\wcfInterceptor.xml"
这样IC模型就部署到BAM的元数据库中。你可以查询
数据中记录了该IC对应的Activity,以及事件的XML描述。
接下来我们要配置WCF程序,enable 一个BAM提供的EndpointBehavior,来横向拦截IC模型的数据。
右键 app.config,选择用EDIT with WCF Configuration.
Advance-> Extensions->new behavior element extension
新建一个Element 叫做bamEndpoinBehavior 名字可以自己随便输入。
Type 是Microsoft.BizTalk.Bam.Interceptors.Wcf.BamEndpointBehavior, Microsoft.BizTalk.Bam.Interceptors, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
当然你可以点击Browser,定位到Gac中的Microsoft.BizTalk.Bam.Interceptors 程序集。
接下来新建一个EndpointBehavior叫做bamBehavior。把新建好的Element拖进去。配置
BAM数据连接字符串:指向本地BAM数据库
以及Polling的时间,写5,代表没5秒查询数据库看有没有新的IC定义。
然后配置service和client endpoint使用该behavior
此时完整的App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="bamEndpointBehavior" type="Microsoft.BizTalk.Bam.Interceptors.Wcf.BamEndpointBehavior, Microsoft.BizTalk.Bam.Interceptors, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="bamBehavior">
<bamEndpointBehavior ConnectionString="Data Source=.;Initial Catalog=bamprimaryimport;Integrated Security=True"
PollingIntervalSec="5" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="enableWSDL">
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="enableWSDL" name="SingleWCF.POServiceImpl">
<endpoint address="ws" binding="basicHttpBinding" bindingConfiguration="basicBindingNoSecurity"
contract="SingleWCF.IPOService" behaviorConfiguration="bamBehavior" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8888/service" />
</baseAddresses>
</host>
</service>
</services>
<client>
<endpoint address="http://localhost:9999/service/ws"
behaviorConfiguration="bamBehavior" binding="basicHttpBinding"
bindingConfiguration="basicBindingNoSecurity"
contract="SingleWCF.IPOService" name="clientendpoint" />
</client>
<bindings>
<basicHttpBinding>
<binding name="basicBindingNoSecurity">
<security mode="None"/>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
大功告成了,Run 一下应用程序。然后打开BAM portal, http://localhost/bam 就可以看到数据进去了。
beginorder,endorder之间相差3秒左右。应为我们程序休息了3秒。 state是WA,amount是120.这些都是WCF 提供的数据。
如何调试知道Event有没有被命中呢? wcf intercepotr 采用.net标准的Trace模型。在App.config中加入
<system.diagnostics>
<sources>
<source name="Microsoft BizTalk Bam Interceptors" switchValue="All">
<listeners>
<add name="consolelistener" type="System.Diagnostics.ConsoleTraceListener"/>
</listeners>
</source>
</sources>
</system.diagnostics>
注意SourceName必须是Microsoft BizTalk Bam Interceptors
我上次写成Microsoft Biztalk Bam Interceptors,结果什么也没有。debug了半天,hoho
再运行程序就看到结果了。一堆日志消息。
这样。WCF程序通过定义个interceptor模型,就可以把业务关键数据喂给bam了。
这里是完整的模型文件。
<?xml version="1.0" encoding="utf-8" ?>
<bam:InterceptorConfiguration xmlns:bam="http://schemas.microsoft.com/BizTalkServer/2004/10/BAM/InterceptorConfiguration"
xmlns:bamwcf="http://schemas.microsoft.com/BizTalkServer/2004/10/BAM/WcfInterceptorConfiguration">
<bam:EventSource
Manifest="SingleWCF.IPOService, SingleWCF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
Name="wcfSource" Technology="WCF">
</bam:EventSource>
<bam:BamActivity Name="HelloBamActivity">
<bam:OnEvent Name="BeginOrder" IsBegin="true" IsEnd="false" Source="wcfSource">
<bam:Filter>
<bam:Expression>
<bamwcf:Operation Name="GetServiceContractCallPoint">
</bamwcf:Operation>
<bam:Operation Name="Constant">
<bam:Argument>ClientRequest</bam:Argument>
</bam:Operation>
<bam:Operation Name="Equals">
</bam:Operation>
<bamwcf:Operation Name="GetOperationName">
</bamwcf:Operation>
<bam:Operation Name="Constant">
<bam:Argument>SubmitPo</bam:Argument>
</bam:Operation>
<bam:Operation Name="Equals">
</bam:Operation>
<bam:Operation Name="And"></bam:Operation>
</bam:Expression>
</bam:Filter>
<bam:CorrelationID>
<bam:Expression>
<bamwcf:Operation Name="XPath">
<bamwcf:Argument>
//*[local-name(.)='Id']
</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:CorrelationID>
<bam:Update DataItemName="BeginOrder" Type="DATETIME">
<bam:Expression>
<bamwcf:Operation Name="GetContextProperty">
<bamwcf:Argument>EventTime</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:Update>
<bam:Update DataItemName="State" Type="NVARCHAR">
<bam:Expression>
<bamwcf:Operation Name="XPath">
<bamwcf:Argument>//*[local-name(.)='State']</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:Update>
<bam:Update DataItemName="Amount" Type="FLOAT">
<bam:Expression>
<bamwcf:Operation Name="XPath">
<bamwcf:Argument>//*[local-name(.)='Amount']</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:Update>
</bam:OnEvent>
<bam:OnEvent Name="EndOrder" IsBegin="false" IsEnd="true" Source="wcfSource">
<bam:Filter>
<bam:Expression>
<bamwcf:Operation Name="GetServiceContractCallPoint">
</bamwcf:Operation>
<bam:Operation Name="Constant">
<bam:Argument>ServiceReply</bam:Argument>
</bam:Operation>
<bam:Operation Name="Equals">
</bam:Operation>
</bam:Expression>
</bam:Filter>
<bam:CorrelationID>
<bam:Expression>
<bamwcf:Operation Name="XPath">
<bamwcf:Argument>
//*[local-name(.)='SubmitPoResult']
</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:CorrelationID>
<bam:Update DataItemName="EndOrder" Type="DATETIME">
<bam:Expression>
<bamwcf:Operation Name="GetContextProperty">
<bamwcf:Argument>EventTime</bamwcf:Argument>
</bamwcf:Operation>
</bam:Expression>
</bam:Update>
</bam:OnEvent>
</bam:BamActivity>
</bam:InterceptorConfiguration>