Introduction

One of the features which is currently not available with the current release of Azure BizTalk Services is Business Activity Monitoring. Business Activity Monitoring or BAM in short, was first introduced with the release of BizTalk Server 2004 and has been available in every subsequent BizTalk Server release.

BAM in short enables you to capture metrics about your Business Processes and subsequently enabling the business to perform all kind, including near real time, analysis on the captured data.

Although the current release of Azure BizTalk Services does not include a cloud implementation of BAM it was made clear during the Microsoft Integration Summit in November of 2013 that Microsoft is heavily investing into adding BPM capabilities including rich BAM capabilities to a future release of Azure BizTalk Services.

This being said it does not prevent you of adding BAM functionality in your current Azure BizTalk Service solutions. As a matter of fact with the extensibility points which are currently available in Azure BizTalk Services one can for example extend a bridge by implementing a message inspector which leverages both a message payload as its context properties to capture BAM metrics and send them downstream for actual processing.

This article will by means of using a problem, solution approach explain a possible solution on how one can leverage BAM in Azure BizTalk Services.

Background

FreeEnergyForAll Inc is a utility company uses a Azure BizTalk Service EAI solution which processes messages send by 'smart energy readers'. These readers are installed at a customer's home and collect metrics containing the daily energy consumption of a household. Once collected, these metrics are send to a Azure BizTalk Service which eventually route these messages to an endpoint which consists of a Servicebus Topic. This topic contains several subscriptions based on the registration country of the smart energy reader. Local branches of FreeEnergyForAll Inc subsequently retrieve these country specific messages for further processing.

Problem

FreeEnergyForAll Inc. currently gets monthly reports from their local branches depicting the average energy consumption of a specific region. This information is then used for forecasting and other BI activities which focuses on improving the Energy acquisition process (buying the actual energy from suppliers).

Recently FreeEnergyForAll Inc management raised their concern that further improvement of the energy acquisition process is not possible as the information they receive from the local branches only enables them to analyze and predict the energy consumption per region in hind-respect (one month after the fact). As a result management has instructed the local IT department to implement a Near Real-time monitoring solution such that they are no longer depending on receiving the data in hind respect of the local branches but are able to analyze the data as soon as it is being processed thus enabling them to optimize the energy consumption predictions and thus adapt the internal energy acquisition process when needed.

Solution

Before we actually start implementing the solution and the challenges that are involved with it. It is recommended to obtain an overall view of the process which we are about to implement. This overview is listed below in figure 1 and figure 2. Note: the complete solution is available as download. See


Figure 1 BMPN Process Overview

  

Figure 2 High-level overview

The solution which needs to be implemented in order to address the above mentioned problem starts when readings from a smart reader are received by the Azure BizTalk Services Bridge. The message in question is displayed below


Figure
3 Smart Reader Message Format

This message includes all information needed by FreeEnergyForAll Inc. In order to tackle the problem as mentioned earlier. This information is then processed by different components which need to be developed. These components are:

  • BAM Event Processor
  • Create the BAM Observation Model
  • The Message Inspector

Once these components are developed, the Message Inspector needs to be added to the current Azure BizTalk Service Solution. This part is covered here configuring your Azure BizTalk Service Bridge

BAM Event Processor

There are several ways to capture and store BAM data. One could choose to use one or a combination of the following options:

For the FreeEnergyForAll inc. solution it was decided to utilize their current on premise BizTalk Server and implement a generic orchestration which uses the OrchestrationEventStream .

The BAM Event Processor orchestration is instantiated once a BusinessActivityMonitoring event has been published to the BizTalk MessageBox. This BusinessActivityMonitoring event message is constructed in a generic way such that it can hold all information which makes up or is part of a BAM activity and includes information like

  • BAM Activity name to use
  • BAM Activity ID to use
  • Indication of the kind of activity
  • Activity Items (name, value and data type)
  • Settings which are to be used in case of a Continuation or for indicating a related activity

Figure 4 BusinessActivityMonitoring.xsd

Once the BusinessActivityMonitoring event message has been received. Logic regarding the chosen "activity type" is executed. This logic is depicted in the table below

