How to integrate BizTalk Server 2013 with Service Bus for Windows Server using SAS for authorization
There is a great article by Paolo Salvatori about integrating BizTalk Server with Service Bus for Windows Server (the on-premises version). You can find the article from here:
https://code.msdn.microsoft.com/windowsapps/How-to-integrate-BizTalk-07fada58
However, it does not talk about how to use Shared Access Signatures (SAS) for authorization.
Let us assume that you have a Service Bus topic and subscription set up with SAS authorization. In addition, you have deployed an orchestration to BizTalk, which can process messages available in the subscription. Now you just need to know how to connect these dots, which basically means setting up a Receive Location.
The problem is that when Service Bus is configured to use SAS, SB-Messaging adapter in BizTalk Server 2013 cannot be used, as there is no way to configure the SAS key (by the way, SB-Messaging adapter in BizTalk Server 2013 R2 does support this, so first you should consider upgrading to the newest version). Therefore, WCF-Custom adapter has to be used and few custom endpoint behaviors need to implemented to enable the integration.
Create ListenUriEndpointBehavior
You have to specify the URL of the Service Bus topic as the address for WCF-Custom adapter. There is no place to set the actual URL of the subscription where the messages are read. Therefore, we need a custom endpoint behavior to set it at runtime.
You can use Paolo's example for implementing this endpoint behavior, check the link from the top of this post.
Create TokenProviderEndpointBehavior
We need to have another custom endpoint behavior for providing the token to access the Service Bus.
This is where the implementation differs from Paolo's example, as we do not use WindowsTokenProvider or OAuthTokenProvider for creating the token. Instead, we are using SharedAccessSignatureTokenProvider and you can find the code example for the endpoint behavior below (modified from Paolo's original example). Please note that code needs a reference to Microsoft.ServiveBus.dll, which is located at "C:\Program Files\Service Bus\(your Service Bus version number)\" by default.
using System;
using System.Configuration;
using System.Diagnostics;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using Microsoft.ServiceBus;
public class TokenProviderBehaviorExtensionElement : BehaviorExtensionElement
{
#region Private Constants
private const string KeyNamePropertyName = "sharedAccessKeyName";
private const string KeyNamePropertyDescription = "Gets or sets the shared access key name.";
private const string KeyPropertyName = "sharedAccessKey";
private const string KeyPropertyDescription = "Gets or sets the shared access key.";
private const string IsTrackingEnabledPropertyName = "isTrackingEnabled";
private const string IsTrackingEnabledPropertyDescription = "Gets or sets a value indicating whether tracking is enabled.";
#endregion
#region BehaviorExtensionElement Members
/// <summary>
/// Creates a behavior extension based on the current configuration settings.
/// </summary>
/// <returns>The behavior extension.</returns>
protected override object CreateBehavior()
{
return new TokenProviderEndpointBehavior(SharedAccessKeyName, SharedAccessKey, IsTrackingEnabled);
}
/// <summary>
/// Gets the type of behavior.
/// </summary>
public override Type BehaviorType
{
get
{
return typeof(TokenProviderEndpointBehavior);
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the shared access key name.
/// </summary>
[ConfigurationProperty(KeyNamePropertyName, IsRequired = true)]
[SettingsDescription(KeyNamePropertyDescription)]
public string SharedAccessKeyName
{
get
{
return (string)base[KeyNamePropertyName];
}
set
{
base[KeyNamePropertyName] = value;
}
}
/// <summary>
/// Gets or sets the shared access key.
/// </summary>
[ConfigurationProperty(KeyPropertyName, IsRequired = true)]
[SettingsDescription(KeyPropertyDescription)]
public string SharedAccessKey
{
get
{
return (string)base[KeyPropertyName];
}
set
{
base[KeyPropertyName] = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether tracking is enabled.
/// </summary>
[ConfigurationProperty(IsTrackingEnabledPropertyName, DefaultValue = true, IsRequired = false)]
[SettingsDescription(IsTrackingEnabledPropertyDescription)]
public bool IsTrackingEnabled
{
get
{
return (bool)base[IsTrackingEnabledPropertyName];
}
set
{
base[IsTrackingEnabledPropertyName] = value;
}
}
#endregion
}
public class TokenProviderEndpointBehavior : IEndpointBehavior
{
#region Private Constants
private const string MessageFormat = "[TokenProviderEndpointBehavior] TokenProvider = {0}, key = {1}";
#endregion
#region Private Fields
private readonly string sharedAccessKeyName;
private readonly string sharedAccessKey;
private readonly bool isTrackingEnabled;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the TokenProviderEndpointBehavior class.
/// </summary>
/// <param name="sharedAccessKeyName">The shared access key name.</param>
/// <param name="sharedAccessKey">The shared access key.</param>
/// <param name="isTrackingEnabled">A boolean value indicating whether tracking is enabled.</param>
public TokenProviderEndpointBehavior(string sharedAccessKeyName,
string sharedAccessKey,
bool isTrackingEnabled)
{
this.sharedAccessKeyName = sharedAccessKeyName;
this.sharedAccessKey = sharedAccessKey;
this.isTrackingEnabled = isTrackingEnabled;
}
#endregion
#region IEndpointBehavior Members
/// <summary>
/// Implement to pass data at runtime to bindings to support custom behavior.
/// </summary>
/// <param name="endpoint">The endpoint to modify.</param>
/// <param name="bindingParameters">The objects that binding elements require to support the behavior.</param>
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(sharedAccessKeyName, sharedAccessKey);
bindingParameters.Add(new TransportClientEndpointBehavior
{
TokenProvider = tokenProvider
});
Trace.WriteLineIf(isTrackingEnabled, string.Format(MessageFormat, tokenProvider.GetType(), sharedAccessKeyName));
}
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
{
}
#endregion
}
Create additional endpoint behaviors
Paolo also introduces SessionChannelEndpoint behavior for receiving messages from sessionful queue or subscription and ServiceBusMessageInspector for converting Service Bus message properties into BizTalk context properties. Consider whether they are useful for you, but they are not necessary in order to get the basic integration working.
Build and deploy endpoint behaviors
After you have created all custom endpoint behaviors, preferably to a separate class library project, you can go ahead, build the project, and deploy the DLL into GAC (for example by using the GACUTIL tool).
Also, make sure that the referenced Service Bus assembly (Microsoft.ServiceBus.dll) can be accessed from your assemblies at runtime. Simplest way to ensure that is by inserting it also into GAC.
Present endpoint behaviors to WCF-Custom adapter
Create a configuration file for WCF-Custom adapter:
- Copy content from the example below and save it to a temporary location with .config -extension
- Replace assembly type attribute contents with correct values of your assembly built in the previous step, only 'bizTalkListenUri' and 'bizTalkSecurity' rows
- Verify that version number of Microsoft.ServiceBus assembly is correct in all other rows (Service Bus 1.1 works with the DLL version 2.1.0.0)
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="connectionStatusBehavior" type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="transportClientEndpointBehavior" type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="serviceRegistrySettings" type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="bizTalkListenUri" type="YourAssembly.ListenUriBehaviorExtensionElement, YourAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=224520fd53ada681" />
<add name="bizTalkSecurity" type="YourAssembly.TokenProviderBehaviorExtensionElement, YourAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=224520fd53ada681" />
</behaviorExtensions>
<bindingElementExtensions>
<add name="netMessagingTransport" type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="tcpRelayTransport" type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="httpRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="httpsRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="onewayRelayTransport" type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingElementExtensions>
<bindingExtensions>
<add name="basicHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="webHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="ws2007HttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netTcpRelayBinding" type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netOnewayRelayBinding" type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netEventRelayBinding" type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netMessagingBinding" type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
</system.serviceModel>
</configuration>
Import the configuration file:
- Open 'BizTalk Administration Console'
- Expand the navigation tree to 'BizTalk Administration' > 'BizTalk Group' > 'Platform Settings' > 'Adapters'
- Select 'WCF-Custom' from the navigation tree
- Double-click receive handler in the right-hand pane and click Properties-button
- Click Import-button and select the configuration file from your hard drive
Create and configure the receive location
Create new Receive Location and specify following properties:
- Select 'WCF-Custom' adapter
- Select 'XMLReceive' pipeline
- Click Configure-button
In the General-tab:
- Specify the address of the topic, for example "sb://yourserver.domain.com/ServiceBusDefaultNameSpace/your_topic_name"
In the Binding-tab:
- Select 'netMessagingBinding' as binding type
In he Behavior-tab:
- Right-click 'EndpointBehavior' in left-hand pane and select 'Add extension'
- Select 'bizTalkSecurity' from the list and click OK
- Enter shared access key and key name in right-hand pane
- Right-click 'EndpointBehavior' in left-hand pane and select 'Add extension'
- Select 'bizTalkListenUri' from the list and click OK
- Enter full URL to your subscription in right-hand pane, for example: "sb://yourserver.domain.com/ServiceBusDefaultNameSpace/your_topic_name/subscriptions/your_subscription_name"
That should do it and by starting the receive location, messages from the Service Bus should start appearing in BizTalk.
Comments
- Anonymous
July 08, 2015
After importing the configuration file the EndPoint bhavior is not reflected while creating the receive location. What will be the consequences for the behavior and the solution?