1. Problem
You want to send custom events
to BAM and be able to track these events as if they were published by a
core BizTalk component (such as an orchestration).
2. Solution
The BAM Interceptor is part of
the API that is available to interact with BAM from a .NET code base
(BizTalk or otherwise). The Interceptor is used primarily by the core
BizTalk components, such as orchestrations and pipelines, but can be
tied to custom code by a developer. By calling the Interceptor and the
associated API methods, you have full control over when and what is sent
to deployed and tracked BAM instances.
When orchestrations
execute, they constantly send events to the BAM Interceptor. Events that
are being subscribed to (tracked events, set in the BAM Tracking
Profile Editor) will be saved, and all others will be ignored. When
external components to the orchestration are called (such as referenced
assemblies), however, they do not log to BAM, and events that occur can
be inferred only by the surrounding events sent from the orchestration.
This solution will walk through the sample that is included in the BizTalk SDK. You can find the SDK project files at Microsoft BizTalk Server 2010\SDK\ Samples\BAM\BamApiSample.
You must go through several key steps to interface with the BAM Interceptor:
Create and deploy a BAM definition, and import it into the Tracking Profile Editor.
Create
an Interceptor instance, which can be used to interface with the BAM
Interceptor. This contains the configuration needed to associate the
data in the custom event being fired with the event in the BAM
definition.
Implement the code to call the BAM Interceptor.
The first thing to do is to import the BAM definition and understand how it corresponds to the rest of the files in the SDK:
Run the Setup.bat file. This will install the components.
Open
the BizTalk Tracking Profile Editor. Click File, and then select Import
BAM Activity Definition. Assuming the BAM definition has been deployed
successfully, the option to select BAMApiPo will be available, as shown
in Figure 1.
During the setup process, two console applications are compiled. One of them is the InterceptorConfig.exe
file, which takes a series of inputs to determine how to map data
logged to the Interceptor to the BAM definition. The second is the BAMApiSample.exe file, which references the output of the InterceptorConfig.exe file to determine how to push data to the BAM Interceptor.
Open the BAMApiSample.sln file.
First, examine the InterceptorConfig.cs file to observe how it creates the binary file, which will be referenced by BAMApiSample.
It creates an Interceptor instance, which is a binary file containing
the information needed to map the values to the definition. The XPaths
are in reference to the document, which is statically created within BAMApiSample.
An example of the mapping between the definition and BAMApiSample is as follows:
locNewInvoice [in BAMApiSample] = Received
Event [in Tracking Editor]
@PoID [inBAMApiSample] = xpath in xml
document to InvoiceID [in Tracking Editor]
Open the PurchaseOrder_config.xml document to see all the mappings as configured by InterceptorConfig.exe. You can change these values; however, you also have to change the document in BAMApiSample to match the new values.
Assume
for a moment that the only fields in the Tracking Profile Editor are
Start and End. By simplifying the definition and the creation of the
binary reference file, the solution becomes more manageable. Use the
following steps to create an Interceptor instance based on the
simplified definition.
In
Visual Studio, create a new Visual C# Console Application project. This
will allow code to be written that will produce the Interceptor
instance.
Add a reference to the Microsoft.BizTalk.Bam.EventObservation.dll assembly. This is located in the Tracking directory of the main BizTalk installation path.
The code in Main will look as shown in Listing 1.
Example 1. Main Function to Create Binary File
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using Microsoft.BizTalk.Bam.EventObservation; // etc. static void Main(string[] args) { // the following creates a new instance based on the definition that // has been deployed to Tracking. If BAMApiPo does not exist as a valid // deployed definition, this code will fail. ActivityInterceptorConfiguration interceptorConfig = new ActivityInterceptorConfiguration("BAMApiPo"); // registrations map the name of the tracked event to the // name that is in the binary file/Interceptor code. interceptorConfig.RegisterDataExtraction("Start","StartSample",""); interceptorConfig.RegisterDataExtraction("End","EndSample",""); // this is the indicator for when the Interceptor is to start and stop // listening for events. @ID is an XPath to where to locate the ID // in the document passed in (see code for calling Interceptor, below) interceptorConfig.RegisterStartNew("StartSample", "@ID"); interceptorConfig.RegisterEnd("EndSample"); // Create the Interceptor bin file BAMInterceptor interceptor = new BAMInterceptor(); interceptorConfig.UpdateInterceptor(interceptor); // write the file out BinaryFormatter format = new BinaryFormatter(); Stream file = File.Create("BAMApiSample.bin"); format.Serialize(file, interceptor); file.Close(); }
|
Build
the solution. Run the executable. This will write out a binary file,
which can now be referenced by the code to call the interceptor.
Additionally, an XML document could be generated, which would look like the following (this is not a required step):
<TraceInterceptorConfiguration xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<TrackPoint Type="Start">
<Location xsi:type="xsd:string">StartSample</Location>
<ExtractionInfo xsi:type="xsd:string">@ID</ExtractionInfo>
</TrackPoint>
<TrackPoint Type="End">
<Location xsi:type="xsd:string">EndSample</Location>
</TrackPoint>
</TraceInterceptorConfiguration>
Now, open the BAMApiSample.cs file to observe how it is structured. The key items to look at follow:
The class MainApp:
This class contains the main function that constructs the XML document
and event stream that will be used to call the BAM Interceptor.
The class DataExtractor:
This class contains the extractor that returns the data within a given
node of the XML document based on the XPath passed as a parameter. The
XPath comes directly from the binary configuration file created by the InterceptorConfig.exe file.
The #If Interceptor statements: All statements within these directives call the BAM Interceptor.
The following code is directly from one of the SDK's #If Interceptor directives. It shows how the binary file is loaded and how the OnStep method in the BAM Interceptor is called. In this case, the dataExtractor contains information about how to extract the data from a given node (with the XPath from the binary file), and locNewPo maps to the Received Activity in the Tracking Profile Editor.
BAMInterceptor interceptor=Global.LoadInterceptor("BAMApiPo_interceptor.bin");
interceptor.OnStep(Global.dataExtractor,"locNewPo",xePO,Global.es);
3. How It Works
The previous solution
explored how to use the Interceptor API and introduced the concept of
interaction with the BAM Interceptor. The functionality of this SDK
sample is limited in use with true BizTalk implementations. The most
likely use of calling the BAM Interceptor directly would be in the case
of a custom assembly, which is called from an orchestration. Modifying
the console application (BAMApiSample) to a .NET class is fairly straightforward.
Listing 2 shows a simplified approach to the application as a class that can be called directly from an Expression shape.
Example 2. Interceptor in a .NET Class
{
// the EventStream and DataExtractor tie into Interceptor events
public static EventStream es=null;
public static DataExtractor dataExtractor=new DataExtractor();
public void PassDocument(System.Xml.XmlDocument xmlDoc)
{
// instantiate the interceptor by referencing the binary file
Stream file=File.Open("C:\BAMApiSample.bin", FileMode.Open, FileAccess.Read,
FileShare.Read);
BinaryFormatter format=new BinaryFormatter();
BAMInterceptor interceptor=(BAMInterceptor)format.Deserialize(file);
file.Close();
// send an event that will start the tracking
// xmlDoc is the instance of the document passed in
interceptor.OnStep(Sample.dataExtractor,"StartSample",xmlDoc,Sample.es);
// end the tracking
interceptor.OnStep(Sample.dataExtractor,"EndSample",xmlDoc,Sample.es);
}
// add functions for constructing the EventStream and the DataExtractor
}