ActivityType

Actions executed

BeginActivity

  • Invoke BeginActivity on the OrchestrationEventStream, passing in the ActivityName and ActivityId
  • Extract ActivityItems
  • Invoke UpdateActivity on the OrchestrationEventStream for each activityItem (loop) passing in the ActivityName, ActivityId , Activityitem name and ActivityItem value

BeginActivityWithRelatedActivity

  • Invoke BeginActivity on the OrchestrationEventStream passing in the ActivityName and ActivityId
  • Invoke AddRelatedActivity on the OrchestrationEventStream passing in the ActivityName, ActivityId, RelatedActivityName and RelatedActivityId
  • Extract ActivityItems
  • Invoke UpdateActivity on the OrchestrationEventStream for each activityItem (loop) passing in the ActivityName, ActivityId , Activityitem name and ActivityItem value

BeginActivityWithContinuationSetting

  • Invoke BeginActivity on the OrchestrationEventStream passing in the ActivityName and ActivityId
  • Invoke EnableContinuation on the OrchestrationEventStream passing in the ActivityName, ActivityId and the ContinuationToken
  • Extract ActivityItems
  • Invoke UpdateActivity on the OrchestrationEventStream for each activityItem (loop) passing in the ActivityName, ActivityId , Activityitem name and ActivityItem value

BeginActivityAndEndActivity

  • Invoke BeginActivity on the OrchestrationEventStream passing in the ActivityName and ActivityId
  • Extract ActivityItems
  • Invoke UpdateActivity on the OrchestrationEventStream for each activityItem (loop) passing in the ActivityName, ActivityId , Activityitem name and ActivityItem value
  • Invoke EndActivity on the OrchestrationEventStream passing in the ActivityName and ActivityId

UpdateActivity

  • Extract ActivityItems
  • Invoke UpdateActivity on the OrchestrationEventStream for each activityItem (loop) passing in the ActivityName, ActivityId , Activityitem name and ActivityItem value

UpdateActivityAndEndActivity

  • Extract ActivityItems
  • Invoke UpdateActivity on the OrchestrationEventStream for each activityItem (loop) passing in the ActivityName, ActivityId , Activityitem name and ActivityItem value
  • Invoke EndActivity on the OrchestrationEventStream passing in the ActivityName and ActivityId

EndActivity

  • Invoke EndActivity on the OrchestrationEventStream passing in the ActivityName and ActivityId

The image below shows the actual BizTalk Orchestration implementation.


Figure 5 Bam Event Processor

Create the BAM Observation Model

At the heart of Business Activity Monitoring lies the BAM observation model which enables a business analyst by means of an Excel Plugin to define a BAM Activity and a BAM View

This section will not dive into how to create an activity and the corresponding view(s) but it will list the Activity and View which was created for FreeEnergyForAll Inc.

Activity Definition "EnergyConsumptionRegistration"

Name

ItemType

DataType

Country

Business Data - Text

Text

CustomerId

Business Data - Text

Text

MeterId

Business Data - Text

Text

MeterRegistrationDateTime

Business Milestones

DateTime

MeterValue

Business Data - Decimal

Decimal

ZipCode

Business Data - Text

Text

 

View Aggregations Dimensions and Measures

Name

Type

Notes

AvgConsumption

Measure

Average MeterValue

CountryZipMeterId

Data Dimension

Country, ZipCode and MeterId Dimension Levels

MeterRegistration

Time Dimension

Year, Week, Day MeterRegistrationDateTime

TotalConsumption

Measure

Sum MeterValue

TotalRegistrations

Measure

Count EnergyConsumptionRegistration

 

The Message Inspector

At this point all the prerequisites which enables the processing of the BAM data has been developed. Which includes the Bam Event Processor Orchestration as well as the BAM Observation Model.

The next step in our solution consists of coding our custom Message Inspector which extracts the data which needs to be captured from the inbound message and it's context and send this data downstream in the format of a the BusinessActivityMonitoring Event (Figure 3 BusinessActivityMonitoring.xsd) The custom message inspector which has been developed for FreeEnergyForAll Inc will perform the following steps.

  • BAM Data Extraction
    • using defined properties (context properties)
    • using the actual payload
  • Downstream Delivery (Send BAM data)
  • Relay Endpoint -or-
  • Servicebus queue -or-
  • Servicebys topic -or-
  • BizTalk Services Bridge -or-
  • Any other endpoint accepting HTTP POST

BAM Data Extraction

In order for the component to figure out which data from the inbound message needs to be extracted it needs some configuration information. This information is fed into the component by means of utilizing input parameters. This configuration information is then

  • used to extract the data from the actual inbound message
  • construct the bam endpoint configuration (uri, authentication)

A list of possible parameters as well as a short explanation and code sample relating to the parameters is mentioned below. Please note that all parameters are of type string.

BamEndpointUri

This field is mandatory and contains the endpoint location to which the actual BAM data which has been extracted is send. The following types of endpoints are allowed:

  • Microsoft Azure Service Bus Topic*
  • Microsoft Azure Service Bus Queue*
  • Microsoft Azure Service Bus Relay*
  • Microsoft Azure BizTalk Services Bridge*
  • Any other endpoint accepting HTTP POST

* All Azure related endpoints which require authentication require the following dependent input parameters

  • (ACS) IssuerName
  • (ACS) IssuerKey
Code

 

/// <summary>
/// Determines if the BAM endpoint is a Servicebus endpoint and check if an acs token is required (queue or topic)
/// </summary>
  
 private void DetermineIsServiceBusEndPointOrBizTalkServiceBridgeEndpoint()
 {
     this.AcsNsPrefix = string.Empty;
  
     if (this.BamEndPointUri.Contains("servicebus.windows.net"))
     {
         this.AcsNsPrefix = "sb";
         this.IsServicebusOrBizTalkServiceBridgeEndpoint = true;
     }
     else if (this.BamEndPointUri.Contains("biztalk.windows.net"))
     {
         this.AcsNsPrefix = "bts";
         this.IsServicebusOrBizTalkServiceBridgeEndpoint = true;
     }
     else
     {
         this.IsServicebusOrBizTalkServiceBridgeEndpoint = false;
     }
  
     if (this.IsServicebusOrBizTalkServiceBridgeEndpoint)
     {
         DetermineAcsTokenRequired();
         if (IsAcsTokenRequired)
         {
             ExtractAcsEndpoint();
             GetAcsToken();
         }
     }
 }
   
  
 /// <summary>
 /// Determines using basic logic if an acs token is assumed to be 
 /// required and sets the IsAcsTokenRequired boolean appropriately
 /// </summary>
  
 private void DetermineAcsTokenRequired()
 {
     //we assume no acs token is required
     if ((String.IsNullOrEmpty(this.IssuerName)) || (String.IsNullOrEmpty(this.IssuerKey)))
     {
         this.IsAcsTokenRequired = false;
     }
     else
     {
         this.IsAcsTokenRequired = true;
     }
 }
  
/// <summary>
/// Using basic logic it tries to extract the Acs Endpoint. Once extracted the value 
/// is stored in AccessControlEndpoint
/// </summary>
 public void ExtractAcsEndpoint()
 {
     string AcsNamespace = string.Empty;
  
     if (String.IsNullOrEmpty(this.CustomAcsNamespaceName))
     {
         //extract namespace 
         AcsNamespace = this.BamEndPointUri.Split(".".ToCharArray())[0].Replace("https://", string.Empty).Replace("http://", string.Empty);
     }
     else
     {
         //use the custom namespace
         AcsNamespace = CustomAcsNamespaceName;
     }
  
     //construct the acs endpoint
     this.AccessControlEndpoint = String.Format("https://{0}-{1}.accesscontrol.windows.net/WRAPv0.9/", AcsNamespace, this.AcsNsPrefix);
 }
  
   
/// <summary>
/// Sets the AcsToken variable which is required to authenticate to the servicebus endpoint
/// </summary>
 public void GetAcsToken()
 {
     this.AcsToken = string.Empty;
  
      try
     {
         string issuerName = this.IssuerName;
         string issuerKey = this.IssuerKey;
         Uri acsEndPoint = new Uri(this.AccessControlEndpoint);
  
         var client = new WebClient();
  
         // Create the body of the Post Request, formatted as a HTTP Form
         string postData = "wrap_name=" + Uri.EscapeDataString(issuerName) +
                             "&wrap_password=" + Uri.EscapeDataString(issuerKey) +
                             "&wrap_scope=" + Uri.EscapeDataString(this.BamEndPointUri.Replace("https", "http"));
         client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
         string[] tokenVariables = client.UploadString(acsEndPoint, "POST", postData).Split("&".ToCharArray());
         string[] tokenVariable = tokenVariables[0].Split("=".ToCharArray());
         string[] authorizationTokenVariables = Uri.UnescapeDataString(tokenVariable[1]).Split("&".ToCharArray());
         this.AcsToken = Uri.UnescapeDataString(tokenVariable[1]);
  
          //close
         client = null;
     }
     catch
     {
         //TODO implement exception logic, as BAM event can not be send due to the fact that something
         //went wrong while obtaining the acs token.
     }
 }

 

BamActivityId

This field is optional and should only be used in case you want to control which activityId to use. (I.e. Activity Continuation scenario). Once a value is supplied for this parameter the component logic will iterate through the available property definitions (context properties) and if a property is found which matches the value of the BamActivityid it will use the value assigned to that specific property. If there is no match, the value will be a assigned a unique value (guid)

Code
/// <summary>
/// Extract the actual items which hold the to capture BAM data
/// </summary>
private void ExtractActivityItems()
{
        //Code left out; see description of input parameter : BamActivityItemsRecognitionPrefix
  
        //Check if input parameters are null or empty
  
        if (String.IsNullOrEmpty(this.BamActivityId))
        {
            //Use a guid
            this.BamActivityIdValue = Guid.NewGuid().ToString();
  
        }
        else
        {
            //Check if property exists if not use guid
            this.BamActivityIdValue = GetContextProperty(this.BamActivityId);
            if(String.IsNullOrEmpty(this.BamActivityIdValue))
            {
                 this.BamActivityIdValue = Guid.NewGuid().ToString();
            }
        }
    }
}
  
/// <summary>
/// Extract value from context
/// </summary>
/// <param name="propertyName">context property name</param>
/// <returns>context property value</returns>
private string GetContextProperty(string propertyName)
{
    string returnValue = null;
    if (propertyCollection != null)
    {
  
        try
        {
            var propertyValue = propertyCollection.Where(x => x.Key == propertyName).First();
  
            if (!String.IsNullOrEmpty(propertyValue.Value.ToString()))
            {
                returnValue = propertyValue.Value.ToString();
            }
        }
        catch
        {
            //TODO
        }
  
    }
      
    return returnValue;
}

 

BamActivityName

This field is mandatory and should contain the BAM Activity

BamActivityType

This field is mandatory and should contain one of the following values:

  • BeginActivity
  • BeginActivityWithRelatedActivity
  • BeginActivityWithContinuationSetting
  • BeginActivityAndEndActivity
  • UpdateActivity
  • UpdateActivityAndEndActivity
  • EndActivity

The assigned value determines the processing logic in the BAM Event Processing orchestration.

Code
/// <summary> 
/// Determines the BAM Eventtype which should be executed downstream 
/// </summary> 
private void DetermineSelectedBamEventType() 
    this.AddContinuation = false
    this.AddRelationShip = false
    this.IsEndActivity = false
    
    switch (this.BamActivityType) 
    
        case "BeginActivity"
            
                this.ActivityType = BusinessActivityMonitoringEventHeaderActivityType.BeginActivity; 
                break
            
        case "BeginActivityWithRelatedActivity"
            
                this.ActivityType = BusinessActivityMonitoringEventHeaderActivityType.BeginActivityWithRelatedActivity; 
                this.AddRelationShip = true
                break
            
        case "BeginActivityWithContinuationSetting"
            
                this.ActivityType = BusinessActivityMonitoringEventHeaderActivityType.BeginActivityWithContinuationSetting; 
                this.AddContinuation = true
                break
            
        case "BeginActivityAndEndActivity"
            
                this.ActivityType = BusinessActivityMonitoringEventHeaderActivityType.BeginActivityAndEndActivity; 
                break
            
        case "UpdateActivity"
            
                this.ActivityType = BusinessActivityMonitoringEventHeaderActivityType.UpdateActivity; 
                break
            
        case "UpdateActivityAndEndActivity"
            
                this.ActivityType = BusinessActivityMonitoringEventHeaderActivityType.UpdateActivityAndEndActivity; 
                break
            
        case "EndActivity"
            
                this.ActivityType = BusinessActivityMonitoringEventHeaderActivityType.EndActivity; 
                this.IsEndActivity = true
                break
            
        default
            
                break
            
    
}

 

BamActivityContinuationToken

This field is optional and should only be used in case the BamActivityType parameter has been set to BeginActivityWithContinuationSetting. Once a value is supplied for this parameter the component logic will iterate through the available property definitions (context properties) and if a property is found which matches the value of the BamActivityContinuationToken it will use the value assigned to that specific property. If there is no match an exception will be thrown (although it will not cause the BizTalk Service to malfunction, it will however result in the not sending a BAM event downstream)

Code
/// <summary>
/// Extract item which is needed in order to set up a continuation for an activity which spans multiple
/// processes
/// </summary>
private void ExtractContinuationItem()
{
    if (this.AddContinuation)
    {
        if (String.IsNullOrEmpty(this.BamContinuationToken))
        {
            //throw exception
            throw new Exception(String.Format("Parameter BamContinuationToken not filled out!"));
        }
        else
        {
            //use assigned value as context property name and extract it's value
            this.BamContinuationTokenValue = GetContextProperty(this.BamContinuationToken);
            if (String.IsNullOrEmpty(this.BamContinuationTokenValue))
            {
                throw new Exception(String.Format("Parameter BamContinuationToken not filled out!"));
            }
        }
    }
}
   
/// <summary>
/// Extract value from context
/// </summary>
/// <param name="propertyName">context property name</param>
/// <returns>context property value</returns>
private string GetContextProperty(string propertyName)
{
    string returnValue = null;
    if (propertyCollection != null)
    {
        try
        {
            var propertyValue = propertyCollection.Where(x => x.Key == propertyName).First();
            if (!String.IsNullOrEmpty(propertyValue.Value.ToString()))
            {
                returnValue = propertyValue.Value.ToString();
            }
        }
        catch
        {
            //TODO
        }
    }
  
    return returnValue;
}

BamRelatedActivityInstanceId & BamRelatedActivityName

These fields are optional and should only be used in case the BamActivityType parameter has been set to BeginActivityWithRelatedActivity. These parameters are used to identify a related BAM activity.

Once a value is supplied for these parameters the component logic will iterate through the available property definitions (context properties) and if a property is found which matches the value of either the BamRelatedActivityInstanceId or BamRelatedActivityName it will use the value assigned to that specific property. If there is no match an exception will be thrown (although it will not cause the BizTalk Service to malfunction, it will however result in the not sending a BAM event downstream)

Code
/// <summary>
/// Extract items which are needed once indicated that the event has a relation with an other activity
/// </summary>
private void ExtractRelationshipItems()
{
    if (this.AddRelationShip)
    {
        if (String.IsNullOrEmpty(this.BamRelatedActivityInstanceId))
        {
            //thow exception
            throw new Exception(String.Format("Parameter BamRelatedActivityInstanceId not filled out!"));
        }
        else
        {
            //use assigned value as context property name and extract it's value
            this.BamRelatedActivityInstanceIdValue = GetContextProperty(this.BamRelatedActivityInstanceId);
            if (String.IsNullOrEmpty(this.BamRelatedActivityInstanceIdValue))
            {
                throw new Exception(String.Format("Parameter BamRelatedActivityInstanceId not filled out!"));
            }
        }
  
        if (String.IsNullOrEmpty(this.BamRelatedActivityName))
        {
            //thow exception
            throw new Exception(String.Format("Parameter BamRelatedActivityName not filled out!"));
        }
        else
        {
            //use assigned value as context property name and extract it's value
            this.BamRelatedActivityNameValue = GetContextProperty(this.BamRelatedActivityName);
  
            if (String.IsNullOrEmpty(this.BamRelatedActivityNameValue))
            {
                throw new Exception(String.Format("Parameter BamRelatedActivityName not filled out!"));
            }
        }
    }
}
  
/// <summary>
/// Extract value from context
/// </summary>
/// <param name="propertyName">context property name</param>
/// <returns>context property value</returns>
private string GetContextProperty(string propertyName)
{
    string returnValue = null;
    if (propertyCollection != null)
    {
        try
        {
            var propertyValue = propertyCollection.Where(x => x.Key == propertyName).First();
            if (!String.IsNullOrEmpty(propertyValue.Value.ToString()))
            {
                returnValue = propertyValue.Value.ToString();
            }
        }
        catch
        {
            //TODO
        }
    }
  
    return returnValue;
}

 

BamActivityItemsRecognitionPrefix

This field although optional ought to be used. This parameter is used within the component such that the actual activity items (BAM values) can be easily identified and extracted from the context properties.

Once a value is supplied for this parameter the component logic will iterate through the available property definitions (context properties) and if a property is found which contains the value contained in the parameter BamActivityItemsRecognitionPrefix it will use the value assigned to that specific property. If there is no match all available context properties will be treated as being an ActivityItem.

Code
/// <summary>
/// Extract the actual items which hold the to capture BAM data
/// </summary>
private void ExtractActivityItems()
{
    if (!this.IsEndActivity)
    {
        ActivityItems = new List<BusinessActivityMonitoringEventActivityDataActivityItem>();
        
        //Get the actual activity items
        if (propertyCollection.Count() > 0)
        {
            if (!String.IsNullOrEmpty(this.BamActivityItemsRecognitionPrefix))
            {
                foreach (var item in propertyCollection.Where(x => x.Key.Contains(this.BamActivityItemsRecognitionPrefix)))
                {
                    var correctDataType = StringConverter(item.Value.ToString());
                    ActivityItems.Add(new BusinessActivityMonitoringEventActivityDataActivityItem()
                    {
                        ItemName = item.Key.Replace(this.BamActivityItemsRecognitionPrefix, ""),
                        ItemValue = item.Value.ToString(),
                        ItemDataType = correctDataType.Name
                    });
  
                    correctDataType = null;
                }
            }
            else
            {
                //Use all available properties
                foreach (var item in propertyCollection)
                {
                    var correctDataType = StringConverter(item.Value.ToString());
                    ActivityItems.Add(new BusinessActivityMonitoringEventActivityDataActivityItem()
                    {
                        ItemName = item.Key,
                        ItemValue = item.Value.ToString(),
                        ItemDataType = correctDataType.Name
                    });
  
                    correctDataType = null;
                }
            }
        }
        else
        {
            //No items can be used, let's throw an exception
            throw new Exception(String.Format("No Bam Activity Items could be extracted as no context properties are available!"));
        }
  
        //Check if input parameters are null or empty
        if (String.IsNullOrEmpty(this.BamActivityId))
        {
            //Use a guid
            this.BamActivityIdValue = Guid.NewGuid().ToString();
        }
        else
        {
            //Check if property exists if not use guid
            this.BamActivityIdValue = GetContextProperty(this.BamActivityId);
  
            if(String.IsNullOrEmpty(this.BamActivityIdValue))
            {
                 this.BamActivityIdValue = Guid.NewGuid().ToString();
            }
        }
    }
}
  
/// <summary>
/// Get the actual data-type of a string
/// </summary>
/// <param name="value">string value to evaluate to a predefined type</param>
/// <returns>object type</returns>
private Type StringConverter(String value)
{
  
    List<Type> types = new List<Type>();
  
    types.Add(typeof(Int32));
    types.Add(typeof(decimal));
    types.Add(typeof(DateTime));
  
    int counter = 0;
    foreach (Type typeValue in types)
    {
        counter++;
        if (typeValue == typeof(DateTime))
        {
            DateTime dtResult;
            DateTimeConverter dtConverter = TypeDescriptor.GetConverter(typeValue) as DateTimeConverter;
            if (DateTime.TryParse(value, out dtResult))
            {
                return typeValue;
            }
        }
        else
        {
            TypeConverter converter = TypeDescriptor.GetConverter(typeValue);
            if (converter.IsValid(value))
            {
                return typeValue;
            }
        }
    }
  
    types = null;
    return typeof(string);
}

 

IssuerName & IssuerKey

These fields are only required in case the BamEndpointUri requires an ACS token in order to be authenticated.


CustomAcsNamespaceName

This field although not required, should be used in case the ACS namespace can not be reduced from the BamEndpointUri parameters. If this parameter is not included and authentication by means of ACS is required, the logic within the inspector will try obtain the namespace from the endpoint (ie; https://<NameSpace>.servicebus.windows.net or https://<NameSpace>.biztalk.windows.net)

Code
/// <summary>
/// Using basic logic it tries to extract the Acs Endpoint. Once extracted the value 
/// is stored in AccessControlEndpoint
/// </summary>
public void ExtractAcsEndpoint()
{
    string AcsNamespace = string.Empty;
  
    if (String.IsNullOrEmpty(this.CustomAcsNamespaceName))
    {
        //extract namespace 
        AcsNamespace = this.BamEndPointUri.Split(".".ToCharArray())[0].Replace("https://", string.Empty).Replace("http://", string.Empty);
    }
    else
    {
        //use the custom namespace
        AcsNamespace = CustomAcsNamespaceName;
    }
  
    //construct the acs endpoint
    this.AccessControlEndpoint = String.Format("https://{0}-{1}.accesscontrol.windows.net/WRAPv0.9/", AcsNamespace, this.AcsNsPrefix);
}

 

Downstream Delivery

Once all the BAM Data has been extracted. The component stores the extracted data in a class which reflects the BusinessActivityMonitoringEvent schema (Figure 3 BusinessActivityMonitoring.xsd) which was defined earlier and once done it will publish the message to the endpoint as was configured using the BamEndpointUri parameter. In order to publish the message System.Net.WebClient is used.

Code
//Send BAM Event downstream
public void PublishMessage<T>(T messagePayload)
{
  
    var client = new WebClient();
    Uri EndPoint = new Uri(this.BamEndPointUri);
  
    //Serialize Content
    string content = null;
    using (var ms = new MemoryStream())
    {
        var ser = new DataContractSerializer(messagePayload.GetType());
        ser.WriteObject(ms, messagePayload);
        ms.Position = 0;
        content = new StreamReader(ms).ReadToEnd();
    }
  
    //Check if the endpoint is a servicebus endpoint (queue or topic)          
    if (this.IsServicebusOrBizTalkServiceBridgeEndpoint)
    {
  
        //Add WRAP ACCESS TOKEN
        if (!String.IsNullOrEmpty(this.AcsToken))
        {
            client.Headers["Authorization"] = String.Format("WRAP access_token=\"{0}\"", this.AcsToken);
        }
    }
  
    //Add Content Type
    client.Headers["Content-Type"] = "application/xml";
  
    //Publish Message   
    client.UploadStringCompleted += new UploadStringCompletedEventHandler(Client_UploadStringCompletedAzureServiceBusMessageSend);
    client.UploadStringAsync(EndPoint, "POST", content, null);
  
    //close
    client = null;
}
  
   
  
//Get response message published 
void Client_UploadStringCompletedAzureServiceBusMessageSend(object sender, UploadStringCompletedEventArgs e)
{
    if (e.Error != null)
    {
        //TODO implement logic
    }
}

 

ACS Authentication

In case the logic within the component decides that authentication is required in order to access the BamEndPointUri, it will contact the ACS and obtain a WRAP access_token. This token is then added and as value for the authorization http-header.

Code
/// <summary>
/// Sets the AcsToken variable which is required to authenticate to the service bus endpoint
/// </summary>
public void GetAcsToken()
{
    this.AcsToken = string.Empty;
  
    try
    {
        string issuerName = this.IssuerName;
        string issuerKey = this.IssuerKey;
        Uri acsEndPoint = new Uri(this.AccessControlEndpoint);
  
        var client = new WebClient();
  
        // Create the body of the Post Request, formatted as a HTTP Form
        string postData = "wrap_name=" + Uri.EscapeDataString(issuerName) +
                            "&wrap_password=" + Uri.EscapeDataString(issuerKey) +
                            "&wrap_scope=" + Uri.EscapeDataString(this.BamEndPointUri.Replace("https", "http"));
  
        client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
        string[] tokenVariables = client.UploadString(acsEndPoint, "POST", postData).Split("&".ToCharArray());
        string[] tokenVariable = tokenVariables[0].Split("=".ToCharArray());
        string[] authorizationTokenVariables = Uri.UnescapeDataString(tokenVariable[1]).Split("&".ToCharArray());
        this.AcsToken = Uri.UnescapeDataString(tokenVariable[1]);
  
         //close
        client = null;
    }
    catch
    {
        //TODO implement exception logic, as BAM event can not be send due to the fact that something
        //went wrong while obtaining the acs token.
    }
}

  

Re-Configuring the Azure BizTalk Service Bridge

Once the Message Inspector has been completed it can be added to the existing FreeEnergyForAll inc. BizTalk Service Bridge. The BizTalk Bridge configuration steps required are listed below.

  • Open the Azure BizTalk Service Solution and select the bridge to be reconfigured
  • Double click the bridge in order to configure it.
  • Select the first enrich stage and define the properties which are used to identify the data to intercept. The configuration settings are being displayed as used by FreeEnergyForAll Inc. in the table below.

Property Definitions

Source Type

Source (see Figure 3 Smart Reader Message Format)

Property Name

XPath

Xpath to element containing the customers Zip Code

bam_ZipCode

XPath

Xpath to element containing the customers Country

bam_Country

XPath

Xpath to element containing the customer id

bam_CustomerId

XPath

Xpath to element containing the customers MeterId

bam_MeterId

XPath

Xpath to element containing the registration date time of the reading

bam_MererRegistrationDateTime

XPath

Xpath to element containing the actual consumption (meter reading)

bam_MeterValue

  • Select the enrich main shape, and select the configuration option of the Exit Inspector property

Figure 6 Select the On Exit Inspector

  • Configure the Message Inspector component to once need. In figure 7 the configuration for FreeEnergyForAll Inc is displayed. This configuration information instruct the component such that:
    • The BAM Activity named EnergyConsumptionRegistration is used
    • Context properties containing the prefix bam_ are used as the BAM Activity data
    • A new BAM Activity is to be created and ended after processing by the BAM Event Processor (BeginActivityAndEndActivity event type)
    • Uses a Servicebus endpoint (Relay) which ensures the message to be picked up by the on premise BAM Event Processor.
    • Uses the assigned issuerName and issuerKey required for authentication when sending the message to the Relay

Figure 7 Custom Code Inspector Configuration

  • Select Ok.
  • Compile and deploy

Summary

At the end of the day it actually did not take too much effort for FreeEnergyForAll.inc to incorporate the required functionality. It all boiled down to:

  • Creating a BizTalk Orchestration which leverages the BAM API using the OrchestrationEventStreaming class. This orchestration in a nutshell is responsible for writing the actual BAM Activity Data to the BAM Store
  • Create a BAM Observation model
  • Add a custom message inspector which would extract the required BAM Activity Data and send it downstream to the BAM Event Processing orchestration.

Code Samples

For the benefit of the reader I’ve added the complete solution, which can be downloaded in Microsoft Code Gallery:

See Also

Read suggested related topics:

Another important place to find a huge amount of Azure BizTalk Services related articles is the TechNet Wiki itself. The best entry point is Azure BizTalk Services resources on the TechNet Wiki.

If you are also looking for BizTalk Server related articles, the best entry point is BizTalk Server Resources on the TechNet Wiki.